友情提示:380元/半年,儿童学编程,就上码丁实验室。
我是潘,曾经是个工程师。这是“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将不能使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/*
作者:Ardui.co
效果:定时器溢出产生闪烁
版本:1.0
更新时间:2018年6月2日
*/
voidsetup(){
bitSet(DDRB,5);//bitSet() 代替pinMode() 函数节省不少空间
TCCR1A=0;
TCCR1B=1<<CS12;
bitSet(TIMSK1,TOIE1);
}
voidloop(){
}
ISR(TIMER1_OVF_vect){
bitSet(PINB,5);// 翻转
}
|
又一个闪烁程序!这里直接用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 )。
最后就是中断处理函数,非常简单:
1
2
3
|
ISR(TIMER1_OVF_vect){
bitSet(PINB,5);// 翻转
}
|
Timer1_OVF_vect 含义如果Timer1 溢出,返回真值。Timer1溢出的含义即在分频器驱动下, 从0数到65535,一旦超过65535,即溢出。分频设定成256,不是随便来的,而是刚好设定为1秒左右,Timer1溢出(1/16Mhz * 65535 * 256 = 1.0486s )。
这只是内部定时器提供多种模式之一。还可以使用CTC(Clear Timer on Compare match,比较匹配时清除定时器),这个模式可以理解为设定一个值,然后跟定时器比较,一旦超过这个值,就溢出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/*
作者:Ardui.co
效果:CTC模式定时器溢出产生闪烁
版本:1.0
更新时间:2018年5月29日
*/
voidsetup(){
bitSet(DDRB,5);
TCCR1A=0;
TCCR1B=1<<WGM13|1<<WGM12|1<<CS12|1<<CS10;
ICR1=15625;
bitSet(TIMSK1,ICIE1);// 允许输入捕捉中断
}
voidloop(){
}
ISR(TIMER1_CAPT_vect){
bitSet(PINB,5);// 翻转
}
|
寄存器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