feat: Support for WS2812 RGB LEDs (#3909)
Co-authored-by: 3djc <lesitewebdejc@hotmail.com>
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user