qmk_firmware

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

ws2812_spi.c (8836B)


      1 #include "ws2812.h"
      2 #include "gpio.h"
      3 #include "util.h"
      4 #include "chibios_config.h"
      5 
      6 /* Adapted from https://github.com/gamazeps/ws2812b-chibios-SPIDMA/ */
      7 
      8 // Define the spi your LEDs are plugged to here
      9 #ifndef WS2812_SPI_DRIVER
     10 #    define WS2812_SPI_DRIVER SPID1
     11 #endif
     12 
     13 #ifndef WS2812_SPI_MOSI_PAL_MODE
     14 #    define WS2812_SPI_MOSI_PAL_MODE 5
     15 #endif
     16 
     17 #ifndef WS2812_SPI_SCK_PAL_MODE
     18 #    define WS2812_SPI_SCK_PAL_MODE 5
     19 #endif
     20 
     21 #ifndef WS2812_SPI_DIVISOR
     22 #    define WS2812_SPI_DIVISOR 16
     23 #endif
     24 
     25 // Push Pull or Open Drain Configuration
     26 // Default Push Pull
     27 #ifndef WS2812_EXTERNAL_PULLUP
     28 #    if defined(USE_GPIOV1)
     29 #        define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE_PUSHPULL
     30 #    else
     31 #        define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL
     32 #    endif
     33 #else
     34 #    if defined(USE_GPIOV1)
     35 #        define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE_OPENDRAIN
     36 #    else
     37 #        define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_OUTPUT_TYPE_OPENDRAIN
     38 #    endif
     39 #endif
     40 
     41 // Define SPI config speed
     42 // baudrate should target 3.2MHz
     43 #if defined(AT32F415)
     44 #    if WS2812_SPI_DIVISOR == 2
     45 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (0)
     46 #    elif WS2812_SPI_DIVISOR == 4
     47 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
     48 #    elif WS2812_SPI_DIVISOR == 8
     49 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1)
     50 #    elif WS2812_SPI_DIVISOR == 16 // default
     51 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
     52 #    elif WS2812_SPI_DIVISOR == 32
     53 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2)
     54 #    elif WS2812_SPI_DIVISOR == 64
     55 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0)
     56 #    elif WS2812_SPI_DIVISOR == 128
     57 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1)
     58 #    elif WS2812_SPI_DIVISOR == 256
     59 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
     60 #    elif WS2812_SPI_DIVISOR == 512
     61 #        define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
     62 #    elif WS2812_SPI_DIVISOR == 1024
     63 #        define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
     64 #        define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
     65 #    else
     66 #        error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
     67 #    endif
     68 #else
     69 // F072 fpclk = 48MHz
     70 // 48/16 = 3Mhz
     71 #    if WS2812_SPI_DIVISOR == 2
     72 #        define WS2812_SPI_DIVISOR_CR1_BR_X (0)
     73 #    elif WS2812_SPI_DIVISOR == 4
     74 #        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0)
     75 #    elif WS2812_SPI_DIVISOR == 8
     76 #        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1)
     77 #    elif WS2812_SPI_DIVISOR == 16 // default
     78 #        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0)
     79 #    elif WS2812_SPI_DIVISOR == 32
     80 #        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2)
     81 #    elif WS2812_SPI_DIVISOR == 64
     82 #        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0)
     83 #    elif WS2812_SPI_DIVISOR == 128
     84 #        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1)
     85 #    elif WS2812_SPI_DIVISOR == 256
     86 #        define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
     87 #    else
     88 #        error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
     89 #    endif
     90 #endif
     91 
     92 // Use SPI circular buffer
     93 #ifdef WS2812_SPI_USE_CIRCULAR_BUFFER
     94 #    define WS2812_SPI_BUFFER_MODE 1 // circular buffer
     95 #else
     96 #    define WS2812_SPI_BUFFER_MODE 0 // normal buffer
     97 #endif
     98 
     99 #if defined(USE_GPIOV1)
    100 #    define WS2812_SCK_OUTPUT_MODE PAL_MODE_ALTERNATE_PUSHPULL
    101 #else
    102 #    define WS2812_SCK_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_SCK_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL
    103 #endif
    104 
    105 #define BYTES_FOR_LED_BYTE 4
    106 #ifdef WS2812_RGBW
    107 #    define WS2812_CHANNELS 4
    108 #else
    109 #    define WS2812_CHANNELS 3
    110 #endif
    111 #define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * WS2812_CHANNELS)
    112 #define DATA_SIZE (BYTES_FOR_LED * WS2812_LED_COUNT)
    113 #define RESET_SIZE (1000 * WS2812_TRST_US / (2 * WS2812_TIMING))
    114 #define PREAMBLE_SIZE 4
    115 
    116 static uint8_t txbuf[PREAMBLE_SIZE + DATA_SIZE + RESET_SIZE] = {0};
    117 
    118 /*
    119  * As the trick here is to use the SPI to send a huge pattern of 0 and 1 to
    120  * the ws2812b protocol, we use this helper function to translate bytes into
    121  * 0s and 1s for the LED (with the appropriate timing).
    122  */
    123 static uint8_t get_protocol_eq(uint8_t data, int pos) {
    124     uint8_t eq = 0;
    125     if (data & (1 << (2 * (3 - pos))))
    126         eq = 0b1110;
    127     else
    128         eq = 0b1000;
    129     if (data & (2 << (2 * (3 - pos))))
    130         eq += 0b11100000;
    131     else
    132         eq += 0b10000000;
    133     return eq;
    134 }
    135 
    136 static void set_led_color_rgb(ws2812_led_t color, int pos) {
    137     uint8_t* tx_start = &txbuf[PREAMBLE_SIZE];
    138 
    139 #if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB)
    140     for (int j = 0; j < 4; j++)
    141         tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.g, j);
    142     for (int j = 0; j < 4; j++)
    143         tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.r, j);
    144     for (int j = 0; j < 4; j++)
    145         tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.b, j);
    146 #elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB)
    147     for (int j = 0; j < 4; j++)
    148         tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.r, j);
    149     for (int j = 0; j < 4; j++)
    150         tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.g, j);
    151     for (int j = 0; j < 4; j++)
    152         tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.b, j);
    153 #elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR)
    154     for (int j = 0; j < 4; j++)
    155         tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.b, j);
    156     for (int j = 0; j < 4; j++)
    157         tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.g, j);
    158     for (int j = 0; j < 4; j++)
    159         tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.r, j);
    160 #endif
    161 #ifdef WS2812_RGBW
    162     for (int j = 0; j < 4; j++)
    163         tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 3 + j] = get_protocol_eq(color.w, j);
    164 #endif
    165 }
    166 
    167 ws2812_led_t ws2812_leds[WS2812_LED_COUNT];
    168 
    169 void ws2812_init(void) {
    170     palSetLineMode(WS2812_DI_PIN, WS2812_MOSI_OUTPUT_MODE);
    171 
    172 #ifdef WS2812_SPI_SCK_PIN
    173     palSetLineMode(WS2812_SPI_SCK_PIN, WS2812_SCK_OUTPUT_MODE);
    174 #endif // WS2812_SPI_SCK_PIN
    175 
    176     // TODO: more dynamic baudrate
    177     static const SPIConfig spicfg = {
    178 #ifndef HAL_LLD_SELECT_SPI_V2
    179 // HAL_SPI_V1
    180 #    if SPI_SUPPORTS_CIRCULAR == TRUE
    181         WS2812_SPI_BUFFER_MODE,
    182 #    endif
    183         NULL, // end_cb
    184         PAL_PORT(WS2812_DI_PIN),
    185         PAL_PAD(WS2812_DI_PIN),
    186 #    if defined(WB32F3G71xx) || defined(WB32FQ95xx)
    187         0,
    188         0,
    189         WS2812_SPI_DIVISOR
    190 #    else
    191         WS2812_SPI_DIVISOR_CR1_BR_X, 0
    192 #    endif
    193 #else
    194     // HAL_SPI_V2
    195 #    if SPI_SUPPORTS_CIRCULAR == TRUE
    196         WS2812_SPI_BUFFER_MODE,
    197 #    endif
    198 #    if SPI_SUPPORTS_SLAVE_MODE == TRUE
    199         false,
    200 #    endif
    201         NULL, // data_cb
    202         NULL, // error_cb
    203         PAL_PORT(WS2812_DI_PIN),
    204         PAL_PAD(WS2812_DI_PIN),
    205 #    if defined(AT32F415)
    206         WS2812_SPI_DIVISOR_CTRL1_MDIV_X,
    207 #        if (WS2812_SPI_DIVISOR == 512 || WS2812_SPI_DIVISOR == 1024)
    208         WS2812_SPI_DIVISOR_CTRL2_MDIV_X,
    209 #        endif
    210         0
    211 #    else
    212         WS2812_SPI_DIVISOR_CR1_BR_X,
    213         0
    214 #    endif
    215 #endif
    216     };
    217 
    218     spiAcquireBus(&WS2812_SPI_DRIVER);     /* Acquire ownership of the bus.    */
    219     spiStart(&WS2812_SPI_DRIVER, &spicfg); /* Setup transfer parameters.       */
    220     spiSelect(&WS2812_SPI_DRIVER);         /* Slave Select assertion.          */
    221 #ifdef WS2812_SPI_USE_CIRCULAR_BUFFER
    222     spiStartSend(&WS2812_SPI_DRIVER, ARRAY_SIZE(txbuf), txbuf);
    223 #endif
    224 }
    225 
    226 void ws2812_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) {
    227     ws2812_leds[index].r = red;
    228     ws2812_leds[index].g = green;
    229     ws2812_leds[index].b = blue;
    230 #if defined(WS2812_RGBW)
    231     ws2812_rgb_to_rgbw(&ws2812_leds[index]);
    232 #endif
    233 }
    234 
    235 void ws2812_set_color_all(uint8_t red, uint8_t green, uint8_t blue) {
    236     for (int i = 0; i < WS2812_LED_COUNT; i++) {
    237         ws2812_set_color(i, red, green, blue);
    238     }
    239 }
    240 
    241 void ws2812_flush(void) {
    242     for (int i = 0; i < WS2812_LED_COUNT; i++) {
    243         set_led_color_rgb(ws2812_leds[i], i);
    244     }
    245 
    246     // Send async - each led takes ~0.03ms, 50 leds ~1.5ms, animations flushing faster than send will cause issues.
    247     // Instead spiSend can be used to send synchronously (or the thread logic can be added back).
    248 #ifndef WS2812_SPI_USE_CIRCULAR_BUFFER
    249 #    ifdef WS2812_SPI_SYNC
    250     spiSend(&WS2812_SPI_DRIVER, ARRAY_SIZE(txbuf), txbuf);
    251 #    else
    252     spiStartSend(&WS2812_SPI_DRIVER, ARRAY_SIZE(txbuf), txbuf);
    253 #    endif
    254 #endif
    255 }