8

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)
  {

  }

}
Bence Kaulics
  • 6,353
  • 12
  • 33
  • 60
oboist B
  • 83
  • 1
  • 1
  • 7
  • I ended up going back to the old libraries because I had them working no problem - I was working on a proto-board with the stm32F417IG and just wanted to get it working again! Hopefully I'll have time to come back to this and get the HAL stuff working. Thanks for all the answers! – oboist B Dec 03 '15 at 22:30

7 Answers7

9

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.
Sixtyfive
  • 141
  • 8
akohlsmith
  • 11,162
  • 1
  • 35
  • 62
  • Tried other suggestions but none worked, this worked for me, utilising Olimex H103 board. this is based on the STM32F103RBT6. I simply added the following line between USER CODE BEGIN 2 AND USER CODE END 2 in main.c HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); – Pop24 Mar 26 '18 at 15:32
  • 1
    As a note - to start complimentary PWM outputs, each of them has to be started separately. e.g. HAL_TIMEx_PWMN_Start(&htim8, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2); – stiebrs Jan 28 '22 at 09:34
  • I spent hours trying to get this to work right. In the version of HAL I have (V1.27.1 in mid 2022), I found that I had to first stop the PWM, then change the config and then start it again: `HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1); HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); ` – Topknot Jul 17 '22 at 01:11
7

When using Cube Mx, the generated code initializes the timer peripheral but does not start it running. The solution is as suggested by others: add the Start functions.

HAL_TIM_Base_Start(&htim3); 
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_ALL); 

Place this code in the "USER CODE" section after the generated code that calls MX_TIM3_Init().

Ricardo
  • 6,134
  • 19
  • 52
  • 85
baron
  • 71
  • 1
  • 2
  • 2
    I tried this for a Nucleo-32 (microcontroller STM32F042K6), also for timer 3. It did ***not*** work using `TIM_CHANNEL_ALL` in the second call, but using `TIM_CHANNEL1` ***instead*** worked (it started actual PWM output on the output pin). Similarly for the other 3 channels, using `TIM_CHANNEL2`, `TIM_CHANNEL3` and `TIM_CHANNEL14`, respectively, in the call to `HAL_TIM_PWM_Start()` (so 4 calls in all to `HAL_TIM_PWM_Start()`). – Peter Mortensen Jan 05 '17 at 00:50
  • The call to `HAL_TIM_Base_Start(&htim3);` is ***not*** needed (empirically determined and also in [akohlsmith's answer](http://electronics.stackexchange.com/questions/179546/getting-pwm-to-work-on-stm32f4-using-sts-hal-libraries/239783#239783)). – Peter Mortensen Jan 05 '17 at 01:08
  • @PeterMortensen that is brilliant. Amazing how so little information can mean so much. Thank you for sharing, this was also crucial on a STM32F412 custom board. – Heath Raftery Aug 30 '18 at 01:40
7

I'm using STCubeMX and the generated HAL initialization files. Process verified on my F302 Nucleo Board. I set up advanced Timer 1 (TIM1) with a normal and complementary output, no dead time.

Here's how I configured PWM in CubeMX:

  1. In pinout view, I selected two pins as the TIM1_CH & TIM1_CHN pins. On the left hand pane, set TIM1 channel 1 as "PWM Generation CH1 CH1N".
  2. In the configuration tab, I put the following setting (TIM1 clk is 64MHz) CubeMX TIM1 Config Settings
  3. After code is generated, we still need to start the PWM. This is done by calling the following functions in the user code region before while(1), within main():

    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); //starts PWM on CH1 pin HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); //starts PWM on CH1N pin

  4. To change the config settings, use the following macros provided in stm32f3xx_hal_tim.h. HAL_TIM_PWM_ConfigChannel() is not the only way to update PWM setting manually as said by @akohlsmith.

    __HAL_TIM_GET_AUTORELOAD(&htim1); //gets the Period set for PWm __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, dutyCycle); //sets the PWM duty cycle (Capture Compare Value)

No need to again call HAL_TIM_PWM_Start(). These macros change the setting during run time.

2

You need to start timer first. Add next line to the main() to start timer3 on the CH1:

HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
Ricardo
  • 6,134
  • 19
  • 52
  • 85
Kolovskiy
  • 21
  • 1
  • 4
0

I think you need to initialize the GPIO clock by calling __GPIOC_CLK_ENABLE();

I have similar code working - but I left out the master config stuff.

There is an example in the stm32g-eval folder that can be adapted to the discovery board (if that is the board you are using).

Ricardo
  • 6,134
  • 19
  • 52
  • 85
0

You need to this:

HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_ALL);
HAL_TIMEx_PWMN_Start(&htim3,TIM_CHANNEL_ALL);
Ricardo
  • 6,134
  • 19
  • 52
  • 85
0
        void PWM_Output(void)
        {


          TimerPeriod = 1000;



          /* TIM1 clock enable */
          RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE);

          /* Time Base configuration */
          TIM_TimeBaseStructure.TIM_Prescaler = 48;
          TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
          TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
          TIM_TimeBaseStructure.TIM_ClockDivision = 1;
          TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

          TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

          /* Channel 1, 2, 3 and 4 Configuration in PWM mode */
          TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
          TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
          TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;



          TIM_OCInitStructure.TIM_Pulse = 899;
          TIM_OC1Init(TIM1, &TIM_OCInitStructure);

          TIM_OCInitStructure.TIM_Pulse = 899;
          TIM_OC2Init(TIM1, &TIM_OCInitStructure);

          TIM_OCInitStructure.TIM_Pulse = 899;
          TIM_OC3Init(TIM1, &TIM_OCInitStructure);

          TIM_OCInitStructure.TIM_Pulse = 899;
          TIM_OC4Init(TIM1, &TIM_OCInitStructure);

          /* TIM1 counter enable */
          TIM_Cmd(TIM1, ENABLE);

          /* TIM1 Main Output Enable */
          TIM_CtrlPWMOutputs(TIM1, DISABLE);

        }

    void GPIO_init(){
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);


              GPIO_PWMEnPins.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
              GPIO_PWMEnPins.GPIO_Mode = GPIO_Mode_AF;
              GPIO_PWMEnPins.GPIO_Speed = GPIO_Speed_Level_1;
              GPIO_PWMEnPins.GPIO_OType = GPIO_OType_PP;
              GPIO_PWMEnPins.GPIO_PuPd = GPIO_PuPd_NOPULL;
              GPIO_Init(GPIOA, &GPIO_PWMEnPins);

 /*SMT32F0 has different config for Alternate Function assignment; below is the example. STM32F4 might have less code footprint to configure alternate func.*/
              GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2);
              GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2);
              GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2);
              GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2);

    }

This code works in CooCox with standard STM32f0xx_yyy libraries. You also need to enable GPIOC's clock in GPIO init.

ammar.cma
  • 542
  • 4
  • 12