单片机pwm控制基本原理详解
前言
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^;
sbit ADDR0 = P1^;
sbit ADDR1 = P1^;
sbit ADDR2 = P1^;
sbit ADDR3 = P1^;
sbit ENLED = P1^;
unsigned char HighRH = ; //高电平重载值的高字节
unsigned char HighRL = ; //高电平重载值的低字节
unsigned char LowRH = ; //低电平重载值的高字节
unsigned char LowRL = ; //低电平重载值的低字节
void ConfigPWM(unsigned int fr, unsigned char dc);
void ClosePWM();
void main(){
unsigned int i;
EA = ; //开总中断
ENLED = ; //使能独立 LED
ADDR3 = ;
ADDR2 = ;
ADDR1 = ;
ADDR0 = ;
while (){
ConfigPWM(, ); //频率 100Hz,占空比 10%
for (i=; i40000; i++);
ClosePWM();
ConfigPWM(, ); //频率 100Hz,占空比 40%
for (i=; i40000; i++);
ClosePWM();
ConfigPWM(, ); //频率 100Hz,占空比 90%
for (i=; i40000; i++);
ClosePWM(); //关闭 PWM,相当于占空比 100%
for (i=; i40000; i++);
}
}
/* 配置并启动 PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc){
unsigned int high, low;
unsigned long tmp;
tmp = (/) / fr; //计算一个周期所需的计数值
high = (tmp*dc) / ; //计算高电平所需的计数值
low = tmp - high; //计算低电平所需的计数值
high = - high + ; //计算高电平的重载值并补偿中断延时
low = - low + ;//计算低电平的重载值并补偿中断延时
HighRH = (unsigned char)(high>>); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 为模式 1
TH0 = HighRH; //加载 T0 重载值
TL0 = HighRL;
ET0 = ; //使能 T0 中断
TR0 = ; //启动 T0
PWMOUT = ; //输出高电平
}
/* 关闭 PWM */
void ClosePWM(){
TR0 = ; //停止定时器
ET0 = ; //禁止中断
PWMOUT = ; //输出高电平
}
/* T0 中断服务函数,产生 PWM 输出 */
void InterruptTimer0() interrupt {
if (PWMOUT == ){ //当前输出为高电平时,装载低电平值并输出低电平
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = ;
}else{ //当前输出为低电平时,装载高电平值并输出高电平
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = ;
}
}
需要提醒大家的是,由于标准 51 单片机中没有专门的 PWM 模块,所以我们用定时器加中断的方式来产生 PWM,而现在有很多的单片机都会集成硬件的 PWM 模块,这种情况下需要我们做的就仅仅是计算一下周期计数值和占空比计数值然后配置到相关的 SFR 中即可,既使程序得到了简化又确保了 PWM 的输出品质(因为消除了中断延时的影响)。
大家编译下载程序后,会发现小灯从最亮到灭一共 4 个亮度等级。如果我们让亮度等级更多,并且让亮度等级连续起来,会产生一个小灯渐变的效果,与呼吸有点类似,所以我们习惯上称之为呼吸灯,程序代码如下,这个程序用了 2 个定时器 2 个中断,这是我们第一次这样用,大家可以学习一下。我们来试试这个程序,试完了大家一定要能自己把程序写出来,切记。
#include
sbit PWMOUT = P0^;
sbit ADDR0 = P1^;
sbit ADDR1 = P1^;
sbit ADDR2 = P1^;
sbit ADDR3 = P1^;
sbit ENLED = P1^;
unsigned long PeriodCnt = ; //PWM 周期计数值
unsigned char HighRH = ; //高电平重载值的高字节
unsigned char HighRL = ; //高电平重载值的低字节
unsigned char LowRH = ; //低电平重载值的高字节
unsigned char LowRL = ; //低电平重载值的低字节
unsigned char T1RH = ; //T1 重载值的高字节
unsigned char T1RL = ; //T1 重载值的低字节
void ConfigTimer1(unsigned int ms);
void ConfigPWM(unsigned int fr, unsigned char dc);
void main(){
EA = ; //开总中断
ENLED = ; //使能独立 LED
ADDR3 = ;
ADDR2 = ;
ADDR1 = ;
ADDR0 = ;
ConfigPWM(, ); //配置并启动 PWM
ConfigTimer1(); //用 T1 定时调整占空比
while ();
}
/* 配置并启动 T1,ms-定时时间 */
void ConfigTimer1(unsigned int ms){
unsigned long tmp; //临时变量
tmp = / ; //定时器计数频率
tmp = (tmp * ms) / ; //计算所需的计数值
tmp = - tmp; //计算定时器重载值
tmp = tmp + ; //补偿中断响应延时造成的误差
T1RH = (unsigned char)(tmp>>); //定时器重载值拆分为高低字节
T1RL = (unsigned char)tmp;
TMOD &= 0x0F; //清零 T1 的控制位
TMOD |= 0x10; //配置 T1 为模式 1
TH1 = T1RH; //加载 T1 重载值
TL1 = T1RL;
ET1 = ; //使能 T1 中断
TR1 = ; //启动 T1
}
/* 配置并启动 PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc){
unsigned int high, low;
PeriodCnt = (/) / fr; //计算一个周期所需的计数值
high = (PeriodCnt*dc) / ; //计算高电平所需的计数值
low = PeriodCnt - high; //计算低电平所需的计数值
high = - high + ; //计算高电平的定时器重载值并补偿中断延时
low = - low + ; //计算低电平的定时器重载值并补偿中断延时
HighRH = (unsigned char)(high>>); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
TMOD &= 0xF0; //清零 T0 的控制位
TMOD |= 0x01; //配置 T0 为模式 1
TH0 = HighRH; //加载 T0 重载值
TL0 = HighRL;
ET0 = ; //使能 T0 中断
TR0 = ; //启动 T0
PWMOUT = ; //输出高电平
}
/* 占空比调整函数,频率不变只调整占空比 */
void AdjustDutyCycle(unsigned char dc){
unsigned int high, low;
high = (PeriodCnt*dc) / ; //计算高电平所需的计数值
low = PeriodCnt - high; //计算低电平所需的计数值
high = - high + ; //计算高电平的定时器重载值并补偿中断延时
low = - low + ; //计算低电平的定时器重载值并补偿中断延时
HighRH = (unsigned char)(high>>); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
}
/* T0 中断服务函数,产生 PWM 输出 */
void InterruptTimer0() interrupt {
if (PWMOUT == ){ //当前输出为高电平时,装载低电平值并输出低电平
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = ;
}else{ //当前输出为低电平时,装载高电平值并输出高电平
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = ;
}
}

