Arduino Uno 定时器中断 timer 0,1, 2 (自定义配置、MsTimer2、TimerOne库)
定时器简介
Arduino UNO有三个定时器,
timer0:一个被Arduino的delay(), millis()和micros()使用的8位定时器
timer1:一个被Arduino的Servo()库使用的16位定时器
timer2:一个被Arduino的Tone()库使用的8位定时器
"Arduino Mega"板有另外三个可使用的timer(3,4,5)
每个定时器都有一个计数器,在计时器的每个时钟周期递增。当计数器达到存储在比较匹配寄存器中指定值时触发CTC(Clear Timer on Compare Match)定时器中断。
一旦定时器计数器达到该值,它将在定时器时钟的下一个定时器上清零(复位为零),然后它将继续再次计数到比较匹配值。
通过选择比较匹配值并设置定时器递增计数器的速度,你可以控制定时器中断的频率。
根据定时周期的大小以及时钟使用情况(是否与已使用库冲突)来选择不同的定时器。
相关数值计算
Arduino时钟以 16MHz 运行。计数器的一个刻度值表示1 / 16,000,000秒(~63ns),跑完1s需要计数值16,000,000。
1、Timer0和timer2是8位定时器,可以存储最大计数器值255。
2、Timer1是一个16位定时器,可以存储最大计数器值65535。
一旦计数器达到其最大值,它将回到零(这称为溢出)。
因此,需要对时钟频率进行分频处理,即预分频器。通过预分频器控制定时计数器的增量速度。
预分频器(prescaler)与定时器的计数速度如下:
定时器速度(HZ) = Arduino时钟速度(16MHz) / 预分频器系数
因此,1预分频器将以16MHz递增计数器,8预分频器将在2MHz递增,64预分频器= 250kHz,依此类推。
由中断频率公式:
中断频率(Hz)=(Arduino时钟速度16MHz)/(预分频器*(比较匹配寄存器+ 1)
可以计算一定中断频率下,比较匹配寄存器的预设值为:
比较匹配寄存器 = [16,000,000Hz / (预分频器*所需的中断频率)] - 1
当你使用定时器0和2时,这个数字必须小于256,对于timer1小于65536。
如果你想每秒一次中断(频率为1Hz),采用最大预分频器为1024,此时,比较匹配寄存器 = [16,000,000 /(1024 * 1)] -1 = 15624,因为256 <15624 <65536,你必须使用timer1来实现这个中断。
1. 自定义配置 timer0, 1, 2
Arduino定时器配置(Timer0,Timer1,Timer2)
Arduino-Timer-Interrupts
//https://www.instructables.com/id/Arduino-Timer-Interrupts/
void setup(){
noInterrupts();//stop interrupts
//set timer0 interrupt at 2kHz
TCCR0A = 0;// set entire TCCR0A register to 0
TCCR0B = 0;// same for TCCR0B
TCNT0 = 0;//initialize counter value to 0
// set compare match register for 2khz increments
OCR0A = 124;// = (16*10^6) / (2000*64) - 1 (must be <256)
// turn on CTC mode
TCCR0A |= (1 << WGM01);
// Set CS01 and CS00 bits for 64 prescaler
TCCR0B |= (1 << CS01) | (1 << CS00);
// enable timer compare interrupt
TIMSK0 |= (1 << OCIE0A);
//set timer1 interrupt at 1Hz
TCCR1A = 0;// set entire TCCR1A register to 0
TCCR1B = 0;// same for TCCR1B
TCNT1 = 0;//initialize counter value to 0
// set compare match register for 1hz increments
OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS10 and CS12 bits for 1024 prescaler
TCCR1B |= (1 << CS12) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
//set timer2 interrupt at 8kHz
TCCR2A = 0;// set entire TCCR2A register to 0
TCCR2B = 0;// same for TCCR2B
TCNT2 = 0;//initialize counter value to 0
// set compare match register for 8khz increments
OCR2A = 249;// = (16*10^6) / (8000*8) - 1 (must be <256)
// turn on CTC mode
TCCR2A |= (1 << WGM21);
// Set CS21 bit for 8 prescaler
TCCR2B |= (1 << CS21);
// enable timer compare interrupt
TIMSK2 |= (1 << OCIE2A);
interrupts();//allow interrupts
}//end setup
// timer0 callback
ISR(TIMER0_COMPA_vect){
}
// timer1 callback
ISR(TIMER1_COMPA_vect){
}
// timer2 callback
ISR(TIMER2_COMPA_vect){
}
void loop(){
}
-
OCR#A
表示 timer# 的比较匹配寄存器值; -
TCNT#
表示 timer# 的当前的计数值,可在任意时刻自定义赋值; -
TCCR0A |= (1 << WGM01);//for timer0
TCCR1B |= (1 << WGM12);//for timer1
TCCR2A |= (1 << WGM21);//for timer2
针对三个不同的定时器打开CTC模式; -
对
TCCR#B
进行CS#0
,CS#1
,CS#2
移位配置预分频器系数(1, 8, 64, 256, 1024)
请注意,在最后一步中,不同的计时器有不同的预分频系数。 例如,timer2 没有 1024 预分频器。
定时器预分频系数配置表:
-
TIMSK# |= (1 << OCIE#A);
启用 timer# 定时器比较中断。 -
最后编写定时器 timer# 溢出执行的中断服务函数:
ISR(TIMER#_COMPA_vect){ }
此外,尽量使中断函数尽可能短,尤其是在以高频率中断时。
甚至可以直接对ATMEL芯片的端口/引脚进行寻址,而不是使用digitalWrite() 和 digitalRead()功能。
2. MsTimer2 库
该库在 timer2 上“硬编码”了分辨率为 1毫秒 的定时中断。
example:
// Toggle LED on pin 13 each second
#include <MsTimer2.h>
void flash() {
static boolean output = HIGH;
digitalWrite(13, output);
output = !output;
}
void setup() {
pinMode(13, OUTPUT);
MsTimer2::set(500, flash); // 500ms period
MsTimer2::start(); // enables the interrupt.
// MsTimer2::stop(); // disables the interrupt.
}
void loop() {
}
3. TimerOne 库
该库使用 timer1 产生自定义载波频率下不同 pwm 占空比输出 和 定时器中断。
#include <TimerOne.h>
void setup()
{
pinMode(10, OUTPUT);
Timer1.initialize(500000); // initialize timer1, and set a 1/2 second period
Timer1.pwm(9, 512); // setup pwm on pin 9, 50% duty cycle
Timer1.attachInterrupt(callback); // attaches callback() as a timer overflow interrupt
}
void callback()
{
digitalWrite(10, digitalRead(10) ^ 1); // 状态翻转
}
void loop()
{
// your program here...
}
- 注意事项:
(1)如果你使用了 MsTimer2 库, 则 pin11 和 pin3 就不能再用做 PWM 输出了! 因为该 pin3 和 pin11 的 PWM 是靠 timer2 帮忙的! (tone()
也是)
(2)注意 Servo.h 库与 TimerOne 都是使用内部定时器 timer1 会影响pin 9, pin 10 的 PWM
(3)tone()
使用 timer2 定时器; 若使用 Tone 库的 Tone 对象(Tone 变量)也是优先使用 timer2 定时器,若用两个 Tone 变量则 timer1 也会被用掉, 用三个 Tone 则连控制 millis( )的 timer0 也会被用掉。
(4)别忘了, timer0 负责帮忙控制 pin 5 和 pin 6 的 PWM 输出。只要不去改变 timer 的 Prescaler 就不会影响其控制的 PWM pin, 但MsTimer2 库与 tone( )都会改变 Prescaler ! !