前言

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控制基本原理详解的更多相关文章

  1. spring2.5IOC控制反转详解

    spring2.5IOC控制反转详解 19. 五 / J2EE / 一条评论   基本的代码结构 1 IOC包下 基本的spring创建对象 将类添加到配置文件中,由容器创建. Source code ...

  2. M1卡区块控制位详解

    M1卡区块控制位详解 Mifare 1S50/Mifare 1S70 每个扇区的密码和存取控制都是独立的,可以根据实际需要设定各自的密码及存取 控制.存取控制为4个字节,共32位,扇区中的每个块(包括 ...

  3. Xen虚拟化基本原理详解

    标签:虚拟化 xen 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://wangzan18.blog.51cto.com/80210 ...

  4. php_ThinkPHP的RBAC(基于角色权限控制)详解

    一.什么是RBAC 基于角色的访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注. 在RBAC中,权限与角色相关联,用户通 ...

  5. ThinkPHP框架下基于RBAC的权限控制模式详解

    这几天因为要做一个项目,需要可以对Web应用中通用功能进行封装,其中一个很重要的涉及到了对用户.角色和权限部分的灵活管理.所以基于TP框架自己封装了一个对操作权限和菜单权限进行灵活配置的可控制模式. ...

  6. struts2标签库----控制标签详解

         前面几篇文章我们介绍了有关struts2的基本内容,简单理解了整个框架的运作流程.从本篇开始我们逐渐了解和使用struts2为我们提供的标签库技术,使用这些标签可以大大降低我们表现层的开发难 ...

  7. 分享知识-快乐自己:Spring_IOC(控制反转)详解

    IoC是什么: 1):Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想. 2):在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的 ...

  8. Spring IOC(控制反转)详解及示例

    控制反转——Spring通过一种称作控制反转(IOC)的技术促进了低耦合.当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象.你可以认为IoC与JN ...

  9. 两种RBAC权限控制模型详解

    序言 由于最近一直卡在权限控制这个坎上,原来设计的比较简单的权限控制思路已经无法满足比较复杂一些的场景,因此一直在探索一种在大部分场景下比较通用的权限模型. 首先,这里说明一下两种RBAC权限模型分别 ...

随机推荐

  1. Nginx会话保持之nginx-sticky-module模块

    Nginx会话保持之nginx-sticky-module模块 - 天行健,君子以自强不息:地势坤,君子以厚德载物. - CSDN博客https://blog.csdn.net/huangjinjin ...

  2. Python&R&量化 金融之路

    [ 分类 ]- 金融之路 - 闲云孤鹤(人生在世五十年,大千世界一瞬间,浮生若梦,仿佛间,幻境一场,生者无常,终须尽.) - CSDN博客 https://blog.csdn.net/robertso ...

  3. Jenkins deploy war to tomcat over https

    ssl - HTTPS login with Spring Security redirects to HTTP - Stack Overflow https://stackoverflow.com/ ...

  4. C#复习笔记(3)--C#2:解决C#1的问题(泛型)

    这一章会描述在C#2中所做的主要的变化 泛型 泛型的概念中包含类型参数和类型实参,类型参数相当于类型实参的蓝图. 泛型类型分为未绑定泛型类型和已构造泛型类型.已构造泛型类型又分为开放的泛型类型和封闭的 ...

  5. js如何复制一个对象?

    方法一: 把原来对象的属性遍历一遍,赋给一个新的对象. //深复制对象方法 var cloneObj = function (obj) { var newObj = {}; if (obj insta ...

  6. vue.js 添加 fastclick的支持

    fastclick:处理移动端click事件300毫秒延迟 1.兼容性 iOS 3及更高版本的移动Safari iOS 5及更高版本的Chrome Android上的Chrome(ICS) Opera ...

  7. Excel文件读取的两种方式

    1.Pandas库的读取操作 from pandas import read_excel dr=read_excel(filename,header) dr#dataframe数据 dw=DataFr ...

  8. python爬虫之scrapy的pipeline的使用

    scrapy的pipeline是一个非常重要的模块,主要作用是将return的items写入到数据库.文件等持久化模块,下面我们就简单的了解一下pipelines的用法. 案例一: items池 cl ...

  9. 九、.net core用orm继承DbContext(数据库上下文)方式操作数据库

    一.创建一个DataContext普通类继承DbContext  安装程序集:Pomelo.EntityFrameworkCore.MySql   二.配置连接字符串(MySql/SqlServer都 ...

  10. Hibernate **关于hibernate4.3版本之后org.hibernate.service.ServiceRegistryBuilder被弃用**

    之前一直都是使用hibernate4.2.21的我,有一天突然没有使用本地的jar包而是让IDEA自动下载最新版本的hibernate5.2.2之后,发现有几个经常使用的方法报错了. //创建配置对象 ...