Electronic – WS2812B 16 Channel Drive with STM32 problem

dmastm32stm32f10x

I am working with this library.

This library uses DMA1 channel 2,5,7 but these channels are required by USART. So I have changed the channels and corresponding to timers ( totimer2 and to timer4).

Also the library uses GPIOA, GPIOA pins not fully available for full drive (work with 16 channel), so I have changed GPIOA to GPIOB. After these changes the code worked well, but I realized after a week that some rows(channel 13,12,10,3, etc. )do not work.

I'm sure all GPIOB pins are works as digital I/O. I have check them, but DMA operation can't drive the GPIOB. I don't understand why.
After WS2812_sendbuf command, the state of these pins go from low to high and stay there forever. Not falling back to low, only if I reset them manually. So the problem is that the DMA does not clear them.

My MCU is an STM32f103C8T6 @72Mhz.

My Code:

    /*my code*/
#define WS2812_DEADPERIOD 19
uint16_t WS2812_IO_High = 0xFFFF;
uint16_t WS2812_IO_Low = 0x0000;
volatile uint8_t WS2812_TC = 1;
volatile uint8_t TIM4_overflows = 0;
uint16_t WS2812_IO_framedata[6144]; //WS2812 framebuffer buffersize = (#LEDs / 16) * 24 //for 256 leds
uint8_t allow_update=0;
void GPIOB_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure; 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_NoJTRST, ENABLE); 
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); 
    GPIO_InitStructure.GPIO_Pin = 0xFFFF;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

void TIM4_init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    uint16_t PrescalerValue;
    // TIM4 Periph clock enable
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, 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(TIM4, &TIM_TimeBaseStructure);
    TIM_ARRPreloadConfig(TIM4, DISABLE);
    /* Timing Mode configuration: Channel 1 */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
    TIM_OCInitStructure.TIM_Pulse = 8;
    TIM_OC1Init(TIM4, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Disable);  
    /* Timing Mode configuration: Channel 2 */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
    TIM_OCInitStructure.TIM_Pulse = 17;
    TIM_OC2Init(TIM4, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Disable);

    /* configure TIM4 interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

void WS2812_sendbuf(uint32_t buffersize)
{       
    // transmission complete flag, indicate that transmission is taking place
    WS2812_TC = 0;
    // clear all relevant DMA flags
    DMA_ClearFlag(DMA1_FLAG_TC7 | DMA1_FLAG_HT7 | DMA1_FLAG_GL7 | DMA1_FLAG_TE7);
    DMA_ClearFlag(DMA1_FLAG_TC1 | DMA1_FLAG_HT1 | DMA1_FLAG_GL1 | DMA1_FLAG_TE1);
    DMA_ClearFlag(DMA1_FLAG_HT4 | DMA1_FLAG_GL4 | DMA1_FLAG_TE4);

    // configure the number of bytes to be transferred by the DMA controller
    DMA_SetCurrDataCounter(DMA1_Channel7, buffersize);  //dma channel 2 --> 7
    DMA_SetCurrDataCounter(DMA1_Channel1, buffersize);  //dma channel 5 --> 1 
    DMA_SetCurrDataCounter(DMA1_Channel4, buffersize);  //dma channel 7 --> 4 
    // clear all TIM4 flags
    TIM4->SR = 0;
    // enable the corresponding DMA channels
    DMA_Cmd(DMA1_Channel7, ENABLE);
    DMA_Cmd(DMA1_Channel1, ENABLE);
    DMA_Cmd(DMA1_Channel4, ENABLE);
    // IMPORTANT: enable the TIM4 DMA requests AFTER enabling the DMA channels!
    TIM_DMACmd(TIM4, TIM_DMA_CC1, ENABLE);
    TIM_DMACmd(TIM4, TIM_DMA_CC2, ENABLE);
    TIM_DMACmd(TIM4, TIM_DMA_Update, ENABLE);
    // preload counter with 29 so TIM4 generates UEV directly to start DMA transfer
    TIM_SetCounter(TIM4, 29);
    // start TIM4
    TIM_Cmd(TIM4, ENABLE);
}

/* DMA1 Channel7 Interrupt Handler gets executed once the complete framebuffer has been transmitted to the LEDs */
void DMA1_Channel4_IRQHandler(void)
{
    // clear DMA7 transfer complete interrupt flag
    DMA_ClearITPendingBit(DMA1_IT_TC4); 
    // enable TIM4 Update interrupt to append 50us dead period
    TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
    // disable the DMA channels
    DMA_Cmd(DMA1_Channel7, DISABLE);    
    DMA_Cmd(DMA1_Channel1, DISABLE);
    DMA_Cmd(DMA1_Channel4, DISABLE);
    // IMPORTANT: disable the DMA requests, too!
    TIM_DMACmd(TIM4, TIM_DMA_CC1, DISABLE);
    TIM_DMACmd(TIM4, TIM_DMA_CC2, DISABLE);
    TIM_DMACmd(TIM4, TIM_DMA_Update, DISABLE);

}

