提醒:在本文的标准库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标准库版


1、TIM-PWM参数配置讲解

        在许多仪器仪表产品中,需要对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-1period(自动重装载值填入):1000-1,就可以实现TIM定时器定时1ms的参数配置。

脉冲宽度调制模式可以生成一个信号,该信号频率自动重装载寄存器(TIMx_ARR)寄存器值决定,其占空比则由捕获/比较寄存器(TIMx_CRx)寄存器决定。

-------------------------------------------------------------------------------------------

PWM频率:Freq = 定时器时钟主频  /  [(ARR+1) * (PSC+1)]

PWM占空比: Duty = Pluse / (ARR+1)

Pulse 是 捕获/比较寄存器(CCR)的值

-------------------------------------------------------------------------------------------

        

提醒:16位的ARR自动重装载值的最大值65535

2、PWM调节STM32 HAL库版

通过上一部分讲解的公式计算,配置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);

3、PWM调节STM32标准库版

        通过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);
}

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