Skip to main content
Explorer
September 6, 2025
Solved

STM32C071RB Problem with PWM + DMA (only works once)

  • September 6, 2025
  • 5 replies
  • 832 views

I'm working on a STM32C071RB, which I've managed to generate PWM pulses as desired. However, I try to more than once while the chip is running, it stops working.

My main.c file:

#include "main.h"

#define PWM_HI (12)
#define PWM_LO (6)


TIM_HandleTypeDef htim1;
DMA_HandleTypeDef hdma_tim1_ch1;

uint8_t wr_buf[32] = {0};

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_TIM1_Init(void);

void led_set_RGB(uint8_t r, uint8_t g, uint8_t b) {
	for(int i = 0; i < 8; i++){
		wr_buf[i] = (g & (1 << i)) ? PWM_HI : PWM_LO;
		wr_buf[i + 8] = (r & (1 << i)) ? PWM_HI : PWM_LO;
		wr_buf[i + 16] = (b & (1 << i)) ? PWM_HI : PWM_LO;
	}
}

void led_render() {
	while (HAL_DMA_GetState(&hdma_tim1_ch1) != HAL_DMA_STATE_READY) {}
	HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)wr_buf, 32);
}

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
	if (htim->Instance == TIM1) {
		// Stop PWM and DMA after all pulses are transfered
		HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_1);
 }
}

int main(void)
{
 HAL_Init();
 SystemClock_Config();
 MX_GPIO_Init();
 MX_DMA_Init();
 MX_TIM1_Init();
 
 while (1)
 {
	 led_set_RGB(0xFF,0x00,0x00);
	 led_render();
	 HAL_Delay(500);

	 led_set_RGB(0x00,0xFF,0x00);
	 led_render();
	 HAL_Delay(500);

	 led_set_RGB(0x00,0x00,0xFF);
	 led_render();
	 HAL_Delay(500);

 }

}

void SystemClock_Config(void)
{
 RCC_OscInitTypeDef RCC_OscInitStruct = {0};
 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

 __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_1);

 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
 RCC_OscInitStruct.HSIState = RCC_HSI_ON;
 RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;
 RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
 {
 Error_Handler();
 }

 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
 |RCC_CLOCKTYPE_PCLK1;
 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
 RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
 RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
 RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV1;

 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
 {
 Error_Handler();
 }
}

static void MX_TIM1_Init(void)
{
 TIM_ClockConfigTypeDef sClockSourceConfig = {0};
 TIM_MasterConfigTypeDef sMasterConfig = {0};
 TIM_OC_InitTypeDef sConfigOC = {0};
 TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

 htim1.Instance = TIM1;
 htim1.Init.Prescaler = 2;
 htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
 htim1.Init.Period = 19;
 htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
 htim1.Init.RepetitionCounter = 0;
 htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
 if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
 {
 Error_Handler();
 }
 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
 if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
 {
 Error_Handler();
 }
 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
 sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
 sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
 if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
 {
 Error_Handler();
 }
 sConfigOC.OCMode = TIM_OCMODE_PWM1;
 sConfigOC.Pulse = 0;
 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
 sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
 sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
 sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
 sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
 if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
 {
 Error_Handler();
 }
 sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
 sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
 sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
 sBreakDeadTimeConfig.DeadTime = 0;
 sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
 sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
 sBreakDeadTimeConfig.BreakFilter = 0;
 sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;
 sBreakDeadTimeConfig.Break2State = TIM_BREAK2_DISABLE;
 sBreakDeadTimeConfig.Break2Polarity = TIM_BREAK2POLARITY_HIGH;
 sBreakDeadTimeConfig.Break2Filter = 0;
 sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
 sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
 if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
 {
 Error_Handler();
 }

 HAL_TIM_MspPostInit(&htim1);

}

static void MX_DMA_Init(void)
{
 __HAL_RCC_DMA1_CLK_ENABLE();

 HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
 HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

}

static void MX_GPIO_Init(void)
{

 __HAL_RCC_GPIOA_CLK_ENABLE();

}

void Error_Handler(void)
{

 __disable_irq();
 while (1)
 {
 }
}
#ifdef USE_FULL_ASSERT

