写在前面:
此例程使用间隔时间固定的定时器中断。比如此处使用100Us的定时器中断。下一篇写使用另一个方式实现。
一、PWM介绍
PWM(Pulse Width Modulation)控制——脉冲宽度调制技术。一般使用方式是通过数字电路信号的占空比来模拟达到输出连续电压的目的。
比如在数字电路中,IO能输出的电压为0V或3.3V,但是如果想要输出1.65V,该怎么做呢?此时可通过在一定时间T内输出(1/2)T时间的0V,(1/2)T时间的3.3V,那么平均电压为1.65V,如果时间T足够小,则认为输出电压为1.65V,此时高电平“占空比”为50%。实际使用中,这里所说的一定时间T为周期。
如下图所示,在左边时,高电平时间为0,此时对应的电压为0,随着占空比的增加,对应的电压增加。在中间时占空比为100%(即一定时间T内,全部输出高电平),此时电压为最大值。随后又减小。
如下图为一连续变化占空比的示意图。
二、应用场景
PWM在各个领域应用广泛,通过调整占空比达到模拟输出不同电压的目的,一般可用于呼吸灯、PID调速等场景。
三、实例介绍——舵机控制:
舵机控制原理是周期为20Ms,通过调整0.5Ms~1.5Ms的高电平占空比,从而达到舵机不同的角度的目的
一般芯片都带有PWM硬件模块,只需设置频率和占空比,则在IO自动输出相应的信号。但也有芯片没有PWM硬件模块,则可通过定时器来实现。原理为在定时器中断中判断阈值,调整IO电平,从而达到实现PWM信号。
原理详解:设定一个每隔100Us的定时器中断,设定周期为200次,当阈值为10次,在每次定时器中断中判断前10次输出高电平,后190次输出低电平。则输出的IO电平现象是:周期为100Us*200次=20Ms,高电平时间为100Us*10次=1Ms,低电平时间为100Us*190次=19Ms的信号。
以下为操作实例
1、定义相关参数,关键参数为PWM周期、占空比。
2、初始化IO和相关参数,同时也需自定义一个定时器中断,此处我定义了一个100Us的定时器中断。
3、定时器中断中调用判断阈值函数,用于刷新IO电平。
4、在主循环中可定时刷新占空比阈值,从而达到呼吸灯效果等目的
以下为程序全文,实现周期为20Ms,高电平占比时间1.5Ms~2.5Ms循环:
#include "Main.h"
#include "Pwm.h"
#define PWM_IO_PORT GPIO_PORT0
#define PWM_IO_PIN BIT4
#define PWM_IO_SET do{GpioOutDataSetBits(PWM_IO_PORT, PWM_IO_PIN);}while(0)
#define PWM_IO_CLR do{GpioOutDataClrBits(PWM_IO_PORT, PWM_IO_PIN);}while(0)
typedef struct{
/*关键参数*/
INT16 cycle; //Pwm周期
INT16 dutyThd; //占空比
INT16 statieCount; //状态计数值
/*以下做呼吸灯等需要的参数*/
INT16 thdMax; //占空比Max
INT16 thdMin; //占空比Min
INT16 addDir; //调整方向
INT16 adjInterval; //主循环中调整阈值的时间间隔
INT16 adjSpeed; //主循环中调整阈值的速度
UINT32 timerCount; //时间计数值
}pwmPara;
pwmPara pwmParaVal;
/*定时器中断调用函数*/
void PwmIntProcess(void)
{
if (pwmParaVal.statieCount < pwmParaVal.dutyThd)
{
PWM_IO_SET; //Set 1
}
else
{
PWM_IO_CLR; //Set 0
}
pwmParaVal.statieCount++;
if(pwmParaVal.statieCount >= pwmParaVal.cycle)
{//满一个周期重新计数
pwmParaVal.statieCount = 0;
}
}
/*可在主循环中随时修改阈值和周期*/
void PwmLoopProcess(void)
{
UINT32 tempData;
tempData = GetMs(0);
if((tempData - pwmParaVal.timerCount) > pwmParaVal.adjInterval)
{//每隔一定时间进入调整占空比值
pwmParaVal.timerCount = tempData;
//调整阈值
pwmParaVal.dutyThd += pwmParaVal.addDir;
if(pwmParaVal.dutyThd >= pwmParaVal.thdMax)
{//判断是否需要改变方向
pwmParaVal.addDir = -pwmParaVal.adjSpeed;
}
else if((pwmParaVal.dutyThd <= pwmParaVal.thdMin))
{
pwmParaVal.addDir = pwmParaVal.adjSpeed;
}
}
}
/*初始化函数*/
void PwmInit(void)
{
GpioFunSetAll(PWM_IO_PORT, GPIO_FUN1, PWM_IO_PIN);
GpioOutSet(PWM_IO_PORT, PWM_IO_PIN);
/*此处根据需求还需初始化定时器中断,我这里设置了100Us定时器中断*/
pwmParaVal.cycle = 200; //设置周期 = 200*100Us=20Ms;
pwmParaVal.dutyThd = 20; //占空比20*100Us=2Ms;
pwmParaVal.statieCount = 0;
pwmParaVal.thdMax = 25; //占空比最大25*100Us=2.5Ms;
pwmParaVal.thdMin = 15; //占空比最小15*100Us=1.5Ms;
pwmParaVal.addDir = 0;
pwmParaVal.adjInterval = 100; //100Ms调整一次阈值
pwmParaVal.adjSpeed = 1; //每次调整阈值单位为1
pwmParaVal.timerCount = 0;
}