/* TIM4 Interrupt Handler gets executed on every TIM4 Update if enabled */
void TIM4_IRQHandler(void)
{
    // Clear TIM4 Interrupt Flag
    TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
    if (TIM4_overflows < (uint8_t)WS2812_DEADPERIOD)
    {
        // count the number of occured overflows
        TIM4_overflows++;
    }
    else
    {
        // clear the number of overflows
        TIM4_overflows = 0; 
        // stop TIM4 now because dead period has been reached
        TIM_Cmd(TIM4, DISABLE);

        TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE);
        // finally indicate that the data frame has been transmitted
        WS2812_TC = 1;  
    }
}

void WS2812_framedata_setPixel(uint8_t row, uint16_t column, uint8_t red, uint8_t green, uint8_t blue)
{
    uint8_t i;
    for (i = 0; i < 8; i++)
    {
        // clear the data for pixel 
        WS2812_IO_framedata[((column*24)+i)] &= ~(0x01<<row);
        WS2812_IO_framedata[((column*24)+8+i)] &= ~(0x01<<row);
        WS2812_IO_framedata[((column*24)+16+i)] &= ~(0x01<<row);
        // write new data for pixel
        WS2812_IO_framedata[((column*24)+i)] |= ((((green<<i) & 0x80)>>7)<<row);
        WS2812_IO_framedata[((column*24)+8+i)] |= ((((red<<i) & 0x80)>>7)<<row);
        WS2812_IO_framedata[((column*24)+16+i)] |= ((((blue<<i) & 0x80)>>7)<<row);
    }
}

void DMA_init(void) //port b için 
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    // TIM4 Update event
    /* DMA1 Channel2 configuration ----------------------------------------------*/
    DMA_DeInit(DMA1_Channel7);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOB->ODR;
    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_Word;
    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);

    // TIM4 CC1 event
    /* DMA1 Channel1 configuration ----------------------------------------------*/
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOB->ODR;
    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_Word;
    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_Channel1, &DMA_InitStructure);

    // TIM4 CC2 event
    /* DMA1 Channel7 configuration ----------------------------------------------*/
    DMA_DeInit(DMA1_Channel4);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&GPIOB->ODR;
    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_Word;
    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_Channel4, &DMA_InitStructure);
    /* configure DMA1 Channel7 interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_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_Channel4, DMA_IT_TC, ENABLE);
}

uint8_t Get_WS2812_TC()
{
    return WS2812_TC;
}

void WS2812B_Init()
{
    GPIOB_init();
    DMA_init();
    TIM4_init();
}

Update:

Finally I found the bug in the library.

wrong:

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)WS2812_IO_High; //("&" mistaked)

right:

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&WS2812_IO_High;

Best Answer

I think, until you reduce the size of the problem, you will struggle. We can't do magic. (It is unlikely someone in the community will set up a similar system, especially without schematics and maybe photos, and find the bug).

So we can only work from the code you post, and information in your question.

Cut out everything extra from the program. Remove the other DMA, extra timers, etc.

If the only issue is some pin don't change, then driving the WS2812 is not necessary, so remove it too.

AFAICT, all you need to test this is some dumb LEDs on the pins that don't seem to change, a timer, the DMA controller, and some test data.

Reducing the problem to the smallest case that demonstrates the problem is not me being lazy, and asking you to do unnecessary work.

The process of reducing a system to its simplest case is a very powerful problem solving technique. Going through that process may be enough for you to discover the bugs yourself. At least reducing the system's complexity should remove some potential sources of bugs, which will help the community to help you.