ws2812_spi.c 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. #include "quantum.h"
  2. #include "ws2812.h"
  3. /* Adapted from https://github.com/gamazeps/ws2812b-chibios-SPIDMA/ */
  4. #ifdef RGBW
  5. # error "RGBW not supported"
  6. #endif
  7. // Define the spi your LEDs are plugged to here
  8. #ifndef WS2812_SPI
  9. # define WS2812_SPI SPID1
  10. #endif
  11. #ifndef WS2812_SPI_MOSI_PAL_MODE
  12. # define WS2812_SPI_MOSI_PAL_MODE 5
  13. #endif
  14. #define BYTES_FOR_LED_BYTE 4
  15. #define NB_COLORS 3
  16. #define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * NB_COLORS)
  17. #define DATA_SIZE (BYTES_FOR_LED * RGBLED_NUM)
  18. #define RESET_SIZE 200
  19. #define PREAMBLE_SIZE 4
  20. static uint8_t txbuf[PREAMBLE_SIZE + DATA_SIZE + RESET_SIZE] = {0};
  21. /*
  22. * As the trick here is to use the SPI to send a huge pattern of 0 and 1 to
  23. * the ws2812b protocol, we use this helper function to translate bytes into
  24. * 0s and 1s for the LED (with the appropriate timing).
  25. */
  26. static uint8_t get_protocol_eq(uint8_t data, int pos) {
  27. uint8_t eq = 0;
  28. if (data & (1 << (2 * (3 - pos))))
  29. eq = 0b1110;
  30. else
  31. eq = 0b1000;
  32. if (data & (2 << (2 * (3 - pos))))
  33. eq += 0b11100000;
  34. else
  35. eq += 0b10000000;
  36. return eq;
  37. }
  38. static void set_led_color_rgb(LED_TYPE color, int pos) {
  39. uint8_t* tx_start = &txbuf[PREAMBLE_SIZE];
  40. for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.g, j);
  41. for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.r, j);
  42. 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);
  43. }
  44. void ws2812_init(void) {
  45. #if defined(USE_GPIOV1)
  46. palSetLineMode(RGB_DI_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
  47. #else
  48. palSetLineMode(RGB_DI_PIN, PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL);
  49. #endif
  50. // TODO: more dynamic baudrate
  51. static const SPIConfig spicfg = {
  52. NULL, PAL_PORT(RGB_DI_PIN), PAL_PAD(RGB_DI_PIN),
  53. SPI_CR1_BR_1 | SPI_CR1_BR_0 // baudrate : fpclk / 8 => 1tick is 0.32us (2.25 MHz)
  54. };
  55. spiAcquireBus(&WS2812_SPI); /* Acquire ownership of the bus. */
  56. spiStart(&WS2812_SPI, &spicfg); /* Setup transfer parameters. */
  57. spiSelect(&WS2812_SPI); /* Slave Select assertion. */
  58. }
  59. void ws2812_setleds(LED_TYPE* ledarray, uint16_t leds) {
  60. static bool s_init = false;
  61. if (!s_init) {
  62. ws2812_init();
  63. s_init = true;
  64. }
  65. for (uint8_t i = 0; i < leds; i++) {
  66. set_led_color_rgb(ledarray[i], i);
  67. }
  68. // Send async - each led takes ~0.03ms, 50 leds ~1.5ms, animations flushing faster than send will cause issues.
  69. // Instead spiSend can be used to send synchronously (or the thread logic can be added back).
  70. #ifdef WS2812_SPI_SYNC
  71. spiSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
  72. #else
  73. spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
  74. #endif
  75. }