> 技术文档 > PWM讲解+STM32任意频率、占空比、脉宽生成函数介绍_stm32发送1-5000hzpwm 脉宽精度可控

PWM讲解+STM32任意频率、占空比、脉宽生成函数介绍_stm32发送1-5000hzpwm 脉宽精度可控


1.PWM讲解

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制。

脉宽调制

最开始使用PWM时,是做智能车时使用的舵机打角,电机驱动。这都属于比较浅显,普通的应用。下面和大家简单分享一下PWM的一些东西。

1.2 PWM参数

1.2.1 频率(重复频率,重频)

(注:重复频率、重频等是一些行业对频率的不同叫法,这都是一个东西)

PWM波是一串重复的矩形波,频率越高,周期越短(f=T/1),代表1S内重复出现的次数越多,这个应该不难理解。(并非是高电平出现次数,而是高+低,因为一个周期是高+低)

2kHz50%
5kHz50%

比如常用的50Hz对应周期20ms,含义就是1S内有50个重复的波形出现,每个完整波形出现的时间是20ms。1kHz对应周期1ms,5kHz对应周期200us。

1.2.2 占空比

占空比是PWM的第二大重要参数。一般来说,根据实际需要,PWM有输出极性的选择,高极性或低极性。指在有效输出的是高电平还是低电平。

占空比的定义是,在一个周期内,有效电平(此文章中指高电平)所占时间的百分比。

PWM占空比

比如下面这张图,我可以理解为2kHz20%高电平(正脉冲)的PWM波,也可以理解为2kHz80%低电平(负脉冲)的PWM波。

(本文的PWM讲解均以高极性示例。例:下图为20%占空比,指在一个周期中,20%时间为高电平)

2kHz20%
2kHz10%

下图为占空比从0%-100%的变化过程。

占空比在0%-100%之间调整

PWM还有一种作用,等效电平。

在一些条件受限无法使用dac,可以用PWM等效电压进行输出。

比如当某个IO口输出PWM时候,使用示波器测量端口,测到的就是矩形波。

但是你用万用表测量这个端口的电压你显然无法得到一系列图像,你得到的只有一个电压。

如下图,在频率固定的情况下,使用万用表测得的等效电压=幅值电压*占空比

等效电压

这就是使用数字量PWM波去等效模拟量电压。

还有一种PWM的高端玩法叫SPWM,就是利用这种原理来让PWM控制MOS管之类来等效正弦波输出,这里暂时不做介绍,大家有兴趣可以自行查阅。

1.2.3 脉宽

脉宽指的是PWM输出过程中有效电平(本文中指高电平)所占的时间长度

脉宽和占空比,在同频率是可以换算的。

以上文图示的1kHz/2kHz,20%占空比为例:

2kHz对应周期是 500us,20%占空比,则是500us*20%=100us。

1kHz对应周期是1000us,20%占空比,则是1000*20%=200us。

一般在说PWM参数时,更多的使用的是频率和占空比,因为单纯使用脉宽会出现相互矛盾的情况,而且也无法确认PWM唯一。

例如:要求2kHz,1ms脉宽。2kHz周期就已经是500us,我怎么可能给你生成1ms的脉宽???

例如:1ms脉宽,不设置频率。

那么我输出100Hz,10%占空比,200Hz,20%占空比,10Hz1%占空比,都是输出1ms的脉宽,我又怎么确定呢?

所以我一般在换算,参数设置的时候,都用占频率和占空比。只要频率,占空比确定了,输出的PWM就是确定的,不会存在参数冲突,不会存输出参数不唯一的情况。

2.基于STM32库函数的任意频率,任意占空比生产函数介绍

简单介绍一下STM32定时器的PWM输出设置。

在stm32的PWM初始化函数中,我们需要填两个参数,一个arr,一个psc。

这个arr在一些其他芯片也叫period,周期。

PWM输出原理

pwm的输出原理其实非常的简单粗暴,定时器不断的向上计数(根据设置模式不同,也有向下计数,中央对齐计数,本文以向上计数为例),不断与两个数做对比,一个是ccr,输出比较值,一个是arr自动装载值。

当定时器自身计数TIMx->CNT值大于CCRx的值,PWM输出引脚电平改变(高/低),

