魔法书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

 

Leave a Reply

Your email address will not be published. Required fields are marked *