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)
{
}
}
-
\$\begingroup\$ 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! \$\endgroup\$oboist B– oboist B2015年12月03日 22:30:17 +00:00Commented Dec 3, 2015 at 22:30
7 Answers 7
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.
- 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. - Don't call
HAL_TIM_PWM_MspInit()
manually. It is automatically called when you callHAL_TIM_PWM_Init()
, but you must make sure your handle variables are properly initialized (see #1 above). - 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. - 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:
- You MUST call
HAL_TIM_PWM_Start()
after every call toHAL_TIM_PWM_ConfigChannel()
-- the first thingHAL_TIM_PWM_ConfigChannel()
does is to disable the timer outputs (theCCxE
bits inTIMx_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:
- Do NOT call
HAL_TIM_Base_Start()
, norHAL_TIM_Base_Init()
, nor any of the other non-PWM calls; First, the HAL will not call yourPWM_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. - You do not have to call
HAL_TIMEx_PWMN_Start()
or any of the otherTIMEx
functions unless you're using the complimentary outputs. Again, this is fairly well documented in the HAL source.
-
\$\begingroup\$ 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); \$\endgroup\$Pop24– Pop242018年03月26日 15:32:56 +00:00Commented Mar 26, 2018 at 15:32
-
1\$\begingroup\$ 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); \$\endgroup\$stiebrs– stiebrs2022年01月28日 09:34:02 +00:00Commented Jan 28, 2022 at 9:34
-
\$\begingroup\$ 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);
\$\endgroup\$Topknot– Topknot2022年07月17日 01:11:12 +00:00Commented Jul 17, 2022 at 1:11
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:
- 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".
- In the configuration tab, I put the following setting (TIM1 clk is 64MHz) CubeMX TIM1 Config Settings
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
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.
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()
.
-
2\$\begingroup\$ 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 usingTIM_CHANNEL1
instead worked (it started actual PWM output on the output pin). Similarly for the other 3 channels, usingTIM_CHANNEL2
,TIM_CHANNEL3
andTIM_CHANNEL14
, respectively, in the call toHAL_TIM_PWM_Start()
(so 4 calls in all toHAL_TIM_PWM_Start()
). \$\endgroup\$Peter Mortensen– Peter Mortensen2017年01月05日 00:50:26 +00:00Commented Jan 5, 2017 at 0:50 -
\$\begingroup\$ The call to
HAL_TIM_Base_Start(&htim3);
is not needed (empirically determined and also in akohlsmith's answer). \$\endgroup\$Peter Mortensen– Peter Mortensen2017年01月05日 01:08:37 +00:00Commented Jan 5, 2017 at 1:08 -
\$\begingroup\$ @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. \$\endgroup\$Heath Raftery– Heath Raftery2018年08月30日 01:40:42 +00:00Commented Aug 30, 2018 at 1:40
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);
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);
-
3\$\begingroup\$ Please explain where and why. \$\endgroup\$JRE– JRE2015年11月12日 08:07:09 +00:00Commented Nov 12, 2015 at 8:07
-
\$\begingroup\$ The "_IT" refers to something interrupt-related. Why would that be needed just to get PWM output? \$\endgroup\$Peter Mortensen– Peter Mortensen2017年01月05日 00:55:59 +00:00Commented Jan 5, 2017 at 0:55
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.
-
\$\begingroup\$ That may work, but the question was about using the ST HAL libraries. \$\endgroup\$Peter Mortensen– Peter Mortensen2017年01月05日 00:57:37 +00:00Commented Jan 5, 2017 at 0:57
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).
Explore related questions
See similar questions with these tags.