265c415f |
1 | /** |
2 | * @file ws2812.c |
3 | * @author Austin Glaser <austin.glaser@gmail.com>, Joerg Wangemann <joerg.wangemann@gmail.com> |
4 | * @brief WS2812 LED driver |
5 | * |
6 | * Copyright (C) 2016 Austin Glaser, 2017 Joerg Wangemann |
7 | * |
8 | * This software may be modified and distributed under the terms |
9 | * of the MIT license. See the LICENSE file for details. |
10 | * |
11 | * @todo Put in names and descriptions of variables which need to be defined to use this file |
12 | * |
13 | * @addtogroup WS2812 |
14 | * @{ |
15 | */ |
16 | |
17 | /* --- PRIVATE DEPENDENCIES ------------------------------------------------- */ |
18 | |
19 | // This Driver |
20 | #include "ws2812_f4.h" |
21 | |
22 | // Standard |
23 | #include <stdint.h> |
24 | |
25 | // ChibiOS |
26 | #include "ch.h" |
27 | #include "hal.h" |
28 | |
29 | #include "wait.h" |
30 | // Application |
31 | //#include "board.h" |
32 | |
33 | // TODO: Add these #define's to the headers of your project. |
34 | // Pin, timer and dma are all connected, check them all if you change one. |
35 | // Tested with STM32F4, working at 144 or 168 MHz. |
36 | //#define WS2812_LED_N 2 // Number of LEDs |
37 | //#define PORT_WS2812 GPIOB |
38 | //#define PIN_WS2812 9 |
39 | //#define WS2812_TIM_N 4 // timer, 1-11 |
40 | //#define WS2812_TIM_CH 3 // timer channel, 0-3 |
41 | //#define WS2812_DMA_STREAM STM32_DMA1_STREAM2 // DMA stream for TIMx_UP (look up in reference manual under DMA Channel selection) |
42 | //#define WS2812_DMA_CHANNEL 6 // DMA channel for TIMx_UP |
43 | // The WS2812 expects 5V signal level (or at least 0.7 * VDD). Sometimes it works |
44 | // with a 3V signal level, otherwise the easiest way to get the signal level to 5V |
45 | // is to add an external pullup resistor from the DI pin to 5V (10k will do) and |
46 | // configure the pin as open drain. |
47 | // (An SMD resistor is easily solders on the connections of a light strip) |
48 | // Uncomment the next line if an external pullup resistor is used. |
49 | //#define WS2812_EXTERNAL_PULLUP |
50 | |
51 | /* --- CONFIGURATION CHECK -------------------------------------------------- */ |
52 | |
53 | #if !defined(WS2812_LED_N) |
54 | #error WS2812 LED chain length not specified |
55 | #elif WS2812_LED_N <= 0 |
56 | #error WS2812 LED chain length set to invalid value |
57 | #endif |
58 | |
59 | #if !defined(WS2812_TIM_N) |
60 | #error WS2812 timer not specified |
61 | #endif |
62 | #if defined(STM32F2XX) || defined(STM32F4XX) || defined(STM32F7XX) |
63 | #if WS2812_TIM_N <= 2 |
64 | #define WS2812_AF 1 |
65 | #elif WS2812_TIM_N <= 5 |
66 | #define WS2812_AF 2 |
67 | #elif WS2812_TIM_N <= 11 |
68 | #define WS2812_AF 3 |
69 | #endif |
70 | #elif !defined(WS2812_AF) |
71 | #error WS2812_AF timer alternate function not specified |
72 | #endif |
73 | |
74 | #if !defined(WS2812_TIM_CH) |
75 | #error WS2812 timer channel not specified |
76 | #elif WS2812_TIM_CH >= 4 |
77 | #error WS2812 timer channel set to invalid value |
78 | #endif |
79 | |
80 | /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ |
81 | |
82 | #define WS2812_PWM_FREQUENCY (STM32_SYSCLK/2) /**< Clock frequency of PWM, must be valid with respect to system clock! */ |
83 | #define WS2812_PWM_PERIOD (WS2812_PWM_FREQUENCY/800000) /**< Clock period in ticks. 1 / 800kHz = 1.25 uS (as per datasheet) */ |
84 | |
85 | /** |
86 | * @brief Number of bit-periods to hold the data line low at the end of a frame |
87 | * |
88 | * The reset period for each frame must be at least 50 uS; so we add in 50 bit-times |
89 | * of zeroes at the end. (50 bits)*(1.25 uS/bit) = 62.5 uS, which gives us some |
90 | * slack in the timing requirements |
91 | */ |
92 | #define WS2812_RESET_BIT_N (50) |
93 | #define WS2812_COLOR_BIT_N (WS2812_LED_N*24) /**< Number of data bits */ |
94 | #define WS2812_BIT_N (WS2812_COLOR_BIT_N + WS2812_RESET_BIT_N) /**< Total number of bits in a frame */ |
95 | |
96 | /** |
97 | * @brief High period for a zero, in ticks |
98 | * |
99 | * Per the datasheet: |
100 | * WS2812: |
101 | * - T0H: 200 nS to 500 nS, inclusive |
102 | * - T0L: 650 nS to 950 nS, inclusive |
103 | * WS2812B: |
104 | * - T0H: 200 nS to 500 nS, inclusive |
105 | * - T0L: 750 nS to 1050 nS, inclusive |
106 | * |
107 | * The duty cycle is calculated for a high period of 350 nS. |
108 | */ |
109 | #define WS2812_DUTYCYCLE_0 (WS2812_PWM_FREQUENCY/(1000000000/450)) |
110 | |
111 | /** |
112 | * @brief High period for a one, in ticks |
113 | * |
114 | * Per the datasheet: |
115 | * WS2812: |
116 | * - T1H: 550 nS to 850 nS, inclusive |
117 | * - T1L: 450 nS to 750 nS, inclusive |
118 | * WS2812B: |
119 | * - T1H: 750 nS to 1050 nS, inclusive |
120 | * - T1L: 200 nS to 500 nS, inclusive |
121 | * |
122 | * The duty cycle is calculated for a high period of 800 nS. |
123 | * This is in the middle of the specifications of the WS2812 and WS2812B. |
124 | */ |
125 | #define WS2812_DUTYCYCLE_1 (WS2812_PWM_FREQUENCY/(1000000000/900)) |
126 | |
127 | /* --- PRIVATE MACROS ------------------------------------------------------- */ |
128 | |
129 | /** |
130 | * @brief Generates a reference to a numbered PWM driver |
131 | * |
132 | * @param[in] n: The driver (timer) number |
133 | * |
134 | * @return A reference to the driver |
135 | */ |
136 | #define PWMD(n) CONCAT_EXPANDED_SYMBOLS(PWMD, n) |
137 | |
138 | #define WS2812_PWMD PWMD(WS2812_TIM_N) /**< The PWM driver to use for the LED chain */ |
139 | |
140 | /** |
141 | * @brief Determine the index in @ref ws2812_frame_buffer "the frame buffer" of a given bit |
142 | * |
143 | * @param[in] led: The led index [0, @ref WS2812_LED_N) |
144 | * @param[in] byte: The byte number [0, 2] |
145 | * @param[in] bit: The bit number [0, 7] |
146 | * |
147 | * @return The bit index |
148 | */ |
149 | #define WS2812_BIT(led, byte, bit) (24*(led) + 8*(byte) + (7 - (bit))) |
150 | |
151 | /** |
152 | * @brief Determine the index in @ref ws2812_frame_buffer "the frame buffer" of a given red bit |
153 | * |
154 | * @note The red byte is the middle byte in the color packet |
155 | * |
156 | * @param[in] led: The led index [0, @ref WS2812_LED_N) |
157 | * @param[in] bit: The bit number [0, 7] |
158 | * |
159 | * @return The bit index |
160 | */ |
161 | #define WS2812_RED_BIT(led, bit) WS2812_BIT((led), 1, (bit)) |
162 | |
163 | /** |
164 | * @brief Determine the index in @ref ws2812_frame_buffer "the frame buffer" of a given green bit |
165 | * |
166 | * @note The green byte is the first byte in the color packet |
167 | * |
168 | * @param[in] led: The led index [0, @ref WS2812_LED_N) |
169 | * @param[in] bit: The bit number [0, 7] |
170 | * |
171 | * @return The bit index |
172 | */ |
173 | #define WS2812_GREEN_BIT(led, bit) WS2812_BIT((led), 0, (bit)) |
174 | |
175 | /** |
176 | * @brief Determine the index in @ref ws2812_frame_buffer "the frame buffer" of a given blue bit |
177 | * |
178 | * @note The blue byte is the last byte in the color packet |
179 | * |
180 | * @param[in] led: The led index [0, @ref WS2812_LED_N) |
181 | * @param[in] bit: The bit index [0, 7] |
182 | * |
183 | * @return The bit index |
184 | */ |
185 | #define WS2812_BLUE_BIT(led, bit) WS2812_BIT((led), 2, (bit)) |
186 | |
187 | /* --- PRIVATE VARIABLES ---------------------------------------------------- */ |
188 | |
189 | static uint32_t ws2812_frame_buffer[WS2812_BIT_N + 1]; /**< Buffer for a frame */ |
190 | |
191 | /* --- PUBLIC FUNCTIONS ----------------------------------------------------- */ |
192 | /* |
193 | * Gedanke: Double-buffer type transactions: double buffer transfers using two memory pointers for |
194 | the memory (while the DMA is reading/writing from/to a buffer, the application can |
195 | write/read to/from the other buffer). |
196 | */ |
197 | |
198 | void ws2812_init(void) |
199 | { |
200 | // Initialize led frame buffer |
201 | uint32_t i; |
202 | for (i = 0; i < WS2812_COLOR_BIT_N; i++) ws2812_frame_buffer[i] = WS2812_DUTYCYCLE_0; // All color bits are zero duty cycle |
203 | for (i = 0; i < WS2812_RESET_BIT_N; i++) ws2812_frame_buffer[i + WS2812_COLOR_BIT_N] = 0; // All reset bits are zero |
204 | // Configure pin as AF output. If there's an external pull up resistor the signal level is brought to 5V using open drain mode. |
205 | #ifdef WS2812_EXTERNAL_PULLUP |
206 | palSetPadMode(PORT_WS2812, PIN_WS2812, PAL_MODE_ALTERNATE(WS2812_AF) | PAL_STM32_OTYPE_OPENDRAIN); |
207 | #else |
208 | palSetPadMode(PORT_WS2812, PIN_WS2812, PAL_MODE_ALTERNATE(WS2812_AF) | PAL_STM32_OTYPE_PUSHPULL); //PAL_MODE_STM32_ALTERNATE_PUSHPULL); |
209 | #endif |
210 | //palClearPad(PORT_WS2812, PIN_WS2812); |
211 | //wait_ms(1); |
212 | // PWM Configuration |
213 | #pragma GCC diagnostic ignored "-Woverride-init" // Turn off override-init warning for this struct. We use the overriding ability to set a "default" channel config |
214 | static const PWMConfig ws2812_pwm_config = { |
215 | .frequency = WS2812_PWM_FREQUENCY, |
216 | .period = WS2812_PWM_PERIOD, //Mit dieser Periode wird UDE-Event erzeugt und ein neuer Wert (Länge WS2812_BIT_N) vom DMA ins CCR geschrieben |
217 | .callback = NULL, |
218 | .channels = { |
219 | [0 ... 3] = {.mode = PWM_OUTPUT_DISABLED, .callback = NULL}, // Channels default to disabled |
220 | [WS2812_TIM_CH] = {.mode = PWM_OUTPUT_ACTIVE_HIGH, .callback = NULL}, // Turn on the channel we care about |
221 | }, |
222 | .cr2 = 0, |
223 | .dier = TIM_DIER_UDE, // DMA on update event for next period |
224 | }; |
225 | #pragma GCC diagnostic pop // Restore command-line warning options |
226 | |
227 | // Configure DMA |
228 | //dmaInit(); // Joe added this |
229 | dmaStreamAllocate(WS2812_DMA_STREAM, 10, NULL, NULL); |
230 | dmaStreamSetPeripheral(WS2812_DMA_STREAM, &(WS2812_PWMD.tim->CCR[WS2812_TIM_CH])); // Ziel ist der An-Zeit im Cap-Comp-Register |
231 | dmaStreamSetMemory0(WS2812_DMA_STREAM, ws2812_frame_buffer); |
232 | dmaStreamSetTransactionSize(WS2812_DMA_STREAM, WS2812_BIT_N); |
233 | dmaStreamSetMode(WS2812_DMA_STREAM, |
234 | STM32_DMA_CR_CHSEL(WS2812_DMA_CHANNEL) | STM32_DMA_CR_DIR_M2P | STM32_DMA_CR_PSIZE_WORD | STM32_DMA_CR_MSIZE_WORD | |
235 | STM32_DMA_CR_MINC | STM32_DMA_CR_CIRC | STM32_DMA_CR_PL(3)); |
236 | // M2P: Memory 2 Periph; PL: Priority Level |
237 | |
238 | // Start DMA |
239 | dmaStreamEnable(WS2812_DMA_STREAM); |
240 | |
241 | // Configure PWM |
242 | // NOTE: It's required that preload be enabled on the timer channel CCR register. This is currently enabled in the |
243 | // ChibiOS driver code, so we don't have to do anything special to the timer. If we did, we'd have to start the timer, |
244 | // disable counting, enable the channel, and then make whatever configuration changes we need. |
245 | pwmStart(&WS2812_PWMD, &ws2812_pwm_config); |
246 | pwmEnableChannel(&WS2812_PWMD, WS2812_TIM_CH, 0); // Initial period is 0; output will be low until first duty cycle is DMA'd in |
247 | } |
248 | |
249 | ws2812_err_t ws2812_write_led(uint32_t led_number, uint8_t r, uint8_t g, uint8_t b) |
250 | { |
251 | // Check for valid LED |
252 | if (led_number > WS2812_LED_N) return WS2812_LED_INVALID; |
253 | |
254 | // Write color to frame buffer |
255 | for (uint32_t bit = 0; bit < 8; bit++) { |
256 | ws2812_frame_buffer[WS2812_RED_BIT(led_number, bit)] = ((r >> bit) & 0x01) ? WS2812_DUTYCYCLE_1 : WS2812_DUTYCYCLE_0; |
257 | ws2812_frame_buffer[WS2812_GREEN_BIT(led_number, bit)] = ((g >> bit) & 0x01) ? WS2812_DUTYCYCLE_1 : WS2812_DUTYCYCLE_0; |
258 | ws2812_frame_buffer[WS2812_BLUE_BIT(led_number, bit)] = ((b >> bit) & 0x01) ? WS2812_DUTYCYCLE_1 : WS2812_DUTYCYCLE_0; |
259 | } |
260 | |
261 | // Success |
262 | return WS2812_SUCCESS; |
263 | } |
264 | |
265 | void ws2812_setleds(LED_TYPE *ledarray, uint16_t number_of_leds) |
266 | { |
267 | for(int i = 0; i < number_of_leds; i++) { |
268 | ws2812_write_led(i, ledarray[i].r, ledarray[i].g, ledarray[i].b); |
269 | } |
270 | } |
271 | |
272 | /** @} addtogroup WS2812 */ |