Skip to content

RGB Control

Thomas Maier-Komor edited this page May 1, 2023 · 3 revisions

RGB-Control Board for Color-LED-Strips

The RGB-Control board shown below can drive an RGB-LED-strip and set its color interactively using buttons, a rotary encoder, and a display. The Atrium firmware therefore provides the necessary drivers and infrastructure, which just need to be configured for this specific application. The concept of configurable state-machines, events, and actions makes it possible to setup very flexible applications.

Having a single firmware image configured for a specific application, makes it possible to use roll out firmware updates across multiple embedded hardware and applications without having to rebuild for all different setups. The article below, shows how to configure hardware and software in Atrium. The demonstrated approach is a major advantage of Atrium over Arduino based implementation that require one software for every individual application.

Demo hardware

The RGB-Control board provides:

  • an ESP32
  • an interface for a BME280 module with I2C interface
  • an interface for an OLED display with I2C interface
  • 3 low-side MOSFETs
  • a high-side MOSFET
  • a current sensor
  • a light sensor
  • a rotary-encoder
  • 3 push-buttons with LEDs
  • additional status LEDs
  • a debug and flashing UART interface

Below you can see how to configure the Atrium firmware without writing a single line of code to provide a user-interface for adjusting an RGB strip interactively, using the display, the buttons, and the rotary encoder. This is done by setting up a state-machine that routes events of the rotary encoder and buttons to each of the 3 PWM drivers for the RGB low-side MOSFETs. Adjusting the values can be observed on the display in parallel.

Setup Procedures

  • flashing the Atrium firmware
  • initial WiFi configuraton
  • configuring drivers
  • runtime setup

Flashing Atrium

The binary distribution of Atrium provides 2 script to flash an ESP device. One for initial setup called flash-atrium.sh that flashes the bootloader, the partition table, and the application. This script will also erase the complete device before flashing the images.

The second one is called update-atrium.sh, which will only update the application image and keep everything else in place, including the configuration.

Updating can also be performe over-the-air (OTA) via telnet http/ftp/tftp download or via http upload using the integtated web server if approriate .html pages are on the device.

For initial flashing of an ESP32 with 4MB of flash, run:

$ ./flash-atrium.sh esp32_4m

Initial network configuration

Before being able to contact an Atrium device via MQTT or telnet, its WiFi settings must be configured appropriately. As this configuration is common to all Atrium devices, it is recommended to prepare this configuration with the config tool atriumcfg, so that you can distribute it afterwards easily to multiple devices either as hex-dump or directly as an partition upload.

But it is even easier to perform the WiFi configuration on the device with a serial console.

The on-target configuation steps are as follows:

> config set nodename <name-of-the-device>
> config set station.ssid <SSID-of-WiFi>
> config set station.pass <PSK-of-WiFi>
> config set station.activate true
> config add dns_server <dns-ip-address>
> config set domainname <domain-name>
> config set sntp_server <sntp-server-name-or-IP>
# optionally
> config set mqtt.uri mqtt://<mqtt-host>:1883
> config set mqtt.enable true
# write the configuartion to the non-volatile storage
> config write my-wifi.cfg

Setting the WiFi SSID, password, and activate is required to let the device contact your network. If you omit setting its nodename, it will create a default nodename based on its MAC address. Setting the DNS server and domain-name is required if yout want to be able to used hostnames instead of IP addresses.

Atrium devices are also resolvable by their nodename via mDNS in a LAN that uses DHCP to provide an IP address to all devices in the WiFi. Just append .local to the nodename, when using HTTP or telnet to contact a Atrium device.

Setting an SNTP server is required if you want to use execute actions based on date or weekday and time.

Setting up MQTT is optionally and also requires that you have an MQTT server running on your network. This will enable you to interact with the Atrium device via MQTT.

Hardware configuration

To configure the hardware, you can either use a matthcing NVM image, use the hex-dump of the prepared config or follow the manual steps below.

For manual configuration you can either use the config tool atriumcfg, which means you have to upload the config to the target (e.g. using telnet xxd dump or remote configuraton with atriumctrl.py) or you can perform the configuration on the target directly. If you use the config tool, you must omit the prefixed hwconf on every line, but you have to switch to the hardware configuration in atriumcfg using the hw command.

Here are the command, how they are executed on the target either via telnet or via serial console.

# configure the current measurement ADC
> hwconf add adc.channels
> hwconf set adc.channels[0].name current
> hwconf set adc.channels[0].ch 0
> hwconf set adc.channels[0].unit 1

