feat: Support for WS2812 RGB LEDs (#3909)

Co-authored-by: 3djc <lesitewebdejc@hotmail.com>
This commit is contained in:
Raphael Coeffic
2023-10-14 14:06:55 +10:00
committed by GitHub
co-authored by 3djc
parent f8ad3a7bda
commit b22a1b5d32
60 changed files with 788 additions and 122 deletions
@@ -28,7 +28,7 @@ static void* module_timer_init(void* hw_def, const etx_timer_config_t* cfg)
bool polarity = cfg->polarity;
uint32_t ocval = cfg->cmp_val;
uint32_t ocmode = (cfg->type == ETX_PWM) ? LL_TIM_OCMODE_FORCED_INACTIVE : LL_TIM_OCMODE_TOGGLE;
uint32_t ocmode = LL_TIM_OCMODE_FORCED_INACTIVE;
stm32_pulse_init(timer, 0);
stm32_pulse_config_output(timer, polarity, ocmode, ocval);
@@ -46,16 +46,14 @@ static void module_timer_send(void* ctx, const etx_timer_config_t* cfg,
const void* pulses, uint16_t length)
{
auto timer = (const stm32_pulse_timer_t*)ctx;
if (!stm32_pulse_if_not_running_disable(timer)) {
LL_DMA_DeInit(timer->DMAx, timer->DMA_Stream);
if (!stm32_pulse_if_not_running_disable(timer))
return;
}
// Set polarity
stm32_pulse_set_polarity(timer, cfg->polarity);
// Start DMA request and re-enable timer
uint32_t ocmode = (cfg->type == ETX_PWM) ? LL_TIM_OCMODE_PWM1 : LL_TIM_OCMODE_TOGGLE;
uint32_t ocmode = LL_TIM_OCMODE_PWM1;
uint32_t ocval = cfg->cmp_val;
stm32_pulse_start_dma_req(timer, pulses, length, ocmode, ocval);
}
@@ -24,6 +24,12 @@
#include <stdint.h>
#include "stm32_hal_ll.h"
#define __STM32_DMA_IS_STREAM_SUPPORTED(stream) \
((stream) == LL_DMA_STREAM_0 || (stream) == LL_DMA_STREAM_1 || \
(stream) == LL_DMA_STREAM_2 || (stream) == LL_DMA_STREAM_3 || \
(stream) == LL_DMA_STREAM_4 || (stream) == LL_DMA_STREAM_5 || \
(stream) == LL_DMA_STREAM_6 || (stream) == LL_DMA_STREAM_7)
inline static bool stm32_dma_check_tc_flag(DMA_TypeDef* DMAx, uint32_t DMA_Stream)
{
switch(DMA_Stream) {
@@ -64,4 +70,44 @@ inline static bool stm32_dma_check_tc_flag(DMA_TypeDef* DMAx, uint32_t DMA_Strea
return true;
}
inline static bool stm32_dma_check_ht_flag(DMA_TypeDef* DMAx, uint32_t DMA_Stream)
{
switch(DMA_Stream) {
case LL_DMA_STREAM_0:
if (!LL_DMA_IsActiveFlag_HT0(DMAx)) return false;
LL_DMA_ClearFlag_HT0(DMAx);
break;
case LL_DMA_STREAM_1:
if (!LL_DMA_IsActiveFlag_HT1(DMAx)) return false;
LL_DMA_ClearFlag_HT1(DMAx);
break;
case LL_DMA_STREAM_2:
if (!LL_DMA_IsActiveFlag_HT2(DMAx)) return false;
LL_DMA_ClearFlag_HT2(DMAx);
break;
case LL_DMA_STREAM_3:
if (!LL_DMA_IsActiveFlag_HT3(DMAx)) return false;
LL_DMA_ClearFlag_HT3(DMAx);
break;
case LL_DMA_STREAM_4:
if (!LL_DMA_IsActiveFlag_HT4(DMAx)) return false;
LL_DMA_ClearFlag_HT4(DMAx);
break;
case LL_DMA_STREAM_5:
if (!LL_DMA_IsActiveFlag_HT5(DMAx)) return false;
LL_DMA_ClearFlag_HT5(DMAx);
break;
case LL_DMA_STREAM_6:
if (!LL_DMA_IsActiveFlag_HT6(DMAx)) return false;
LL_DMA_ClearFlag_HT6(DMAx);
break;
case LL_DMA_STREAM_7:
if (!LL_DMA_IsActiveFlag_HT7(DMAx)) return false;
LL_DMA_ClearFlag_HT7(DMAx);
break;
}
return true;
}
void stm32_dma_enable_clock(DMA_TypeDef* DMAx);
@@ -35,8 +35,49 @@
// - DMA IRQ prio configurable (now 7)
// - Timer IRQ prio configurable (now 7)
void stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq)
static void init_dma_arr_mode(const stm32_pulse_timer_t* tim)
{
// re-init DMA stream
LL_DMA_DeInit(tim->DMAx, tim->DMA_Stream);
LL_DMA_InitTypeDef dmaInit;
LL_DMA_StructInit(&dmaInit);
// Direction
dmaInit.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
// Source
dmaInit.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
// Destination
dmaInit.PeriphOrM2MSrcAddress = CONVERT_PTR_UINT(&tim->TIMx->ARR);
dmaInit.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
// Data width
if (IS_TIM_32B_COUNTER_INSTANCE(tim->TIMx)) {
dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
} else {
dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
}
dmaInit.Channel = tim->DMA_Channel;
dmaInit.Priority = LL_DMA_PRIORITY_VERYHIGH;
stm32_dma_enable_clock(tim->DMAx);
LL_DMA_Init(tim->DMAx, tim->DMA_Stream, &dmaInit);
}
int stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq)
{
// check if timer is free first (clock disabled)
if (stm32_timer_is_clock_enabled(tim->TIMx)) return -1;
// .. and GPIO pin is not used
uint32_t pin_mode = LL_GPIO_GetPinMode(tim->GPIOx, tim->GPIO_Pin);
if (pin_mode != LL_GPIO_MODE_INPUT) return -1;
if (tim->DMA_TC_CallbackPtr) {
memset(tim->DMA_TC_CallbackPtr, 0, sizeof(stm32_pulse_dma_tc_cb_t));
}
@@ -61,24 +102,30 @@ void stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq)
stm32_timer_enable_clock(tim->TIMx);
LL_TIM_Init(tim->TIMx, &timInit);
if (tim->DMAx) {
// Enable DMA IRQ
if (tim->DMAx && (int32_t)tim->DMA_IRQn >= 0) {
init_dma_arr_mode(tim);
NVIC_EnableIRQ(tim->DMA_IRQn);
NVIC_SetPriority(tim->DMA_IRQn, 7);
}
// Enable timer IRQ
NVIC_EnableIRQ(tim->TIM_IRQn);
NVIC_SetPriority(tim->TIM_IRQn, 7);
if ((int32_t)tim->TIM_IRQn >= 0) {
NVIC_EnableIRQ(tim->TIM_IRQn);
NVIC_SetPriority(tim->TIM_IRQn, 7);
}
return 0;
}
void stm32_pulse_deinit(const stm32_pulse_timer_t* tim)
{
// Disable IRQs
if (tim->DMAx) {
if ((int32_t)tim->DMA_IRQn >= 0) {
NVIC_DisableIRQ(tim->DMA_IRQn);
}
NVIC_DisableIRQ(tim->TIM_IRQn);
if ((int32_t)tim->TIM_IRQn >= 0) {
NVIC_DisableIRQ(tim->TIM_IRQn);
}
if (tim->DMAx) {
LL_DMA_DeInit(tim->DMAx, tim->DMA_Stream);
@@ -100,6 +147,25 @@ void stm32_pulse_deinit(const stm32_pulse_timer_t* tim)
LL_GPIO_Init(tim->GPIOx, &pinInit);
}
static inline bool _is_complementary_channel(uint32_t channel)
{
return (channel == LL_TIM_CHANNEL_CH1N) || (channel == LL_TIM_CHANNEL_CH2N) ||
(channel == LL_TIM_CHANNEL_CH3N);
}
static const uint32_t _base_channel[] = {
LL_TIM_CHANNEL_CH1, LL_TIM_CHANNEL_CH1,
LL_TIM_CHANNEL_CH2, LL_TIM_CHANNEL_CH2,
LL_TIM_CHANNEL_CH3, LL_TIM_CHANNEL_CH3,
LL_TIM_CHANNEL_CH4
};
static inline uint32_t _get_base_channel(uint32_t channel)
{
auto idx = TIM_GET_CHANNEL_INDEX(channel);
return _base_channel[idx];
}
void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity,
uint32_t ocmode, uint32_t cmp_val)
{
@@ -109,14 +175,14 @@ void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity,
ocInit.OCMode = ocmode;
ocInit.CompareValue = cmp_val;
uint32_t channel = tim->TIM_Channel;
if (tim->TIM_Channel != LL_TIM_CHANNEL_CH1N) {
uint32_t channel = _get_base_channel(tim->TIM_Channel);
bool comp_ch = _is_complementary_channel(tim->TIM_Channel);
if (!comp_ch) {
ocInit.OCState = LL_TIM_OCSTATE_ENABLE;
ocInit.OCNState = LL_TIM_OCSTATE_DISABLE;
} else {
ocInit.OCState = LL_TIM_OCSTATE_DISABLE;
ocInit.OCNState = LL_TIM_OCSTATE_ENABLE;
channel = LL_TIM_CHANNEL_CH1;
}
uint32_t ll_polarity;
@@ -126,7 +192,7 @@ void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity,
ll_polarity = LL_TIM_OCPOLARITY_LOW;
}
if (tim->TIM_Channel != LL_TIM_CHANNEL_CH1N) {
if (!comp_ch) {
ocInit.OCPolarity = ll_polarity;
} else {
ocInit.OCNPolarity = ll_polarity;
@@ -138,7 +204,6 @@ void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity,
if (IS_TIM_BREAK_INSTANCE(tim->TIMx)) {
LL_TIM_EnableAllOutputs(tim->TIMx);
}
}
void stm32_pulse_set_polarity(const stm32_pulse_timer_t* tim, bool polarity)
@@ -173,9 +238,8 @@ bool stm32_pulse_if_not_running_disable(const stm32_pulse_timer_t* tim)
static void set_compare_reg(const stm32_pulse_timer_t* tim, uint32_t val)
{
switch(tim->TIM_Channel){
switch(_get_base_channel(tim->TIM_Channel)){
case LL_TIM_CHANNEL_CH1:
case LL_TIM_CHANNEL_CH1N:
LL_TIM_OC_SetCompareCH1(tim->TIMx, val);
break;
case LL_TIM_CHANNEL_CH2:
@@ -190,40 +254,39 @@ static void set_compare_reg(const stm32_pulse_timer_t* tim, uint32_t val)
}
}
void stm32_pulse_set_period(const stm32_pulse_timer_t* tim, uint32_t period)
{
LL_TIM_SetAutoReload(tim->TIMx, period - 1);
}
void stm32_pulse_set_cmp_val(const stm32_pulse_timer_t* tim, uint32_t cmp_val)
{
set_compare_reg(tim, cmp_val);
}
void stm32_pulse_start(const stm32_pulse_timer_t* tim)
{
LL_TIM_EnableCounter(tim->TIMx);
}
void stm32_pulse_stop(const stm32_pulse_timer_t* tim)
{
LL_TIM_DisableCounter(tim->TIMx);
}
static void set_oc_mode(const stm32_pulse_timer_t* tim, uint32_t ocmode)
{
uint32_t channel = tim->TIM_Channel;
if (channel == LL_TIM_CHANNEL_CH1N)
channel = LL_TIM_CHANNEL_CH1;
uint32_t channel = _get_base_channel(tim->TIM_Channel);
LL_TIM_OC_SetMode(tim->TIMx, channel, ocmode);
}
void stm32_pulse_wait_for_completed(const stm32_pulse_timer_t* tim)
{
uint32_t channel = tim->TIM_Channel;
if (channel == LL_TIM_CHANNEL_CH1N)
channel = LL_TIM_CHANNEL_CH1;
uint32_t channel = _get_base_channel(tim->TIM_Channel);
while(LL_TIM_IsEnabledCounter(tim->TIMx) &&
LL_TIM_OC_GetMode(tim->TIMx, channel) != LL_TIM_OCMODE_FORCED_INACTIVE);
}
static void force_start_level(const stm32_pulse_timer_t* tim)
{
uint32_t channel = tim->TIM_Channel;
if (channel == LL_TIM_CHANNEL_CH1N)
channel = LL_TIM_CHANNEL_CH1;
uint32_t mode = LL_TIM_OC_GetMode(tim->TIMx, channel);
LL_TIM_OC_SetMode(tim->TIMx, channel, LL_TIM_OCMODE_FORCED_ACTIVE);
LL_TIM_OC_SetMode(tim->TIMx, channel, mode);
}
void stm32_pulse_start_dma_req(const stm32_pulse_timer_t* tim,
const void* pulses, uint16_t length,
@@ -232,62 +295,23 @@ void stm32_pulse_start_dma_req(const stm32_pulse_timer_t* tim,
// Re-configure timer output
set_compare_reg(tim, cmp_val);
set_oc_mode(tim, ocmode);
// re-init DMA stream
LL_DMA_DeInit(tim->DMAx, tim->DMA_Stream);
LL_DMA_InitTypeDef dmaInit;
LL_DMA_StructInit(&dmaInit);
// Direction
dmaInit.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
// Source
dmaInit.MemoryOrM2MDstAddress = CONVERT_PTR_UINT(pulses);
dmaInit.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
// Destination
dmaInit.PeriphOrM2MSrcAddress = CONVERT_PTR_UINT(&tim->TIMx->ARR);
dmaInit.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
// Data width
if (IS_TIM_32B_COUNTER_INSTANCE(tim->TIMx)) {
// TODO: try using 16-bit source as well
dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD;
dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
} else {
dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
}
dmaInit.NbData = length;
dmaInit.Channel = tim->DMA_Channel;
dmaInit.Priority = LL_DMA_PRIORITY_VERYHIGH;
LL_DMA_Init(tim->DMAx, tim->DMA_Stream, &dmaInit);
LL_DMA_SetDataLength(tim->DMAx, tim->DMA_Stream, length);
LL_DMA_SetMemoryAddress(tim->DMAx, tim->DMA_Stream, (uint32_t)pulses);
// Enable TC IRQ
LL_DMA_EnableIT_TC(tim->DMAx, tim->DMA_Stream);
if (ocmode == LL_TIM_OCMODE_PWM1) {
// preloads first period for PWM)
LL_TIM_SetCounter(tim->TIMx, 0x00);
// Reset counter
LL_TIM_SetCounter(tim->TIMx, 0x00);
// only on PWM (preloads the first period)
if (ocmode == LL_TIM_OCMODE_PWM1)
LL_TIM_GenerateEvent_UPDATE(tim->TIMx);
} else {
// Reset counter close to overflow
if (IS_TIM_32B_COUNTER_INSTANCE(tim->TIMx)) {
LL_TIM_SetCounter(tim->TIMx, 0xFFFFFFFF);
} else {
LL_TIM_SetCounter(tim->TIMx, 0xFFFF);
}
}
LL_TIM_EnableDMAReq_UPDATE(tim->TIMx);
LL_DMA_EnableStream(tim->DMAx, tim->DMA_Stream);
// Trigger update to effect the first DMA transaction
// and thus load ARR with the first duration
// start timer
LL_TIM_EnableCounter(tim->TIMx);
}
@@ -50,7 +50,7 @@ struct stm32_pulse_timer_t {
stm32_pulse_dma_tc_cb_t* DMA_TC_CallbackPtr;
};
void stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq);
int stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq);
void stm32_pulse_deinit(const stm32_pulse_timer_t* tim);
void stm32_pulse_config_input(const stm32_pulse_timer_t* tim);
@@ -62,8 +62,13 @@ void stm32_pulse_set_polarity(const stm32_pulse_timer_t* tim, bool polarity);
bool stm32_pulse_get_polarity(const stm32_pulse_timer_t* tim);
void stm32_pulse_set_period(const stm32_pulse_timer_t* tim, uint32_t period);
void stm32_pulse_set_cmp_val(const stm32_pulse_timer_t* tim, uint32_t cmp_val);
void stm32_pulse_start(const stm32_pulse_timer_t* tim);
void stm32_pulse_stop(const stm32_pulse_timer_t* tim);
void stm32_pulse_wait_for_completed(const stm32_pulse_timer_t* tim);
// return true if it could be disabled without interrupting a pulse train, false otherwise
@@ -81,9 +86,6 @@ void stm32_pulse_tim_update_isr(const stm32_pulse_timer_t* tim);
#define __STM32_PULSE_IS_TIMER_CHANNEL_SUPPORTED(ch) \
((ch) == LL_TIM_CHANNEL_CH1 || (ch) == LL_TIM_CHANNEL_CH1N || \
(ch) == LL_TIM_CHANNEL_CH2 || (ch) == LL_TIM_CHANNEL_CH3 || \
(ch) == LL_TIM_CHANNEL_CH2 || (ch) == LL_TIM_CHANNEL_CH2N || \
(ch) == LL_TIM_CHANNEL_CH3 || (ch) == LL_TIM_CHANNEL_CH3N || \
(ch) == LL_TIM_CHANNEL_CH4)
#define __STM32_PULSE_IS_DMA_STREAM_SUPPORTED(stream) \
((stream) == LL_DMA_STREAM_1 || (stream) == LL_DMA_STREAM_5 || \
(stream) == LL_DMA_STREAM_7)
@@ -62,3 +62,23 @@ void stm32_timer_disable_clock(TIM_TypeDef *TIMx)
LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_TIM14);
}
}
bool stm32_timer_is_clock_enabled(TIM_TypeDef *TIMx)
{
if (TIMx == TIM1) {
return LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_TIM1) != 0;
} else if (TIMx == TIM2) {
return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM2) != 0;
} else if (TIMx == TIM3) {
return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM3) != 0;
} else if (TIMx == TIM4) {
return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM4) != 0;
} else if (TIMx == TIM5) {
return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM5) != 0;
} else if (TIMx == TIM8) {
return LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_TIM8) != 0;
}
// not supported
return false;
}
@@ -25,3 +25,5 @@
void stm32_timer_enable_clock(TIM_TypeDef *TIMx);
void stm32_timer_disable_clock(TIM_TypeDef *TIMx);
bool stm32_timer_is_clock_enabled(TIM_TypeDef *TIMx);
@@ -0,0 +1,241 @@
/*
* Copyright (C) EdgeTx
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "stm32_ws2812.h"
#include "stm32_dma.h"
#if defined(DEBUG_WS2812)
// LED_STRIP_DEBUG_GPIO && LED_STRIP_DEBUG_GPIO_PIN
#include "hal.h"
#endif
#include "definitions.h"
#include <string.h>
// Pixel values
static uint8_t _led_colors[WS2812_BYTES_PER_LED * WS2812_MAX_LEDS];
// Timer used
// static const stm32_pulse_timer_t* _led_timer;
// LED strip length
static uint8_t _led_strip_len;
// DMA buffer contains data for 2 LEDs and is filled
// half by half on DMA HT and TC IRQs
#define WS2821_DMA_BUFFER_HALF_LEN (WS2812_BYTES_PER_LED * 8)
#define WS2821_DMA_BUFFER_LEN (WS2821_DMA_BUFFER_HALF_LEN * 2)
#define WS2812_FREQ 800000UL // 800 kHz
#define WS2812_TIMER_PERIOD 20UL
#define WS2812_ONE (3 * WS2812_TIMER_PERIOD / 4)
#define WS2812_ZERO (1 * WS2812_TIMER_PERIOD / 4)
#define WS2812_DMA_IRQ_PRIO 3
// Debug facility
#if defined(LED_STRIP_DEBUG_GPIO) && defined(LED_STRIP_DEBUG_GPIO_PIN)
#define WS2812_DBG_INIT _led_dbg_init()
#define WS2812_DBG_HIGH \
LL_GPIO_SetOutputPin(LED_STRIP_DEBUG_GPIO, LED_STRIP_DEBUG_GPIO_PIN)
#define WS2812_DBG_LOW \
LL_GPIO_ResetOutputPin(LED_STRIP_DEBUG_GPIO, LED_STRIP_DEBUG_GPIO_PIN)
static void _led_dbg_init() {
LL_GPIO_InitTypeDef pinInit{0};
pinInit.Mode = LL_GPIO_MODE_OUTPUT;
pinInit.Pin = LED_STRIP_DEBUG_GPIO_PIN;
LL_GPIO_Init(LED_STRIP_DEBUG_GPIO, &pinInit);
WS2812_DBG_LOW;
}
#else // LED_STRIP_DEBUG_GPIO && LED_STRIP_DEBUG_GPIO_PIN
#define WS2812_DBG_INIT
#define WS2812_DBG_HIGH
#define WS2812_DBG_LOW
#endif
#if defined(STM32_SUPPORT_32BIT_TIMERS)
typedef uint32_t led_timer_value_t;
#else
typedef uint16_t led_timer_value_t;
#endif
// DMA buffer contains pulses for 2 LED at a time
// (allows for refill at HT and TC)
static led_timer_value_t _led_dma_buffer[WS2821_DMA_BUFFER_LEN] __DMA;
static uint8_t _led_seq_cnt;
static void _fill_byte(uint8_t c, led_timer_value_t* dma_buffer)
{
for (int i = 0; i < 8; i++) {
dma_buffer[i] = c & 1 ? WS2812_ONE : WS2812_ZERO;
c >>= 1;
}
}
static void _fill_pulses(const uint8_t* colors, led_timer_value_t* dma_buffer, uint32_t len)
{
for (uint32_t i = 0; i < len; i++) {
_fill_byte(*colors, dma_buffer);
dma_buffer += 8;
colors++;
}
}
static inline uint32_t _calc_offset(uint8_t tc)
{
return tc * WS2821_DMA_BUFFER_HALF_LEN;
}
static void _update_dma_buffer(const stm32_pulse_timer_t* tim, uint8_t tc)
{
WS2812_DBG_HIGH;
if (_led_seq_cnt < _led_strip_len) {
auto idx = WS2812_BYTES_PER_LED * _led_seq_cnt;
auto offset = _calc_offset(tc);
_fill_pulses(&_led_colors[idx], &_led_dma_buffer[offset], WS2812_BYTES_PER_LED);
_led_seq_cnt++;
} else if(_led_seq_cnt < _led_strip_len + WS2812_TRAILING_RESET) {
// no need to reset the buffer after 2 cycles
if (_led_seq_cnt < _led_strip_len + 2) {
auto offset = _calc_offset(tc);
auto size = WS2821_DMA_BUFFER_HALF_LEN * sizeof(led_timer_value_t);
memset(&_led_dma_buffer[offset], 0, size);
}
_led_seq_cnt++;
} else {
LL_DMA_DisableIT_TC(tim->DMAx, tim->DMA_Stream);
LL_DMA_DisableIT_HT(tim->DMAx, tim->DMA_Stream);
LL_DMA_DisableStream(tim->DMAx, tim->DMA_Stream);
LL_TIM_CC_DisableChannel(tim->TIMx, tim->TIM_Channel);
}
WS2812_DBG_LOW;
}
void ws2812_dma_isr(const stm32_pulse_timer_t* tim)
{
if (LL_DMA_IsEnabledIT_HT(tim->DMAx, tim->DMA_Stream) &&
stm32_dma_check_ht_flag(tim->DMAx, tim->DMA_Stream)) {
_update_dma_buffer(tim, 0);
}
if (LL_DMA_IsEnabledIT_TC(tim->DMAx, tim->DMA_Stream) &&
stm32_dma_check_tc_flag(tim->DMAx, tim->DMA_Stream)) {
_update_dma_buffer(tim, 1);
}
}
static void _led_set_dma_periph_addr(const stm32_pulse_timer_t* tim)
{
volatile uint32_t* cmp_reg = nullptr;
switch(tim->TIM_Channel) {
case LL_TIM_CHANNEL_CH1:
case LL_TIM_CHANNEL_CH1N:
cmp_reg = &tim->TIMx->CCR1;
break;
case LL_TIM_CHANNEL_CH2:
cmp_reg = &tim->TIMx->CCR2;
break;
case LL_TIM_CHANNEL_CH3:
cmp_reg = &tim->TIMx->CCR3;
break;
case LL_TIM_CHANNEL_CH4:
cmp_reg = &tim->TIMx->CCR4;
break;
}
LL_DMA_SetPeriphAddress(tim->DMAx, tim->DMA_Stream, (uint32_t)cmp_reg);
}
static void _init_timer(const stm32_pulse_timer_t* tim)
{
stm32_pulse_init(tim, WS2812_FREQ * WS2812_TIMER_PERIOD);
stm32_pulse_config_output(tim, true, LL_TIM_OCMODE_PWM1, 0);
LL_TIM_SetAutoReload(tim->TIMx, WS2812_TIMER_PERIOD - 1);
// pulse driver uses DMA to ARR, but we need CCRx
_led_set_dma_periph_addr(tim);
LL_DMA_SetMode(tim->DMAx, tim->DMA_Stream, LL_DMA_MODE_CIRCULAR);
LL_DMA_SetDataLength(tim->DMAx, tim->DMA_Stream, WS2821_DMA_BUFFER_LEN);
LL_DMA_SetMemoryAddress(tim->DMAx, tim->DMA_Stream, (uint32_t)_led_dma_buffer);
// we need to use a higher prio to avoid having
// issues with some other things used during boot
NVIC_SetPriority(tim->DMA_IRQn, WS2812_DMA_IRQ_PRIO);
}
void ws2812_init(const stm32_pulse_timer_t* timer, uint8_t strip_len)
{
WS2812_DBG_INIT;
memset(_led_colors, 0, sizeof(_led_colors));
memset(_led_dma_buffer, 0, sizeof(_led_dma_buffer));
if (strip_len <= WS2812_MAX_LEDS) {
_led_strip_len = strip_len;
} else {
_led_strip_len = WS2812_MAX_LEDS;
}
_init_timer(timer);
}
void ws2812_set_color(uint8_t led, uint8_t r, uint8_t g, uint8_t b)
{
if (led >= _led_strip_len) return;
uint8_t* pixel = &_led_colors[led * WS2812_BYTES_PER_LED];
pixel[0] = g;
pixel[1] = r;
pixel[2] = b;
}
void ws2812_update(const stm32_pulse_timer_t* tim)
{
WS2812_DBG_HIGH;
if (!stm32_pulse_if_not_running_disable(tim)) return;
_led_seq_cnt = 0;
memset(_led_dma_buffer, 0, sizeof(_led_dma_buffer));
LL_DMA_EnableIT_HT(tim->DMAx, tim->DMA_Stream);
LL_DMA_EnableIT_TC(tim->DMAx, tim->DMA_Stream);
LL_DMA_EnableStream(tim->DMAx, tim->DMA_Stream);
LL_TIM_EnableDMAReq_UPDATE(tim->TIMx);
LL_TIM_CC_EnableChannel(tim->TIMx, tim->TIM_Channel);
LL_TIM_EnableCounter(tim->TIMx);
WS2812_DBG_LOW;
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) EdgeTx
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#include "stm32_pulse_driver.h"
// RGB
#define WS2812_BYTES_PER_LED 3
#define WS2812_MAX_LEDS 8
// Number of LED periods used for trailing reset
#define WS2812_TRAILING_RESET 10
void ws2812_init(const stm32_pulse_timer_t* timer, uint8_t strip_len);
void ws2812_update(const stm32_pulse_timer_t* timer);
void ws2812_dma_isr(const stm32_pulse_timer_t* timer);
void ws2812_set_color(uint8_t led, uint8_t r, uint8_t g, uint8_t b);