查看: 16949|回复: 11

G哥撸Arduino之:深入理解PWM输出

[复制链接]

该用户从未签到

发表于 2018-7-31 00:45 | 显示全部楼层 |阅读模式
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哥小时候,
夏日晚上,最期待就是露天电影

坝坝电影

坝坝电影
那时的电影播放机,是这个样子的
并不像现在电影里院看电影时,
一束光从背后的洞洞里投射出来,
播放机长啥样,完全不知道。
手摇电影播放机.jpg
电影制作时,
把故事情节按16格(每秒16个画面)的频率拍成一张张的图片。
电影播放时,
照样按16格的频率摇动播放机,
于是人物情节的动画就出来了。

电影中的人物动作是连续的吗?
可以认为是连续的,
因为观影的人们真真切切感受到人物动作的连续和自然。
真实的情况却是不连续的,
1秒16格的离散图片怎能替代连续的事物呢?!

场景二:行驶中的地铁广告
曾几何时(莫怪,G哥脑海里就这个词可以得瑟下了)
地铁候车站里、车厢里,到处都挂满了平面广告,
但广告主们还对乘客们的注意力侵占不够,
于是乎,更加奸诈的广告商人把目光瞄准了行驶中的列车。
昏暗的外部环境(车厢外),焦躁的人乘客,
车窗外忽亮忽暗的广告动画,
一下子就掠夺了大部分人的目光……

说了这么多,只是想告诉你们:
真实的外部世界,到底是不是连续的,
单靠我们的肉眼,其实是分辨不出来的,
眼见也不一定为实啊!
科JIAN学SHANG家MEN依据人眼对15频以上速度播放一帧帧画面时人眼就会感觉是连续的现实验证的理论,
通过输出离散的图片,就满足了我们眼睛识别连续的条件。

数字信号达到一定的要求(频率),就足以让外部事物(不限于人眼)产生连续的“假象”!
这也是本篇要讲的PWM方式输出“连续”信号的原理!

信号本身连不连续并不重要,重要是作用对象的实际效果是否满足连续的效果!

PWM简介及原理说明

PWM,全称:Pulse Width Modulation,
通常中文翻译为:脉冲宽度调制,
通过将一段数字信号编码为方波信号,在外部作用事物上达到拟输出效果的一种手段。
实际中,使用数字控制产生占空比不同的方波(一个不停在开与关之间切换的信号)来控制模拟输出。
pwm1.gif

一般情况下,方波在输出时,
代电平代表输出0V,高电平代表输出5V(或3.3V,视驱动电源而定)
当要输出中间(诸如3.7V)的“等效电压”时,
可以先输出5V一段时间,再输出0V一段时间,就可以了。
等效电压.png
所谓“等效电压”,
就是任何一个周期内,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 干不了其他事情了;

方法二: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,值越小,输出等效电压越低。


示例代码: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输出机制其实是通过
内部的定时/计时器单元实现的!

计数原理.png

计数单元持续不断的计数,
每计一次数,就比较一下当前计数(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章节!
单向计数.png
双向计数.png

以撸会友,共嗨共爽!
个人的VX公众号:let-us-arduino
一起撸Arduino

该用户从未签到

发表于 2018-7-31 08:07 | 显示全部楼层
好帖子,对PWM理解的更透彻了。

该用户从未签到

发表于 2018-7-31 09:00 | 显示全部楼层
感谢G哥的好帖,

该用户从未签到

发表于 2018-7-31 09:21 | 显示全部楼层
感谢G哥的好帖

该用户从未签到

发表于 2018-11-27 11:13 | 显示全部楼层
写的真的很好啊,感谢楼主

该用户从未签到

发表于 2018-12-28 15:42 | 显示全部楼层
感谢G哥的好帖

该用户从未签到

发表于 2019-2-4 13:53 | 显示全部楼层
感谢G哥的帖子,但是我没看懂,哈,19年新年快乐

该用户从未签到

发表于 2019-3-28 01:05 | 显示全部楼层
好文,学习中。终于弄明白内部计时器和pin的关系啦

该用户从未签到

发表于 2019-5-23 19:38 | 显示全部楼层
太感谢了!可是怎么没找到其他的关于I/O帖子呢?

该用户从未签到

发表于 2019-10-30 15:26 | 显示全部楼层
写的完美,我正准备看这么用两行代码改变255输出频率是,戛然而止,谁来指导一下。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

arduino程序设计基础 blinker物联网解决方案

热门推荐

【分享】Blynk+M5timer_camera(esp32 core),易于实现部署的家居...
【分享】Blynk+M5timer_ca
Blynk+M5timer_camera(esp32 core),易于实现部署的家居监测【背景故事】本教程源
OLED 128*64自制可达10000000个选项的菜单(已更新)
OLED 128*64自制可达10000
OLED 128*64自制可达10000000个选项的菜单 温馨提示: 建议占个楼再食用本帖子
安卓app版本拒绝录音授权提示错误
安卓app版本拒绝录音授权
录音授权拒绝后,会显示 美车驿站,而不是点灯app的名称
如何用将esp8266(nodemcu 1.0)的串口监视内容显示到oled0.9(4pin)
如何用将esp8266(nodemcu
小白一个,前段时间一直想做一个检测WiFi设备的检测器,然后花了一下午的时间,搞出来
教你让OLED动起来!多重字符串版!
教你让OLED动起来!多重字
大家都知道:arduino单片机是单线程的 而上次教程中的多段字符串的运行速度必须一致
Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
快速回复 返回顶部 返回列表