对于学习单片机没多久,刚开始接触PWM的同学们一定觉得PWM不好理解,然而参加蓝桥杯又总是爱考到,例如最近的第九届和第七届就都考到了PWM,这可如何是好哇~然而没关系,好在我们还有时间来学习,下边我们就一起来看一看,传说中的PWM究竟是何方妖孽神圣。
PulseWidthModulation脉冲宽度调制,简称PWM。
PWM(脉冲宽度调制)是一种对模拟信号电平进行数字编码的方法,我们手头的单片机只能输出0或5V的数字信号而不能输出模拟信号,那么如果想使用单片机来获得一个模拟电压信号,则需通过使用单片机配合程序改变IO口输出的方波的占空比从而获得使用数字信号来模拟的模拟电压信号。这里涉及到了PWM脉宽调制的重要指标:占空比。
占空比通常用字母q来表示,是指电路被接通的时间(t高电平)占整个电路工作周期T的百分比。用公式来描述就是:
占空比=t高电平/T
举个栗子,假设一个电路在它一个工作周期中有一半时间被接通了,那么它的占空比就是50%。如果加在该工作元件上的信号电压为5V,则实际的工作电压平均值或电压有效值就是2.5V。
再例如,「三天打渔,两天晒网」,是说五天里有两天在晒网,即周期为5天,“打渔”的占空比为5分之3,也就是60%。
那么我们回到单片机上来看一看,单片机的IO口依然输出的是数字信号,因为满幅值的直流供电只有5V(1)和0V(0)两种。电压是以一种连接(1)或断开(0)的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压。例如下图:
经过上文中对占空比的讲解,相信同学们应该能够理解这个有效电压是如何产生的了吧~当然,如果上图中这种信号加在LED灯上,我们应该能够看到LED灯发生什么现象呢?下面有两个选项:
A. 连续发光,并发出稍微暗一点的光
B. 一会亮,一会灭,闪烁发光
请选择~
如果你选择的是A,那么恭喜你,非常正确!如果你选择的是B,那么恭喜你,你也答对了。这是怎么回事儿呢?因为这里面还涉及到PWM一个比较重要的描述指标,叫做信号频率。
怎么解释这个频率呢?我们都写过过点亮LED灯的程序,可以想象一下,如果我假设上图中占空比为50%的波形信号频率为1Hz,也就是高电平持续0.5s,低电平持续0.5s,信号周期为1s,那么我们看到的灯是什么样的状态呢?没错,就是在闪烁,因此选B,没毛病。但是试想一下,如果我让这个信号的频率变大,让它变为1KHz,也就是说每个周期时长仅有1ms,那么高电平在每个周期内持续0.5ms,低电平也持续0.5ms,这样一来,你还能看得见LED灯在闪烁嘛?看不出来了吧?为什么呢?因为人眼反应不过来啦!不用说1KHz了,就是我们日常教室头顶上的日光灯管,以50Hz的频率闪烁,我们就看不出来它有闪烁的感觉了呦喂。因此呢,当频率加上来的时候,我们看不出来它在闪烁,那么这个LED灯会一直亮,但是它的亮度和完全加高电平的时候一样亮吗?当然不是啦,这个亮度,就完全由刚才所讲到的占空比q来控制啦~当然控制直流电机的转速,也是如此~
在以上对PWM的介绍之后,我们接下来还是要通过实例来向同学们们展现PWM,毕竟看到的才最直观嘛。然后大家可以再结合概念进行理解。下面的例程,大家可以自己烧写到自己的开发板上,试一试,感受一下哦~
首先做一个通过PWM来控制小灯的亮度的实验。
#include<reg52.h> sbit led=P2^0; //定义一个引脚 unsigned int i; void Timer0Init(void) //1毫秒@11.0592MHz { TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置定时器模式 TL0 = 0x33; //设置定时初值 TH0 = 0xFE; //设置定时初值 TR0 = 1; //定时器0开始计时 ET0=1; EA=1; } void main() //主函数 { Timer0Init(); while(1); } void init0() interrupt 1 // 中断服务函数 { i++; TL0 = 0x33; //定时器赋初值 TH0 = 0xFE; if(i==3) //通过变量i来设置pwm的占空比 { led=1; } if(i==10) //这个10代表PWM的周期 { i=0; led=1; } }
这个程序主要是看变量i,(第一个i是用来确定PWM的占空比的,第二个i是用来确定PWM周期的)所以这是一个以10毫秒周期PWM信号。当i在3毫秒之前引脚为低电平,3到10毫秒之间为高电平,所以这就引出了PWM占空比的概念了,简单来说也就是低电平占三份,高电平占七份。也就是通过占空比来改变了10毫秒内的输出电压值,从而改变了灯的亮度。当我们改变i(第一个)的值的时候,就会看见不同亮度的情况。但是周期不能太大,如果你以一秒为周期的话,那样就能明显的看到闪烁了。有了这个基础我们可以多加一个循环写一个呼吸灯的程序。
下边是一个呼吸灯的实验。
#include<reg52.h> sbit led=P2^0; //定义一个引脚 unsigned int i,j,,k,b=0; void Timer0Init(void) //1毫秒@11.0592MHz { TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置定时器模式 TL0 = 0x33; //设置定时初值 TH0 = 0xFE; //设置定时初值 TR0 = 1; //定时器0开始计时 ET0=1; EA=1; } void main() //主函数 { Timer0Init(); while(1); } void init0() interrupt 1 // 中断服务函数 { i++;j++; TL0 = 0x33; //定时器赋初值 TH0 = 0xFE; if(j==300) //变量j,控制呼吸灯的频率 { j=0; if(b==0) //变量b,使k进行反转 { k++; if(k==10) b=1; } else { k--; if(k==1) b=0; } } if(i==k) //通过变量i来设置pwm的占空比 { led=0; } if(i==10) //这个10代表PWM的周期 { i=0; led=1; } }
这个程序就是我们期待的呼吸灯的程序了,比上边的程序多了三个变量j,k和b,j程序中用的是300ms。就是每隔300ms就通过k重新赋予i占空比,大家也可以自己改一下,看一看实际的效果。变量b就是用来反转k的。
总结一下,PWM就是在合适的信号频率下,通过一个周期里改变占空比的方式来改变输出的有效电压。写呼吸灯的程序大家一定要理解程序中几个变量的使用,一个周期,一个占空比,一个用来控制灯频率。自己动手亲自改变一下相应的值,看一下实际的效果,会有更加深刻的理解哦。