2 ******************************************************************************
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
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
30 #ifdef PIOS_INCLUDE_WS2811
32 #include "pios_ws2811_cfg.h"
33 #include <stm32f4xx_rcc.h>
34 #include <pios_stm32.h>
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] = {
70 .GPIO_Pin = GPIO_Pin_0,
71 .GPIO_Speed = GPIO_Speed_25MHz,
72 .GPIO_Mode = GPIO_Mode_OUT,
73 .GPIO_OType = GPIO_OType_PP,
77 [HWSETTINGS_WS2811LED_OUT_FLEXIIOPIN4] = {
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 = {
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,
100 .streamCh1 = DMA2_Stream1,
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
123 .flags = (DMA_IT_TCIF1),
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
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);
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
;
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
);
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
,
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
)
235 TIM_OC1Init(TIMx
, TIM_OCInitStruct
);
238 TIM_OC2Init(TIMx
, TIM_OCInitStruct
);
241 TIM_OC3Init(TIMx
, TIM_OCInitStruct
);
244 TIM_OC4Init(TIMx
, TIM_OCInitStruct
);
249 void genericTIM_OCxPreloadConfig(TIM_TypeDef
*TIMx
, uint16_t TIM_OCPreload
, uint8_t ch
)
253 TIM_OC1PreloadConfig(TIMx
, TIM_OCPreload
);
256 TIM_OC2PreloadConfig(TIMx
, TIM_OCPreload
);
259 TIM_OC3PreloadConfig(TIMx
, TIM_OCPreload
);
262 TIM_OC4PreloadConfig(TIMx
, TIM_OCPreload
);
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
)
300 for (i
= 0; i
< 8; i
++) {
301 buf
[i
] = ((color
<< i
) & 0b10000000 ? 0x0 : dmaSource
[0]);
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
) {
316 setColor(c
.G
, fb
+ (led
* 24));
317 setColor(c
.R
, fb
+ 8 + (led
* 24));
318 setColor(c
.B
, fb
+ 16 + (led
* 24));
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
)) {
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
);
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