Close
0%
0%

Hardware based RGBW Lamp controller

Or how to write firmware the weird way to submit a project in the 1kB Challenge.

Public Chat
Similar projects worth following
This project consist on the implementation of a multicolor lamp, which swings all the colors at 24 bit depth and adjust the white saturation, through the user buttons or from serial commands.

The original code was for the Lino project which uses somehow 8kB+ of code, but was the only nice project that I had in my mind that could be submitted for the challenge.

So, instead to learn how to code in assembly and reduce the overall code as one should be, I'd preferred to use the method that will be the less valuable in a contest like the 1kB Challenge and the most difficult to implement: move all the excess on FPGA using an iCE40 Lattice stick. What an evil genius.

Moreover, some code is even more butchered by using analog debouncing circuits for the user input.

For the actual memory status, see the [FW] log. For how is designed everything, see the links and the other logs.


This projects is an hardware adaptation of a firmware based RGBW lamp. With few (a lot of) modifications it has became an huge FPGA based project.

How is made this lamp

This lamp uses an Arduino Duemilanove board, which controls 4 power LEDs, for the red, green, blue and white shades. It can be made on true 8 bit colors, but due to FPGA and memory constraints, it runs on 512 pure colors (3 effective bits per color) and on overall 4096 colors if white shades are used too. The board handle the user buttons of the eventual data stream from a PC in order to control the lamp.

The FPGA runs all the calculations that were made originally on the Atmega (which was able to represent 16.7M colors), so that the overall code is actually now less than 1024 kB. In the FPGA I tried to not implement any explicit LUT/RAM/EEPROM or any other type of memory, just raw logic.

I decided to go forward with this implementation because it is not yet meaningless, but educational (at least for myself) since the challenge is related to the code size, but not the overall (here ridiculously increased) cost. In this way I have found a way to use my new Lattice FPGA.

All the debounce is made as much possible in hardware to avoid any code.

System block diagram


Inside the blocks are present the following modules, better described in the HW project log:

  1. Lattice ICE Stick (iCE40 FPGA)
  2. Arduino Duemilanove (or Uno)
  3. Op-amps based Schmitt trigger for the input debounce (see Github links)
  4. Self assembled switching LED drivers (see Github links)

How it works

Buttons and the SPI protocol

The input is made by 3 buttons: mode, up and down. With mode, you can sweep from the white saturation, color selection, intensity. With the up and down buttons, according to the mode selected, the perfect color can be chosen, starting from the color, then adjusting its white component and finally the intensity.

The atmega handle the color selector signal, the white saturation and the uses the SPI protocol to communicate with the FPGA. This is based on a Lattice iCE40, which implements the following designs, better described in the logs:

  1. SPI slave implementation
  2. Clock prescaler
  3. Data deserializer
  4. Color generator
  5. Four channel PWM module implementation

When the user uses the buttons, the data is immediately updated and fed through the SPI. The Atmega sends the following serial data:

  • sync char (1 byte),
  • intensity (1 byte),
  • color selecor index (1 byte),
  • red (1 byte, not currently used),
  • green (1 byte, not currently used),
  • blue (1 byte, not currently used),
  • white (1 byte),
  • mode (1 byte, not currently used)

Serial protocol

When a command is received from the serial interface of the Arduino, the Atmega instruct, using the SPI previously described, the FPGA to switch to the full slave mode and then represent directly the red, green, blue and white received from serial.

The protocol allow to handle and filter away any spurious character, reaching a good dependability on the communication. This is already used in my previous project.

Quoting what I have already written in my previous project, it consist in an array of 18 bytes sent from a master PC or device (over serial/USB) made by the following sequence, from the first to the last byte sent, MSB to LSB:

[StartChar] [CTRL#00] [CTRL#01] [R#0] [R#1] [G#0] [G#1] [B#0] [B#1] [W#0] [W#1] [INT#0] [INT#1] [RES#00] [RES#01] [RES#10] [RES#11] [EndChar]

Where each byte is represented from LSB to MSB, the data is sent using UART (8 bits, 1 Start + 1 Stop bits, no parity).

Here below is listed the meaning of each byte:

  1. StartChar: the char used to discriminate a begin of transmission after startup, or a StopChar, after a timeout or any subsequent StartChar in case of line issues
  2. CTRL#00/#01: The combination of characters reserved for any auxiliary command. #00 is the MSB, #01 the LSB.
  3. R#0/#1: channel Red data (from 0 to 0xFF). #0 is the MSB, #1 the LSB.
  4. G#0/#1: channel Green data (from 0 to 0xFF). #0 is the MSB, #1...
Read more »

  • Forgottings: forgotthings

    Enrico06/01/2017 at 22:25 0 comments

    I have realized only now that the VHDL code was not available from GitHub. Now it is back, and can be accessed from the links (in the ice40 folder).

    And no, no improvements. Too many other new ideas, too little time, for now.

  • [Test] A brief demo of the buttons and the serial commands

    Enrico01/04/2017 at 23:09 0 comments

    Here is attached the link of the videos which are showing the utilization of the system.

    The first video is a raw button interaction, where I try to select one mode and check what it is by pushing the up and down buttons. If changes the intensity in a discrete way, it is the intensity mode, otherwise it changes the white saturation or selects the color. The button is acquired from the Atmega, which selects, according to the mode, the intensity, white or color and send it in to the FPGA fabric. From there, the PWM output is connected to the 4 LED drivers powered from 9V wall adapter.


    The second video is the serial data interpretation. The tool used is a Python one in the links of the project. The tested color pattern is the following, where it is represented in #RRGGBB:

    #000000 (all is off), #39ec20, #ff0d0d, #16b8f5, #eb1cf0, #f8ad14, #fef9ed, #403d31, #000000 (all is off).

    The data is packed according to a protocol described in the logs, sent to the Atmega and then transferred to the FPGA logic.

    Remember that the switch between the serial mode and the user buttons mode is made just by pressing any button or by sending a complete frame over serial. The last mode will take over the previous one, but all the data of the last mode is kept in the Atmega's memory so that when the user will press a button, the previous setting is restored.

  • [FW] The butchered code

    Enrico01/04/2017 at 22:12 0 comments

    The initial, ideal version of the firmware was used on other projects, like this. It was implementing every step of the color generation, from the autowhite adjustment, to the main color selection, the PWM generation, the power monitor, the serial commands, the encoder management and so on. Everything in 8098 kB of program memory, and 368 bytes of RAM.

    Cutting out almost 90% of the code was an overwhelming challenge. The main constraint was my idea to stick with the RGB project for the 1kB Challenge, instead focusing on something else. And for pure coincidence I had also bought a Lattice FPGA. Then the works has just started: what could be moved away from the software and that can be represented in raw logic? How can the information be passed between the microcontroller and the FPGA?

    The result is an hated spaghetti code, with no safety features from the ADC for the power monitoring, no encoder management and so on. The only thing was to rely as much as possible on the MCU hardware, for the UART, PWM and.... no, there are no pins available and there is no way to implement any protocol in bit banged way.

    And now I realized that the only thing that keep a soul in the MCU is the data management from the user and the overall control. Moreover, also the data management from UART is kept inside the MCU, since it was implementing all the characters filtering and the numeric conversions. In this way, the user can still manage the double functionality, which is from hexadecimal commands from serial or using 3 buttons (instead of a rotary encoder).

    All the functions were optimized accordingto the ASCII tables that allow huge data compression. A snippet of the original character conversion function:

    static unsigned char lamp_c2h(unsigned char msb, unsigned char lsb){
    	if ((msb < 'a' && msb > '9') || msb < '0' || msb > 'f' ||
    	(lsb < 'a' && lsb > '9') || lsb < '0' || lsb > 'f'){
    		// printf("Not HEX value. msb: %c, lsb: %c\n", msb, lsb);
    		return 0;
    	}
    	if (msb >= 'a' && msb <= 'f'){
    		msb = msb - 'a' + 10;
    		} else {
    		msb = msb - '0';
    	}
    	if (lsb >= 'a' && lsb <= 'f'){
    		lsb = lsb - 'a' + 10;
    		} else {
    		lsb = lsb - '0';
    	}
    	return (lsb+(msb*16));
    }

    became this:

    static unsigned char lamp_c2h(unsigned char msb){
        uint8_t tmp = 0;
        tmp = msb;
        msb=msb&0x0F ;
        if (tmp>'9')
        msb+=9;
        return (msb);
    }
    And I also learned how a shift instruction could take a lot of space, when you start noticing every single byte eaten up by your code.

    The double edge weapon of the hardware relief

    In this way a small SPI master control is written, in order to send the correct data to the FPGA. Again, you can feel how much work you move away from the MCU's processor (and so from the memory occupation) when you have a dedicated HW that does the job.

    And I am not talking about the FPGA, but I am referring on how just the SPI hardware of the Atmega was of huge help in memory reduction, try to think in implement this in firmware, how much more space have been taken off? This is obvious, but not always appreciated.

    A byte assignment in the FW, and a lot of stuff moving in the hardware. That is fun...or, no, not when you have to deal with designing another piece of hardware that shall receive all that beauty from the SPI of the Atmega. And now I am talking about the FPGA.

    The final memory status

    Or the final countdown to 1024 bytes. At the end, I discovered that I was trained to freeing up the RAM. To me, was way more tricky to find out how to save on byte from flash rather than from RAM.

    This meas that for coincidence I have freed the 92% of the RAM. While my efforts were in freeing up the program memory, which now is reduced by the 88%.

    As a result, this is my memory implementation, from the build output of the Atmel Studio 7:

    Program Memory Usage : 954 bytes 2,9 % Full
    Data Memory Usage : 29 bytes 1,4 % Full

  • [HDL] The FPGA design

    Enrico01/04/2017 at 21:33 0 comments

    Top level description

    Here is shown the top-level implementation, written in VHDL and synthesized using free licence tools from Lattice, which are the Diamond LSE for the sysnthesys, Diamond Programmer Tool for flashing the configuration memory, ICE Cube for the P&R and the integration of the LSE tool, Aldec Active HDL for the simulation.

    In the top level, you can spot the SPI slave, which sends data to the Deserializer, where on its turn splits the data in paraller and synchronous way to the Color Generator. This module will implement the intensity management, the white saturation, the color and the mode. The mode simply instruct the Color Generator to excludes all the white, intensity and color management and follow the SPI data. Otherwise, will ignore the RGBW data from the SPI and generates it according to an index, generated in the Atmega.

    The original idea was to send back the data to the MCU, but then I realized that I have not enough pins, so in the FPGA it is also implemented a 4 channel PWM module.

    Nice similarities in Atmega products

    It is nice to see that my PWM have the same bug as the Atmega: it cannot swing from 0 to 100%, but it will have a glitch. So the minimum is 1/255 if not inverted or the maximum can be 254/255 if inverted. I simply inverted internally the control circuitry, so that I have a PWM that swing from 0/255 up to 254/255, but it is a not inverted type. In this way, this glitch cannot be seen. This glitch is totally normal in a raw implementation, and can be easily masked. I wonder why it isn't in Atmegas. What a lazy one the designer.

    Below a simulation snapshot showing the glitch on D3 signal:

    Overall FPGA utilization

    The FPGA itself is almost full, especially when optimizing for speed. This project gives to me some feedback on how really problematic could be an HDL design when you have few resources. For example, I have initially implemented a division, a combinatorial default one: 50% of the FPGA was filled with this logic, while the other 50% shall be filled with 80% of resources. My iCE40 does not have the 130% of space.

    So the division for implmenting the dimming was made by issuing a dynamic shift in the Color Generator, reducing the dimming steps for the user. In the Atmega, a division was performed very well. But the previous code was fine tuned, smooth and big 8kB more.

  • [HW] The overall schematics

    Enrico01/02/2017 at 22:41 0 comments

    Since this project is all breadboarded, I will share the schematics (and pictures) since there is no a real layout, and Fritzing would became insanely complex. Below, a picture with the regions herein described through the schematics and the connections, just to make the wirings not too overwhelming:

    Here you can see the iCE Stick FPGA used, and the highlighted in orange connector J2, and pin 1 highlighted too:

    From here, I am referring to the FPGA connections as iCE Stick header J2 pin XX.

    The hardware debounce for the UP and DOWN is made using a schmitt trigger obtained through an LM324, while the MODE is simply a pull-up which works nice in the firmware which somehow filters away some glithes (a cap shall be added, btw):

    The communication between the Arduino and the FPGA happens through unilateral SPI communication, so that a simple 2/3 resistor voltage divider is sufficient to safely connecting the 3.3V FPGA to the 5V Atmega:

    If needed, it is provided a RESET buttons to re-initialize the FPGA:

    If you take the entire VHDL implementation "as is", for debugging purposes you will see the SCK pin redirected on ICE board LED D1, while the other 4 color signals are also duplicated on the remaining ICE debugging LEDs.

    The output of the FPGA is feeding the power LEDs through a module that I've build few months ago, but you can power up anything else. Here you can see a picture of one module which can be fed with a PWM signal as high as the supply voltage, where such supply can be as high as 20V. As already said, you can connect any LED to try out the Arduino+FPGA setup.

    Here is the module, powered at 9V, where the PWM swings between GND and 3.3V of the ICE Stick:

    Power LED module

    The LED connections is shown on the board too.

    Here you can see the briefing of the entire connection setup within this log, where some of them are conditioned using the above schematics (all names are referred as in the schematics, so that you can reproduce the connections):

    • RESET active low button S4 -> FPGA J2 pin 1
    • Arduino pin 13 -> FPGA J2 pin 2
    • Arduino pin 11 -> FPGA J2 pin 3
    • One active low switch S1 -> Arduino pin 4
    • One active low switch S2 -> Arduino pin Analog 0
    • FPGA J2 pin 7 -> enable/PWM pin RED driver
    • FPGA J2 pin 8 -> enable/PWM pin GREEN driver
    • FPGA J2 pin 9 -> enable/PWM pin BLUE driver
    • FPGA J2 pin 10 -> enable/PWM pin WHITE driver

    Remember to keep the GND reference between Arduino, ICE Stick and all the 4 LED drivers in order to avoid fire. The 4 LED drivers are soldered on perfboard and a 4 wire strips is connected in J2 of the ICE Stick.

    You can spot a second board, which provides power to the LEDs: it mounts a second Atmega which is not used. That board (a Lino board) was lying around and I decided to use it, since I am also late in the submission for the challenge. It provides only few basic electrical protections. :)




View all 5 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates