Category Archives: Arduino 入门教程

魔法书4:Arduino UNO 内部定时器之谜

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 魔法书” 系列的专栏。在基础教程中,已简单介绍过内部定时器的基本概念。定时器就是一个内部闹钟,定时让Arduino 干些事情,比如,关联 PWM 输出,让睡眠的Arduino 醒来。内部定时器独立于CPU之外运行,不受CPU影响。要充分发挥 Arduino 的性能,就离不开定时器的灵活运用。

Arduino UNO (ATmega328) 内置了3个独立的定时器,它们分别是Timer0、Timer1、Timer2,虽然规格各不相同,但原理一样。我们先看看Timer0 是如何运转的。

Timer0 的核心是一个8位定时器,本质上它是一个计数器。这意味着它可以从0数到255,然后可以回归0,也可以倒过来,从255数到0。周而复始,这是它的基本功能,也是它存在的原因。

当然计数器可以被指定一个上限,不一定是255。当它做定时器时,可以由CPU的晶振产生的时钟信号驱动(16MHz),或者由预分频器驱动,分频比可以是 1、8、64、256、1024。当时作为计数器时,可以由外部输入驱动。Timer0 关联了两个PWM引脚:D5和D6。

Timer1 的核心是一个16位计数器,它可以从0数到65535,与Timer0一样,可以一直递增,也可以一直递减,或者一会儿递增一会递增,并周而复始。

Timer1 还有一个输入捕获单元,可以将一个时间戳放到输入信号上。Arduino UNO 上,Timer1关联了两路PWM,在Arduino Mege2560 上关联了3路PWM。

Timer2 和Timer0 类似,也是8位计数器,关联两路PWM。Timer2 最特别之处在于可以连接到 MCU 内置32KHz晶振上,这个晶振在 TOSC1和 TOSC2 引脚上。

遗憾的是,Arduino UNO 的 ATmega328P 这两个引脚和与 XTAL1和 XTAL2 外部16MHz 晶振的引脚复用,大部分Arduino UNO 就无法使用这个功能了,不过,在不连接外部晶振的最小系统上可以。

对于ATmega2560来说,因为XTAL1和XTAL2有专门的引脚,没有冲突,Timer2 可以由功耗极低的 32KHz 驱动,当MCU  进入深度睡眠模式时,也可以保持计时,定时将MCU唤醒。

深入内部中断

现在通过一个简单的内部中断程序,去了解定时器的运作机制。Arduino 已经将Timer0用于记录时间,其他两个则配备给了analogWrite() 函数。如果要用Timer1做实验,D9、D10的PWM将不能使用。

又一个闪烁程序!这里直接用Timer1来计时。首先,需要把Timer1 的 TCCR1A 寄存器(Timer/Counter1 Control Register A)中所有的位置零,这个寄存器控制着Timer1的工作状态,默认被Arduino 跟PWM关联在一起,置零后,Timer1 就被释放出来了。TCCR1A 的具体用法可以参见ATmega328P的DataSheet 第135 页。

Timer1 第二个寄存器 TCCR1B 可以设置分频比,即以什么速度驱动Timer1。别忘了Timer1本质是个计数器,这是理解的关键,如果不分频,Timer1 将以16Mhz速度运行,从0数到65535,大约需要 0.0041s:

系统时钟是16MHz, 这里设置为 16MHz / 256 =  62.5KHz。因为满了256倍,所以从0数到65535 大约需要0.0041s * 256 = 1.0486s 约1s 时间。

下一步使用 bitSet() 设置TIMSK1(时间中断寄存器),TOIE1 即允许中断 (Timer Overflow Interrupt Enable 1 )。

最后就是中断处理函数,非常简单:

Timer1_OVF_vect 含义如果Timer1 溢出,返回真值。Timer1溢出的含义即在分频器驱动下, 从0数到65535,一旦超过65535,即溢出。分频设定成256,不是随便来的,而是刚好设定为1秒左右,Timer1溢出(1/16Mhz * 65535 * 256 = 1.0486s )。

这只是内部定时器提供多种模式之一。还可以使用CTC(Clear Timer on Compare match,比较匹配时清除定时器),这个模式可以理解为设定一个值,然后跟定时器比较,一旦超过这个值,就溢出。

寄存器TCCR1B 中 WGM13、WGM12 两个位,可以设定Timer1 的模式,而CS1x位则设置分频,前面提到过了。

bitSet(TIMSK1, ICIE1) 中,ICIE 即 Input Capture Interrupt Enable 输入捕捉中断。ICR1 即捕捉寄存器,可以存储一个值。当Timer1数到这个值时,立刻重新计数。我们设置了1024分频比,即16Mhz / 1024 = 15.625KHz,ICR1 设置位15625,意思就是让计数器从0数到15625时,定时器溢出,重新计数,刚好1秒:

(1/16MHz)  * 1024 * 15625 = 1s

 

魔法书3:让 Arduino 慢下来 20倍省电

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 魔法书” 系列的专栏。上节课介绍了怎么让Arduino 全速运行,但大部分时候我们更希望 Arduino 能慢下来,节省更多的电能,让设备在户外数据采集等取电不便的地方,运行的更久。

降低 Arduino 的速度,最简单粗暴的方式就是换一个频率低的晶振(硬件层面),UNO 和Mage2560 默认使用16MHz的晶振,换成8MHz,速度就会降下来一半。对即将进入成品阶段的设计,建议在硬件层面修改,因为这是最可靠稳定的。如果还在验证和开发的阶段呢?软件方式会比较便利。

