So I'm currently trying to interface an STM32F100 @ 24MHz to WS2812 addressable RGB LEDs using the same approach that the OctoWS2811 library uses.
The idea to create 16 individual data streams is the following:
1. Have a Timer (TIM2) run at 800kHz and configure the compare modules so that there is a match on the low bit time after ~375ns (CC1) and the high bit time after ~700ns (CC2).
2. Use three DMA streams to control the output data register of an IO bank and set the IOs appropriately. DMA channel 2 transfers all bits high to the GPIOA output data register on a TMR2 update event. DMA channel 5 then transfers the actual data bytes to the output data register on CC1 event, if the bit is supposed to represent a 0, then the pin goes low, otherwise it stays high. Finally DMA channel 7 sets all remaining IO pins to low on a CC2 event.
The code I have here works fine for the first frame (after reset) but when I restart the timer for transfer after reconfiguring the DMA, the first timer period seems to be only half the speed that I actually configured resulting in the first bit being about twice as long. The strange part is that after that the other bits get transferred without problems.
Currently I am handling the end of data transfer with an interrupt. When the DMA channel 7 transfer complete interrupt occurs, I disable the timer and DMA channels, then clear all relevant timer and DMA flags.
I have been trying out various things the whole day but I can't seem to find the solution to this, my assumption that some flag is still set is obviously wrong as resetting them is not helping.
The code currently looks like this:
#include <stm32f10x.h>
#define GPIOA_ODR_Address 0x4001080C
uint16_t WS2812_IO_High = 0xFFFF;
uint16_t WS2812_IO_Low = 0x0000;
uint16_t WS2812_IO_framedata[48] =
{
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
};
void Delay(__IO uint32_t nCount) {
while(nCount--) {
}
}
void GPIO_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// GPIOA Periph clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIOA pins WS2812 data outputs
GPIO_InitStructure.GPIO_Pin = 0xFFFF;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void TIM2_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t PrescalerValue = 0;
// TIM2 Periph clock enable
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
PrescalerValue = (uint16_t) (SystemCoreClock / 24000000) - 1;
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 29; // 800kHz
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM2, DISABLE);
/* Timing Mode configuration: Channel 1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 9;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* Timing Mode configuration: Channel 2 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 17;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);
/* TIM2 CC1 DMA Request enable */
TIM_DMACmd(TIM2, TIM_DMA_CC1, ENABLE);
/* TIM2 CC2 DMA Request enable */
TIM_DMACmd(TIM2, TIM_DMA_CC2, ENABLE);
/* TIM2 Update DMA Request enable */
TIM_DMACmd(TIM2, TIM_DMA_Update, ENABLE);
}
void DMA_init(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// TIM2 Update event
/* DMA1 Channel2 configuration ----------------------------------------------*/
DMA_DeInit(DMA1_Channel2);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)GPIOA_ODR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)WS2812_IO_High;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 0;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
// TIM2 CH1 event
/* DMA1 Channel5 configuration ----------------------------------------------*/
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)GPIOA_ODR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)WS2812_IO_framedata;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 0;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
// TIM2 CH2 event
/* DMA1 Channel7 configuration ----------------------------------------------*/
DMA_DeInit(DMA1_Channel7);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)GPIOA_ODR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)WS2812_IO_Low;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 0;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel7, &DMA_InitStructure);
/* configure DMA1 Channel7 interrupt */
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* enable DMA1 Channel7 transfer complete interrupt */
DMA_ITConfig(DMA1_Channel7, DMA_IT_TC, ENABLE);
}
void WS2812_send(uint16_t buffersize)
{
DMA_SetCurrDataCounter(DMA1_Channel2, buffersize);
DMA_SetCurrDataCounter(DMA1_Channel5, buffersize);
DMA_SetCurrDataCounter(DMA1_Channel7, buffersize);
/* enable DMA1 Channels */
DMA_Cmd(DMA1_Channel2, ENABLE);
DMA_Cmd(DMA1_Channel5, ENABLE);
DMA_Cmd(DMA1_Channel7, ENABLE);
TIM_SetCounter(TIM2, 29);
TIM_Cmd(TIM2, ENABLE);
}
void DMA1_Channel7_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_IT_TC7))
{
GPIOB->ODR ^= 0x0001;
TIM_Cmd(TIM2, DISABLE);
DMA_Cmd(DMA1_Channel2, DISABLE);
DMA_Cmd(DMA1_Channel5, DISABLE);
DMA_Cmd(DMA1_Channel7, DISABLE);
TIM_ClearFlag(TIM2, TIM_FLAG_Update | TIM_FLAG_CC1 | TIM_FLAG_CC2);
DMA_ClearFlag(DMA1_FLAG_TC2 | DMA1_FLAG_HT2 | DMA1_FLAG_GL2 | DMA1_FLAG_TE2);
DMA_ClearFlag(DMA1_FLAG_TC5 | DMA1_FLAG_HT5 | DMA1_FLAG_GL5 | DMA1_FLAG_TE5);
DMA_ClearFlag(DMA1_FLAG_HT7 | DMA1_FLAG_GL7 | DMA1_FLAG_TE7);
DMA_ClearITPendingBit(DMA1_IT_TC7);
}
}
int main(void) {
GPIO_init();
DMA_init();
TIM2_init();
while (1){
WS2812_send(24);
Delay(5000000L);
}
}
The first attachment shows the waveform directly after reset (first transfer) and the second looks like all frames after the first transfer.
Can anyone spot an obvious mistake here?
Cheers,
Elia