Merged in f5soh/librepilot/update_credits (pull request #529)
[librepilot.git] / flight / pios / stm32f4xx / pios_ws2811.c
blob5a3a8a1111afe8df64deb5ec59e7700463451b22
1 /**
2 ******************************************************************************
4 * @file pios_ws2811.c
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2014.
6 * @brief A driver for ws2811 rgb led controller.
7 * this is a plain PiOS port of the very clever solution
8 * implemented by Omri Iluz in the chibios driver here:
9 * https://github.com/omriiluz/WS2812B-LED-Driver-ChibiOS
10 * @see The GNU Public License (GPL) Version 3
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22 * for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include <pios.h>
30 #ifdef PIOS_INCLUDE_WS2811
32 #include "pios_ws2811_cfg.h"
33 #include <stm32f4xx_rcc.h>
34 #include <pios_stm32.h>
35 #include "FreeRTOS.h"
36 #include "task.h"
39 // framebuffer
40 static ledbuf_t *fb = 0;
41 // bitmask with pin to be set/reset using dma
42 static ledbuf_t dmaSource[4];
44 static const struct pios_ws2811_cfg *pios_ws2811_cfg;
45 static const struct pios_ws2811_pin_cfg *pios_ws2811_pin_cfg;
47 static void setupTimer();
48 static void setupDMA();
50 // generic wrapper around corresponding SPL functions
51 static void genericTIM_OCxInit(TIM_TypeDef *TIMx, const TIM_OCInitTypeDef *TIM_OCInitStruct, uint8_t ch);
52 static void genericTIM_OCxPreloadConfig(TIM_TypeDef *TIMx, uint16_t TIM_OCPreload, uint8_t ch);
54 // timer creates a 1.25 uS signal, with duty cycle controlled by frame buffer values
56 /* Example configuration fragment for REVOLUTION
58 #ifdef PIOS_INCLUDE_WS2811
59 #include <pios_ws2811.h>
60 #include <hwsettings.h>
61 #define PIOS_WS2811_TIM_DIVIDER (PIOS_PERIPHERAL_APB2_CLOCK / (800000 * PIOS_WS2811_TIM_PERIOD))
63 // interrupt vector for DMA streamCh1
64 void DMA2_Stream1_IRQHandler(void) __attribute__((alias("PIOS_WS2811_irq_handler")));
66 const struct pios_ws2811_pin_cfg pios_ws2811_pin_cfg[] = {
67 [HWSETTINGS_WS2811LED_OUT_SERVOOUT1] = {
68 .gpio = GPIOB,
69 .gpioInit = {
70 .GPIO_Pin = GPIO_Pin_0,
71 .GPIO_Speed = GPIO_Speed_25MHz,
72 .GPIO_Mode = GPIO_Mode_OUT,
73 .GPIO_OType = GPIO_OType_PP,
76 ....
77 [HWSETTINGS_WS2811LED_OUT_FLEXIIOPIN4] = {
78 .gpio = GPIOB,
79 .gpioInit = {
80 .GPIO_Pin = GPIO_Pin_13,
81 .GPIO_Speed = GPIO_Speed_25MHz,
82 .GPIO_Mode = GPIO_Mode_OUT,
83 .GPIO_OType = GPIO_OType_PP,
88 const struct pios_ws2811_cfg pios_ws2811_cfg = {
89 .timer = TIM1,
90 .timerInit = {
91 .TIM_Prescaler = PIOS_WS2811_TIM_DIVIDER - 1,
92 .TIM_ClockDivision = TIM_CKD_DIV1,
93 .TIM_CounterMode = TIM_CounterMode_Up,
94 // period (1.25 uS per period
95 .TIM_Period = PIOS_WS2811_TIM_PERIOD,
96 .TIM_RepetitionCounter = 0x0000,
99 .timerCh1 = 1,
100 .streamCh1 = DMA2_Stream1,
101 .timerCh2 = 3,
102 .streamCh2 = DMA2_Stream6,
103 .streamUpdate = DMA2_Stream5,
105 // DMA streamCh1, triggered by timerCh1 pwm signal.
106 // if FrameBuffer indicates, reset output value early to indicate "0" bit to ws2812
107 .dmaInitCh1 = PIOS_WS2811_DMA_UPDATE_CONFIG(DMA_Channel_6),
108 .dmaItCh1 = DMA_IT_TEIF1 | DMA_IT_TCIF1,
110 // DMA streamCh2, triggered by timerCh2 pwm signal.
111 // Reset output value late to indicate "1" bit to ws2812.
112 .dmaInitCh2 = PIOS_WS2811_DMA_CH1_CONFIG(DMA_Channel_6),
113 .dmaItCh2 = DMA_IT_TEIF6 | DMA_IT_TCIF6,
115 // DMA streamUpdate Triggered by timer update event
116 // Outputs a high logic level at beginning of a cycle
117 .dmaInitUpdate = PIOS_WS2811_DMA_CH2_CONFIG(DMA_Channel_6),
118 .dmaItUpdate = DMA_IT_TEIF5 | DMA_IT_TCIF5,
119 .dmaSource = TIM_DMA_CC1 | TIM_DMA_CC3 | TIM_DMA_Update,
121 // DMA streamCh1 interrupt vector, used to block timer at end of framebuffer transfer
122 .irq = {
123 .flags = (DMA_IT_TCIF1),
124 .init = {
125 .NVIC_IRQChannel = DMA2_Stream1_IRQn,
126 .NVIC_IRQChannelPreemptionPriority = PIOS_IRQ_PRIO_HIGH,
127 .NVIC_IRQChannelSubPriority = 0,
128 .NVIC_IRQChannelCmd = ENABLE,
133 void PIOS_WS2811_irq_handler(void)
135 PIOS_WS2811_DMA_irq_handler();
137 #endif // PIOS_INCLUDE_WS2811
142 * How it works:
143 * a timer and two channels will produce the timings events:
144 * timer period will be 1.25us
145 * Ch1 CC event will be raised at 0.40uS from the beginning of the cycle
146 * Ch2 CC event will be raised at 0.80uS from the beginning of the cycle
147 * At cycle init an Update event will be raised.
149 * Three dma streams will handle the output pin as following:
150 * - streamUpdate dma stream, triggered by update event will produce a logic 1 on the output pin
151 * - streamCh1 will bring the pin to 0 if framebuffer location is set to dmaSource value to send a "0" bit to WS281x
152 * - streamCh2 will bring pin to 0 once .8us are passed to send a "1" bit to ws281x
153 * Once StreamCh1 has finished to send the buffer the IRQ handler will stop the timer.
158 * @brief Initialize WS2811 Led Driver
159 * @details Initialize the Led Driver based on passed configuration
161 * @param[in] ws2811_cfg ws2811 driver configuration
162 * @param[in] ws2811_pin_cfg pin to be used as output
166 #define PIOS_WS2811_MAGIC 0x00281100
168 void PIOS_WS2811_Init(uint32_t *dev_id, const struct pios_ws2811_cfg *ws2811_cfg, const struct pios_ws2811_pin_cfg *ws2811_pin_cfg)
170 assert_param(ws2811_cfg);
171 assert_param(ws2811_pin_cfg);
173 pios_ws2811_pin_cfg = ws2811_pin_cfg;
174 pios_ws2811_cfg = ws2811_cfg;
175 GPIO_Init(pios_ws2811_pin_cfg->gpio, &pios_ws2811_pin_cfg->gpioInit);
176 for (uint8_t i = 0; i < 4; i++) {
177 dmaSource[i] = (ledbuf_t)pios_ws2811_pin_cfg->gpioInit.GPIO_Pin;
180 fb = (ledbuf_t *)pios_malloc(PIOS_WS2811_BUFFER_SIZE * sizeof(ledbuf_t));
181 memset(fb, 0, PIOS_WS2811_BUFFER_SIZE * sizeof(ledbuf_t));
182 const Color_t ledoff = Color_Off;
183 for (uint8_t i = 0; i < PIOS_WS2811_NUMLEDS; i++) {
184 PIOS_WS2811_setColorRGB(ledoff, i, false);
186 // Setup timers
187 setupTimer();
188 setupDMA();
190 // This is required so client (for example Notify module)
191 // can test for != 0 to know if device is configured or not.
192 *dev_id = PIOS_WS2811_MAGIC;
195 void setupTimer()
197 // Stop timer
198 TIM_Cmd(pios_ws2811_cfg->timer, DISABLE);
199 // Configure timebase and internal clock
200 TIM_TimeBaseInit(pios_ws2811_cfg->timer, &pios_ws2811_cfg->timerInit);
201 TIM_InternalClockConfig(pios_ws2811_cfg->timer);
203 genericTIM_OCxPreloadConfig(pios_ws2811_cfg->timer, TIM_OCPreload_Enable, pios_ws2811_cfg->timerCh1);
204 genericTIM_OCxPreloadConfig(pios_ws2811_cfg->timer, TIM_OCPreload_Enable, pios_ws2811_cfg->timerCh2);
205 TIM_ARRPreloadConfig(pios_ws2811_cfg->timer, ENABLE);
207 // enable outputs
208 // TIM_CtrlPWMOutputs(pios_ws2811_cfg->timer, ENABLE);
210 TIM_DMACmd(pios_ws2811_cfg->timer, pios_ws2811_cfg->dmaSource, ENABLE);
212 TIM_OCInitTypeDef oc = {
213 .TIM_OCMode = TIM_OCMode_PWM1,
214 .TIM_OutputState = TIM_OutputState_Enable,
215 .TIM_OutputNState = TIM_OutputNState_Disable,
216 .TIM_Pulse = 0,
217 .TIM_OCPolarity = TIM_OCPolarity_High,
218 .TIM_OCNPolarity = TIM_OCNPolarity_High,
219 .TIM_OCIdleState = TIM_OCIdleState_Reset,
220 .TIM_OCNIdleState = TIM_OCNIdleState_Reset,
223 // (duty in ticks) / (period in ticks) * 1.25uS (period in S) = 0.40 uS
224 oc.TIM_Pulse = PIOS_WS2811_T0_HIGH_PERIOD * PIOS_WS2811_TIM_PERIOD / 125;
225 genericTIM_OCxInit(pios_ws2811_cfg->timer, &oc, pios_ws2811_cfg->timerCh1);
226 // (duty in ticks) / (period in ticks) * 1.25uS (period in S) = 0.80 uS
227 oc.TIM_Pulse = PIOS_WS2811_T1_HIGH_PERIOD * PIOS_WS2811_TIM_PERIOD / 125;
228 genericTIM_OCxInit(pios_ws2811_cfg->timer, &oc, pios_ws2811_cfg->timerCh2);
231 void genericTIM_OCxInit(TIM_TypeDef *TIMx, const TIM_OCInitTypeDef *TIM_OCInitStruct, uint8_t ch)
233 switch (ch) {
234 case 1:
235 TIM_OC1Init(TIMx, TIM_OCInitStruct);
236 break;
237 case 2:
238 TIM_OC2Init(TIMx, TIM_OCInitStruct);
239 break;
240 case 3:
241 TIM_OC3Init(TIMx, TIM_OCInitStruct);
242 break;
243 case 4:
244 TIM_OC4Init(TIMx, TIM_OCInitStruct);
245 break;
249 void genericTIM_OCxPreloadConfig(TIM_TypeDef *TIMx, uint16_t TIM_OCPreload, uint8_t ch)
251 switch (ch) {
252 case 1:
253 TIM_OC1PreloadConfig(TIMx, TIM_OCPreload);
254 break;
255 case 2:
256 TIM_OC2PreloadConfig(TIMx, TIM_OCPreload);
257 break;
258 case 3:
259 TIM_OC3PreloadConfig(TIMx, TIM_OCPreload);
260 break;
261 case 4:
262 TIM_OC4PreloadConfig(TIMx, TIM_OCPreload);
263 break;
268 void setupDMA()
270 // Configure Ch1
271 DMA_Init(pios_ws2811_cfg->streamCh1, (DMA_InitTypeDef *)&pios_ws2811_cfg->dmaInitCh1);
272 pios_ws2811_cfg->streamCh1->PAR = (uint32_t)&pios_ws2811_pin_cfg->gpio->BSRRH;
273 pios_ws2811_cfg->streamCh1->M0AR = (uint32_t)fb;
275 NVIC_Init((NVIC_InitTypeDef *)&(pios_ws2811_cfg->irq.init));
276 DMA_ITConfig(pios_ws2811_cfg->streamCh1, DMA_IT_TC, ENABLE);
279 DMA_Init(pios_ws2811_cfg->streamCh2, (DMA_InitTypeDef *)&pios_ws2811_cfg->dmaInitCh2);
280 pios_ws2811_cfg->streamCh2->PAR = (uint32_t)&pios_ws2811_pin_cfg->gpio->BSRRH;
281 pios_ws2811_cfg->streamCh2->M0AR = (uint32_t)dmaSource;
283 DMA_Init(pios_ws2811_cfg->streamUpdate, (DMA_InitTypeDef *)&pios_ws2811_cfg->dmaInitUpdate);
284 pios_ws2811_cfg->streamUpdate->PAR = (uint32_t)&pios_ws2811_pin_cfg->gpio->BSRRL;
285 pios_ws2811_cfg->streamUpdate->M0AR = (uint32_t)dmaSource;
287 DMA_ClearITPendingBit(pios_ws2811_cfg->streamCh1, pios_ws2811_cfg->dmaItCh1);
288 DMA_ClearITPendingBit(pios_ws2811_cfg->streamCh2, pios_ws2811_cfg->dmaItCh2);
289 DMA_ClearITPendingBit(pios_ws2811_cfg->streamUpdate, pios_ws2811_cfg->dmaItUpdate);
291 DMA_Cmd(pios_ws2811_cfg->streamCh2, ENABLE);
292 DMA_Cmd(pios_ws2811_cfg->streamCh1, ENABLE);
293 DMA_Cmd(pios_ws2811_cfg->streamUpdate, ENABLE);
296 void setColor(uint8_t color, ledbuf_t *buf)
298 uint8_t i;
300 for (i = 0; i < 8; i++) {
301 buf[i] = ((color << i) & 0b10000000 ? 0x0 : dmaSource[0]);
306 * Set a led color
307 * @param c color
308 * @param led led number
309 * @param update Perform an update after changing led color
311 void PIOS_WS2811_setColorRGB(Color_t c, uint8_t led, bool update)
313 if (led >= PIOS_WS2811_NUMLEDS) {
314 return;
316 setColor(c.G, fb + (led * 24));
317 setColor(c.R, fb + 8 + (led * 24));
318 setColor(c.B, fb + 16 + (led * 24));
320 if (update) {
321 PIOS_WS2811_Update();
326 * trigger an update cycle if not already running
328 void PIOS_WS2811_Update()
330 // does not start if framebuffer is not allocated (init has not been called yet) or a transfer is still on going
331 if (!fb || (pios_ws2811_cfg->timer->CR1 & TIM_CR1_CEN)) {
332 return;
335 // reset counters for synchronization
336 pios_ws2811_cfg->timer->CNT = PIOS_WS2811_TIM_PERIOD - 1;
338 DMA_Cmd(pios_ws2811_cfg->streamCh2, ENABLE);
339 DMA_Cmd(pios_ws2811_cfg->streamCh1, ENABLE);
340 DMA_Cmd(pios_ws2811_cfg->streamUpdate, ENABLE);
341 // Start a new cycle
342 TIM_Cmd(pios_ws2811_cfg->timer, ENABLE);
346 * Stop timer once the complete framebuffer has been sent
349 void PIOS_WS2811_DMA_irq_handler()
351 pios_ws2811_pin_cfg->gpio->BSRRH = dmaSource[0];
352 pios_ws2811_cfg->timer->CR1 &= (uint16_t) ~TIM_CR1_CEN;
353 DMA_ClearFlag(pios_ws2811_cfg->streamCh1, pios_ws2811_cfg->irq.flags);
354 DMA_Cmd(pios_ws2811_cfg->streamCh2, DISABLE);
355 DMA_Cmd(pios_ws2811_cfg->streamCh1, DISABLE);
356 DMA_Cmd(pios_ws2811_cfg->streamUpdate, DISABLE);
359 #endif // PIOS_INCLUDE_WS2811