# configure the ambient light ADC
> hwconf add adc.channels
> hwconf set adc.channels[1].name amblight
> hwconf set adc.channels[1].ch 3
> hwconf set adc.channels[1].unit 1

# configure the rotary encoder
> hwconf add button
> hwconf set button[0].name rotenc
> hwconf set button[0].gpio 13
> hwconf set button[0].dt 14
> hwconf set button[0].clk 27

# configure the color buttons
> hwconf add button
> hwconf set button[1].name btnr
> hwconf set button[1].gpio 34
> hwconf add button
> hwconf set button[2].name btng
> hwconf set button[2].gpio 35
> hwconf add button
> hwconf set button[3].name btnb
> hwconf set button[3].gpio 26

# configure the LEDs on the buttons
> hwconf add led
> hwconf set led[0].name swledr
> hwconf set led[0].gpio 32
> hwconf add led
> hwconf set led[1].name swledg
> hwconf set led[1].gpio 33
> hwconf add led
> hwconf set led[2].name swledb
> hwconf set led[2].gpio 25

# configure the status LED
> hwconf add led
> hwconf set led[3].name status
> hwconf set led[3].gpio 23

# configure the application LED
> hwconf add led
> hwconf set led[4].name appled
> hwconf set led[4].gpio 23

# configure the PWM channels
> hwconf add led
> hwconf set led[5].name red
> hwconf set led[5].gpio 4
> hwconf set led[5].pwm_ch 0
> hwconf add led
> hwconf set led[6].name green
> hwconf set led[6].gpio 15
> hwconf set led[6].pwm_ch 1
> hwconf add led
> hwconf set led[7].name blue
> hwconf set led[7].gpio 17
> hwconf set led[7].pwm_ch 2

# configure the high-side MOSFET, which is shared across all LEDs
> hwconf add led
> hwconf set led[8].name hss
> hwconf set led[8].gpio 2
> hwconf set led[8].config.active_high 1

# configure the I2C bus
> hwconf set i2c.sda 19
> hwconf set i2c.scl 18

# configure the display
> hwconf set display.type dt_ssd1306
> hwconf set display.maxx 128
> hwconf set display.maxy 64
# set display option alternating rows (see ssd1306.h)
> hwconf set display.options 0x10

# finally make the config persistent
> hwconf write

# and reboot to activate the configuration
> reboot

Run-Time setup

Once the hardware configuration has been performed and activated (via rebooting), all drivers should provide their necessary events and actions that are needed for the run-time configuration. You can use event -l and action -l to list available actions and events accoringly.

Below follows an example how the configured drivers can be setup with a state machine to allow easy user interaction for setting the RGB values on the LED strip and storing and restoring the related PWM values.

# add a state machine for user-interface
> sm add ui

# add states for changing the individual colors and for general operation
> sm add ui main
> sm add ui red
> sm add ui green
> sm add ui blue

# Increment/decrement PWM value of relevant channel when turning rotary
# encoder when in the relevant state. I.e. red state to modify the red
# PWM.
> sm on ui:red rotenc`left red!dec
> sm on ui:red rotenc`right red!inc
> sm on ui:green rotenc`left green!dec
> sm on ui:green rotenc`right green!inc
> sm on ui:blue rotenc`left blue!dec
> sm on ui:blue rotenc`right blue!inc

# To change between the states, the buttons need to be configured. We
# start in the main state to switch to the color-changing modes.
> sm on ui:main btnr`short sm!set ui:red
> sm on ui:main btng`short sm!set ui:green
> sm on ui:main btnb`short sm!set ui:blue

# In red state we want to switch to all other states.
> sm on ui:red btnr`short sm!set ui:main
> sm on ui:red btng`short sm!set ui:green
> sm on ui:red btnb`short sm!set ui:blue

# Same in green...
> sm on ui:green btnr`short sm!set ui:red
> sm on ui:green btng`short sm!set ui:main
> sm on ui:green btnb`short sm!set ui:blue

# ... and blue.
> sm on ui:blue btnr`short sm!set ui:red
> sm on ui:blue btng`short sm!set ui:green
> sm on ui:blue btnb`short sm!set ui:main