/* T1 中断服务函数,定时动态调整占空比 */
void InterruptTimer1() interrupt {
static bit dir = ;
static unsigned char index = ;
unsigned char code table[] = { //占空比调整表
, , , , , , , , , , , ,
};
TH1 = T1RH; //重新加载 T1 重载值
TL1 = T1RL;
AdjustDutyCycle(table[index]); //调整 PWM 的占空比
if (dir == ){ //逐步增大占空比
index++;
if (index >= ){
dir = ;
}
}else{ //逐步减小占空比
index--;
if (index == ){
dir = ;
}
}
}
呼吸灯效果做出来后,利用这个基本原理,其它各种效果的灯光闪烁都应该可以做出来,大家看到的 KTV 里边那绚丽的灯光闪烁,其实就是采用的 PWM 技术控制的。
单片机pwm控制基本原理详解的更多相关文章
- spring2.5IOC控制反转详解
spring2.5IOC控制反转详解 19. 五 / J2EE / 一条评论 基本的代码结构 1 IOC包下 基本的spring创建对象 将类添加到配置文件中,由容器创建. Source code ...
- M1卡区块控制位详解
M1卡区块控制位详解 Mifare 1S50/Mifare 1S70 每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取 控制.存取控制为4个字节,共32位,扇区中的每个块(包括 ...
- Xen虚拟化基本原理详解
标签:虚拟化 xen 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://wangzan18.blog.51cto.com/80210 ...
- php_ThinkPHP的RBAC(基于角色权限控制)详解
一.什么是RBAC 基于角色的访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注. 在RBAC中,权限与角色相关联,用户通 ...
- ThinkPHP框架下基于RBAC的权限控制模式详解
这几天因为要做一个项目,需要可以对Web应用中通用功能进行封装,其中一个很重要的涉及到了对用户.角色和权限部分的灵活管理.所以基于TP框架自己封装了一个对操作权限和菜单权限进行灵活配置的可控制模式. ...
- struts2标签库----控制标签详解
前面几篇文章我们介绍了有关struts2的基本内容,简单理解了整个框架的运作流程.从本篇开始我们逐渐了解和使用struts2为我们提供的标签库技术,使用这些标签可以大大降低我们表现层的开发难 ...
- 分享知识-快乐自己:Spring_IOC(控制反转)详解
IoC是什么: 1):Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想. 2):在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的 ...
- Spring IOC(控制反转)详解及示例
控制反转——Spring通过一种称作控制反转(IOC)的技术促进了低耦合.当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象.你可以认为IoC与JN ...
- 两种RBAC权限控制模型详解
序言 由于最近一直卡在权限控制这个坎上,原来设计的比较简单的权限控制思路已经无法满足比较复杂一些的场景,因此一直在探索一种在大部分场景下比较通用的权限模型. 首先,这里说明一下两种RBAC权限模型分别 ...
随机推荐
- 【Python3练习题 013】 求s=a+aa+aaa+aaaa+aa...a的值,其中a是一个数字
a=input('输入数字>>>') count=int(input('几个数字相加>>>')) ret=[] for i in range(1,count+1): ...
- MyEclipse10 复制之前的项目部署到tomcat时项目名称对不上,还是复制前的项目名称,哪里修改设置
工程 -- 右键属性 -- Myeclispse -- web修改一下发布名字就可以了.
- [转帖] SS, SP, BP 三个寄存器
SS, SP, BP 三个寄存器 https://blog.csdn.net/vspiders/article/details/55669265 这么看 计算机组成原理 还有 考试的很多题目非常有用啊 ...
- docker开启加速(第三篇)
前言: docker的镜像仓库在国外,下载会很慢,启用阿里云加速. 第一步:cd /etc/docker目录下,打开daemon.json 第二步:修改daemon.json文件,添加阿里云加速: ...
- python之路--初识函数
一 . 函数 什么是函数 f(x) = x + 1 y = x + 1 # 函数是对功能或者动作的封装 函数的语法 def 函数名(): 函数体 调用: 函数名() def play(): print ...
- k8s容器的资源限制
1.k8s支持内存和cpu的限制 requests:容器运行需求,最低保障limits:限制,硬限制(资源上限) CPU: 1颗逻辑CPU(1核CPU=4个逻辑CPU) 1物理核=1000个微核(mi ...
- 简单聊聊Linux学习经历
学习,是我们一生中都规避不了的一个话题,人的一生中都是在不断的学习,无论是功成名就的人士,还是一无是处的小混混,始终都处在一个不断学习的环境中,只是学习的内容千差万别,有的人是为了提升自己各方面的能力 ...
- Civil 3D 2017本地化中VBA程序移植到2018版中
中国本地化包简直就是一块鸡肋, 但对于某些朋友来说还真离不了: 可惜中国本地化包的推出一直滞后, 在最新版软件出来后1年多, 本地化还不一定能够出来, 即使出来了, 也只能是购买了速博服务的用户才能得 ...
- CentOS 部署.net core 2.0 项目
上传项目到服务器 安装Nginx(反向代理服务器),配置文件 https://www.cnblogs.com/xiaonangua/p/9176137.html 安装supervisor https: ...
- HBase 清空表数据
public int clearTableByTableName(String tableName) throws Exception { logger.debug("======InitH ...