2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
20 * "Note that the timing on the WS2812/WS2812B LEDs has changed as of batches from WorldSemi
21 * manufactured made in October 2013, and timing tolerance for approx 10-30% of parts is very small.
22 * Recommendation from WorldSemi is now: 0 = 400ns high/850ns low, and 1 = 850ns high, 400ns low"
24 * Currently the timings are 0 = 350ns high/800ns and 1 = 700ns high/650ns low.
35 #include "build/build_config.h"
36 #include "build/debug.h"
38 #include "common/color.h"
39 #include "common/colorconversion.h"
41 #include "drivers/dma.h"
42 #include "drivers/io.h"
43 #include "drivers/timer.h"
44 #include "drivers/light_ws2811strip.h"
46 #include "config/parameter_group_ids.h"
47 #include "fc/settings.h"
48 #include "fc/runtime_config.h"
50 #define WS2811_PERIOD (WS2811_TIMER_HZ / WS2811_CARRIER_HZ)
51 #define WS2811_BIT_COMPARE_1 ((WS2811_PERIOD * 2) / 3)
52 #define WS2811_BIT_COMPARE_0 (WS2811_PERIOD / 3)
54 PG_REGISTER_WITH_RESET_TEMPLATE(ledPinConfig_t
, ledPinConfig
, PG_LEDPIN_CONFIG
, 0);
56 PG_RESET_TEMPLATE(ledPinConfig_t
, ledPinConfig
,
57 .led_pin_pwm_mode
= SETTING_LED_PIN_PWM_MODE_DEFAULT
60 static DMA_RAM timerDMASafeType_t ledStripDMABuffer
[WS2811_DMA_BUFFER_SIZE
];
62 static IO_t ws2811IO
= IO_NONE
;
63 static TCH_t
* ws2811TCH
= NULL
;
64 static bool ws2811Initialised
= false;
65 static bool pwmMode
= false;
67 static hsvColor_t ledColorBuffer
[WS2811_LED_STRIP_LENGTH
];
69 void setLedHsv(uint16_t index
, const hsvColor_t
*color
)
71 ledColorBuffer
[index
] = *color
;
74 void getLedHsv(uint16_t index
, hsvColor_t
*color
)
76 *color
= ledColorBuffer
[index
];
79 void setLedValue(uint16_t index
, const uint8_t value
)
81 ledColorBuffer
[index
].v
= value
;
84 void scaleLedValue(uint16_t index
, const uint8_t scalePercent
)
86 ledColorBuffer
[index
].v
= ((uint16_t)ledColorBuffer
[index
].v
* scalePercent
/ 100);
89 void setStripColor(const hsvColor_t
*color
)
92 for (index
= 0; index
< WS2811_LED_STRIP_LENGTH
; index
++) {
93 setLedHsv(index
, color
);
97 void setStripColors(const hsvColor_t
*colors
)
100 for (index
= 0; index
< WS2811_LED_STRIP_LENGTH
; index
++) {
101 setLedHsv(index
, colors
++);
105 bool ledConfigureDMA(void) {
106 /* Compute the prescaler value */
107 uint8_t period
= WS2811_TIMER_HZ
/ WS2811_CARRIER_HZ
;
109 timerConfigBase(ws2811TCH
, period
, WS2811_TIMER_HZ
);
110 timerPWMConfigChannel(ws2811TCH
, 0);
112 return timerPWMConfigChannelDMA(ws2811TCH
, ledStripDMABuffer
, sizeof(ledStripDMABuffer
[0]), WS2811_DMA_BUFFER_SIZE
);
115 void ledConfigurePWM(void) {
116 timerConfigBase(ws2811TCH
, 100, WS2811_TIMER_HZ
);
117 timerPWMConfigChannel(ws2811TCH
, 0);
118 timerPWMStart(ws2811TCH
);
119 timerEnable(ws2811TCH
);
123 void ws2811LedStripInit(void)
125 const timerHardware_t
* timHw
= timerGetByTag(IO_TAG(WS2811_PIN
), TIM_USE_ANY
);
127 if (!(timHw
->usageFlags
& TIM_USE_LED
)) { // Check if it has not been reassigned
128 timHw
= timerGetByUsageFlag(TIM_USE_LED
); // Get first pin marked as LED
135 ws2811TCH
= timerGetTCH(timHw
);
136 if (ws2811TCH
== NULL
) {
140 ws2811IO
= IOGetByTag(timHw
->tag
); //IOGetByTag(IO_TAG(WS2811_PIN));
141 IOInit(ws2811IO
, OWNER_LED_STRIP
, RESOURCE_OUTPUT
, 0);
142 IOConfigGPIOAF(ws2811IO
, IOCFG_AF_PP_FAST
, timHw
->alternateFunction
);
144 if (ledPinConfig()->led_pin_pwm_mode
== LED_PIN_PWM_MODE_LOW
) {
146 *timerCCR(ws2811TCH
) = 0;
147 } else if (ledPinConfig()->led_pin_pwm_mode
== LED_PIN_PWM_MODE_HIGH
) {
149 *timerCCR(ws2811TCH
) = 100;
151 if (!ledConfigureDMA()) {
152 // If DMA failed - abort
153 ws2811Initialised
= false;
157 // Zero out DMA buffer
158 memset(&ledStripDMABuffer
, 0, sizeof(ledStripDMABuffer
));
159 if ( ledPinConfig()->led_pin_pwm_mode
== LED_PIN_PWM_MODE_SHARED_HIGH
) {
160 ledStripDMABuffer
[WS2811_DMA_BUFFER_SIZE
-1] = 255;
162 ws2811Initialised
= true;
168 bool isWS2811LedStripReady(void)
170 return !timerPWMDMAInProgress(ws2811TCH
);
173 STATIC_UNIT_TESTED
uint16_t dmaBufferOffset
;
174 static int16_t ledIndex
;
176 STATIC_UNIT_TESTED
void fastUpdateLEDDMABuffer(rgbColor24bpp_t
*color
)
178 uint32_t grb
= (color
->rgb
.g
<< 16) | (color
->rgb
.r
<< 8) | (color
->rgb
.b
);
180 for (int8_t index
= 23; index
>= 0; index
--) {
181 ledStripDMABuffer
[WS2811_DELAY_BUFFER_LENGTH
+ dmaBufferOffset
++] = (grb
& (1 << index
)) ? WS2811_BIT_COMPARE_1
: WS2811_BIT_COMPARE_0
;
186 * This method is non-blocking unless an existing LED update is in progress.
187 * it does not wait until all the LEDs have been updated, that happens in the background.
189 void ws2811UpdateStrip(void)
191 static rgbColor24bpp_t
*rgb24
;
193 // don't wait - risk of infinite block, just get an update next time round
194 if (pwmMode
|| timerPWMDMAInProgress(ws2811TCH
)) {
198 dmaBufferOffset
= 0; // reset buffer memory index
199 ledIndex
= 0; // reset led index
201 // fill transmit buffer with correct compare values to achieve
202 // correct pulse widths according to color values
203 while (ledIndex
< WS2811_LED_STRIP_LENGTH
)
205 rgb24
= hsvToRgb24(&ledColorBuffer
[ledIndex
]);
206 fastUpdateLEDDMABuffer(rgb24
);
210 // Initiate hardware transfer
211 if (!ws2811Initialised
|| !ws2811TCH
) {
215 timerPWMPrepareDMA(ws2811TCH
, WS2811_DMA_BUFFER_SIZE
);
216 timerPWMStartDMA(ws2811TCH
);
220 void ledPinStartPWM(uint16_t value
) {
221 if (ws2811TCH
== NULL
) {
226 timerPWMStopDMA(ws2811TCH
);
227 //FIXME: implement method to release DMA
228 ws2811TCH
->dma
->owner
= OWNER_FREE
;
232 *timerCCR(ws2811TCH
) = value
;
235 void ledPinStopPWM(void) {
236 if (ws2811TCH
== NULL
|| !pwmMode
) {
240 if ( ledPinConfig()->led_pin_pwm_mode
== LED_PIN_PWM_MODE_HIGH
) {
241 *timerCCR(ws2811TCH
) = 100;
243 } else if ( ledPinConfig()->led_pin_pwm_mode
== LED_PIN_PWM_MODE_LOW
) {
244 *timerCCR(ws2811TCH
) = 0;
249 if (!ledConfigureDMA()) {
250 ws2811Initialised
= false;