ws2812_spi.c (8844B)
- #include "ws2812.h"
- #include "gpio.h"
- #include "util.h"
- #include "chibios_config.h"
- /* Adapted from https://github.com/gamazeps/ws2812b-chibios-SPIDMA/ */
- // Define the spi your LEDs are plugged to here
- #ifndef WS2812_SPI_DRIVER
- # define WS2812_SPI_DRIVER SPID1
- #endif
- #ifndef WS2812_SPI_MOSI_PAL_MODE
- # define WS2812_SPI_MOSI_PAL_MODE 5
- #endif
- #ifndef WS2812_SPI_SCK_PAL_MODE
- # define WS2812_SPI_SCK_PAL_MODE 5
- #endif
- #ifndef WS2812_SPI_DIVISOR
- # define WS2812_SPI_DIVISOR 16
- #endif
- // Push Pull or Open Drain Configuration
- // Default Push Pull
- #ifndef WS2812_EXTERNAL_PULLUP
- # if defined(USE_GPIOV1)
- # define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE_PUSHPULL
- # else
- # define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL
- # endif
- #else
- # if defined(USE_GPIOV1)
- # define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE_OPENDRAIN
- # else
- # define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_OUTPUT_TYPE_OPENDRAIN
- # endif
- #endif
- // Define SPI config speed
- // baudrate should target 3.2MHz
- #if defined(AT32F415)
- # if WS2812_SPI_DIVISOR == 2
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (0)
- # elif WS2812_SPI_DIVISOR == 4
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
- # elif WS2812_SPI_DIVISOR == 8
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1)
- # elif WS2812_SPI_DIVISOR == 16 // default
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
- # elif WS2812_SPI_DIVISOR == 32
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2)
- # elif WS2812_SPI_DIVISOR == 64
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_0)
- # elif WS2812_SPI_DIVISOR == 128
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1)
- # elif WS2812_SPI_DIVISOR == 256
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_2 | SPI_CTRL1_MDIV_1 | SPI_CTRL1_MDIV_0)
- # elif WS2812_SPI_DIVISOR == 512
- # define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
- # elif WS2812_SPI_DIVISOR == 1024
- # define WS2812_SPI_DIVISOR_CTRL2_MDIV_X (SPI_CTRL1_MDIV_3)
- # define WS2812_SPI_DIVISOR_CTRL1_MDIV_X (SPI_CTRL1_MDIV_0)
- # else
- # error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
- # endif
- #else
- // F072 fpclk = 48MHz
- // 48/16 = 3Mhz
- # if WS2812_SPI_DIVISOR == 2
- # define WS2812_SPI_DIVISOR_CR1_BR_X (0)
- # elif WS2812_SPI_DIVISOR == 4
- # define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_0)
- # elif WS2812_SPI_DIVISOR == 8
- # define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1)
- # elif WS2812_SPI_DIVISOR == 16 // default
- # define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_1 | SPI_CR1_BR_0)
- # elif WS2812_SPI_DIVISOR == 32
- # define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2)
- # elif WS2812_SPI_DIVISOR == 64
- # define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_0)
- # elif WS2812_SPI_DIVISOR == 128
- # define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1)
- # elif WS2812_SPI_DIVISOR == 256
- # define WS2812_SPI_DIVISOR_CR1_BR_X (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
- # else
- # error "Configured WS2812_SPI_DIVISOR value is not supported at this time."
- # endif
- #endif
- // Use SPI circular buffer
- #ifdef WS2812_SPI_USE_CIRCULAR_BUFFER
- # define WS2812_SPI_BUFFER_MODE 1 // circular buffer
- #else
- # define WS2812_SPI_BUFFER_MODE 0 // normal buffer
- #endif
- #if defined(USE_GPIOV1)
- # define WS2812_SCK_OUTPUT_MODE PAL_MODE_ALTERNATE_PUSHPULL
- #else
- # define WS2812_SCK_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_SCK_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL
- #endif
- #define BYTES_FOR_LED_BYTE 4
- #ifdef WS2812_RGBW
- # define WS2812_CHANNELS 4
- #else
- # define WS2812_CHANNELS 3
- #endif
- #define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * WS2812_CHANNELS)
- #define DATA_SIZE (BYTES_FOR_LED * WS2812_LED_COUNT)
- #define RESET_SIZE (1000 * WS2812_TRST_US / (2 * WS2812_TIMING))
- #define PREAMBLE_SIZE 4
- static uint8_t txbuf[PREAMBLE_SIZE + DATA_SIZE + RESET_SIZE] = {0};
- /*
- * As the trick here is to use the SPI to send a huge pattern of 0 and 1 to
- * the ws2812b protocol, we use this helper function to translate bytes into
- * 0s and 1s for the LED (with the appropriate timing).
- */
- static uint8_t get_protocol_eq(uint8_t data, int pos) {
- uint8_t eq = 0;
- if (data & (1 << (2 * (3 - pos))))
- eq = 0b1110;
- else
- eq = 0b1000;
- if (data & (2 << (2 * (3 - pos))))
- eq += 0b11100000;
- else
- eq += 0b10000000;
- return eq;
- }
- static void set_led_color_rgb(ws2812_led_t color, int pos) {
- uint8_t* tx_start = &txbuf[PREAMBLE_SIZE];
- #if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB)
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.g, j);
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.r, j);
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.b, j);
- #elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB)
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.r, j);
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.g, j);
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.b, j);
- #elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR)
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.b, j);
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.g, j);
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.r, j);
- #endif
- #ifdef WS2812_RGBW
- for (int j = 0; j < 4; j++)
- tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 3 + j] = get_protocol_eq(color.w, j);
- #endif
- }
- ws2812_led_t ws2812_leds[WS2812_LED_COUNT];
- void ws2812_init(void) {
- palSetLineMode(WS2812_DI_PIN, WS2812_MOSI_OUTPUT_MODE);
- #ifdef WS2812_SPI_SCK_PIN
- palSetLineMode(WS2812_SPI_SCK_PIN, WS2812_SCK_OUTPUT_MODE);
- #endif // WS2812_SPI_SCK_PIN
- // TODO: more dynamic baudrate
- static const SPIConfig spicfg = {
- #ifndef HAL_LLD_SELECT_SPI_V2
- // HAL_SPI_V1
- # if SPI_SUPPORTS_CIRCULAR == TRUE
- WS2812_SPI_BUFFER_MODE,
- # endif
- NULL, // end_cb
- PAL_PORT(WS2812_DI_PIN),
- PAL_PAD(WS2812_DI_PIN),
- # if defined(WB32F3G71xx) || defined(WB32FQ95xx)
- 0,
- 0,
- WS2812_SPI_DIVISOR
- # else
- WS2812_SPI_DIVISOR_CR1_BR_X,
- 0
- # endif
- #else
- // HAL_SPI_V2
- # if SPI_SUPPORTS_CIRCULAR == TRUE
- WS2812_SPI_BUFFER_MODE,
- # endif
- # if SPI_SUPPORTS_SLAVE_MODE == TRUE
- false,
- # endif
- NULL, // data_cb
- NULL, // error_cb
- PAL_PORT(WS2812_DI_PIN),
- PAL_PAD(WS2812_DI_PIN),
- # if defined(AT32F415)
- WS2812_SPI_DIVISOR_CTRL1_MDIV_X,
- # if (WS2812_SPI_DIVISOR == 512 || WS2812_SPI_DIVISOR == 1024)
- WS2812_SPI_DIVISOR_CTRL2_MDIV_X,
- # endif
- 0
- # else
- WS2812_SPI_DIVISOR_CR1_BR_X,
- 0
- # endif
- #endif
- };
- spiAcquireBus(&WS2812_SPI_DRIVER); /* Acquire ownership of the bus. */
- spiStart(&WS2812_SPI_DRIVER, &spicfg); /* Setup transfer parameters. */
- spiSelect(&WS2812_SPI_DRIVER); /* Slave Select assertion. */
- #ifdef WS2812_SPI_USE_CIRCULAR_BUFFER
- spiStartSend(&WS2812_SPI_DRIVER, ARRAY_SIZE(txbuf), txbuf);
- #endif
- }
- void ws2812_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) {
- ws2812_leds[index].r = red;
- ws2812_leds[index].g = green;
- ws2812_leds[index].b = blue;
- #if defined(WS2812_RGBW)
- ws2812_rgb_to_rgbw(&ws2812_leds[index]);
- #endif
- }
- void ws2812_set_color_all(uint8_t red, uint8_t green, uint8_t blue) {
- for (int i = 0; i < WS2812_LED_COUNT; i++) {
- ws2812_set_color(i, red, green, blue);
- }
- }
- void ws2812_flush(void) {
- for (int i = 0; i < WS2812_LED_COUNT; i++) {
- set_led_color_rgb(ws2812_leds[i], i);
- }
- // Send async - each led takes ~0.03ms, 50 leds ~1.5ms, animations flushing faster than send will cause issues.
- // Instead spiSend can be used to send synchronously (or the thread logic can be added back).
- #ifndef WS2812_SPI_USE_CIRCULAR_BUFFER
- # ifdef WS2812_SPI_SYNC
- spiSend(&WS2812_SPI_DRIVER, ARRAY_SIZE(txbuf), txbuf);
- # else
- spiStartSend(&WS2812_SPI_DRIVER, ARRAY_SIZE(txbuf), txbuf);
- # endif
- #endif
- }