aboutsummaryrefslogtreecommitdiff
path: root/platforms/chibios/drivers/ws2812_spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'platforms/chibios/drivers/ws2812_spi.c')
-rw-r--r--platforms/chibios/drivers/ws2812_spi.c159
1 files changed, 159 insertions, 0 deletions
diff --git a/platforms/chibios/drivers/ws2812_spi.c b/platforms/chibios/drivers/ws2812_spi.c
new file mode 100644
index 000000000..377a929b9
--- /dev/null
+++ b/platforms/chibios/drivers/ws2812_spi.c
@@ -0,0 +1,159 @@
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#ifndef WS2812_SPI_SCK_PAL_MODE
20# define WS2812_SPI_SCK_PAL_MODE 5
21#endif
22
23// Push Pull or Open Drain Configuration
24// Default Push Pull
25#ifndef WS2812_EXTERNAL_PULLUP
26# if defined(USE_GPIOV1)
27# define WS2812_MOSI_OUTPUT_MODE PAL_MODE_STM32_ALTERNATE_PUSHPULL
28# else
29# define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL
30# endif
31#else
32# if defined(USE_GPIOV1)
33# define WS2812_MOSI_OUTPUT_MODE PAL_MODE_STM32_ALTERNATE_OPENDRAIN
34# else
35# define WS2812_MOSI_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_STM32_OTYPE_OPENDRAIN
36# endif
37#endif
38
39// Define SPI config speed
40// baudrate should target 3.2MHz
41// F072 fpclk = 48MHz
42// 48/16 = 3Mhz
43#if WS2812_SPI_DIVISOR == 2
44# define WS2812_SPI_DIVISOR (0)
45#elif WS2812_SPI_DIVISOR == 4
46# define WS2812_SPI_DIVISOR (SPI_CR1_BR_0)
47#elif WS2812_SPI_DIVISOR == 8
48# define WS2812_SPI_DIVISOR (SPI_CR1_BR_1)
49#elif WS2812_SPI_DIVISOR == 16 // same as default
50# define WS2812_SPI_DIVISOR (SPI_CR1_BR_1 | SPI_CR1_BR_0)
51#elif WS2812_SPI_DIVISOR == 32
52# define WS2812_SPI_DIVISOR (SPI_CR1_BR_2)
53#elif WS2812_SPI_DIVISOR == 64
54# define WS2812_SPI_DIVISOR (SPI_CR1_BR_2 | SPI_CR1_BR_0)
55#elif WS2812_SPI_DIVISOR == 128
56# define WS2812_SPI_DIVISOR (SPI_CR1_BR_2 | SPI_CR1_BR_1)
57#elif WS2812_SPI_DIVISOR == 256
58# define WS2812_SPI_DIVISOR (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
59#else
60# define WS2812_SPI_DIVISOR (SPI_CR1_BR_1 | SPI_CR1_BR_0) // default
61#endif
62
63// Use SPI circular buffer
64#ifdef WS2812_SPI_USE_CIRCULAR_BUFFER
65# define WS2812_SPI_BUFFER_MODE 1 // circular buffer
66#else
67# define WS2812_SPI_BUFFER_MODE 0 // normal buffer
68#endif
69
70#if defined(USE_GPIOV1)
71# define WS2812_SCK_OUTPUT_MODE PAL_MODE_STM32_ALTERNATE_PUSHPULL
72#else
73# define WS2812_SCK_OUTPUT_MODE PAL_MODE_ALTERNATE(WS2812_SPI_SCK_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL
74#endif
75
76#define BYTES_FOR_LED_BYTE 4
77#define NB_COLORS 3
78#define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * NB_COLORS)
79#define DATA_SIZE (BYTES_FOR_LED * RGBLED_NUM)
80#define RESET_SIZE (1000 * WS2812_TRST_US / (2 * 1250))
81#define PREAMBLE_SIZE 4
82
83static uint8_t txbuf[PREAMBLE_SIZE + DATA_SIZE + RESET_SIZE] = {0};
84
85/*
86 * As the trick here is to use the SPI to send a huge pattern of 0 and 1 to
87 * the ws2812b protocol, we use this helper function to translate bytes into
88 * 0s and 1s for the LED (with the appropriate timing).
89 */
90static uint8_t get_protocol_eq(uint8_t data, int pos) {
91 uint8_t eq = 0;
92 if (data & (1 << (2 * (3 - pos))))
93 eq = 0b1110;
94 else
95 eq = 0b1000;
96 if (data & (2 << (2 * (3 - pos))))
97 eq += 0b11100000;
98 else
99 eq += 0b10000000;
100 return eq;
101}
102
103static void set_led_color_rgb(LED_TYPE color, int pos) {
104 uint8_t* tx_start = &txbuf[PREAMBLE_SIZE];
105
106#if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB)
107 for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.g, j);
108 for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.r, j);
109 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);
110#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB)
111 for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.r, j);
112 for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.g, j);
113 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);
114#elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR)
115 for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.b, j);
116 for (int j = 0; j < 4; j++) 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++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.r, j);
118#endif
119}
120
121void ws2812_init(void) {
122 palSetLineMode(RGB_DI_PIN, WS2812_MOSI_OUTPUT_MODE);
123
124#ifdef WS2812_SPI_SCK_PIN
125 palSetLineMode(WS2812_SPI_SCK_PIN, WS2812_SCK_OUTPUT_MODE);
126#endif // WS2812_SPI_SCK_PIN
127
128 // TODO: more dynamic baudrate
129 static const SPIConfig spicfg = {WS2812_SPI_BUFFER_MODE, NULL, PAL_PORT(RGB_DI_PIN), PAL_PAD(RGB_DI_PIN), WS2812_SPI_DIVISOR};
130
131 spiAcquireBus(&WS2812_SPI); /* Acquire ownership of the bus. */
132 spiStart(&WS2812_SPI, &spicfg); /* Setup transfer parameters. */
133 spiSelect(&WS2812_SPI); /* Slave Select assertion. */
134#ifdef WS2812_SPI_USE_CIRCULAR_BUFFER
135 spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
136#endif
137}
138
139void ws2812_setleds(LED_TYPE* ledarray, uint16_t leds) {
140 static bool s_init = false;
141 if (!s_init) {
142 ws2812_init();
143 s_init = true;
144 }
145
146 for (uint8_t i = 0; i < leds; i++) {
147 set_led_color_rgb(ledarray[i], i);
148 }
149
150 // Send async - each led takes ~0.03ms, 50 leds ~1.5ms, animations flushing faster than send will cause issues.
151 // Instead spiSend can be used to send synchronously (or the thread logic can be added back).
152#ifndef WS2812_SPI_USE_CIRCULAR_BUFFER
153# ifdef WS2812_SPI_SYNC
154 spiSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
155# else
156 spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
157# endif
158#endif
159}