当TIMx->CNT大于ARR时,计数器归零,重新计数。

如此往复,所以定时器本质就是一个计数器,PWM输出的原理就是不断的比较。

当然,也因为这个原因,当一个定时器用作PWM输出时,就不可以进行其他功能,比如定时器中断,比如输入捕获等。

同时,一个定时器往往有多个通道,同一个定时器可以进行多通道输出,不同通道输出的占空比可以不一样,但是他们的频率一定是一样的。

原因就是ARRx是自动装载值,决定了周期,且一个定时器使用同一个装载值;

而控制占空比的CCRx每个通道都有一个,所以可以根据CCRx的不同,控制不同占空比输出。

相同频率, 不同占空比输出

好了,我们已经基本了解了PWM输出原理,下面开始根据需要设置输出。

设置PWM输出最重要的公式在下面:

f=\\frac{CLK}{(arr+1)(psc+1)}

注:上式的arr+1,psc+1是因为他们是分母,且是u16位的数据类型。如果人为赋值为零,会造成除0,会有无法预计的后果,单片机会自动给他+1,我们在设置时候记得手动减一即可。

上式中:CLK为单片机时钟频率,STM32F103系列一般为72M,是固定值;

              arr为装载值,u16,数据范围0-65535;

              psc为预分频系数,u16,数据范围0-65535;

              f为需要输出的PWM频率;

还有一个值,ccr代表占空比。

下面先介绍占空比计算方法:占空比=ccr/arr*100%

(假设arr为100,ccr为20,那么计数器从0开始计数,计数到20时,计数器值大于ccr,输出电平改变,当计数到100时,计时器自动归零,重新计数。)

在数值范围不超u16范围限制情况下,arr越大,可调占空比精度越高。

假设ccr最大值为10k:

最大分辨率为1/10k*100%=0.01%;//万分之一

假设ccr最大值为50:

最大分辨率为1/50*100%=20%;//五分之一

我们再返回来看频率公式:

f=\\frac{CLK}{(arr+1)(psc+1)}

既然CLK是固定的,输出频率f固定的情况下,我们尽可能让psc降低,使arr变大。但是不能让arr太大,因为他是u16类型,最大也只有65535。那么我们根据不同的频率,调整psc的值,让arr始终在1k-10k,也就是千分之一到万分之一精度即可。

我的想法是做简单的判断,根据不同的频率,给不同的分频系数,代码参考如下:

#define MAIN_CLK 72000000u16 arr = 0;u16 ccr = 0;u16 psc = 0;u16 ckl_temp = 0;if(freq>=5000)//5kHz+,{ psc = 1;//72M/1=72M时钟}else if(100<=freq && freq<5000)//100-5000{ psc = 72;//72M/72=1M时钟}else //1Hz-100Hz{ psc = 7200;//72M/7200=10k时钟}ckl_temp = MAIN_CLK /psc;arr =(u16)(ckl_temp /freq);ccr =(u16)(arr*duty/100.0); TIMx_PWM_Init((u16)(ccr-1),(u16)(psc));

这里的分界线是100Hz和5000Hz。

1Hz-100Hz时,7200分频,72M/7200=10k时钟,

1Hz时,    arr=10k/1=10k

100Hz时,arr=10k/100=100

100Hz-5000Hz时,72分频,72M/72=1M时钟,

100Hz时,  arr=1M/100=10k

5000Hz时,arr=1M/5000=200

5000Hz以上,1分频,72M时钟,

5000Hz时,arr=72M/5000=14400

整体精度在百分之一以上,就是不优雅。

那我们为什么不能大胆一点,直接将arr设置为65535呢?

此时clk是确定值,输入的f也是确定值,arr我们人工设置为65535这样他就是确定值,解方程解出psc即可。

psc=\\frac{CLK}{f*65535}-1

当然,如果f太大,以STM32的72M时钟来说,f=1098时

psc=\\frac{72000000}{1098*65535}-1\\approx 0

显然psc作为预分频系数是不能为0的,那我们需要简单处理一下,如果psc小于1那么让他保持为1。

这里也还需要考虑到下psc的溢出情况,首先我们可以限制f频率的输入,限制为1Hz即可。

