Electronic – Getting PWM to work on STM32F4 using ST’s HAL libraries

hal-librarypwmstm32cubemxstm32f4

I'm trying to switch to the ST HAL libraries and can't seem to get the PWM to work. Compiles fine, just doesn't start.

In my main() I call the Timer initialization function:

/* TIM3 init function */
void MX_TIM3_Init(void)
{

  TIM_MasterConfigTypeDef sMasterConfig;
  TIM_OC_InitTypeDef sConfigOC;

  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 0;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 1300;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  HAL_TIM_PWM_Init(&htim3);

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);

  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_SET;
  sConfigOC.Pulse = 650;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;

  HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
  HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2);
  HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3);
  HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4);
  HAL_TIM_PWM_MspInit(&htim3);

} 

The GPIO is initialized in the HAL_TIM_PWM_MspInit() function:

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(htim_pwm->Instance==TIM3)
  {
    /* Peripheral clock enable */
    __TIM3_CLK_ENABLE();

    /**TIM3 GPIO Configuration    
    PC9     ------> TIM3_CH4
    PC8     ------> TIM3_CH3
    PC7     ------> TIM3_CH2
    PC6     ------> TIM3_CH1 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_8|GPIO_PIN_7|GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

  }

}

finally my main() looks like this: (I'm calling SystemInit() from main because I'm using STCube generated files with coocox coide)

int main(void)
{

    SystemInit() ;

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_TIM3_Init();
  MX_GPIO_Init();
  MX_LWIP_Init();
  while (1)
  {

  }

}

Best Answer

I'm late to the party, but I found myself in a similar situation and have the real answer, or at least the answer that I've personally verified to work with my own STM32 board and the STM32CubeMx drivers.

  1. Make sure you initialize (zero out) your stack-allocated structs. e.g. memset(&sConfigOC, 0, sizeof(sConfigOC)); Local variables are not automatically initialized and you will have unexpected/unintended configuration because the HAL makes very basic assumptions about the peripheral state based on these handle variables. It is particularly maddening when the behaviour changes from compile to compile or from function call to function call as the stack memory changes.
  2. Don't call HAL_TIM_PWM_MspInit() manually. It is automatically called when you call HAL_TIM_PWM_Init(), but you must make sure your handle variables are properly initialized (see #1 above).
  3. You don't need to call HAL_TIMEx_MasterConfigSynchronization() if you're not changing from the default (non chained/synchronized) configuration. It won't hurt anything, but it's also not necessary.
  4. Your MspInit() function is good, except you should also enable the clock of the GPIOC peripheral.

And the magic that is preventing it from working:

  1. You MUST call HAL_TIM_PWM_Start() after every call to HAL_TIM_PWM_ConfigChannel() -- the first thing HAL_TIM_PWM_ConfigChannel() does is to disable the timer outputs (the CCxE bits in TIMx_CCER). HAL_TIM_PWM_ConfigChannel() does not re-enable the timer outputs!

Since the only HAL way of manually updating the PWM duty cycle is through HAL_TIM_PWM_ConfigChannel(), you must always call HAL_TIM_PWM_Start() or you will not have a PWM output. I think this is done because the normal way of working with PWM is to use the _IT() or _DMA() variants of HAL_TIM_PWM_Start(), and thus you are updating the duty cycle "in the background" so to speak.

Some of the other answers (including the one you had originally marked as accepted) are wrong:

  1. Do NOT call HAL_TIM_Base_Start(), nor HAL_TIM_Base_Init(), nor any of the other non-PWM calls; First, the HAL will not call your PWM_MspInit() function because the peripheral handle will no longer be in reset state, but more importantly, the PWM (and OC, and other variants) all call the low level internal functions properly without you manually doing it. The entire point of the HAL is to not have to worry so much about the details, but you've got to have a good handle on what the HAL is doing and what it's expecting. The full source is available and is fairly well documented. You just need to read it.
  2. You do not have to call HAL_TIMEx_PWMN_Start() or any of the other TIMEx functions unless you're using the complimentary outputs. Again, this is fairly well documented in the HAL source.