ws2812_spi.c 6.0 KB

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