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 }