根据上述公式,f越小,psc越大。在72M时钟下,如果f是1,psc也只是1098而已,不会溢出。

如果换一个操作平台,他的主频较高,会不会溢出呢?

答案是不会,计算过程如下:

psc=\\frac{CLK}{f*65535}-1

我们让psc最大,为65536;让f最小,为1。反推clk的值。(暂时先不考虑psc的-1问题)

ckl=psc*f*65535=65535*1*65535=4,294,836,225大概是4.29GHz的频率。

所以当主时钟小于4.2GHz时,最小输出1Hz情况下,大胆的让arr=65535吧,无需考虑psc的溢出问题。

参考代码如下:

#define MAIN_CLK 72000000float psc_temp= 0;psc_temp=(MAIN_CLK /(freq * 65535.0))-1;//arr设定为65535if(psc_temp < 1){ psc_temp= 1;}psc = (u16) psc_temp;arr = MAIN_CLK / (freq * psc ); //arr计算ccr = arr * duty / 100.0f; //通过占空比,计算比较值//定时器初始化TIM4_PWM_Init((u16)(arr - 1), psc);TIM_SetCompare1(TIM4, (u16)(ccr));//TIM4 CH1 -PD12

这里我们还可以再次改进,利用位操作,将除法改为位移,省去了判断0的步骤,代码更为优雅。

(此处灵感来源(抄袭)于逐飞科技,逐飞科技的驱动代码还是非常值得学习。在此感谢逐飞科技对我嵌入式学习的大力支持,我在大学买了他家很多东西,这也算是交过学费了吧)

#define MAIN_CLK 72000000 //STM32时钟72M/*------------------------------------------------------------------------------------------------------------------- @brief 定时器4初始化 @param arr 装载值 psc 分频系数 @return null Sample TIM4_PWM_Init((ccr - 1), psc - 1); @note 由其他函数调用,无需关心-------------------------------------------------------------------------------------------------------------------*/void TIM4_PWM_Init(u16 arr, u16 psc){ //**结构体声明**// TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //声明定时器 TIM_OCInitTypeDef TIM_OCInitStructure; //声明PWM通道 GPIO_InitTypeDef GPIO_InitStructure; //声明GPIO //**时钟使能**// RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);//使能定时器TIM4时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);//使能PD端口时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //复用功能模块时钟 GPIO_PinRemapConfig(GPIO_Remap_TIM4, ENABLE); //重映射 //PD14 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;//D14 端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//IO口速度为50MHz GPIO_Init(GPIOD, &GPIO_InitStructure); //根据设定参数初始化GPIOD14 GPIO_ResetBits(GPIOD, GPIO_Pin_14); //PD12 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PD12端口配置 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz GPIO_Init(GPIOD, &GPIO_InitStructure); //根据设定参数初始化GPIOD12 GPIO_ResetBits(GPIOD, GPIO_Pin_12); //初始化TIM4 TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 //初始化TIM4 Channel1 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高 TIM_OC1Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC3 //初始化TIM4 Channel3 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高 TIM_OC3Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC3 TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4_CH1预装载寄存器 TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4_CH3预装载寄存器 TIM_Cmd(TIM4, DISABLE); //默认失能TIM4 //TIM_Cmd(TIM4, ENABLE); //使能TIM4}/*------------------------------------------------------------------------------------------------------------------- @brief 设置频率,占空比PWM输出 @param freq 频率查看宏定义 duty 占空比0-100(代表0%-100%) @return null Sample Set_PWM_Duty_Output(5000,20);//5kHz,20%占空比 @note 注意,0-100占空比代表0%-100% 输出通道为TIM4 CH1 -PD12,如需修改,在最后输出定时器,通道处修改即可-------------------------------------------------------------------------------------------------------------------*/void Set_PWM_Duty_Output(u32 freq, double duty){ u16 psc = 0; //分频系数 u16 arr = 0; //装载值 u16 ccr = 0; //比较值 if(freq >= FREQ_MAX) { freq = FREQ_MAX; } else if(freq = DUTY_MAX) { duty = DUTY_MAX; } else if(duty > 16); //多少分频 arr= (u16)( MAIN_CLK /(freq*(psc+ 1))); //周期 ccr= arr* duty / 100;  //占空比 TIM4_PWM_Init((u16)(arr- 1), psc); if(duty = DUTY_MAX ) { ccr= arr+ 1;//满占空比时,ccr要比arr大,不能相等 } TIM_SetCompare1(TIM4, (u16)(ccr));//TIM4 CH1 -PD12// TIM_SetCompare3(TIM4, (u16)(ccr));//TIM4 CH3 -PD14 TIM_Cmd(TIM4, ENABLE); //使能TIM4}

 psc= (u16)((MAIN_CLK / freq) >> 16);    //多少分频

