对于Arduino新手来说,刚开始使用实例的Blink亮灯代码,其实就开始进入了定时器的世界,因为其中的delay()函数就是通过定时定时器实现的,不过这些都被Arduino的封装库隐藏起来了,为了让使用者更快更便捷地开发项目,除了delay()函数,millis() ,micros() ,delayMicroseconds() ,PWM波生成的analogWrite()和tone() ,同时Servo库里面也使用了定时器。
接下来就进入Arduino定时器的世界,我同为新手,整理一下我的一些认识。
定时器对于单片机来说就类似我们现实生活中的时钟,记录很多和时间相关的事件。
我主要使用的Arduino单片机为UNO,NANO和MEGA 2560。
UNO和NANO都使用的是ATmega328芯片,这款芯片有3个定时器,Timer0,Timer1,
Timer2,其中Timer0和Timer2都是8位寄存器(256),Timer1是16位寄存器(65536),意味着更高的分辨率。
mege2560使用的是ATmege2560芯片,这款芯片有6个定时器,在328的基础上,增加了Timer3,Timer4,Timer5。这三个定时器都是16位的寄存器。
用表格整理如下
定时器 | 位数 | 封装函数 | mega2560引脚对应 |
---|---|---|---|
Timer0 | 8bit | delay() , millis() 和 micros()等 | 4,13 |
Timer1 | 16bit | UNO的servo库 | 11,12 |
Timer2 | 8bit | tone()等 | 9,10 |
Timer3 | 16bit | 2,3,5 | |
Timer4 | 16bit | 6,7,8 | |
Timer5 | 16bit | mega2560的servo库 | 46,45,44 |
可以到网上下载TimerOne, TimerThree等库文件,TimerOne.h就对应为timer1定时器。
TimerOne.h的下载链接:
https://www.arduinolibraries.info/libraries/timer-one
附上代码
#include<TimerOne.h>
#define state digitalRead(13) //读出引脚13的状态
int i=1; //记录次数
void setup()
{
Timer1.initialize(100000); //设置中断速度 1s
Timer1.attachInterrupt(tongxun); //关联中断函数
Serial.begin(115200); //设置串口波特率
interrupts(); //开启中断
}
void tongxun()
{
i++;
digitalWrite(13,!state); //亮灭交替
if(i>=10)
{
i=1;
digitalWrite(13,!state); //第十次亮灭灯时,事件多进行一次
}
}
void loop()
{
}
此时loop函数里面什么的没有写,但是烧录进单片机的时候每隔1s会进行亮灭灯,每隔10s停顿一次,这些完全依靠定时中断来完成,意味着你可以运行你的loop程序时,每隔一段时间就会进入中断函数中执行一次任务,在某种意义上让Arduino可以进行“多”线程的工作。
其他Timer的使用基本一样,只要找到合适的库函数就能够使用,注意使用的定时器不要和你已使用的封装函数冲突,比如对于UNO来说,你在使用Servo.h的时候,就不能再使用timer1了,此时IDE会给你编译报错。
接下来可以来些高阶的定时器使用,之前使用的定时器库是别人已经给你封装好了的,你直接调用一些简单的函数就可以便捷地实现功能,如果想一探究竟,就可以操作寄存器来更深入地了解,更灵活地使用。
虽然Arduino成熟的封装极力避免用户进行寄存器的操作,让用户更专注于功能的实现。
寄存器这边我也知之甚少,搬运一些网上的内容吧。
寄存器列表如下,x代表0,1,2,3,4,5这6种定时器
寄存器 | 作用 |
---|---|
TCNTx | 定时器/计数器寄存器。实际的计时器值存储在此处。 |
OCRx | 输出比较寄存器 |
ICRx | 输入捕捉寄存器(仅适用于16位定时器) |
TIMSKx | 定时器/计数器中断屏蔽寄存器。启用/禁用定时器中断。 |
TIFRx | 定时器/计数器中断标志寄存器。表示挂起的定时器中断。 |
以timer2定时器50Hz为例
Arduino晶振16MHz,CPU分频(16000000/256=62500),产生10Hz频率(62500/50Hz=125),同8位256比较(125<256),可行
如果是10Hz,最后6250>256,不可行,就只能换分辨率更大的timer1了,16位,65536。
x代表定时器编号0,1,2,3,4
y代表输出编号A,B,C
定时器溢出:
定时器溢出意味着定时器已达到限制值。发生定时器溢出中断时,定时器溢出位TOVx将在中断标志寄存器TIFRx中置1。当中断屏蔽寄存器TIMSKx中的定时器溢出中断使能位TOIEx置1时,将调用定时器溢出中断服务程序ISR(TIMERx_OVF_vect)。
输出比较匹配:
当输出比较匹配中断发生时,OCFxy标志将在中断标志寄存器TIFRx中置1。当中断屏蔽寄存器TIMSKx中的输出比较中断使能位OCIExy置1时,将调用输出比较匹配中断服务ISR(TIMERx_COMPy_vect)例程。
定时器输入捕获:
当发生定时器输入捕捉中断时,输入捕捉标志位ICFx将被设置在中断标志寄存器TIFRx中。当中断屏蔽寄存器TIMSKx中的输入捕捉中断使能位ICIEx置1时,将调用定时器输入捕捉中断服务程序ISR(TIMERx_CAPT_vect)。
代码我涂涂改改了几次,可能会有问题,如果运行失败,你们再找别的代码试试吧,基本的配置和使用都在里面了。
boolean toggle1=0;
void setup()
{
cli(); //停止中断
//设置2kHz的timer0中断
TCCR0A = 0; //将整个TCCR0A寄存器设置为0
TCCR0B = 0; // TCCR0B相同
TCNT0 = 0; //将计数器值初始化为0
//设置比较匹配寄存器2khz增量
OCR0A = 124; // =(16 * 10 ^ 6)/(?Hz * 64) - 1(必须<256) //比较匹配寄存器= [16,000,000Hz /(预分频器*所需中断频率)] - 1
//打开CTC模式 // timer0 min 1kHz
TCCR0A|=(1 << WGM01);
//为64预分频器设置CS01和CS00位
TCCR0B|=(1 << CS01)| (1 << CS00);
//启用定时器比较中断
TIMSK0|=(1 << OCIE0A);
//设置1Hz的timer1中断
TCCR1A = 0; //将整个TCCR1A寄存器设置为016
TCCR1B = 0; // TCCR1B相同
TCNT1 = 0; //将计数器值初始化为0
//设置比较匹配寄存器1hz的增量
OCR1A = 15624; // =(16 * 10 ^ 6)/(?Hz* 1024) - 1(必须<65536) //timer1 min //10hz
//打开CTC模式
TCCR1B|=(1<<WGM12);
//为1024预分频器设置CS10和CS12位
TCCR1B|=(1<<CS12)|(1<<CS10);
//启用定时器比较中断
TIMSK1|=(1<<OCIE1A);
//设置8kHz的timer2中断
TCCR2A = 0; //将整个TCCR2A寄存器设置为0
TCCR2B = 0; // TCCR2B相同
TCNT2 = 0; //将计数器值初始化为0
//设置比较匹配寄存器为8khz增量
OCR2A = 249; // =(16 * 10 ^ 6)/(?Hz* 8) - 1(必须<256) //timer2 min 8kHz
//打开CTC模式
TCCR2A |=(1 << WGM21);
//将CS21位设置为8个预分频器
TCCR2B |=(1 << CS21);
//启用定时器比较中断
TIMSK2 |=(1 << OCIE2A);
pinMode(13,OUTPUT);
sei(); //允许中断
}
//Timer0中断函数
ISR(TIMER0_COMPA_vect){
}
//Timer1中断函数
ISR(TIMER1_COMPA_vect){// timer1中断1Hz切换引脚13(LED)
//产生频率为1Hz / 2 = 0.5kHz的脉冲波(全波切换为两个周期,然后切换为低)
if(toggle1){
digitalWrite(13,HIGH);
toggle1 = 0;
}
else
{
digitalWrite(13,LOW);
toggle1 = 1;
}
}
void loop()
{
}
初写博客,诸多问题还请各位包涵,有疑问欢迎留言,我们再继续谈论。
[1] https://www.robotshop.com/community/forum/t/arduino-101-timers-and-interrupts/13072