void assert_failed(uint8_t *file, uint32_t line)
{

}
#endif /* USE_FULL_ASSERT */

 
As you can see in the while loop in the main function, it manages to run the first segment of led_render(), however, whenever led_render() is called again, it just leads to HAL_TIM_PWM_Start_DMA() to return HAL_ERROR, which seem to be coming from HAL_DMA_Start_IT().


Update:

I discovered, by not calling HAL_TIM_PWM_Stop_DMA(), it works fine. Might be a bug that you cannot call the _Start_DMA() after _Stop_DMA() ??

 

    This topic has been closed for replies.
    Best answer by EngEmil

    TLDR; HAL_TIM_PWM_Stop_DMA() is unnecessary in Normal Mode.


    I got answer from ST supports. It seems like HAL_TIM_PWM_Stop_DMA() is designed with circular mode in mind, and not normal mode.

    As you call HAL_TIM_PWM_Stop_DMA(), it calls HAL_DMA_Abort_IT(), and here the DMA gets locked (__HAL_LOCK(hdma);).

    Now if DMA is NOT busy (HAL_DMA_STATE_BUSY != hdma->State), it ends up not calling __HAL_UNLOCK(hdma);. This leads to a locked DMA, so when next time I call HAL_TIM_PWM_Start_DMA(), it won't work as DMA is locked.

    I've asked if this design is intentional, or they will make it possible to call HAL_TIM_PWM_Stop_DMA() even when DMA is not busy, so it doesn't accidentally lock it. For now I'll just avoid calling HAL_TIM_PWM_Stop_DMA() in normal mode, as it is unnecessary.

    Side note: I'm posting this so other people can see what ST support mentioned about this

    5 replies

    Super User
    September 7, 2025

    Most of what seem to be bugs in Cube/HAL are incorrect usage, or, more precisely, usage different from what was anticipated by Cube/HAL authors. Unfortunately, that's poorly documented, as Cube/HAL is mostly a vehicle to sell through the idea of "programming by clicking", i.e. it's intended to provide the fabric needed to CubeMX to be usable.

    So, you can try to find the expected usage in the provided "documentation". However, Cube/HAL is open source, so you may be better of simply looking into the sources to try to find out the intended usage.

    Comparing differences in TIM and DMA registers content of the "working" and "non-working" cases (just before DMA is enabled, that is, the very line deep in Cube/HAL which enables the DMA channel) will provide insight to the context under which the DMA works/does not work as expected.

    JW

    Graduate
    September 7, 2025

    wr_buf should be declared as uint32_t, as I already wrote in another thread. The likely problem is the alignment error.

    Super User
    September 7, 2025

    > The likely problem is the alignment error.

    But would that be the cause, that would prevent running it in the first time, too, wouldn't it?

    JW

    Super User
    September 8, 2025

    What @gbm  probably meant was, that besides setting DMA to word, you'd change also the array's definition to uint32_t.

    As I wrote above, I don't think this is the root of problem.

    JW

    Technical Moderator
    September 8, 2025

    Hello @EngEmil 

    Is the hdma state is deferent from HAL_DMA_STATE_READY before calling the HAL_DMA_Start_IT()?

    EngEmilAuthorAnswer
    Explorer
    September 9, 2025

    TLDR; HAL_TIM_PWM_Stop_DMA() is unnecessary in Normal Mode.


    I got answer from ST supports. It seems like HAL_TIM_PWM_Stop_DMA() is designed with circular mode in mind, and not normal mode.

    As you call HAL_TIM_PWM_Stop_DMA(), it calls HAL_DMA_Abort_IT(), and here the DMA gets locked (__HAL_LOCK(hdma);).

    Now if DMA is NOT busy (HAL_DMA_STATE_BUSY != hdma->State), it ends up not calling __HAL_UNLOCK(hdma);. This leads to a locked DMA, so when next time I call HAL_TIM_PWM_Start_DMA(), it won't work as DMA is locked.

    I've asked if this design is intentional, or they will make it possible to call HAL_TIM_PWM_Stop_DMA() even when DMA is not busy, so it doesn't accidentally lock it. For now I'll just avoid calling HAL_TIM_PWM_Stop_DMA() in normal mode, as it is unnecessary.

    Side note: I'm posting this so other people can see what ST support mentioned about this