WS2812

We all love colorful blinking LEDs, right? There’s a simple API for accessing the five individually addressable RGB LEDs on the Badge’s kite. The API is found inside a separate ESP-IDF compontent ws2812 that is intended as a [git submodule] (https://github.com/badgeteam/esp32-component-ws2812). If you started your app development from the [template app] (https://github.com/badgeteam/mch2022-template-app), it should be already set up.

There’s a sixth RGB LED on the board (next to the top corner of the display). This LED is controlled via the ICE40 FPGA (see its driver for details).

The LEDs are WS2812-compatible. If you’re not already familiar with these LEDs: Each LED contains red, green and blue LED and a tiny controller that receives 24 bit RGB data from a single serial data line. Further data bits are pushed through to its data output, which is connected to the next LED. This allows many LEDs in a string to be individually controlled.

The LED power supply is switched (together with the SD card). Before using the LEDs, set IO19 (GPIO_SD_PWR) to 1.

Before controlling the LEDs, the driver has to be set up with the data line connected to the LEDs (GPIO_LED_DATA). If you want to control other WS2812 LEDs (e.g. connected to one of the extension connectors), you can specify a different value.

Setting the LEDs is pretty straightforward: Set up an array of 15 unsigned brightness values (R,G,B for 5 LEDs).

A minimal example to set all LEDs to red:

  uint8_t red[] = {0,255,0,0,255,0,0,255,0,0,255,0,0,255,0};
  // turn on LED power
  gpio_set_direction(GPIO_SD_PWR, GPIO_MODE_OUTPUT);
  gpio_set_level(GPIO_SD_PWR, 1);
  // initialize WS2812 driver to the appropriate data pin
  ws2812_init(GPIO_LED_DATA);
  ws2812_send_data(red, sizeof(red));

To animate, change the array values and repeat ws2812_send_data in regular intervals.

Addressable LEDs on an SAO

The LEDs on an add-on are a different story: they hang off the SAO connector, and that pin belongs to the RP2040, not to the ESP32. So the ws2812 component above does not reach them. Instead you ask the RP2040 to shift the data out for you, over the I2C link the badge already uses for the buttons.

The RP2040 drives the WS2812 chain on SAO_IO0, which is pin 5 of the SAO connector — the pin the standard reserves as the data line for addressable LEDs. It can hold up to 10 LEDs. The API lives in the mch2022-rp2040 component that the template app already includes, and needs RP2040 firmware version 9 or newer.

Set it up once:

RP2040* rp2040 = get_rp2040();
rp2040_set_gpio_dir(rp2040, 0, true);    // SAO IO0 becomes an output
rp2040_set_ws2812_mode(rp2040, 1);       // 1 = enabled, 24-bit RGB
rp2040_set_ws2812_length(rp2040, 5);     // number of LEDs on the add-on

Then write the LEDs and push the buffer out:

for (uint8_t i = 0; i < 5; i++) {
    rp2040_set_ws2812_data(rp2040, i, 0x00FF0000);   // red
}
rp2040_ws2812_trigger(rp2040);

Each value is one 32-bit word, and WS2812 LEDs take their colors in GRB order: green in bits 31-24, red in bits 23-16, blue in bits 15-8. The lowest byte is the white channel, which only 32-bit RGBW LEDs use. Nothing lights up until rp2040_ws2812_trigger runs, so you can stage a whole frame and show it at once.

Example app

mch2022-tilde-ws2812-sao-test-app by Renze Nicolai is the template app with exactly this added: it sets up the five LEDs of the tilde add-on, then fades each color up and down, first across all five LEDs together and then one LED at a time. It is a short read and a good starting point for your own add-on.