REVONANO Milestones update
[librepilot.git] / flight / pios / stm32f4xx / pios_ws2811.c
blobf0a9ddf29849ffcc7c2909f1ff7c08192730bae1
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 void PIOS_WS2811_Init(const struct pios_ws2811_cfg *ws2811_cfg, const struct pios_ws2811_pin_cfg *ws2811_pin_cfg)
168 assert_param(ws2811_cfg);
169 assert_param(ws2811_pin_cfg);
171 pios_ws2811_pin_cfg = ws2811_pin_cfg;
172 pios_ws2811_cfg = ws2811_cfg;
173 GPIO_Init(pios_ws2811_pin_cfg->gpio, &pios_ws2811_pin_cfg->gpioInit);
174 for (uint8_t i = 0; i < 4; i++) {
175 dmaSource[i] = (ledbuf_t)pios_ws2811_pin_cfg->gpioInit.GPIO_Pin;
178 fb = (ledbuf_t *)pios_malloc(PIOS_WS2811_BUFFER_SIZE * sizeof(ledbuf_t));
179 memset(fb, 0, PIOS_WS2811_BUFFER_SIZE * sizeof(ledbuf_t));
180 const Color_t ledoff = Color_Off;
181 for (uint8_t i = 0; i < PIOS_WS2811_NUMLEDS; i++) {
182 PIOS_WS2811_setColorRGB(ledoff, i, false);
184 // Setup timers
185 setupTimer();
186 setupDMA();
189 void setupTimer()
191 // Stop timer
192 TIM_Cmd(pios_ws2811_cfg->timer, DISABLE);
193 // Configure timebase and internal clock
194 TIM_TimeBaseInit(pios_ws2811_cfg->timer, &pios_ws2811_cfg->timerInit);
195 TIM_InternalClockConfig(pios_ws2811_cfg->timer);
197 genericTIM_OCxPreloadConfig(pios_ws2811_cfg->timer, TIM_OCPreload_Enable, pios_ws2811_cfg->timerCh1);
198 genericTIM_OCxPreloadConfig(pios_ws2811_cfg->timer, TIM_OCPreload_Enable, pios_ws2811_cfg->timerCh2);
199 TIM_ARRPreloadConfig(pios_ws2811_cfg->timer, ENABLE);
201 // enable outputs
202 // TIM_CtrlPWMOutputs(pios_ws2811_cfg->timer, ENABLE);
204 TIM_DMACmd(pios_ws2811_cfg->timer, pios_ws2811_cfg->dmaSource, ENABLE);
206 TIM_OCInitTypeDef oc = {
207 .TIM_OCMode = TIM_OCMode_PWM1,
208 .TIM_OutputState = TIM_OutputState_Enable,
209 .TIM_OutputNState = TIM_OutputNState_Disable,
210 .TIM_Pulse = 0,
211 .TIM_OCPolarity = TIM_OCPolarity_High,
212 .TIM_OCNPolarity = TIM_OCNPolarity_High,
213 .TIM_OCIdleState = TIM_OCIdleState_Reset,
214 .TIM_OCNIdleState = TIM_OCNIdleState_Reset,
217 // (duty in ticks) / (period in ticks) * 1.25uS (period in S) = 0.40 uS
218 oc.TIM_Pulse = PIOS_WS2811_T0_HIGH_PERIOD * PIOS_WS2811_TIM_PERIOD / 125;
219 genericTIM_OCxInit(pios_ws2811_cfg->timer, &oc, pios_ws2811_cfg->timerCh1);
220 // (duty in ticks) / (period in ticks) * 1.25uS (period in S) = 0.80 uS
221 oc.TIM_Pulse = PIOS_WS2811_T1_HIGH_PERIOD * PIOS_WS2811_TIM_PERIOD / 125;
222 genericTIM_OCxInit(pios_ws2811_cfg->timer, &oc, pios_ws2811_cfg->timerCh2);
225 void genericTIM_OCxInit(TIM_TypeDef *TIMx, const TIM_OCInitTypeDef *TIM_OCInitStruct, uint8_t ch)
227 switch (ch) {
228 case 1:
229 TIM_OC1Init(TIMx, TIM_OCInitStruct);
230 break;
231 case 2:
232 TIM_OC2Init(TIMx, TIM_OCInitStruct);
233 break;
234 case 3:
235 TIM_OC3Init(TIMx, TIM_OCInitStruct);
236 break;
237 case 4:
238 TIM_OC4Init(TIMx, TIM_OCInitStruct);
239 break;
243 void genericTIM_OCxPreloadConfig(TIM_TypeDef *TIMx, uint16_t TIM_OCPreload, uint8_t ch)
245 switch (ch) {
246 case 1:
247 TIM_OC1PreloadConfig(TIMx, TIM_OCPreload);
248 break;
249 case 2:
250 TIM_OC2PreloadConfig(TIMx, TIM_OCPreload);
251 break;
252 case 3:
253 TIM_OC3PreloadConfig(TIMx, TIM_OCPreload);
254 break;
255 case 4:
256 TIM_OC4PreloadConfig(TIMx, TIM_OCPreload);
257 break;
262 void setupDMA()
264 // Configure Ch1
265 DMA_Init(pios_ws2811_cfg->streamCh1, (DMA_InitTypeDef *)&pios_ws2811_cfg->dmaInitCh1);
266 pios_ws2811_cfg->streamCh1->PAR = (uint32_t)&pios_ws2811_pin_cfg->gpio->BSRRH;
267 pios_ws2811_cfg->streamCh1->M0AR = (uint32_t)fb;
269 NVIC_Init((NVIC_InitTypeDef *)&(pios_ws2811_cfg->irq.init));
270 DMA_ITConfig(pios_ws2811_cfg->streamCh1, DMA_IT_TC, ENABLE);
273 DMA_Init(pios_ws2811_cfg->streamCh2, (DMA_InitTypeDef *)&pios_ws2811_cfg->dmaInitCh2);
274 pios_ws2811_cfg->streamCh2->PAR = (uint32_t)&pios_ws2811_pin_cfg->gpio->BSRRH;
275 pios_ws2811_cfg->streamCh2->M0AR = (uint32_t)dmaSource;
277 DMA_Init(pios_ws2811_cfg->streamUpdate, (DMA_InitTypeDef *)&pios_ws2811_cfg->dmaInitUpdate);
278 pios_ws2811_cfg->streamUpdate->PAR = (uint32_t)&pios_ws2811_pin_cfg->gpio->BSRRL;
279 pios_ws2811_cfg->streamUpdate->M0AR = (uint32_t)dmaSource;
281 DMA_ClearITPendingBit(pios_ws2811_cfg->streamCh1, pios_ws2811_cfg->dmaItCh1);
282 DMA_ClearITPendingBit(pios_ws2811_cfg->streamCh2, pios_ws2811_cfg->dmaItCh2);
283 DMA_ClearITPendingBit(pios_ws2811_cfg->streamUpdate, pios_ws2811_cfg->dmaItUpdate);
285 DMA_Cmd(pios_ws2811_cfg->streamCh2, ENABLE);
286 DMA_Cmd(pios_ws2811_cfg->streamCh1, ENABLE);
287 DMA_Cmd(pios_ws2811_cfg->streamUpdate, ENABLE);
290 void setColor(uint8_t color, ledbuf_t *buf)
292 uint8_t i;
294 for (i = 0; i < 8; i++) {
295 buf[i] = ((color << i) & 0b10000000 ? 0x0 : dmaSource[0]);
300 * Set a led color
301 * @param c color
302 * @param led led number
303 * @param update Perform an update after changing led color
305 void PIOS_WS2811_setColorRGB(Color_t c, uint8_t led, bool update)
307 if (led >= PIOS_WS2811_NUMLEDS) {
308 return;
310 setColor(c.G, fb + (led * 24));
311 setColor(c.R, fb + 8 + (led * 24));
312 setColor(c.B, fb + 16 + (led * 24));
314 if (update) {
315 PIOS_WS2811_Update();
320 * trigger an update cycle if not already running
322 void PIOS_WS2811_Update()
324 // does not start if framebuffer is not allocated (init has not been called yet) or a transfer is still on going
325 if (!fb || (pios_ws2811_cfg->timer->CR1 & TIM_CR1_CEN)) {
326 return;
329 // reset counters for synchronization
330 pios_ws2811_cfg->timer->CNT = PIOS_WS2811_TIM_PERIOD - 1;
332 DMA_Cmd(pios_ws2811_cfg->streamCh2, ENABLE);
333 DMA_Cmd(pios_ws2811_cfg->streamCh1, ENABLE);
334 DMA_Cmd(pios_ws2811_cfg->streamUpdate, ENABLE);
335 // Start a new cycle
336 TIM_Cmd(pios_ws2811_cfg->timer, ENABLE);
340 * Stop timer once the complete framebuffer has been sent
343 void PIOS_WS2811_DMA_irq_handler()
345 pios_ws2811_pin_cfg->gpio->BSRRH = dmaSource[0];
346 pios_ws2811_cfg->timer->CR1 &= (uint16_t) ~TIM_CR1_CEN;
347 DMA_ClearFlag(pios_ws2811_cfg->streamCh1, pios_ws2811_cfg->irq.flags);
348 DMA_Cmd(pios_ws2811_cfg->streamCh2, DISABLE);
349 DMA_Cmd(pios_ws2811_cfg->streamCh1, DISABLE);
350 DMA_Cmd(pios_ws2811_cfg->streamUpdate, DISABLE);
353 #endif // PIOS_INCLUDE_WS2811