PWM讲解+STM32任意频率、占空比、脉宽生成函数介绍
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 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即可。
当然,如果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 72000000float 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 nullSample 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); //重映射//PD14GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //D14 端口配置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 OC3//初始化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//TIM_Cmd(TIM4, ENABLE); //使能TIM4
}/*-------------------------------------------------------------------------------------------------------------------@brief 设置频率,占空比PWM输出@param freq 频率查看宏定义duty 占空比0-100(代表0%-100%)@return nullSample 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 -PD14TIM_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 nullSample 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%),此处值将是50if(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 nullSample 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); //重映射//PD14GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; //D14 端口配置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 OC3//初始化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 占空比0-100(代表0%-100%)@return nullSample 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 -PD14TIM_Cmd(TIM4, ENABLE); //使能TIM4
}/*-------------------------------------------------------------------------------------------------------------------@brief 设置频率,脉宽控制PWM输出@param freq 频率 查看宏定义pulse_width 查看宏定义@return nullSample 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%),此处值将是50if(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
希望能够帮助到一些人。
本人菜鸡一只,各位大佬发现问题欢迎留言指出。
相关文章:
PWM讲解+STM32任意频率、占空比、脉宽生成函数介绍
1.PWM讲解 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制。 脉宽调制 最开始使用PWM时,是做智能车时使用的舵机打角,电机驱动。这都属于比较浅显,普通的应用。下面和大家简单分享一下PWM的…...
C++23 范围迭代器作为非范围算法的输入 (P2408R5)
文章目录 一、引言二、C23及范围迭代器的背景知识2.1 C23概述2.2 范围迭代器的概念 三、P2408R5提案的内容3.1 提案背景3.2 提案内容 四、范围迭代器作为非范围算法输入的优势4.1 代码简洁性4.2 提高开发效率4.3 更好的兼容性 五、具体的代码示例5.1 使用范围迭代器进行并行计算…...
CVE-2018-1273 漏洞深度分析
漏洞概述 CVE-2018-1273 是 Spring Data Commons 中的一个高危远程代码执行(RCE)漏洞,影响版本为 Spring Data Commons 1.13–1.13.10 和 2.0–2.0.5。攻击者通过构造包含恶意 SpEL表达式的 HTTP 请求参数,触发表达式注入&#x…...
C++23:修正常量迭代器、哨兵和范围
文章目录 引言C20范围库回顾C23之前常量迭代器的问题视图可能不传播const代理对象的复杂性泛型代码中的一致性 P2278R4提案及C23的改进std::views::as_const的工作原理代码示例 浅const视图(如std::span)的改进总结 引言 在C的发展历程中,每…...
【漫话机器学习系列】266.雅可比矩阵(Jacobian Matrix)
雅可比矩阵(Jacobian Matrix)详解 | 多变量函数微积分的基石 在深度学习、计算图、优化算法、机器人控制、流形学习等众多领域中,“雅可比矩阵(Jacobian Matrix)”是一个非常核心的数学工具。 这篇文章将结合一张视觉…...
Leetcode 3551. Minimum Swaps to Sort by Digit Sum
Leetcode 3551. Minimum Swaps to Sort by Digit Sum 1. 解题思路2. 代码实现 题目链接:3551. Minimum Swaps to Sort by Digit Sum 1. 解题思路 这一题思路上我实现的非常暴力,就是先求出正确的排列,然后从头考察每一个元素是否处在其目标…...
西门子1200/1500博图(TIA Portal)寻址方式详解
西门子博图(TIA Portal)是西门子公司推出的自动化工程软件平台,广泛应用于工业自动化领域。在编写PLC程序时,寻址方式是一个非常重要的概念,它决定了如何访问和操作PLC中的数据和资源。本文将详细介绍西门子博图中的寻…...
STK手动建链+matlab联调
在右边场景区选择你要建链的卫星,右键在弹出的选项中选择Access 选择你要建链的卫星,这里我选择3轨10星与4轨8星建链,点击compute后再close就行了 建链完成,这里链路的颜色跟起始卫星的颜色一致,要想改变颜色只需改变卫…...
MATLAB中的Switch语句讲解
MATLAB中的Switch语句:一个简单的控制流工具 在MATLAB中,switch语句是一种多分支控制结构,通常用于根据某个表达式的值选择不同的代码块进行执行。它的作用类似于一系列的if-elseif-else语句,但在处理多个条件时,swit…...
【SpringBoot】✈️整合飞书群机器人发送消息
💥💥✈️✈️欢迎阅读本文章❤️❤️💥💥 🏆本篇文章阅读大约耗时3分钟。 ⛳️motto:不积跬步、无以千里 📋📋📋本文目录如下:🎁🎁&am…...
上位机知识篇---流式Web服务器模式的实现
文章目录 前言 前言 本文简单介绍了流式Web服务器模式的实现。...
Go 语言中的一等公民(First-Class Citizens)
在 Go 语言中,一等公民(First-Class Citizens) 是指语言中可以像普通值一样被自由操作的元素,包括赋值、传递、返回等。Go 虽然不是纯粹的函数式语言,但支持多种一等公民,以下是 Go 中常见的 一等公民及其特…...
python3.13版本降为3.12
目录 一、下载Python 二、安装PyCharm 三、 彩蛋 粗糙理解: PyThon是编译器(也可以在命令行编辑,但是麻烦) PyCharm是编辑器 一、下载Python https://repo.huaweicloud.com/python/3.12.9/python-3.12.9-amd64.exe 点击Insta…...
Ubuntu搭建TFTP服务器的方法
0 工具 Ubuntu 18.041 Ubuntu搭建TFTP服务器的方法 在Ubuntu下搭建TFTP服务器可以让我们下载文件到开发板更加方便,同时也可以实现TFTP加载Linux镜像,方便调试。 1.1 安装tftp-hpa(TFTP客户端)、tftpd-hpa(TFTP服务…...
【AI】Ubuntu 22.04 4060Ti16G 基于SWIFT框架的LoRA微调 模型Qwen3-1.8B 数据集弱智吧 微调笔记
下载Qwen3-1.8B 先更新安装modescope,然后下载模型 pip install -U modelscope modelscope download --model Qwen/Qwen3-1.7B 下载日志 部署模型 参考:【AI】Ubuntu 22.04 4060Ti 16G vllm-api部署Qwen3-8B-FP8_wsl ubantu rtx4060 vllm镜像-CSDN博…...
系分论文《论信息系统缓存的分析和应用》
【摘要】 2023年3月,我作为系统分析师参与了某大型电商平台"云端购物中心"的性能优化项目。该项目日均订单量突破200万,但在促销高峰期频繁出现系统响应迟缓、数据库过载等问题。本项目以构建多级缓存体系为核心,通过系统化分析缓存应用场景和技术选型,重构了平…...
3.4/Q2,Charls最新文章解读
文章题目:Associations between reversible and potentially reversible cognitive frailty and falls in community-dwelling older adults in China: a longitudinal study DOI:10.1186/s12877-025-05872-2 中文标题:中国社区老年人可逆性和…...
Bash fork 炸弹 —— :(){ :|: };:
🧠 什么是 Fork 炸弹? Fork 炸弹是一种拒绝服务(DoS)攻击技术,利用操作系统的 fork() 系统调用不断创建新进程,直到系统资源(如进程表、CPU、内存)被耗尽,从而使系统无法…...
HarmonyOS AVPlayer 音频播放器
鸿蒙文档中心:使用AVPlayer播放视频(ArkTS)文档中心https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/video-playback 这张图描述的是 HarmonyOS AVPlayer 音频播放器的状态流转过程,展示了 AVPlayer 在不同状态之间的切换条件和关键操作…...
symfonos: 2靶场
symfonos: 2 来自 <https://www.vulnhub.com/entry/symfonos-2,331/> 1,将两台虚拟机网络连接都改为NAT模式 2,攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.182,靶场IP192.168.23.253 3&…...
微服务项目->在线oj系统(Java版 - 2)
相信自己,终会成功 微服务代码: lyyy-oj: 微服务 接口文档定义 响应数据定义: 响应数据格式:通常,HTTP API 的响应数据采用 JSON 格式 例如:成功响应(带数据) {"code": 200,"message": "查询成功","…...
整理了 2009 - 2025 年的【199 管综真题 + 解析】PDF,全套共 34 份文件
每年真题原卷 ✅ 每年详细解析 ✅ 📂【管综真题 2009-2025】 📂【管综解析 2009-2025】 目录树: ├── 2009-2025管综真题 PDF │ ├── 2009年199管综真题.pdf │ ├── 2010年199管综真题.pdf │ ├── 2011年199管综真题.pd…...
HarmonyOS 与 OpenHarmony:同根而不同途
HarmonyOS 与 OpenHarmony:同根而不同途 引言 在操作系统领域,HarmonyOS 和 OpenHarmony 这两个名字频繁出现,它们之间既存在着千丝万缕的联系,又有诸多显著的区别。对于开发者和相关从业者而言,深入了解两者的差异点…...
并发编程(4)
final修饰 1. 用final修饰类 当一个类被final修饰时,意味着它不能被其他类继承,也就是该类无法派生出子类。像 Java 中的String类就是典型的final类。 public final class FinalClass {// 类的内容 }// 下面的代码会报错,因为FinalClass不…...
合并K个升序链表
目录 合并 K 个升序链表 解题思路 ListNode 数组方式给出 k 个链表 ArrayList 方式给出 k 个链表 ArrayList常见操作 合并 K 个升序链表 题目描述 给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后…...
UART、SPI、IIC复习总结
一、UART 1、UART和USART的异同? 相同点 基本功能:都是用于串行通信的数据收发设备,能够实现数据在不同设备之间的传输。在异步通信模式下,二者的工作方式相似,都使用起始位、数据位、校验位(可选&#…...
【AWS入门】Amazon Bedrock简介
【AWS入门】Amazon Bedrock简介 [AWS Essentials] Brief Introduction Amazon Bedrock By JacksonML 1. 引言 Amazon Bedrock,在AWS官网,映入眼帘的第一句话就是,“使用基础模型构建和扩展生成式人工智能应用程序的最简单方法”。如下图所…...
报告精读:华为2024年知行合一通信行业数据治理实践指南报告【附全文阅读】
《华为 2024 年知行合一通信行业数据治理实践指南报告》聚焦通信行业数据治理,指出在数字化转型背景下,通信行业面临数据量庞大、类型多样、时效要求高、价值密度低、安全要求高等特点与数据质量、汇聚、开放等难点。报告提出通信行业数据治理需构建包含…...
Eigen与OpenCV矩阵操作全面对比:最大值、最小值、平均值
功能对比总表 功能Eigen 方法OpenCV 方法主要区别最大值mat.maxCoeff(&row, &col)cv::minMaxLoc(mat, NULL, &maxVal, NULL, &maxLoc)Eigen需要分开调用,OpenCV一次获取最小值mat.minCoeff(&row, &col)cv::minMaxLoc(mat, &minVal, NU…...
机器学习(12)——LGBM(1)
文章目录 LightGBM算法详解1. 算法背景2. 核心创新2.1 基于直方图的决策树算法2.2 单边梯度采样(GOSS)2.3 互斥特征捆绑(EFB) 3. 算法细节3.1 树生长策略3.2 特征并行与数据并行3.3 类别特征处理 4. 关键参数说明4.1 核心参数4.2 控制速度参数4.3 控制过拟合参数 5. 与XGBoost对…...
深入理解TCP与UDP:协议对比、头部结构与连接管理
一、TCP与UDP的核心区别 特性TCPUDP连接特性面向连接(三次握手建立连接)无连接,直接传输数据可靠性通过确认重传、排序、流控保证可靠尽力交付,不保证数据到达流量控制支持滑动窗口机制调节发送速率不支持数据分段支持大数据分段…...
Flask快速入门和问答项目源码
Flask基础入门 源码: gitee:我爱白米饭/Flask问答项目 - 码云 目录 1.安装环境2.【debug、host、port】3.【路由params和query】4.【模板】5.【静态文件】6.【数据库连接】6.1.安装模块6.2.创建数据库并测试连接6.3.创建数据表6.4.ORM增删改查 6.5.ORM模…...
python创建flask项目
好的,我会为你提供一个使用 Flask、pg8000 和 Pandas 构建的后台基本框架,用于手机理财产品 App 的报表分析接口。这个框架将包含异常处理、模块化的结构以支持多人协作,以及交易分析和收益分析的示例接口。 项目结构: financial_report_ap…...
Flink 的窗口机制
🪟 1. 基于时间驱动的滚动时间窗口(Tumbling Time Window - Time-based) ✅ 定义: 每隔固定的时间周期开启一个新的窗口。窗口之间不重叠。 🕒 示例: DataStream<Tuple2<String, Integer>>…...
STM32 OTA 中断向量表重定向
在STM32的OTA(Over-the-Air)升级中,中断向量表重定向是关键技术需求,其核心原因在于STM32的硬件架构和固件运行机制。以下从原理、实现方式及必要性三个角度详细分析: 一、中断向量表的作用与默认机制 中断向量表的…...
先说爱的人为什么先离开
2025年5月19日,15~23℃,贼好的一天,无事发生 待办: 2024年税务申报 《高等数学2》取消考试资格学生名单 《物理[2]》取消考试资格名单 5月24日、25日监考报名 《高等数学2》备课 《物理[2]》备课 职称申报材料 教学技能大赛PPT 遇…...
OnlyOffice秘籍系列.6-表格保护功能详解-灵活设置单元格的权限
最近很多用户在咨询是否可以提供一个灵活控制表格权限的在线协同编辑表格,这里和大家分享如果用OnlyOffice在线表格实现。 OnlyOffice 是一款功能强大的在线协作办公套件,其表格工具(Spreadsheet Editor)提供了完善的数据保护机制,…...
LLM笔记(八)Transformer学习
文章目录 1. Transformer 整体架构2. 核心组件详解2.1. 输入部分 (Input Embedding & Positional Encoding)2.2. 注意力机制 (Attention Mechanism)2.2.1. Padding Mask (填充掩码)2.2.2. Sequence Mask (Look-ahead Mask / Subsequent Mask / Causal Mask) 2.3. Multi-Hea…...
Vue 3 中使用 md-editor-v3 的完整实例markdown文本
下面我将提供一个完整的 Vue 3 项目中使用 md-editor-v3 的 Markdown 编辑器实现,包含常用功能和最佳实践。 1. 基础实现 安装依赖 npm install md-editor-v3也可以是pnpm安装 pnpm install md-editor-v3基础编辑器组件 (BasicEditor.vue) <template> &l…...
OpenCV-图像分割
实验1 实验内容 上述代码通过使用OpenCV和Matplotlib库来执行以下操作: 读取名为’kt.jpg’的图像文件,并存储在变量img中。将图像img转换为灰度图像,将其存储在变量gray中。使用cv2.threshold函数对灰度图gray进行阈值化处理,…...
Vue-计算属性
计算属性 案例 输入姓、名, 全名称姓名 实现 插值语法 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>计算属性与监视</title><!-- 引入Vue --><script type&…...
16. 通用配置文件开发.py
16. 通用配置文件开发.py 一、配置文件架构设计 1.1 模块化结构规划 #mermaid-svg-Iuex47psGWeZj6XQ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Iuex47psGWeZj6XQ .error-icon{fill:#552222;}#mermaid-svg-Iu…...
Python训练营打卡 Day29
复习日:类的装饰器 知识点回顾 类的装饰器:餐厅升级计划 假设你是一家餐厅的老板,餐厅已经运营了一段时间,但你希望提升服务质量,比如在每道菜上增加一些特别的服务(比如日志记录、额外的装饰等)…...
解决 Tailwind CSS 代码冗余问题
解决 Tailwind CSS 代码冗余问题 Tailwind CSS 确实可能导致 HTML 类名过长和冗余的问题,以下是几种有效的解决方案: 1. 使用 apply 指令提取重复样式 /* 在CSS文件中 */ .btn {apply px-4 py-2 rounded-md font-medium; }.card {apply p-6 bg-white …...
【藏经阁】加密机服务完整解决方案,包含客户端+服务端
前言 你是否存在这样的苦恼,数据需要安全存储,但是每个系统大家自己写,很浪费时间。 encryption-local 一个离线版本的金融敏感信息加解密工具,用于数据库敏感信息存储。 离线版本的加解密好处是非常的方便。不过缺点也比较明显…...
互联网大厂Java求职面试:AI与大模型应用集成及云原生挑战
互联网大厂Java求职面试:AI与大模型应用集成及云原生挑战 面试场景设定 郑薪苦是一位具有搞笑风格但技术潜力巨大的程序员,正在接受一位严肃专业的技术总监面试。 第一轮提问 面试官:在我们公司的短视频平台中,需要处理千万级…...
ffmpeg -vf subtitles添加字幕绝对路径问题的解决方法
今天遇到奇怪的问题,老是报 Unable to parse option value Error applying option original_size to filter subtitles: Invalid argument 踩坑很长时间,记录下 因subtitles需要指定绝对路径, 注意点: 外面要用单引号 不能…...
JetBrains IDEA,Android Studio,WebStorm 等IDE 字体出现异常时解决方法
JetBrains IDEA,Android Studio,WebStorm 等IDE 中文字体出现异常,很怪的时候,通常需要设置字体回退才能解决。 需要在 Font 中将字体连写打开,并且设置字体回退为 Microsoft YaHei Ul 只有这样 IDEA 在没有中文字体的样式下,会将…...
鸿蒙AI开发:10-多模态大模型与原子化服务的集成
鸿蒙AI开发:10-多模态大模型与原子化服务的集成 在鸿蒙生态中,多模态大模型与原子化服务的集成是一个重要课题。本文将介绍如何在鸿蒙平台上进行多模态大模型与原子化服务的集成,以及相关的技术细节和实际案例。 鸿蒙AI开发概述 什么是鸿蒙AI…...
信奥赛CSP动态规划入门-最大子段和
针对**“最大子段和”**问题的详细分步解析与程序实现,通过动态规划将大问题分解为小问题: 一、问题拆解步骤 1. 明确问题定义 大问题:在数组[-2,1,-3,4,-1,2,1]中,找到连续子数组的和的最大值。 小问题:以每个位置i结尾的子数组能得到的最大和。 2. 状态定义 定义数组…...