qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

ws2812.md (14472B)


      1 # WS2812 Driver {#ws2812-driver}
      2 
      3 This driver provides support for WorldSemi addressable RGB(W) LEDs, and compatible equivalents:
      4 
      5  * WS2811, WS2812, WS2812B, WS2812C, etc.
      6  * SK6812, SK6812MINI, SK6805
      7 
      8 These LEDs are often called "addressable" because instead of using a wire per color (and per LED), each LED contains a small microchip that understands a special protocol sent over a single wire.
      9 The LEDs can be chained together, and the remaining data is passed on to the next. In this way, you can easily control the color of many LEDs using a single GPIO.
     10 
     11 ## Usage {#usage}
     12 
     13 In most cases, the WS2812 driver code is automatically included if you are using either the [RGBLight](../features/rgblight) or [RGB Matrix](../features/rgb_matrix) feature with the `ws2812` driver set, and you would use those APIs instead.
     14 
     15 However, if you need to use the driver standalone, add the following to your `rules.mk`:
     16 
     17 ```make
     18 WS2812_DRIVER_REQUIRED = yes
     19 ```
     20 
     21 You can then call the WS2812 API by including `ws2812.h` in your code.
     22 
     23 ## Basic Configuration {#basic-configuration}
     24 
     25 Add the following to your `config.h`:
     26 
     27 |Define             |Default                |Description                                                                                     |
     28 |-------------------|-----------------------|------------------------------------------------------------------------------------------------|
     29 |`WS2812_DI_PIN`    |*Not defined*          |The GPIO pin connected to the DI pin of the first LED in the chain                              |
     30 |`WS2812_LED_COUNT` |*Not defined*          |Number of LEDs in the WS2812 chain - automatically set when RGBLight or RGB Matrix is configured|
     31 |`WS2812_TIMING`    |`1250`                 |The total length of a bit (TH+TL) in nanoseconds                                                |
     32 |`WS2812_T1H`       |`900`                  |The length of a "1" bit's high phase in nanoseconds                                             |
     33 |`WS2812_T0H`       |`350`                  |The length of a "0" bit's high phase in nanoseconds                                             |
     34 |`WS2812_TRST_US`   |`280`                  |The length of the reset phase in microseconds                                                   |
     35 |`WS2812_BYTE_ORDER`|`WS2812_BYTE_ORDER_GRB`|The byte order of the RGB data                                                                  |
     36 |`WS2812_RGBW`      |*Not defined*          |Enables RGBW support (except `i2c` driver)                                                      |
     37 
     38 ### Timing Adjustment {#timing-adjustment}
     39 
     40 The WS2812 LED communication protocol works by encoding a "1" bit with a long high pulse (T<sub>1</sub>H), and a "0" bit with a shorter pulse (T<sub>0</sub>H). The total cycle length of a bit is the same.
     41 The "reset" pulse (T<sub>RST</sub>) latches the sent RGB data to all of the LEDs and denotes a completed "frame".
     42 
     43 Some WS2812 variants have slightly different timing parameter requirements, which can be accounted for if necessary using the above `#define`s in your `config.h`.
     44 
     45 ### Byte Order {#byte-order}
     46 
     47 Some WS2812 variants may have their color components in a different physical or logical order. For example, the WS2812B-2020 has physically swapped red and green LEDs, which causes the wrong color to be displayed, because the default order of the bytes sent over the wire is defined as GRB.
     48 If you find your LED colors are consistently swapped, you may need to change the byte order by adding the following to your `config.h`:
     49 
     50 ```c
     51 #define WS2812_BYTE_ORDER WS2812_BYTE_ORDER_GRB
     52 ```
     53 
     54 Where the byte order may be one of:
     55 
     56 |Byte Order|Known Devices               |
     57 |----------|----------------------------|
     58 |`GRB`     |Most WS2812s, SK6812, SK6805|
     59 |`RGB`     |WS2812B-2020                |
     60 |`BGR`     |TM1812                      |
     61 
     62 ### RGBW Support {#rgbw-support}
     63 
     64 Rendering the color white with RGB LEDs is typically inconsistent due to inherent variations between each individual LED die. However, some WS2812 variants (such as SK6812RGBW) also possess a white LED along with the red, green, and blue channels, which allows for a more accurate white to be displayed.
     65 
     66 QMK can automatically convert the RGB data to be sent to the LEDs to mix in the white channel:
     67 
     68 ```
     69 w = min(r, g, b)
     70 r -= w
     71 g -= w
     72 b -= w
     73 ```
     74 
     75 Thus, an RGB triplet of `255,255,255` will simply turn on the white LED fully (`0,0,0,255`).
     76 
     77 To enable RGBW conversion, add the following to your `config.h`:
     78 
     79 ```c
     80 #define WS2812_RGBW
     81 ```
     82 
     83 ## Driver Configuration {#driver-configuration}
     84 
     85 Driver selection can be configured in `rules.mk` as `WS2812_DRIVER`, or in `info.json` as `ws2812.driver`. Valid values are `bitbang` (default), `i2c`, `spi`, `pwm`, `vendor`, or `custom`. See below for information on individual drivers.
     86 
     87 ### Bitbang Driver {#bitbang-driver}
     88 
     89 This is the default WS2812 driver. It operates by "bit-banging" ie. directly toggling the GPIO.
     90 
     91 Please note that on AVR devices, due to the tight timing requirements longer chains and/or heavy CPU loads may cause visible lag. Unfortunately this driver is usually the only option for AVR.
     92 
     93 ```make
     94 WS2812_DRIVER = bitbang
     95 ```
     96 
     97 ### I2C Driver {#i2c-driver}
     98 
     99 A specialized driver mainly used for PS2AVRGB (Bootmapper Client) boards, which possess an ATtiny85 that handles the WS2812 LEDs.
    100 
    101 ```make
    102 WS2812_DRIVER = i2c
    103 ```
    104 
    105 The following `#define`s apply only to the `i2c` driver:
    106 
    107 |Define              |Default|Description                      |
    108 |--------------------|-------|---------------------------------|
    109 |`WS2812_I2C_ADDRESS`|`0xB0` |The I2C address of the ATtiny85. |
    110 |`WS2812_I2C_TIMEOUT`|`100`  |The I2C timeout, in milliseconds.|
    111 
    112 ### PIO Driver {#pio-driver}
    113 
    114 This driver is RP2040-only, and leverages the onboard PIO (programmable I/O) system and DMA to offload processing from the CPU.
    115 
    116 The WS2812 PIO program uses one state machine, six instructions and one DMA interrupt handler callback. Due to the implementation the time resolution for this driver is 50 ns - any value not specified in this interval will be rounded to the next matching interval.
    117 
    118 ```make
    119 WS2812_DRIVER = vendor
    120 ```
    121 
    122 ### PWM Driver {#pwm-driver}
    123 
    124 This driver is ARM-only, and leverages the onboard PWM peripheral and DMA to offload processing from the CPU.
    125 
    126 ```make
    127 WS2812_DRIVER = pwm
    128 ```
    129 
    130 ### SPI Driver {#spi-driver}
    131 
    132 This driver is ARM-only, and leverages the onboard SPI peripheral and DMA to offload processing from the CPU. The DI pin **must** be connected to the MOSI pin on the MCU, and all other SPI pins **must** be left unused. This is also very dependent on your MCU's SPI peripheral clock speed, and may or may not be possible depending on the MCU selected.
    133 
    134 ```make
    135 WS2812_DRIVER = spi
    136 ```
    137 
    138 ## ChibiOS/ARM Configuration {#arm-configuration}
    139 
    140 The following defines apply only to ARM devices:
    141 
    142 |Define      |Default                       |Description                                                                      |
    143 |------------|------------------------------|---------------------------------------------------------------------------------|
    144 |`WS2812_T1L`|`(WS2812_TIMING - WS2812_T1H)`|The length of a "1" bit's low phase in nanoseconds (bitbang and PIO drivers only)|
    145 |`WS2812_T0L`|`(WS2812_TIMING - WS2812_T0H)`|The length of a "0" bit's low phase in nanoseconds (bitbang and PIO drivers only)|
    146 
    147 ### Logic Levels {#logic-levels}
    148 
    149 WS2812 LEDs usually operate at 5V, but some microcontrollers, particularly ARM-based ones, run on 3.3V. This can pose an issue when driving the LED chain as the logic level voltage is lower than the power supply voltage, leading to unreliable data transmission. There are two main workarounds:
    150 
    151 #### 1. Open Drain Circuit {#open-drain-circuit}
    152 
    153 By default, `WS2812_DI_PIN` is configured as a *push-pull* output, meaning the pin is effectively always driven either to VCC or to ground; however, it can be configured in *open drain* mode instead.
    154 
    155 In this mode, the MCU will only pull the GPIO *low*, and leaves it floating otherwise. A pullup resistor (typically around 10kΩ) between DI and 5V is then responsible for pulling the line high when the MCU is not driving the GPIO.
    156 
    157 To use the DI pin in open drain configuration, add the following to your `config.h`:
    158 
    159 ```c
    160 #define WS2812_EXTERNAL_PULLUP
    161 ```
    162 
    163 ::: warning
    164 Because the GPIO is being pulled to 5V in this situation rather than VCC (3.3V), **it must be a 5V tolerant pin**. Consult your MCU's datasheet first – if there are no eligible pins, you must use a level shifter instead.
    165 :::
    166 
    167 #### 2. Level Shifter {#level-shifter}
    168 
    169 A level shifter IC, such as the SN74LV1T34, can be placed between the GPIO and the first LED's DI pin to convert the 3.3V logic to 5V. This requires no additional configuration in the firmware, nor a 5V tolerant GPIO, but may be more expensive and is generally less handwire-friendly.
    170 
    171 ### SPI Driver {#arm-spi-driver}
    172 
    173 Depending on the ChibiOS board configuration, you may need to enable SPI at the keyboard level. For STM32, this would look like:
    174 
    175 ::: code-group
    176 ```c [halconf.h]
    177 #pragma once
    178 
    179 #define HAL_USE_SPI TRUE // [!code focus]
    180 
    181 #include_next <halconf.h>
    182 ```
    183 ```c [mcuconf.h]
    184 #pragma once
    185 
    186 #include_next <mcuconf.h>
    187 
    188 #undef STM32_SPI_USE_SPI1 // [!code focus]
    189 #define STM32_SPI_USE_SPI1 TRUE // [!code focus]
    190 ```
    191 :::
    192 
    193 The following `define`s apply only to the `spi` driver:
    194 
    195 |Define                          |Default      |Description                                                                    |
    196 |--------------------------------|-------------|-------------------------------------------------------------------------------|
    197 |`WS2812_SPI_DRIVER`             |`SPID1`      |The SPI driver to use                                                          |
    198 |`WS2812_SPI_MOSI_PAL_MODE`      |`5`          |The MOSI pin alternative function to use                                       |
    199 |`WS2812_SPI_SCK_PIN`            |*Not defined*|The SCK pin - required for F072 and possibly others                            |
    200 |`WS2812_SPI_SCK_PAL_MODE`       |`5`          |The SCK pin alternative function to use - required for F072 and possibly others|
    201 |`WS2812_SPI_DIVISOR`            |`16`         |The divisor used to adjust the baudrate                                        |
    202 |`WS2812_SPI_USE_CIRCULAR_BUFFER`|*Not defined*|Enable a circular buffer for improved rendering                                |
    203 
    204 #### Setting the Baudrate {#arm-spi-baudrate}
    205 
    206 To adjust the SPI baudrate, you will need to derive the target baudrate from the clock tree provided by STM32CubeMX, and add the following to your `config.h`:
    207 
    208 ```c
    209 #define WS2812_SPI_DIVISOR 16
    210 ```
    211 
    212 Only divisors of 2, 4, 8, 16, 32, 64, 128 and 256 are supported on STM32 devices. Other MCUs may have similar constraints -- check the reference manual for your respective MCU for specifics.
    213 
    214 #### Circular Buffer {#arm-spi-circular-buffer}
    215 
    216 A circular buffer can be enabled if you experience flickering.
    217 
    218 To enable the circular buffer, add the following to your `config.h`:
    219 
    220 ```c
    221 #define WS2812_SPI_USE_CIRCULAR_BUFFER
    222 ```
    223 
    224 ### PIO Driver {#arm-pio-driver}
    225 
    226 The following `#define`s apply only to the PIO driver:
    227 
    228 |Define               |Default      |Description                            |
    229 |---------------------|-------------|---------------------------------------|
    230 |`WS2812_PIO_USE_PIO1`|*Not defined*|Use the PIO1 peripheral instead of PIO0|
    231 
    232 ### PWM Driver {#arm-pwm-driver}
    233 
    234 Depending on the ChibiOS board configuration, you may need to enable PWM at the keyboard level. For STM32, this would look like:
    235 
    236 ::: code-group
    237 ```c [halconf.h]
    238 #pragma once
    239 
    240 #define HAL_USE_PWM TRUE // [!code focus]
    241 
    242 #include_next <halconf.h>
    243 ```
    244 ```c [mcuconf.h]
    245 #pragma once
    246 
    247 #include_next <mcuconf.h>
    248 
    249 #undef STM32_PWM_USE_TIM2 // [!code focus]
    250 #define STM32_PWM_USE_TIM2 TRUE // [!code focus]
    251 ```
    252 :::
    253 
    254 The following `#define`s apply only to the `pwm` driver:
    255 
    256 |Define                           |Default             |Description                                                                               |
    257 |---------------------------------|--------------------|------------------------------------------------------------------------------------------|
    258 |`WS2812_PWM_DRIVER`              |`PWMD2`             |The PWM driver to use                                                                     |
    259 |`WS2812_PWM_CHANNEL`             |`2`                 |The PWM channel to use                                                                    |
    260 |`WS2812_PWM_PAL_MODE`            |`2`                 |The pin alternative function to use                                                       |
    261 |`WS2812_PWM_DMA_STREAM`          |`STM32_DMA1_STREAM2`|The DMA Stream for `TIMx_UP`                                                              |
    262 |`WS2812_PWM_DMA_CHANNEL`         |`2`                 |The DMA Channel for `TIMx_UP`                                                             |
    263 |`WS2812_PWM_DMAMUX_ID`           |*Not defined*       |The DMAMUX configuration for `TIMx_UP` - only required if your MCU has a DMAMUX peripheral|
    264 |`WS2812_PWM_COMPLEMENTARY_OUTPUT`|*Not defined*       |Whether the PWM output is complementary (`TIMx_CHyN`)                                     |
    265 
    266 ::: tip
    267 Using a complementary timer output (`TIMx_CHyN`) is possible only for advanced-control timers (1, 8 and 20 on STM32). Complementary outputs of general-purpose timers are not supported due to ChibiOS limitations.
    268 :::
    269 
    270 ## API {#api}
    271 
    272 ### `void ws2812_init(void)` {#api-ws2812-init}
    273 
    274 Initialize the LED driver. This function should be called first.
    275 
    276 ---
    277 
    278 ### `void ws2812_set_color(int index, uint8_t red, uint8_t green, uint8_t blue)` {#api-ws2812-set-color}
    279 
    280 Set the color of a single LED. This function does not immediately update the LEDs; call `ws2812_flush()` after you are finished.
    281 
    282 #### Arguments {#api-ws2812-set-color-arguments}
    283 
    284  - `int index`  
    285    The LED index in the WS2812 chain.
    286  - `uint8_t red`  
    287    The red value to set.
    288  - `uint8_t green`  
    289    The green value to set.
    290  - `uint8_t blue`  
    291    The blue value to set.
    292 
    293 ---
    294 
    295 ### `void ws2812_set_color_all(uint8_t red, uint8_t green, uint8_t blue)` {#api-ws2812-set-color-all}
    296 
    297 Set the color of all LEDs.
    298 
    299 #### Arguments {#api-ws2812-set-color-all-arguments}
    300 
    301  - `uint8_t red`  
    302    The red value to set.
    303  - `uint8_t green`  
    304    The green value to set.
    305  - `uint8_t blue`  
    306    The blue value to set.
    307 
    308 ---
    309 
    310 ### `void ws2812_flush(void)` {#api-ws2812-flush}
    311 
    312 Flush the PWM values to the LED chain.