# The rotary encoder should turn off the LEDs in main state.
> sm on ui:main rotenc`left red!off
> sm on ui:main rotenc`left green!off
> sm on ui:main rotenc`left blue!off

# And the rotary right event hould restore the saved PWM state on all channels.
> sm on ui:main rotenc`right red!restore
> sm on ui:main rotenc`right green!restore
> sm on ui:main rotenc`right blue!restore

# So that the user knows which state is active, the LED on the relevant
# button should be activated. I.e. turn it on when entering the state,
# and turn it off when leaving the state.
# (This is not depicted in diagram.)
> event -a ui:red`enter swledr!on
> event -a ui:red`exit swledr!off
> event -a ui:green`enter swledg!on
> event -a ui:green`exit swledg!off
> event -a ui:blue`enter swledb!on
> event -a ui:blue`exit swledb!off

# Switching to a different color should fade over. Therefore, set the
# dimmer step interval to 20ms.
> config set dim_step 20

# The user should also be able to see different things on the dispaly.
# Therefore, a short click on the rotary encoder should switch the
# display to the next available mode.
> event -a rotenc`short display!set_mode

# A medium click on the rotary encoder should display the time.
> event -a rotenc`med display!set_mode time

# To be able to store and restore a specific color setting, a
# medium click on a color button should backup the current PWM value.
> event -a btnr`med red!backup
> event -a btng`med green!backup
> event -a btnb`med blue!backup

# To have some feedback that the medium click invoked the backup, we
# additionally set the application LED to blink twice in that situation.
> event -a btnr`med led!set appled:twice
> event -a btng`med led!set appled:twice
> event -a btnb`med led!set appled:twice

# After everything is configure, save the configuation...
> config write

# ... and reboot the device to activate the configuation.
> reboot

Atrium after configuration:

After configuring and rebooting the devcie, you can use action -l to list all available actions, and event -l to list all events an the actions that are associated with the events. If an action is tagged as [disabled], then this is due to this action only being active in a specific state of a state-machine that is currently not active.

You can list the configured actions with action -l which will output for the above configuration following listing:

        all_dimmers!backup        backup dimmer and fade off
        all_dimmers!off           backup dimmer and fade off
        all_dimmers!restore       restore dimmer from backup
        appled!off                led off
        appled!on                 led on
        appled!toggle             toggle led
        blue!backup               backup dimmer value
        blue!dec                  decrement dimmer value
        blue!inc                  increment dimmer value
        blue!off                  turn off with PWM ramp
        blue!on                   turn on with PWM ramp
        blue!restore              restore dimmer value
        bme280!sample             BMP280 sample data
        dim!set                   set dimmer(s) <d> to value <v>: arg = [<d>:]<v>
        display!off               clock: turn off
        display!on                clock: turn on
        display!set_bright        set brightness to argument value
        display!set_mode          switch to next or specified display mode
        gpio!set                  set gpio <name>:<value>, with value=0,1,z,i,o,t
        green!backup              backup dimmer value
        green!dec                 decrement dimmer value
        green!inc                 increment dimmer value
        green!off                 turn off with PWM ramp
        green!on                  turn on with PWM ramp
        green!restore             restore dimmer value
        hss!off                   led off
        hss!on                    led on
        hss!toggle                toggle led
        influx!init               init influx connection
        influx!rtdata             send runtime data
        influx!sysinfo            send system info
        influx!term               term influx connection
        led!set                   set LED mode (on,off,slow,fast,once,twice)
        lua!run                   run argument as Lua script
        mqtt!pub_rtdata           mqtt publish data
        mqtt!start                mqtt start
        mqtt!stop                 mqtt stop
        red!backup                backup dimmer value
        red!dec                   decrement dimmer value
        red!inc                   increment dimmer value
        red!off                   turn off with PWM ramp
        red!on                    turn on with PWM ramp
        red!restore               restore dimmer value
        sm!next                   set next state of state-machine <machine>
        sm!set                    set state of state-machine to <machine>:<state>
        statusled!btnpress        bind to button press event to monitor with status LED
        statusled!btnrel          bind to button release event to monitor with status LED
        sw!pause                  stopwatch pause/resume
        sw!reset                  stopwatch reset
        sw!startstop              stopwatch start/stop
        swledb!off                led off
        swledb!on                 led on
        swledb!toggle             toggle led
        swledg!off                led off
        swledg!on                 led on
        swledg!toggle             toggle led
        swledr!off                led off
        swledr!on                 led on
        swledr!toggle             toggle led
        syslog!send               trigger sendind dmesg to syslog
        timer!disable             disable 'at' execution
        timer!enable              enable 'at' execution
        timer!toggle              toggle 'at' execution
        webtmr!start              start this timefuse
        webtmr!stop               stop this timefuse
        wps!start                 start WPS

