想做一个呼吸灯的小程序,看到有人用pwm模块进行开发。查了很多资料,没有arduino实现的方式来做的。。。发现这一篇文章,给了我很大的启发。
Arduino通过IO口来实现与外界的交互:
感知外部环境、控制外部事物。
针对外界数字(诸如开、并两种状态)信号以及
模拟(诸如温度等连续状态)信号的处理需求,
Arduino又进一步将IO口分为数字IO和模拟IO。
针对数字IO操作,G哥撸了深入理解数字IO操作
针对模拟IO操作,G哥先是撸了模拟IO操作(基础篇),介绍了Arduino对模拟信号输入输出的操作全貌。
针对模拟输入的深入了解,G哥撸了深入理解Arduino的模拟输入A0~A5。
本篇是IO口操作的封篇之作:
深入讨论Arduino的模拟信号输出(PWM)
引子
Arduino Uno是单片机,
其内部处理机制必然也是数字式的,
即用0和1两种状态的组合表达信息。
那么如何对外输出模拟信号呢?
在介绍本文的内容之前,
我们先来重温一下生活中的两个场景。
场景一:电影播放
曾几何时,G哥小时候,
夏日晚上,最期待就是露天电影
那时的电影播放机,是这个样子的
并不像现在电影里院看电影时,
一束光从背后的洞洞里投射出来,
播放机长啥样,完全不知道。
电影制作时,
把故事情节按16格(每秒16个画面)的频率拍成一张张的图片。
电影播放时,
照样按16格的频率摇动播放机,
于是人物情节的动画就出来了。
电影中的人物动作是连续的吗?
可以认为是连续的,
因为观影的人们真真切切感受到人物动作的连续和自然。
真实的情况却是不连续的,
1秒16格的离散图片怎能替代连续的事物呢?!
场景二:行驶中的地铁广告
曾几何时(莫怪,G哥脑海里就这个词可以得瑟下了)
地铁候车站里、车厢里,到处都挂满了平面广告,
但广告主们还对乘客们的注意力侵占不够,
于是乎,更加奸诈的广告商人把目光瞄准了行驶中的列车。
昏暗的外部环境(车厢外),焦躁的人乘客,
车窗外忽亮忽暗的广告动画,
一下子就掠夺了大部分人的目光……
说了这么多,只是想告诉你们:
真实的外部世界,到底是不是连续的,
单靠我们的肉眼,其实是分辨不出来的,
眼见也不一定为实啊!
科JIAN学SHANG家MEN依据人眼对15频以上速度播放一帧帧画面时人眼就会感觉是连续的现实验证的理论,
通过输出离散的图片,就满足了我们眼睛识别连续的条件。
数字信号达到一定的要求(频率),就足以让外部事物(不限于人眼)产生连续的“假象”!
这也是本篇要讲的PWM方式输出“连续”信号的原理!
信号本身连不连续并不重要,重要是作用对象的实际效果是否满足连续的效果!
PWM简介及原理说明
PWM,全称:Pulse Width Modulation,
通常中文翻译为:脉冲宽度调制,
通过将一段数字信号编码为方波信号,在外部作用事物上达到拟输出效果的一种手段。
实际中,使用数字控制产生占空比不同的方波(一个不停在开与关之间切换的信号)来控制模拟输出。
一般情况下,方波在输出时,
代电平代表输出0V,高电平代表输出5V(或3.3V,视驱动电源而定)
当要输出中间(诸如3.7V)的“等效电压”时,
可以先输出5V一段时间,再输出0V一段时间,就可以了。
所谓“等效电压”,
就是任何一个周期内,5V方波输出的电压时(电压作用时间),见上图中绿色+蓝色部分面积。
与3.7V直流输出的电压时一样,见上图中蓝色+橙色部分。
这时,被驱动部件就像人眼,感受不到驱动信号的非连续性了!
几种形成PWM的方式
方法一:数字IO转换法
int pin = 0;
//对于Uno,可以是0~19,或A0~A5,你没看错,G哥就是要和别人不一样,并不是大多数说的仅仅是0~13
void setup()
{
pinMode(pin, OUTPUT);//这是必须的!
}
void loop()
{
digitalWrite(pin, HIGH);
delayMicroseconds(100);
digitalWrite(pin, LOW);
delayMicroseconds(1000 - 100);
}
上面这段代码会产生一个 PWM=0.1 的,周期为 1ms 的方波(1000Hz),这种方式的优缺点都很明显:
1、PWM 的比例可以更精确;
2、周期和频率可控制;
3、所有的 pin 脚都可以输出,不局限于那几个脚;
4、缺点:CPU 干不了其他事情了;
{
pinMode(pin, OUTPUT);//这是必须的!
}
void loop()
{
digitalWrite(pin, HIGH);
delayMicroseconds(100);
digitalWrite(pin, LOW);
delayMicroseconds(1000 - 100);
}
上面这段代码会产生一个 PWM=0.1 的,周期为 1ms 的方波(1000Hz),这种方式的优缺点都很明显:
1、PWM 的比例可以更精确;
2、周期和频率可控制;
3、所有的 pin 脚都可以输出,不局限于那几个脚;
4、缺点:CPU 干不了其他事情了;
方法二:analogWrite(pin,val) 函数法
这是Arduino首推的方法,
因为它Niubility吗?
No!No!No!
因为它太Simple了!
为Pin指定一个IO口,为val指定一个0~255之间的任意一个数,
它就可以输出方波了,
而且,你还能同时并行干其它事!
注意:pin参数:只能用 3,5,6,9,10,11 !画重点了!
val 是 0~255 的整数值,对应电压从 0 到+5V,值越小,输出等效电压越低。
val 是 0~255 的整数值,对应电压从 0 到+5V,值越小,输出等效电压越低。
示例代码:int pin = 3; //3,5,6,9,10,11
void setup()
{
pinMode(pin, OUTPUT);//这也是必须的!
}
void loop()
{
analogWrite(pin, 128);
//注意:下面这个延时并不是产生方波用,而是避免程序太过频率地设置analogWrite(pin,128)函数,因为它们在LOOP区,而且analogWrite(pin,128)函数其实只用设置一次就够了!
delay(500);
}
这种方式优缺点同样很明显:
这种方式优缺点同样很明显:
优点1:Too Simple!一条语句搞定!
优点2 :高效!不占用CPU时间,可以并行干其它事!
缺点:输出的PWM波频率定死了,Arduino并没有提供改频率的接口函数,你能控制的只要改占空比!
Arduino的PWM工作机制详解
Arduino的PWM输出机制其实是通过
内部的定时/计时器单元实现的!
计数单元持续不断的计数,
每计一次数,就比较一下当前计数(TCNT0)和预设值(其实就是analogWrite函数中的val值)OCRnx。
当发现两者相等,就立即给波形发生器(Waveform Generator)一个信号:
你要向OCnx内输出下一个状态了,
而OCnx直接影响其对应的外部IO口,
就这样,PWM就产生了!
由于ATmega328P(Arduino Uno选用的MCU)内部共有3个定时/计数器:
TC0、TC1、TC2
TC0对应数字IO:5、6两个输出口;
TC1对应数字IO:9、10两个输出口;
TC2对应数字IO:3、11两个输出口;
这也就解释了为什么只能在3、5、6、9、10、11端口上产生PWM波了!
PWM其实是通过设置一系列寄存器才能正常工作的,
涉及:TCCRx(控制寄存器)、TCNTx(计数值寄存器)、OCRx(输出比较寄存 器)等等。
为达到简化的目的,Arduino预设了 这些寄存器!
而且,有能力,你是完全可以自己改的!
由于三种计数器的差异性,Arduino分别量身定制了PWM方案:
TC0(端口5、6)输出的PWM方波的频率为980Hz左右。
TC1(端口9、10)输出的PWM方波的频率为490Hz左右。
TC2(端口3、11)输出的PWM方波的频率为490Hz左右。
差异性体现在计数方式上,
TC0采用单向计数,从0计到255算一个周期。
TC1和TC2采用循环向计数,从0计到255,再从255计到0才算一个周期。
在计数频率一致的情况下,
TC0输出的PWM波频率自然是TC1和TC2的两倍了!
下面给出图示,不深入讲解了,
感兴趣的撸友,可以参见ATmega328P数据手册TC章节!
以撸会友,共嗨共爽!
个人的VX公众号:let-us-arduino
一起撸Arduino
附上我的代码
int ledPin=14;
int stemp=4;
int pwm=0;
void setup() {
Serial.begin(115200);
// put your setup code here, to run once:
pinMode(ledPin,OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
pwm += stemp;
analogWrite(ledPin,pwm);
if(pwm>=255 || pwm<=0)
stemp *=-1;
Serial.println(pwm);
delay(500);
}
实现了呼吸灯的效果。控制占空比的方式来让灯的输入电压变大变小