PWM是Pulse Width Modulation的缩写,它的中文姓名是脉冲宽度调制,一种说法是它运用微处理器的数字输出来对模仿电路进行操控的一种有用的技能,其实便是运用数字信号到达一个模仿信号的作用。这是个什么概念呢?咱们一步步来介绍。
首先从它的姓名来看,脉冲宽度调制,便是改动脉冲宽度来完成不同的作用。咱们先来看三组不同的脉冲信号,如图所示。
图 10-1 PWM 波形
这是一个周期是 10ms,即频率是 100Hz 的波形,可是每个周期内,凹凸电平脉冲宽度各不相同,这便是 PWM 的实质。在这儿咱们要记住一个概念,叫做“占空比”。占空比是指高电平的时刻占整个周期的份额。比方榜首部分波形的占空比是 40%,第二部分波形占空比是 60%,第三部分波形占空比是 80%,这便是 PWM 的解说。
那为何它能对模仿电路进行操控呢?咱们想一想,咱们数字电路里,只要 0 和 1 两种状况,比方咱们第 2 章学会的点亮 LED 小灯那个程序,当咱们写一个 LED = 0;小灯就会长亮,当咱们写一个 LED = 1;小灯就会灭掉。当咱们让小灯亮和灭距离运转的时分,小灯是闪耀。
假设咱们把这个距离不断的减小,减小到咱们的肉眼分辩不出来,也便是 100Hz 以上的频率,这个时分小灯表现出来的现象便是既坚持亮的状况,但亮度又没有 LED = 0;时的亮度高。那咱们不断改动时刻参数,让 LED = 0;的时刻大于或许小于 LED = 1;的时刻,会发现亮度都不相同,这便是模仿电路的感觉了,不再是朴实的 0 和 1,还有亮度不断改动。咱们会发现,假设咱们用 100Hz 的信号,如图 10-1 所示,假设高电平平息小灯,低电平点亮小灯的话,榜首部分波形平息 4ms,点亮 6ms,亮度最高,第二部分平息 6ms,点亮 4ms,亮度次之,第三部分平息 8ms,点亮 2ms,亮度最低。那么用程序验证一下咱们的理论,咱们用守时器T0 守时改动 P0.0 的输出来完成 PWM,与纯守时不同的是,这儿咱们每周期内都要重载两次守时器初值,即用两个不同的初值来操控凹凸电平的不同持续时刻。为了使亮度的改动愈加显着,程序中运用的占空比距离更大。
#include
sbit PWMOUT = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char HighRH = 0; //高电平重载值的高字节
unsigned char HighRL = 0; //高电平重载值的低字节
unsigned char LowRH = 0; //低电平重载值的高字节
unsigned char LowRL = 0; //低电平重载值的低字节
void ConfigPWM(unsigned int fr, unsigned char dc);
void ClosePWM;
void main{
unsigned int i;
EA = 1; //开总中止
ENLED = 0; //使能独立 LED
ADDR3 = 1;
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;
while (1){
ConfigPWM(100, 10); //频率 100Hz,占空比 10%
for (i=0; i40000; i++);
ClosePWM;
ConfigPWM(100, 40); //频率 100Hz,占空比 40%
for (i=0; i40000; i++);
ClosePWM;
ConfigPWM(100, 90); //频率 100Hz,占空比 90%
for (i=0; i40000; i++);
ClosePWM; //封闭 PWM,相当于占空比 100%
for (i=0; i40000; i++);
}
}
/* 装备并发动 PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc){
unsigned int high, low;
unsigned long tmp;
tmp = (11059200/12) / fr; //核算一个周期所需的计数值
high = (tmp*dc) / 100; //核算高电平所需的计数值
low = tmp – high; //核算低电平所需的计数值
high = 65536 – high + 12; //核算高电平的重载值并补偿中止延时
low = 65536 – low + 12;//核算低电平的重载值并补偿中止延时
HighRH = (unsigned char)(high》》8); //高电平重载值拆分为凹凸字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low》》8); //低电平重载值拆分为凹凸字节
LowRL = (unsigned char)low;
TMOD &= 0xF0; //清零 T0 的操控位
TMOD |= 0x01; //装备 T0 为形式 1
TH0 = HighRH; //加载 T0 重载值
TL0 = HighRL;
ET0 = 1; //使能 T0 中止
TR0 = 1; //发动 T0
PWMOUT = 1; //输出高电平
}
/* 封闭 PWM */
void ClosePWM{
TR0 = 0; //中止守时器
ET0 = 0; //制止中止
PWMOUT = 1; //输出高电平
}
/* T0 中止服务函数,发生 PWM 输出 */
void InterruptTImer0 interrupt 1{
if (PWMOUT == 1){ //当时输出为高电平时,装载低电平值并输出低电平
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}else{ //当时输出为低电平时,装载高电平值并输出高电平
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}
需求提示咱们的是,由于规范 51 单片机中没有专门的 PWM 模块,所以咱们用守时器加中止的方法来发生 PWM,而现在有许多的单片机都会集成硬件的 PWM 模块,这种情况下需求咱们做的就仅仅是核算一下周期计数值和占空比计数值然后装备到相关的 SFR 中即可,既使程序得到了简化又保证了 PWM 的输出质量(由于消除了中止延时的影响)。
咱们编译下载程序后,会发现小灯从最亮到灭总共 4 个亮度等级。假设咱们让亮度等级更多,而且让亮度等级接连起来,会发生一个小灯突变的作用,与呼吸有点相似,所以咱们习惯上称之为呼吸灯,程序代码如下,这个程序用了 2 个守时器 2 个中止,这是咱们榜首次这样用,咱们能够学习一下。咱们来试试这个程序,试完了咱们必定要能自己把程序写出来,牢记。
#include
sbit PWMOUT = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned long PeriodCnt = 0; //PWM 周期计数值
unsigned char HighRH = 0; //高电平重载值的高字节
unsigned char HighRL = 0; //高电平重载值的低字节
unsigned char LowRH = 0; //低电平重载值的高字节
unsigned char LowRL = 0; //低电平重载值的低字节
unsigned char T1RH = 0; //T1 重载值的高字节
unsigned char T1RL = 0; //T1 重载值的低字节
void ConfigTImer1(unsigned int ms);
void ConfigPWM(unsigned int fr, unsigned char dc);
void main{
EA = 1; //开总中止
ENLED = 0; //使能独立 LED
ADDR3 = 1;
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;
ConfigPWM(100, 10); //装备并发动 PWM
ConfigTImer1(50); //用 T1 守时调整占空比
while (1);
}
/* 装备并发动 T1,ms-守时时刻 */
void ConfigTImer1(unsigned int ms){
unsigned long tmp; //暂时变量
tmp = 11059200 / 12; //守时器计数频率
tmp = (tmp * ms) / 1000; //核算所需的计数值
tmp = 65536 – tmp; //核算守时器重载值
tmp = tmp + 12; //补偿中止呼应延时形成的差错
T1RH = (unsigned char)(tmp》》8); //守时器重载值拆分为凹凸字节
T1RL = (unsigned char)tmp;
TMOD &= 0x0F; //清零 T1 的操控位
TMOD |= 0x10; //装备 T1 为形式 1
TH1 = T1RH; //加载 T1 重载值
TL1 = T1RL;
ET1 = 1; //使能 T1 中止
TR1 = 1; //发动 T1
}
/* 装备并发动 PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc){
unsigned int high, low;
PeriodCnt = (11059200/12) / fr; //核算一个周期所需的计数值
high = (PeriodCnt*dc) / 100; //核算高电平所需的计数值
low = PeriodCnt – high; //核算低电平所需的计数值
high = 65536 – high + 12; //核算高电平的守时器重载值并补偿中止延时
low = 65536 – low + 12; //核算低电平的守时器重载值并补偿中止延时
HighRH = (unsigned char)(high》》8); //高电平重载值拆分为凹凸字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low》》8); //低电平重载值拆分为凹凸字节
LowRL = (unsigned char)low;
TMOD &= 0xF0; //清零 T0 的操控位
TMOD |= 0x01; //装备 T0 为形式 1
TH0 = HighRH; //加载 T0 重载值
TL0 = HighRL;
ET0 = 1; //使能 T0 中止
TR0 = 1; //发动 T0
PWMOUT = 1; //输出高电平
}
/* 占空比调整函数,频率不变只调整占空比 */
void AdjustDutyCycle(unsigned char dc){
unsigned int high, low;
high = (PeriodCnt*dc) / 100; //核算高电平所需的计数值
low = PeriodCnt – high; //核算低电平所需的计数值
high = 65536 – high + 12; //核算高电平的守时器重载值并补偿中止延时
low = 65536 – low + 12; //核算低电平的守时器重载值并补偿中止延时
HighRH = (unsigned char)(high》》8); //高电平重载值拆分为凹凸字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low》》8); //低电平重载值拆分为凹凸字节
LowRL = (unsigned char)low;
}
/* T0 中止服务函数,发生 PWM 输出 */
void InterruptTimer0 interrupt 1{
if (PWMOUT == 1){ //当时输出为高电平时,装载低电平值并输出低电平
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}else{ //当时输出为低电平时,装载高电平值并输出高电平
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}
/* T1 中止服务函数,守时动态调整占空比 */
void InterruptTimer1 interrupt 3{
static bit dir = 0;
static unsigned char index = 0;
unsigned char code table[13] = { //占空比调整表
5, 18, 30, 41, 51, 60, 68, 75, 81, 86, 90, 93, 95
};
TH1 = T1RH; //从头加载 T1 重载值
TL1 = T1RL;
AdjustDutyCycle(table[index]); //调整 PWM 的占空比
if (dir == 0){ //逐渐增大占空比
index++;
if (index 》= 12){
dir = 1;
}
}else{ //逐渐减小占空比
index–;
if (index == 0){
dir = 0;
}
}
}
呼吸灯作用做出来后,运用这个基本原理,其它各种作用的灯火闪耀都应该能够做出来,咱们看到的 KTV 里面那艳丽的灯火闪耀,其实便是选用的 PWM 技能操控的。
责任编辑:ct