You can list the configured events with event -l which will output for the above configuration following listing:

init`done (1x, 248ns) =>
        swledr!off ()
        swledg!off ()
        swledb!off ()
        hss!on ()
wifi`station_up (3x, 6827ns) =>
        udns!init_mdns ()
        mqtt!start ()
        syslog!start ()
wifi`station_down (0x, 0ns) =>
        udns!wifi_down ()
syslog`msg (27x, 20292ns) =>
        syslog!send ()
action`trigger (0x, 0ns) =>
        action!execute ()
rotenc`released (0x, 0ns) =>
rotenc`pressed (0x, 0ns) =>
rotenc`short (0x, 0ns) =>
        display!set_mode ()
rotenc`med (0x, 0ns) =>
        display!set_mode (time)
rotenc`left (41x, 908ns) =>
        red!off ()
        green!off ()
        blue!off ()
        red!dec () [disabled]
        green!dec () [disabled]
        blue!dec () [disabled]
rotenc`right (196x, 4190ns) =>
        red!restore ()
        green!restore ()
        blue!restore ()
        red!inc () [disabled]
        green!inc () [disabled]
        blue!inc () [disabled]
btnr`released (3x, 192ns) =>
        btnr!up ()
btnr`pressed (4x, 168ns) =>
        btnr!down ()
btnr`short (2x, 236ns) =>
        sm!set (ui)
        sm!set (ui) [disabled]
        sm!set (ui:red) [disabled]
        sm!set (ui:red) [disabled]
btnr`med (0x, 0ns) =>
        led!set (appled:twice)
        red!backup ()
btnr`long (0x, 0ns) =>
btng`released (2x, 169ns) =>
        btng!up ()
btng`pressed (2x, 116ns) =>
        btng!down ()
btng`short (2x, 293ns) =>
        sm!set (ui)
        sm!set (ui:green) [disabled]
        sm!set (ui) [disabled]
        sm!set (ui:green) [disabled]
btng`med (0x, 0ns) =>
        led!set (appled:twice)
        green!backup ()
btng`long (0x, 0ns) =>
btnb`released (2x, 121ns) =>
        btnb!up ()
btnb`pressed (2x, 115ns) =>
        btnb!down ()
btnb`short (2x, 183ns) =>
        sm!set (ui)
        sm!set (ui:blue) [disabled]
        sm!set (ui:blue) [disabled]
        sm!set (ui) [disabled]
btnb`med (0x, 0ns) =>
        led!set (appled:twice)
        blue!backup ()
btnb`long (0x, 0ns) =>
mqtt`update (0x, 0ns) =>
system`procwifi (0x, 0ns) =>
        system!procwifi ()
webtmr`started (1x, 0ns) =>
webtmr`stopped (0x, 0ns) =>
webtmr`timeout (688x, 46us) =>
        bme280!sample ()
ui:main`enter (4x, 0ns) =>
ui:main`exit (3x, 0ns) =>
ui:red`enter (1x, 211ns) =>
        swledr!on ()
        display!set_mode (red)
ui:red`exit (1x, 34ns) =>
        swledr!off ()
        display!set_mode (time)
ui:green`enter (1x, 182ns) =>
        swledg!on ()
        display!set_mode (green)
ui:green`exit (1x, 52ns) =>
        swledg!off ()
        display!set_mode (time)
ui:blue`enter (1x, 130ns) =>
        swledb!on ()
        display!set_mode (blue)
ui:blue`exit (1x, 38ns) =>
        swledb!off ()
        display!set_mode (time)
amblight`high (1x, 237ns) =>
        dim!set (0)
amblight`low (0x, 0ns) =>
        red!restore ()
        green!restore ()
        blue!restore ()

Remote operation using MQTT:

Any action of an Atirum device can remotely be triggered via MQTT, using the <nodename>/action topic. I.e. just publish the action name and its arguments seperated by a space to the action topic of the relevant device, and the device will perform the action if it is connected to the same MQTT server.

Similarly Lua scripts can be called remotely using MQTT. But for Lua scripts it is recommended to place larger scripts on the device and add them to the list of Lua files that should be compiled after booting (i.e. config luafiles). These scrips then can be remotely be called by their name using MQTT.