提醒:在本文的标准库PWM频率、占空比调节实验所使用的开发板为STM32F407,HAL库PWM调节实验使用的开发板为STM32G431。已实现PWM输出步进1%调节PWM占空比。如果看不懂,可以先跳过原理部分,先copy代码体验一下。如果需要实现精确调节PWM的输出频率及占空比,这些是必须要掌握的。
ST官方在2022年更新了尘封已久的STM32标准库,新版标准库和上一版本没有很大的区别,主要是对一些Bug进行了修复。
STM32标准外设软件库 - 意法半导体STMicroelectronics
目录
1、TIM-PWM参数配置讲解
2、PWM调节STM32 HAL库版
3、PWM调节STM32标准库版
在许多仪器仪表产品中,需要对PWM进行非常精确、频繁的操作,如果每次都是搬出公式来计算再填入数值,非常的耗时,并且容易算错。因此,将PWM输出频率和占空比封装成一个通用接口,可以极大的减少工作量。
在写这篇博客时,发现目前还没有大佬将TIM-PWM调节的HAL库和标准库归类讲解开源出来,并且没有将封装成一个通用接口,实现一个代码接口函数一键配置PWM的输出频率、占空比,实现真正的灵活、动态调整PWM。
频率值:由计数值决定;
占空比:由比较值决定;
预分频器值 PSC:分频定时器的输入时钟,改变计数器的计数速率,prescaler
自动重装载值 ARR, 也就是 counter period
这个看不懂没关系,了解一下就行,后面会详细简单的说。
PWM是基于TIM的配置实现的,要实现精确修改PWM的输出频率和占空比,需要先了解如何实现TIM定时器精准定时。接下来以配置1ms精确定时来说明如何填写TIM配置的数值。
配置CPU主频为80MHz,APB1和APB2总线分频系数为1,定时1ms。
通过以上计算可知,无论是标准库还是HAL库,在对应TIM定时器配置上将prescaler填入数值:80-1,period(自动重装载值填入):1000-1,就可以实现TIM定时器定时1ms的参数配置。
脉冲宽度调制模式可以生成一个信号,该信号频率由自动重装载寄存器(TIMx_ARR)寄存器值决定,其占空比则由捕获/比较寄存器(TIMx_CRx)寄存器决定。
-------------------------------------------------------------------------------------------
PWM频率:Freq = 定时器时钟主频 / [(ARR+1) * (PSC+1)]
PWM占空比: Duty = Pluse / (ARR+1)
Pulse 是 捕获/比较寄存器(CCR)的值
-------------------------------------------------------------------------------------------
提醒:16位的ARR自动重装载值的最大值65535
通过上一部分讲解的公式计算,配置PA1引脚输出:频率为100Hz,占空比为50%的PWM波。
HAL库版本配置PWM,在STM32CubeMX中,选中PA1引脚,进行TIM2_CH2的GPIO引脚配置
选中TIM2,进行TIM定时器的参数配置。
计算输出占空比参数配置
如果每次配置PWM输出频率和占空比,都和上图一样进行公式计算,手动配置,极其的不方便,很可能因粗心等原因计算错误,因此将公式封装成一个HAL库通用接口。在程序代码中灵活自由的修改PWM输出的频率及占空比。
如下代码为HAL库版本的PWM输出参数配置通用接口
如果使用的芯片MCU频率配置不同,那么将prescaler和clk_freq修改,其它保持不变即可。
例:MCU频率配置为64MHz,APB1和APB2均是1分频,
修改tim_clk_freq = 64000000;
prescaler = 64-1;
//通用接口,主频80MHz,预分频值为80-1,设置PWM的脉冲频率freq(0.16-10kHz)、占空比参数 pulse (0-100)
void set_pwm_param(TIM_HandleTypeDef htim, uint32_t Channel, uint32_t freq, uint16_t duty)
{
uint16_t prescaler = 80-1;
uint64_t tim_clk_freq = 80000000;
//计算PWM频率,所对应的自动重装载值 ---> ARR = 主频 / (预分频+1) / 预期PWM频率(Hz) - 1
float pwm_freq_arr = (tim_clk_freq * 1.0) / (prescaler+1) / freq * 1.0 - 1;
//计算PWM占空比,所对应比较寄存器的值 ---> CCR = 预期占空比 * (自动重装载值+1)
//占空比则由捕获/比较寄存器(TIMx_CRx)寄存器决定。占空比:duty = Pluse / (ARR+1)
float pwm_duty_pulse = duty * 1.0 / 100 * (pwm_freq_arr + 1);
//配置PSC预分频值
__HAL_TIM_SET_PRESCALER(&htim, prescaler);
//配置PWM频率 ARR
__HAL_TIM_SetAutoreload(&htim, (uint16_t)pwm_freq_arr);
//配置PWM占空比
__HAL_TIM_SetCompare(&htim, Channel, (uint16_t)pwm_duty_pulse);
printf("pwm_freq_arr:%.2f\r\n", pwm_freq_arr);
printf("pwm_duty_pulse:%.2f\r\n", pwm_duty_pulse);
}
使用逻辑分析仪解析PWM波:逻辑分析仪使用配置,PulseView通信波形解析-CSDN博客
HAL库版本的精确动态调整PWM频率占空比: 频率:20Hz 占空比:1%
set_pwm_param(htim2, TIM_Channel_2, 20, 1);
HAL库版本的精确动态调整PWM频率占空比: 频率:10kHz 占空比:99%
set_pwm_param(htim2, TIM_Channel_2, 10000, 99);
通过STM32F4xx官方参考手册可知,在STM32F407中,定时器TIM3挂载在APB1总线上。当APB1和APB2分频数为1的时候,TIM1、TIM8~TIM11的时钟为APB2的时钟,TIM2~TIM7、TIM12~TIM14的时钟为APB1的时钟;当APB1和APB2分频数不为1,那么TIM1、TIM8~TIM11的时钟为APB2的时钟的两倍,TIM2~TIM7、TIM12~TIM14的时钟为APB1的时钟的两倍。
查看STM32的system_stm32f4xx.c文件,可确定STM32F407的APB1和APB2总线的时钟分频。
因为系统初始化SystemInit函数里初始化APB1总线时钟为4分频即42M,APB2总线时钟为2分频即84M,所以TIM1、TIM8~TIM11的时钟为APB2时钟的两倍即168M,TIM2~TIM7、TIM12~TIM14的时钟为APB1的时钟的两倍即84M。
如下代码为标准库版本的PWM输出参数配置通用接口
如果使用的芯片MCU频率配置不同,那么将prescaler和clk_freq修改,其它保持不变即可。
例:MCU频率配置为64MHz,代码中自动进行APB1和APB2时钟判断
修改 tim_clk_freq = 64000000;
prescaler = 64-1;
//通用接口,主频168MHz,预分频值根据ABPx总线配置,设置PWM的脉冲频率freq(0.16-10kHz)、占空比参数 pulse (0-100)
void set_pwm_param(TIM_TypeDef* TIMx, uint32_t Channel, uint32_t freq, uint16_t duty)
{
uint16_t prescaler = 168 - 1; //预分频值
uint64_t tim_clk_freq = 168000000; //定时器时钟频率
float pwm_freq_arr;
float pwm_duty_pulse;
//系统时钟分频系数不为1
if((TIMx >= TIM2 && TIMx <= TIM7) || (TIMx>=TIM12 && TIMx<=TIM14)) //APB1总线
{
//PrescalerValue = 168/2-1;
prescaler = (prescaler + 1) / 2 -1;
pwm_freq_arr = tim_clk_freq / 2.0 / (prescaler+1) / freq * 1.0 - 1;
}else if((TIMx == TIM2) || (TIMx >= TIM8 && TIMx <= TIM11)) //APB2总线
{
//PrescalerValue = 168-1;
prescaler = (prescaler+1) - 1;
pwm_freq_arr = tim_clk_freq * 1.0 / (prescaler+1) / freq * 1.0 - 1;
}
pwm_duty_pulse = duty / 100.0 * (pwm_freq_arr+1);
//设置预分频值
TIM_PrescalerConfig(TIMx, prescaler, TIM_PSCReloadMode_Immediate);
//设置AAR自动重装载值
TIM_SetAutoreload(TIMx, (uint32_t)pwm_freq_arr);
//设置CCR捕获/比较寄存器值
TIM_SetCompare3(TIMx, (uint32_t)pwm_duty_pulse);
printf("pwm_freq_arr:%.2f\r\n", pwm_freq_arr);
printf("pwm_duty_pulse:%.2f\r\n", pwm_duty_pulse);
}
逻辑分析仪解析PWM波
标准库版本的精确动态调整PWM频率占空比: 频率:100Hz 占空比:1%
set_pwm_param(TIM3, TIM_Channel_3, 100, 1);
标准库版本的精确动态调整PWM频率占空比: 频率:8000Hz 占空比:99%
set_pwm_param(TIM3, TIM_Channel_3, 8000, 99);
标准库PC8引脚TIM3_CH3_PWM的GPIO配置参考代码,如果是动态调整,则初始化代码中的值可以随便填写。
void TIM3_CH3_PWM_Wave_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t PrescalerValue = 0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PC8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_TIM3);
//PrescalerValue = (uint16_t) ((SystemCoreClock /2) / 21000000) - 1;
PrescalerValue = 168 - 1;
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1000;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}