这里的>>16含义如下:

1.除以65535,位运算更快

2.psc是16位,取结果的高16位,保证不溢出

3.高16位如果都是0,那么他的值就是0,不比考虑小数负数之类的事情

下一步为避免除0的问题,给psc+1再计算arr

其中有一小点需要注意,占空比为0,也就是低电平输出,占空比为100,高电平输出,需要特殊处理一下。因为ccr和arr值不能相等,不然会出现一个计数周期的脉冲跳变。

3.基于STM32库函数的任意频率,任意脉宽生产函数介绍

有了上面的任意占空比函数生成,任意脉宽生产也只需要增加一点占空比计算部分而已。

假设情况如下:

频率:5kHz~50kHz,频率步进100Hz

脉宽:0.5us~10us,步进0.1us

首先确认参数是否有冲突。

5kHz,周期为200us,0.5us占空比为1/400

5kHz,周期为200us,10us占空比为1/20

步进0.1us为占空比的1/2000

50kHz,周期为20us,0.5us占空比为1/40

50kHz,周期为20us,10us占空比为1/2

步进0.1us为占空比的1/200

没出现占空比超过100%,没出现占空比步进值过低的情况。

任意脉宽输出其实只是多了一个脉宽与占空比换算的部分而已。

具体计算过程其实也在上面写了,先算当前频率下的周期,然后计算当前脉宽占周期的值,这个值就是占空比。

计算完毕后考虑一下参数冲突的情况,做好边界处理。

(此处建议大家养成良好的编程习惯,对于所有已知数据范围的变量,一定要做好限幅处理)

其实核心代码只有两行,一个计算周期,一个计算当前脉宽占周期的多少,但是各种数据范围的判断,限制占了大量篇幅。

参考代码如下:

#define MAIN_CLK 72000000 //STM32时钟72M //这里#define WIDTH_MAX 100000 //脉宽100000ns#define WIDTH_MIN 10 //脉宽10ns#define FREQ_MAX 10000 //频率10MHz#define FREQ_MIN 1 //频率1Hz#define DUTY_MAX 100 //占空比100%#define DUTY_MIN 0 //占空比0%/*------------------------------------------------------------------------------------------------------------------- @brief 设置频率,脉宽控制PWM输出 @param freq 频率 范围查看宏定义 pulse_width 脉宽 范围查看宏定义 @return null Sample Set_PWM_Width_Output(5000,1000);//5kHz,1us脉宽 @note 注意,内部计算出占空比后, 内部调用Set_PWM_Duty_Output(u32 freq, double duty)实现,无需重复调用。输出通道为TIM4 CH1 -PD12 可去Set_PWM_Duty_Output函数中自行替换输出通道-------------------------------------------------------------------------------------------------------------------*/void Set_PWM_Width_Output(u32 freq, u16 pulse_width){ double duty = 0; double period = 0; if((0 == freq) && (0 == pulse_width))//输入为0,直接拉低 { freq = FREQ_MIN; duty = DUTY_MIN; } else//输入非0,正常输出 { if(freq >= FREQ_MAX) { freq = FREQ_MAX; } else if (freq = WIDTH_MAX) { pulse_width = WIDTH_MAX; } else if(pulse_width <= WIDTH_MIN) { pulse_width = WIDTH_MIN; } period = 1000000000.0f / freq; //计算周期,单位为ns,所以用1G除频率 duty = pulse_width * 100.f / period;//计算当前脉宽占周期多少,单位是百分比,例:算出前脉宽占周期0.5(50%),此处值将是50 if(duty = DUTY_MAX ) { duty = DUTY_MAX; } } Set_PWM_Duty_Output(freq, duty);//调用占空比函数}

