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