软件方式就是修改时钟分频寄存器(CLKPR),它决定了CPU 运行的速度。要注意的是,速度一旦改变,就会影响delay()、millis() 等时间相关的函数,而且会影响ADC、串口等外围设备。CLKPR 根据下表的值控制CPU的分频比(晶振频率除以分频比,即为CPU运行速度):

修改CLKPR 需要一个特殊过程,第一步,先将它的第7位(即 CLKCE)设置为1,并将其他位置零;第二步,给CLKPR 赋值。而且这个过程必须在4个周期内完成写入动作 -_-!! 所以过程中,最好禁止所有中断。

由于 CPU 的速度只有原来的1/256,Arduino 的示例程序程序Blink 中,延时函数 delay(1000) ,延迟时间即 1000ms * 256,超过4分种,闪烁实在太慢了,所以这里将延迟函数改为  delay(4),即1.024秒。

使用USB计量器测量降速后(下图一)和原速(下图二)的功耗,降速后功耗降低了10mA左右,节约了 20% 功耗。

节约10mA 是什么概念呢?计算一下:

原速运行时,Arduino UNO的功耗为 P = 0.05A * 5V = 0.25W

2节AA碱性电池升压(假设转换效率100%),工作在100mA 放电电流时,电量约 Q = 2Ah * 3V = 6Wh

即Arduino 可以运行 Q/P = 6Wh / 0.25A = 24h, 节能后 24h * (1 + 20%) = 28.8h

Arduino UNO 已经在龟速运行了,应该很省电,为什么也就能跑一天多?看来 Arduino 是相当耗电的。

不过,元凶是CPU吗?

其实,最耗电的是Arduino UNO 上的USB模块,而不是CPU。如果换成不带USB模块的 Arduino mini 来重复这个实验 Arduino 省电效果达90%以上,实际测试一下:

去掉 USB 模块后,耗电大幅下降80%以上。降速(上图一)和满速(上图二)的Arduino mini 功耗分别只有 4mA 和 10.55mA!

由于两颗板载 LED 功耗大概2mA * 2左右(其中一颗常亮),也就是说 Arduino mini 上单块 ATmega328P 的功耗只有 <1mA 和 6.5mA,降速后的功耗还不到原来的1/6!与满速的 Arduino UNO 相比不到 1/50 。

Arduino mini 的供电只需 3.3V,降速后的功率为 0.0033W。2节AA电池就可以让其运行 6Wh / 0.0033W = 1818h = 78天,两个多月时间!

但这个数字还不够理想,如果是户外采集数据,我们往往希望电池的寿命在半年、甚至一年以上,即使采用太阳能供电,Arduino 越省电,电池板体积也就能做的越小、成本越低。

另外,耗电的不止Arduino,还有更耗电的外部设备。有没有比降速更省电,同时让外围设备不耗电的方式呢?答案是肯定的。

后面的章节将介绍 Arduino 睡眠模式,不需要工作时,让系统打个盹,甚至睡个懒觉,进入冬眠状态,达到极致省电的状态,甚至2节AA电池就能让它工作2年以上。

第34课 ENC28J60联网 网络控制 LED

我是潘,曾经是个工程师。这是为 Ardui.Co 制作的 “Arduino 公开课” 系列的入门教程。上节课介绍 让 Arduino 成为 Web 服务器,现在为其加入交互控制的功能,形象来说,即通过浏览器控制板载 LED。同时介绍如何主动抓取命令,以突破无法访问内网的限制。有任何疑问请在评论区提出,我会逐一回答。

通过 Web 界面控制控制 LED 可能是最简单的交互形式了。如果你具有 HTML 的编程基础,立马会想到实现控制的形式就是表单反馈:

检查一下源代码:

Web 服务器执行,按下按钮(“<input>” 是一个按钮)时, “LED Status: ” 的显示状态就会改变:

/?status=ON,打开LED,
/?status=OFF,关闭LED。

“value” 是按钮的值,按下后,以“GET”方式(不是很安全,实际应用中建议用 “POST”,后话),发送到目标网页上(这里是href=”/”,即当前页面;”?status=XXX”是这个网页后缀),然后被服务器当作变量读取。按下按钮后,完整的路径如下:

但注意,这里改变的是网页显示的内容(ON/OFF),而不是 LED 的实际状态。为此,程序设计的思路是,通过检索网页的变化,去调整 LED 的状态

完整的程序:

跟之前的程序相比,这个程序很有点意思。

我们定义了4个指针变量,前两个赋值“ON”和“OFF”。但为什么用指针变量呢?因为可以减少值之间的传递,提高程序的效率。

将 LED 和它的状态初始化为 OFF。

核心在这:strstr(str1, str2) 方法。Arduino 能够直接调用大部分的 C 的方法,strstr() 的含义是在 str1 上,寻找 str2,一旦找到返回 str2 开始的地址(指针),如果找不到反馈 “0”。如果找到 “/?status=ON” 就让 LED 亮起来,反之亦然。

同时,通过改变 statusLabel、buttonLabel 的值,调整网页的显示状态。

$S 是占位符,一共有3个,按顺序由 statusLabel,buttonLabel, buttonLabel 来替换。

为了方便展示,我选择了在手机浏览器上操作:

理解网页操作的整个过程很重要:

用户输入网址 ——> 浏览器向服务器发起请求 ——> 服务器响应并返回初始状态数据 ——> 浏览器显示网页 ——> 用户点击操作 ——> 浏览器向服务器发送数据  ——>  服务器执行 if(pos) 判断 ——> 向浏览器返回数据 ——> 浏览器刷新页面