这里再附上STM32硬件定时器通道表

(文件来自STM32F10XXX中文参考手册_V10,118页-119页)

4.完整文件在此

里面的频率限制完全可以打开,1Hz到很高都可以(显然不能超过72M主时钟频率),只是频率越高,占空比精度控制越差,具体原因上面都写了。

10MHz输出频率准确
50ns实际41.667ns

我在keil中仿真测试测试,10MHz,50%(50ns),实际输出10MHz,41.667ns。虽然与设定有点误差,但我觉得已经不错了。

实际情况下应该不会有人用stm32去产生10MHz的信号,这里仅供娱乐参考。

#include \"PWM.h\"/*------------------------------------------------------------------------------------------------------------------- @brief 定时器PWM初始化 @param arr 装载值psc 分频系数 @return null Sample TIM4_PWM_Init(arr,psc); @note 引脚,定时器模式选择、初始化 TIM4_CH1TIM4_CH3 内部调用,用户无需关心-------------------------------------------------------------------------------------------------------------------*/void TIM4_PWM_Init(u16 arr, u16 psc){//**结构体声明**//GPIO_InitTypeDef GPIO_InitStructure; // 声明GPIOTIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 声明定时器TIM_OCInitTypeDef TIM_OCInitStructure; // 声明PWM通道//**时钟使能**//RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); // 使能定时器TIM4时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); // 使能PD端口时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 复用功能模块时钟GPIO_PinRemapConfig(GPIO_Remap_TIM4, ENABLE); // 重映射// PD14GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; // PD14 端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHzGPIO_Init(GPIOD, &GPIO_InitStructure); // 根据设定参数初始化GPIOD14GPIO_ResetBits(GPIOD, GPIO_Pin_14);// PD12GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // PD12端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度为50MHzGPIO_Init(GPIOD, &GPIO_InitStructure); // 根据设定参数初始化GPIOD12GPIO_ResetBits(GPIOD, GPIO_Pin_12);// 初始化TIM4TIM_TimeBaseStructure.TIM_Period = arr;// 设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler = psc;// 设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = 0;// 设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // TIM向上计数模式TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);// 根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位// 初始化TIM4 Channel1 PWM模式TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // 选择定时器模式:TIM脉冲宽度调制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 输出极性:TIM输出比较极性高TIM_OC1Init(TIM4, &TIM_OCInitStructure); // 根据T指定的参数初始化外设TIM4 OC1// 初始化TIM4 Channel3 PWM模式TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; // 选择定时器模式:TIM脉冲宽度调制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; // 输出极性:TIM输出比较极性高TIM_OC3Init(TIM4, &TIM_OCInitStructure); // 根据T指定的参数初始化外设TIM4 OC3TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); // 使能TIM4_CH1预装载寄存器TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); // 使能TIM4_CH3预装载寄存器TIM_Cmd(TIM4, DISABLE); // 默认失能TIM4}/*------------------------------------------------------------------------------------------------------------------- @brief PWM初始化 @param freq 默认输出频率duty 默认输出占空比 @return null Sample PWM_Init(50,20);//默认50Hz,20占空比 @note 引脚,定时器模式选择、初始化 TIM4_CH1 -PD12TIM4 CH3 -PD14 内部调用,用户无需关心-------------------------------------------------------------------------------------------------------------------*/void PWM_Init(u16 freq, u16 duty){double psc = 0;double arr = 0; // 装载值double ccr = 0; // 比较值if (freq >= FREQ_MAX){freq = FREQ_MAX;}else if (freq = DUTY_MAX){duty = DUTY_MAX;}else if (duty > 16);arr = (u16)(MAIN_CLK / (freq * (psc + 1)));ccr = (u16)(arr * duty / 100.0f);TIM4_PWM_Init((u16)(arr - 1), psc);if (duty = 100) // 满占空比时,com要比ccr大,不能相等{ccr = arr + 1;}TIM_SetCompare1(TIM4, (u16)(ccr)); // TIM4 CH1 -PD12TIM_SetCompare3(TIM4, (u16)(ccr)); // TIM4 CH3 -PD14TIM_Cmd(TIM4, ENABLE); // 使能TIM4}/*------------------------------------------------------------------------------------------------------------------- @brief PWM占空比输出函数 @param freq 输出频率duty 输出占空比 @return null Sample Set_PWM_Output(5000,20);//5000Hz,20占空比 @note TIM4_CH1 -PD12TIM4 CH3 -PD14-------------------------------------------------------------------------------------------------------------------*/void Set_PWM_Output(u16 freq, float duty){double psc = 0;double arr = 0; // 装载值double ccr = 0; // 比较值if (freq >= FREQ_MAX){freq = FREQ_MAX;}else if (freq = DUTY_MAX){duty = DUTY_MAX;}else if (duty > 16);arr = (u16)(MAIN_CLK / (freq * (psc + 1)));ccr = (u16)(arr * duty / 100.0f);if (duty = 100) // 满占空比时,com要比ccr大,不能相等{ccr = arr + 1;}TIM4->PSC = psc;TIM4->ARR = arr;TIM4->CCR3 = ccr;}/*------------------------------------------------------------------------------------------------------------------- @brief 设置频率,脉宽控制PWM输出 @param freq 频率 范围查看宏定义单位Hzpulse_width 脉宽 范围查看宏定义单位ns @return null Sample Set_PWM_Width_Output(5000,1000);//5kHz,1000ns脉宽 @note 注意,内部计算出占空比后,内部调用Set_PWM_Duty_Output(u32 freq, double duty)实现,无需重复调用。TIM4_CH1 -PD12TIM4 CH3 -PD14可去Set_PWM_Duty_Output,以及TIM4_PWM_Init函数中自行替换输出通道-------------------------------------------------------------------------------------------------------------------*/void Set_PWM_Width_Output(u32 freq, u16 pulse_width){double duty = 0;double period = 0;if ((0 == freq) && (0 == pulse_width)) // 输入为0,直接拉低{freq = FREQ_MIN;duty = DUTY_MIN;}else // 输入非0,正常输出{if (freq >= FREQ_MAX){freq = FREQ_MAX;}else if (freq = WIDTH_MAX){pulse_width = WIDTH_MAX;}else if (pulse_width <= WIDTH_MIN){pulse_width = WIDTH_MIN;}period = 1000000000.0f / freq; // 计算周期,单位为ns,所以用1G除频率duty = pulse_width * 100.f / period; // 计算当前脉宽占周期多少,单位是百分比,例:算出前脉宽占周期0.5(50%),此处值将是50if (duty = DUTY_MAX){duty = DUTY_MAX;}}Set_PWM_Duty_Output(freq, duty); // 调用占空比函数}/*------------------------------------------------------------------------------------------------------------------- @brief 输出引脚保持为高 @param null @return null Sample PWM_Keep_High(); @note TIM4_CH1 -PD12TIM4 CH3 -PD14-------------------------------------------------------------------------------------------------------------------*/void PWM_Keep_High(void){Set_PWM_Output(50, 100); // 满占空比}/*------------------------------------------------------------------------------------------------------------------- @brief 输出引脚保持为低 @param null @return null Sample PWM_Keep_Low(); @note TIM4_CH1 -PD12TIM4 CH3 -PD14-------------------------------------------------------------------------------------------------------------------*/void PWM_Keep_Low(void){Set_PWM_Output(50, 0); // 零占空比}
#ifndef __PWM_H__#define __PWM_H__ #include \"sys.h\" #define MAIN_CLK 72000000 //STM32F103时钟72M #define WIDTH_MAX 100000 //脉宽100000ns#define WIDTH_MIN 10 //脉宽10ns#define FREQ_MAX 10000000 //频率10MHz#define FREQ_MIN 1 //频率1Hz#define DUTY_MAX 100 //占空比100%#define DUTY_MIN 0 //占空比0% void PWM_Init(u16 freq,u16 duty);void TIM4_PWM_Init(u16 arr, u16 psc);void Set_PWM_Duty_Output(u32 freq, double duty);void Set_PWM_Width_Output(u32 freq, u16 pulse_width); #endif 

希望能够帮助到一些人。

本人菜鸡一只,各位大佬发现问题欢迎留言指出。