ws2812_spi.c 6.6 KB

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