Skip to main content
Visitor II
April 13, 2020
Question

FreeRTOS stops running after starting timer with DMA to GPIO

  • April 13, 2020
  • 3 replies
  • 1352 views

I'm working on a project where I need to control two WS2812B serial RGB LED strips. These strips are connected to 2 GPIOs which are configured to output modulated PWM to generatate zeros and ones, by letting TIM2 run with DMA configured on CC1 and CC2.

The code works well in a bare metal version of the firmware.

I am now porting this code to a FreeRTOS based version of the firmware. For some reason, as soon as I start timer 2 (after being configured with DMA), FreeRTOS directly stops running. I do see the GPIO output data, but the callback functions XferCpltCallback and XferHalfCpltCallback are never entered.

I'm kinda lost in why everything halts here and am not sure how to debug this.

Configuration of timer 2:

void MX_TIM2_Init(void)
{
 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
 TIM_MasterConfigTypeDef sMasterConfig = {0};
 TIM_OC_InitTypeDef sConfigOC = {0};
 
 htim2.Instance = TIM2;
 htim2.Init.Prescaler = 0;
 htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim2.Init.Period = 80;
 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
 if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
 {
 Error_Handler();
 }
 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
 if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.OCMode = TIM_OCMODE_PWM1;
 sConfigOC.Pulse = 26;
 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
 if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.Pulse = 54;
 if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
 {
 Error_Handler();
 }
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
 
 if(tim_baseHandle->Instance==TIM2)
 {
 /* USER CODE BEGIN TIM2_MspInit 0 */
 
 /* USER CODE END TIM2_MspInit 0 */
 /* TIM2 clock enable */
 __HAL_RCC_TIM2_CLK_ENABLE();
 
 /* TIM2 interrupt Init */
 HAL_NVIC_SetPriority(TIM2_IRQn, 6, 0);
 HAL_NVIC_EnableIRQ(TIM2_IRQn);
 /* USER CODE BEGIN TIM2_MspInit 1 */
 
 /* USER CODE END TIM2_MspInit 1 */
 }

Initialization of DMA:

void LED_Driver_Init(uint8_t Mode, uint8_t Count)
{
 LEDDriver.LEDCount = Count;
 LEDDriver.Mode = LED_MODE_RGB;
 LEDDriver.TransferComplete = 1;
 
 tim_period = SystemCoreClock / 800000;
 
 //DMA controller clock enable
 __HAL_RCC_DMA1_CLK_ENABLE();
 
 dmaUpdate.Init.Direction = DMA_MEMORY_TO_PERIPH;
 dmaUpdate.Init.PeriphInc = DMA_PINC_DISABLE;
 dmaUpdate.Init.MemInc = DMA_MINC_DISABLE;
 dmaUpdate.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
 dmaUpdate.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
 dmaUpdate.Init.Mode = DMA_CIRCULAR;
 dmaUpdate.Init.Priority = DMA_PRIORITY_VERY_HIGH;
 dmaUpdate.Instance = DMA1_Channel2;
 HAL_DMA_Init(&dmaUpdate);
 HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 6, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
 
 
 // TIM2 CC1 event
 dmaCC1.Init.Direction = DMA_MEMORY_TO_PERIPH;
 dmaCC1.Init.PeriphInc = DMA_PINC_DISABLE;
 dmaCC1.Init.MemInc = DMA_MINC_ENABLE;
 dmaCC1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
 dmaCC1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
 dmaCC1.Init.Mode = DMA_CIRCULAR;
 dmaCC1.Init.Priority = DMA_PRIORITY_VERY_HIGH;
 dmaCC1.Instance = DMA1_Channel5;
 HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 6, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
 HAL_DMA_Init(&dmaCC1);
 HAL_DMA_Start(&dmaCC1, reinterpret_cast<uint32_t>(DMABitBuffer),
 reinterpret_cast<uint32_t>(&LED_DRIVER_PORT->BRR),
 sizeof(DMABitBuffer) / sizeof(uint16_t));
 
 // TIM2 CC2 event
 dmaCC2.Init.Direction = DMA_MEMORY_TO_PERIPH;
 dmaCC2.Init.PeriphInc = DMA_PINC_DISABLE;
 dmaCC2.Init.MemInc = DMA_MINC_DISABLE;
 dmaCC2.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
 dmaCC2.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
 dmaCC2.Init.Mode = DMA_CIRCULAR;
 dmaCC2.Init.Priority = DMA_PRIORITY_VERY_HIGH;
 dmaCC2.Instance = DMA1_Channel7;
 dmaCC2.XferCpltCallback = DMA_TransferCompleteHandler;
 dmaCC2.XferHalfCpltCallback = DMA_TransferHalfHandler;
 HAL_DMA_Init(&dmaCC2);
 HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 5, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
 
 //Test write some data to DMABitBuffer
 DMABitBuffer[0] = 0b0101010101010101;
 DMABitBuffer[1] = 0xFFFF;
 DMABitBuffer[2] = 0b110011011001100;
 DMABitBuffer[3] = 0xFFFF;
 DMABitBuffer[4] = 0;
 
 //Test start DMA
 HAL_DMA_Start(&dmaUpdate, reinterpret_cast<uint32_t>(&LED_IO_High),
 reinterpret_cast<uint32_t>(&LED_DRIVER_PORT->BSRR),
 sizeof(DMABitBuffer) / sizeof(uint16_t));
 HAL_DMA_Start_IT(&dmaCC2, reinterpret_cast<uint32_t>(&LED_IO_Low),
 reinterpret_cast<uint32_t>(&LED_DRIVER_PORT->BSRR),
 sizeof(DMABitBuffer) / sizeof(uint16_t));
}

The code to send RGB buffers by DMA:

void LED_SendBuffer(uint8_t BufferCount)
{
 //Transmission complete flag
 LEDDriver.TransferComplete = 0;
 
 for(uint32_t Index = 0; Index < BufferCount; Index++ )
 {
 LEDDriver.Buffer[Index].Counter = 0;
 loadNextFramebufferData(&LEDDriver.Buffer[Index], 0); // ROW 0
 loadNextFramebufferData(&LEDDriver.Buffer[Index], 1); // ROW 1
 }
 
 //Clear all DMA flags
 __HAL_DMA_CLEAR_FLAG(&dmaUpdate, DMA_FLAG_TC2 | DMA_FLAG_HT2 | DMA_FLAG_TE2);
 __HAL_DMA_CLEAR_FLAG(&dmaCC1, DMA_FLAG_TC5 | DMA_FLAG_HT5 | DMA_FLAG_TE5);
 __HAL_DMA_CLEAR_FLAG(&dmaCC2, DMA_FLAG_TC7 | DMA_FLAG_HT7 | DMA_FLAG_TE7);
 
 //Configure the number of bytes to be transferred by the DMA controller
 dmaUpdate.Instance->CNDTR = sizeof(DMABitBuffer) / sizeof(uint16_t);
 dmaCC1.Instance->CNDTR = sizeof(DMABitBuffer) / sizeof(uint16_t);
 dmaCC2.Instance->CNDTR = sizeof(DMABitBuffer) / sizeof(uint16_t);
 
 //Clear all TIM2 flags
 __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE | TIM_FLAG_CC1 | TIM_FLAG_CC2 | TIM_FLAG_CC3 | TIM_FLAG_CC4);
 
 //Enable DMA channels
 __HAL_DMA_ENABLE(&dmaUpdate);
 __HAL_DMA_ENABLE(&dmaCC1);
 __HAL_DMA_ENABLE(&dmaCC2);
 
 //IMPORTANT: enable the TIM2 DMA requests AFTER enabling the DMA channels!
 __HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_UPDATE);
 __HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_CC1);
 __HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_CC2);
 
 TIM2->CNT = tim_period - 1;
 
 //Start TIM2
 __HAL_TIM_ENABLE(&htim2);
}

Any help would be greatly appreciated!

    This topic has been closed for replies.

    3 replies

    luukAuthor
    Visitor II
    April 13, 2020

    A little extra information, I just discovered that shortly after starting timer 2 the program enters the default handler infinite loop in the startup file "startup_stm32f103vetx.s"

     .section .text.Default_Handler,"ax",%progbits
    Default_Handler:
    Infinite_Loop:
     b Infinite_Loop
     .size Default_Handler, .-Default_Handler

    This would suggest some interrupt isn't handled properly. Not sure what I'm missing here.

    luukAuthor
    Visitor II
    April 13, 2020

    Of course, I should have thought of this earlier. I forgot to add the IRQ handler back in after starting over in a new branch. After adding the handler the RTOS keeps running like expected.

    void DMA1_Channel7_IRQHandler(void)
    {
     HAL_DMA_IRQHandler(&dmaCC2);
    }

    Visitor II
    April 13, 2020
    1. Disable timer interrupt: /* TIM2 interrupt Init */
    2. HAL_NVIC_SetPriority(TIM2_IRQn, 6, 0);
    3. HAL_NVIC_EnableIRQ(TIM2_IRQn);