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

最开始使用PWM时,是做智能车时使用的舵机打角,电机驱动。这都属于比较浅显,普通的应用。下面和大家简单分享一下PWM的一些东西。
1.2 PWM参数
1.2.1 频率(重复频率,重频)
(注:重复频率、重频等是一些行业对频率的不同叫法,这都是一个东西)
PWM波是一串重复的矩形波,频率越高,周期越短(f=T/1),代表1S内重复出现的次数越多,这个应该不难理解。(并非是高电平出现次数,而是高+低,因为一个周期是高+低)


比如常用的50Hz对应周期20ms,含义就是1S内有50个重复的波形出现,每个完整波形出现的时间是20ms。1kHz对应周期1ms,5kHz对应周期200us。
1.2.2 占空比
占空比是PWM的第二大重要参数。一般来说,根据实际需要,PWM有输出极性的选择,高极性或低极性。指在有效输出的是高电平还是低电平。
占空比的定义是,在一个周期内,有效电平(此文章中指高电平)所占时间的百分比。

比如下面这张图,我可以理解为2kHz20%高电平(正脉冲)的PWM波,也可以理解为2kHz80%低电平(负脉冲)的PWM波。
(本文的PWM讲解均以高极性示例。例:下图为20%占空比,指在一个周期中,20%时间为高电平)


下图为占空比从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的输出原理其实非常的简单粗暴,定时器不断的向上计数(根据设置模式不同,也有向下计数,中央对齐计数,本文以向上计数为例),不断与两个数做对比,一个是ccr,输出比较值,一个是arr自动装载值。
当定时器自身计数TIMx->CNT值大于CCRx的值,PWM输出引脚电平改变(高/低),
当TIMx->CNT大于ARR时,计数器归零,重新计数。
如此往复,所以定时器本质就是一个计数器,PWM输出的原理就是不断的比较。
当然,也因为这个原因,当一个定时器用作PWM输出时,就不可以进行其他功能,比如定时器中断,比如输入捕获等。
同时,一个定时器往往有多个通道,同一个定时器可以进行多通道输出,不同通道输出的占空比可以不一样,但是他们的频率一定是一样的。
原因就是ARRx是自动装载值,决定了周期,且一个定时器使用同一个装载值;
而控制占空比的CCRx每个通道都有一个,所以可以根据CCRx的不同,控制不同占空比输出。

好了,我们已经基本了解了PWM输出原理,下面开始根据需要设置输出。
设置PWM输出最重要的公式在下面:
注:上式的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%;//五分之一
我们再返回来看频率公式:
既然CLK是固定的,输出频率f固定的情况下,我们尽可能让psc降低,使arr变大。但是不能让arr太大,因为他是u16类型,最大也只有65535。那么我们根据不同的频率,调整psc的值,让arr始终在1k-10k,也就是千分之一到万分之一精度即可。
我的想法是做简单的判断,根据不同的频率,给不同的分频系数,代码参考如下:
#define MAIN_CLK 72000000
u16 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即可。
当然,如果f太大,以STM32的72M时钟来说,f=1098时
显然psc作为预分频系数是不能为0的,那我们需要简单处理一下,如果psc小于1那么让他保持为1。
这里也还需要考虑到下psc的溢出情况,首先我们可以限制f频率的输入,一般最小限制为1Hz,在STM32F103平台上,f越小,psc越大。如果f是1,psc也只是1098而已,不会溢出。
如果换一个操作平台,他的主频较高,会不会溢出呢?
答案是不会,计算过程如下:
我们让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 72000000
float psc_temp= 0;
psc_temp=(MAIN_CLK /(freq * 65535.0))-1;//arr设定为65535
if(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 <= FREQ_MIN)
{
freq = FREQ_MIN;
}
if(duty >= DUTY_MAX)
{
duty = DUTY_MAX;
}
else if(duty <= DUTY_MIN)
{
duty = DUTY_MIN;
}
psc= (u16)((MAIN_CLK / freq) >> 16); //多少分频
arr= (u16)( MAIN_CLK /(freq*(psc+ 1))); //周期
ccr= arr* duty / 100; //占空比
TIM4_PWM_Init((u16)(arr- 1), psc);
if(duty <= DUTY_MIN )
{
ccr= DUTY_MIN ;//零占空比时,compare给0即可
}
else 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.除以65536,位运算更快
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 <= FREQ_MIN)
{
freq = FREQ_MIN;
}
if(pulse_width >= 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_MIN )//如果输入参数矛盾,此处0占空比,满占空比输出
{
duty = DUTY_MIN;
}
else if( duty >= DUTY_MAX )
{
duty = DUTY_MAX;
}
}
Set_PWM_Duty_Output(freq, duty);//调用占空比函数
}
这里再附上STM32硬件定时器通道表
(文件来自STM32F10XXX中文参考手册_V10,118页-119页)
4.完整文件在此
里面的频率限制完全可以打开,1Hz到很高都可以(显然不能超过72M主时钟频率),只是频率越高,占空比精度控制越差,具体原因上面都写了。


我在keil中仿真测试测试,10MHz,50%(50ns),实际输出10MHz,41.667ns。虽然与设定有点误差,但我觉得已经不错了。
实际情况下应该不会有人用stm32去产生10MHz的信号,这里仅供娱乐参考。
#include "PWM.h"
#include "stm32f10x_tim.h"
/*-------------------------------------------------------------------------------------------------------------------
@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
}
/*-------------------------------------------------------------------------------------------------------------------
@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 <= FREQ_MIN)
{
freq = FREQ_MIN;
}
if(duty >= DUTY_MAX)
{
duty = DUTY_MAX;
}
else if(duty <= DUTY_MIN)
{
duty = DUTY_MIN;
}
psc = (u16)((MAIN_CLK / freq) >> 16); //多少分频
arr = (u16)( MAIN_CLK /(freq*(psc+ 1))); //周期
ccr = arr* duty / 100; //占空比
TIM4_PWM_Init((u16)(arr- 1), psc);
if(duty <= DUTY_MIN )
{
ccr= DUTY_MIN ;//零占空比时,compare给0即可
}
else 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
}
/*-------------------------------------------------------------------------------------------------------------------
@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 <= FREQ_MIN)
{
freq = FREQ_MIN;
}
if(pulse_width >= 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_MIN )//如果输入参数矛盾,此将处0占空比,满占空比输出
{
duty = DUTY_MIN;
}
else if( duty >= DUTY_MAX )
{
duty = DUTY_MAX;
}
}
Set_PWM_Duty_Output(freq, duty);//调用占空比函数
}
#ifndef __PWM_H__
#define __PWM_H__
#include "sys.h"
#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%
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
希望能够帮助到一些人。
本人菜鸡一只,各位大佬发现问题欢迎留言指出。