原文地址:https://www.arduino.cn/thread-12452-1-1.html
下面仅仅是将原文给编辑一下,原文阅读有困难,看原文时一定看看下面的评论。
前面跟大家分享了如何自己设定Arduino内部定时器定时做事,我把在以下两篇中对于暂停/继续 timer1 和 timer2 的 CTC 中断之补充也抓过来:
“自己控制 timer1 定时器定时做多件事(教程)”:
http://www.arduino.cn/thread-12445-1-1.html
以及
“自己控制 timer2 定时器定时做多件事(教程)”:
http://www.arduino.cn/thread-12448-1-1.html
很简单:
TIMSK1 &= ( ~ (1 << OCIE1A) ); // 禁止 CTC for TIMER1_COMPA_vect
阿也是很简单:
TIMSK1 |= (1 << OCIE1A); // enable CTC for TIMER1_COMPA_vect
或是写成 function 方便使用:
void stopT1( ) {
TIMSK1 &= ( ~ (1 << OCIE1A) ); // 禁止 CTC for chanel A
}
void resumeT1( ) {
TIMSK1 |= (1 << OCIE1A); // enable CTC for TIMER1_COMPA_vect
}
参看 ATmega328 的 datasheet:
https://www.alldatasheet.com/view.jsp?Searchword=ATMEGA328 (P.134-139)
====================================
也是很简单:
TIMSK2 &= ( ~ (1 << OCIE2A) ); // 禁止 CTC for TIMER2_COMPA_vect
阿也是很简单:
TIMSK2 |= (1 << OCIE2A); // enable CTC for TIMER2_COMPA_vect
或是写成 function 方便使用:
void stopT2( ) {
TIMSK2 &= ( ~ (1 << OCIE2A) ); // 禁止 CTC for TIMER2_COMPA_vect
}
void resumeT2( ) {
TIMSK2 |= (1 << OCIE2A); // enable CTC for ISR(TIMER2_COMPA_vect)
}
参看 ATmega328 的 datasheet:
https://www.alldatasheet.com/view.jsp?Searchword=ATMEGA328 (P.158-162)
不论是使用 timer1 或是 timer2 内部定时器(定时器),这里的两个范例都是设成每 0.1 ms 产生一次中断 !
如果你希望每隔 23.8 ms 做一次 yourJob( );那只要在 ISR( ) 内加入如下即可:
static int y49 = 0;
++y49;
if(y49 == 238) { // 238 * 0.1ms = 23.8 ms
y49 = 0;
yourJob( );
}
这两个范例的中断请求精准度是 0.1 ms,
这是因为Prescaler 设 64, 代表 TOP 的 OCR1A 和 OCR2A 设 24, 以 CPU 使用频率 16 MHz 来计算, 除频 Prescaler 64, 结果:
tick = 1 秒 / (16 000 000 / 64) = 1/250000 = 0.000004 sec / per cycle
(24+1) * 0.000004 sec = 0.0001 sec = 0.1 ms
因为以上除法并没有余数, 所以没有误差, 这 0.1ms 会很精准 !
既然每隔 0.1ms 才来中断处理程序一次,当然没办法设定每隔比 0.1ms 还小的时间做某事 !
如果你想要比 0.1ms 还短的时间产生中断一次, 那你需要改 OCR1A 和 OCR2A and/or 改变 Prescaler 以产生更快的中断 !
虽然很多人对以上两篇教程内的程序写法看不太懂也不想搞懂,
但是总会有人很想看懂,很想知道那些有如天书的语法到底在写啥碗糕(saying what) ?
所以现在我就来写一些补充说明,以下就以对 timer1 定时器的设定原理做补充。
要"定时"做事, 最佳方式当然要采用中断请求(Interrupt ReQuest, IRQ), 其实概念上很简单, 我们日常生活中随时都在处理"中断请求" ! 例如:
吃饭时突然电话响了, 接不接(要不要处理该中断请求)?
接着电话讲话中, 突然门铃响了, 要不要先暂停讲电话先去开门看看?
开完门继续讲电话, 讲完电话阿就继续吃饭
Arduino 的 CPU 只能依照程序内容一次处理一个指令,但也被设计成每个指令做完都会"看看"有没有中断事件要处理!
关于中断的概念可以看看奈何大神写的这篇有趣文章:
http://www.arduino.cn/thread-2421-1-1.html
该篇主要是介绍Arduino外部中断的使用,我们这边要讲的则是透过设定 Arduino 内部定时器(大多数Arduino板子有三个定时器),
以便在某种状况下对 Arduino 的 CPU 发出中断请求。
讲白话一点, 就是设定(config) Arduino 内部某个闹铃(例如定时器 timer1),时间到(条件符合)就对 CPU 发动某种中断请求,
以便要求 CPU 处理一下特殊程序片段。
这特殊程序片段称为中断服务程序(ISR, Interrupt Service Routine),又称为中断处理程序(Interrupt Handler)。
这中断处理程序的写法比较特别, 要使用 Arduino 开发环境预先定义的宏 ISR( ),
例如 ISR(TIMER1_OVF_vect) { } 或 ISR(TIMER1_COMPA_vect) { } 等等。
当然也会用到很多预先定义的符号名称如 TCNT1, OCR1A, WGM12, CS11, CS12 等等。
这些奇怪的名称要看 ATmega328 datasheet:
http://www.atmel.com/Images/doc8161.pdf
这是因为 Arduino 大部份板子使用 ATmega328 这 CPU 微处理器(microcontroller),该 CPU 总共支持 25 种不同的中断(Interrupt),
包括外部中断, 三个 timer 定时器数到 Overflow 溢位的中断,或是定时器数到特定值的 CTC 中断等等,当然也包括我们这使用的 timer1 数到特定值之 CTC 中断:
ISR(TIMER1_COMPA_vect) { }
可是,何时会发生这中断呢 ?
当然就是必须对 timer1 (或 timer2 或 timer0)做一些设定,
就像你可以设定闹铃早上六点半叫你起床那样 !!
所谓的 CTC 中断,
就是设定 timer1 每次被踢一下(tick)就把计数器TCNT1加 1,
加到某个特定值(例如等于 OCR1A)就产生对CPU中断并把计数器TCNT1归零。
何谓被"踢一下"呢(何谓一个 tick) ?
Arduino 的板子大都采用频率 16MHz, 就是有个石英振荡器每秒振荡16000000 次, 也就是每秒钟滴答(tick) 16000000 次, 这频率主要是给 CPU 使用, 振荡一次称为一个 tick 或一个 cycle;每个 tick (cycle)需要 1/16000000 秒 = 0.0000000625 秒;
频率当然也可顺便给 timer 定时器使用,但是通常为了不要这么快, 我们不会把这频率直接拿来踢一下 timer,而是先经过一个除频器, 除频的分母由 timer 的 Prescaler 决定!
每个 timer 可以用不同的 Prescaler 且是可以由程序中设定的 !就是说我们随时可以设定 Prescaler 除频来给各个 timer 定时器使用;
例如把 Prescaler 设 64, 则频率为 16000000 次/ 64 = 250000 Hz
就是说 timer 每隔 1/250000 秒会被"踢一下", 就是 tick 一次,
以 timer1 为例, 此时每个 tick (1/250000)秒 TCNT1 会被加 1,
注意 timer1 是 16bit的定时器, 所以 TCNT1 和 OCR1A 最大是 65535,
如果到了 65535 再加 1 就变成 0 即所谓 Overflow 溢位。
由于我的范例是设定 CTC mode(Clear Timer on Compare mode), 如果 TCNT1 与 OCR1A 内容相同,则 timer1 就进入 “match” 符合状态,它会 把 TCNT1 清除为 0, 并拉动 CPU 的 IRQ 发动中断请求。
于是 CPU 就会暂停正在做的事(当然是在允许中断的状态),改为开始执行 ISR(TIMER1_COMPA_vect) { } 这个处理程序。
再让大家看看我在之前给的控制 timer1 定时做两件事的范例:
(各自负责闪烁 pin 13 LED 与 pin 8 LED)
// 控制 LED on pin 13亮灭, 每秒闪烁 2 次: 亮 0.25 秒灭 0.25 秒 ...
// LED on pin 8 每秒闪烁 1 次: 亮 0.5 秒灭 0.5 秒 ...
const int intA = 2500; // 2500 * 0.1 ms = 250ms
const int intB = 5000; // 5000 * 0.1 ms = 500ms = 0.5秒
// Prescaler 用 64
volatile int ggyy = 1; // 使用这当 Flag 给 ISR 使用 !
int ledPin =13;
int led8 = 8; // pin 8
/// For Prescaler == 64
/// 1 秒 / (16 000 000 / 64) = 1/250000 = 0.000004 sec / per cycle
/// 0.1 sec / 0.000004 sec -1 = 25000 -1 = 24999
/// 0.0001 sec / 0.000004 sec -1 = 25 -1 = 24
const int myTOP = 24; // 0.0001 sec when Prescaler == 64
/ Interrupt Service Routine for TIMER1 CTC on OCR1A as TOP
/// 注意以下名称是有意义的, 不可乱改 !
volatile unsigned long ms;
ISR(TIMER1_COMPA_vect)
{
static unsigned int aaa = 0;
static unsigned int bbb = 0;
ms = millis( );
++aaa; bbb++;
if(aaa == intA){
aaa=0; myJobOne( );
}
if(bbb == intB){
bbb=0; myJobTwo( );
}
}
void setup( ) {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
pinMode(led8, OUTPUT); digitalWrite(led8, 1); // 故意
digitalWrite(ledPin, LOW); // turn Off the LED
delay(1);
setMyTimerOne( );
}
unsigned long doP = 0; // do print ?
void loop() {
//... 做其他事
// if( ggyy == 1) ...
if(doP) {
doP = 0;
Serial.println(ms);
}
}
void myJobOne( ) {
digitalWrite(ledPin, ggyy); // ggyy 是 0 或 1
ggyy = 1 - ggyy; // 给下次进入 用
doP = 1; // tell him to print
}
void myJobTwo( ) {
digitalWrite(led8, ! digitalRead(led8)); // Toggle led8
}
void setMyTimerOne( ){
cli(); // 禁止中断
TCCR1A = 0;
TCCR1B = (1<<WGM12); // CTC mode; Clear Timer on Compare
TCCR1B |= (1<<CS10) | (1<<CS11); // Prescaler == 64
/
OCR1A = myTOP; // TOP count for CTC, 与 prescaler 有关
TCNT1=0; // counter 归零
TIMSK1 |= (1 << OCIE1A); // enable CTC for TIMER1_COMPA_vect
sei(); // 允许中断
}
//
程序中最重要的是以下这函数 void setMyTimerOne( ) { }:
void setMyTimerOne( ){
cli(); // 禁止中断
TCCR1A = 0;
TCCR1B = (1<<WGM12); // CTC mode; Clear Timer on Compare
TCCR1B |= (1<<CS10) | (1<<CS11); // Prescaler == 64
/
OCR1A = myTOP; // TOP count for CTC, 与 prescaler 有关
TCNT1=0; // counter 归零
TIMSK1 |= (1 << OCIE1A); // enable CTC for TIMER1_COMPA_vect
sei(); // 允许中断
}
其工作就是设定 timer1 定时器的 Prescaler 为 64, 且使用 CTC mode, 且 TOP 上限的 OCR1A 设为 myTOP 也就是 24, 这样, timer1 就会每 0.1 ms 发出中断请求, 而 CPU 就会暂时放下正在处理的程序(会记住位置), 并开始优先处理(执行)中断处理程序 ISR(TIMER1_COMPA_vect) { } 内的代码。
再来看看做完全一样的事,
但是改用 timer2 定时器帮忙发出中断请求的该范例:
// 使用 timer2 定时器帮忙发出中断请求
// 控制 LED on pin 13亮灭, 每秒闪烁 2 次: 亮 0.25 秒灭 0.25 秒 ...
// LED on pin 8 每秒闪烁 1 次: 亮 0.5 秒灭 0.5 秒 ...
#define bbs(x) (1<
const int intA = 2500; // 2500 * 0.1 ms = 250ms
const int intB = 5000; // 5000 * 0.1 ms = 500ms = 0.5秒
// Prescaler 用 64
volatile int ggyy = 1; // 使用这当 Flag 给 ISR 使用 !
int ledPin =13;
int led8 = 8; // pin 8
/// For Prescaler == 64
/// 1 秒 / (16 000 000 / 64) = 1/250000 = 0.000004 sec / per cycle
/// 0.1 sec / 0.000004 sec -1 = 25000 -1 = 24999
/// 0.0001 sec / 0.000004 sec -1 = 25 -1 = 24
const uint8_t myTOP = 24; // 0.0001 sec when Prescaler == 64
/ Interrupt Service Routine for TIMER1 CTC on OCR1A as TOP
/// 注意以下名称是有意义的, 不可乱改 !
ISR(TIMER2_COMPA_vect)
{
static unsigned int aaa = 0;
static unsigned int bbb = 0;
++aaa; bbb++;
if(aaa == intA){
aaa=0; myJobOne( );
}
if(bbb == intB){
bbb=0; myJobTwo( );
}
}
void setup( ) {
pinMode(ledPin, OUTPUT);
pinMode(led8, OUTPUT); digitalWrite(led8, 1); // 故意
digitalWrite(ledPin, LOW); // turn Off the LED
setMyTimer2( );
}
void loop() {
//... 做其他事
// if( ggyy == 1) ...
}
void myJobOne( ) {
digitalWrite(ledPin, ggyy); // ggyy 是 0 或 1
ggyy = 1 - ggyy; // 给下次进入 用
}
void myJobTwo( ) {
digitalWrite(led8, ! digitalRead(led8)); // Toggle led8
}
void setMyTimer2( ){
cli(); // 禁止中断
TCCR2A = bbs(WGM21); // CTC mode 2; Clear Timer on Compare, see p.158-162
TCCR2B = bbs(CS22); // Prescaler == 64; see p.162 in datasheet
/ 注意 WGM22 在 TCCR2B, 但 WGM21 与 WGM20 在 TCCR2A;
/ mode 由 WGM22, WGM21, WGM20 决定 (see datasheet p.158-162)
OCR2A = myTOP; // TOP count for CTC, 与 prescaler 有关
TCNT2=0; // counter 归零
TIMSK2 |= bbs(OCIE2A); // enable CTC for TIMER2_COMPA_vect
sei(); // 允许中断
}
程序中最重要的是以下这函数 void setMyTimer2( ){ } :
void setMyTimer2( ){
cli(); // 禁止中断
TCCR2A = bbs(WGM21); // CTC mode 2; Clear Timer on Compare, see p.158-162
TCCR2B = bbs(CS22); // Prescaler == 64; see p.162 in datasheet
/ 注意 WGM22 在 TCCR2B, 但 WGM21 与 WGM20 在 TCCR2A;
/ mode 由 WGM22, WGM21, WGM20 决定 (see datasheet p.158-162)
OCR2A = myTOP; // TOP count for CTC, 与 prescaler 有关
TCNT2=0; // counter 归零
TIMSK2 |= bbs(OCIE2A); // enable CTC for TIMER2_COMPA_vect
sei(); // 允许中断
}
其工作就是设定 timer2 定时器的 Prescaler 也是为 64, 且使用 CTC mode, 且 TOP 上限的 OCR2A 设为 myTOP 也就是 24, 这样, timer2 就会每 0.1 ms 发出中断请求, 而 CPU 就会暂时放下正在处理的程序(会记住位置), 并开始优先处理(执行)中断处理程序 ISR(TIMER2_COMPA_vect) { } 内的代码。
程序中的 bbs(CS22) 其实就是 (1 << CS22), 因前面我有#define bbs() 宏;
至于 WGM21, CS22, TCNT2, OCR2A, TIMSK2 等这些都是事先被定义的符号, 都有特定意义, 有些是代表整数, 有些是代表 timer 的特殊缓存器(寄存器)。
// 关于 timer1 和 timer2 的设定为 CTC mode 与 Prescaler 之设定,
// 在这再简单做个总结:
// Prescaler 是用来除以 CPU 所用频率的频率以控制 timer 定时器;
// CTC mode 就是数到 TOP 值 (OCR1A, OCR2A) 就产生中断并重新计数 !
/
// TCCR1B 的 CS12, CS11, and CS10 这三个 bit 控制 timer1 的 Prescaler
/// CS12 CS11 CS10 为 011 表示 Prescaler 为 64 (See datasheet p.137)
/// 设定 CTC mode 要把 WGM12, WGM11, WGM10 设为 1 0 0
/// 记得要先做 TCCR1A = 0;
/// WGM12 与 CS11 和 CS10 都在 TCCR1B 内!!
// 至于 timer2 则略为不同… (CTC mode 的设定也不同!)
// TCCR2B 的 CS22, CS21, and CS20 这三个 bit 控制 timer2 的 Prescaler
// CS22 CS21 CS20 为 100 表示 Prescaler 为 64 (See datasheet p.162)
/// 设定 CTC mode 要把 WGM22, WGM21, WGM20 设为 0 1 0
/// 但是, 注意 WGM21 是在 TCCR2A 内!
// For more detail, see:
/// http://www.engblaze.com/microcon … -interrupts/#config
/// And the datasheet for ATmega328 :
/// http://www.atmel.com/Images/doc8161.pdf (P.158-162)
/// ======================
A: 可以, 但原则上新来的中断会进入排队状态 !
这是因为进入 ISR 会立即自动禁止其他中断请求,
直到 ISR 程序全部做完才会再开放中断请求 !!
但你可以下达允许新中断的指令, 以上述范例来说,
如果你的 myJobOne( ) 或 myJobTwo( ) 做太久,
希望在里面允许中断,
则你可以在你的 myJobOne( ) 以及 myJobTwo( )里面写:
sei( );
这样会允许其他中断请求来打断目前的处理程序,
可是要注意, 如果中断来得太快, 这样很可能会因旯不及处理而出问题,
而且还有重复进入同一个中断处理程序还有变量被毁以及是否可共享的问题,
当然后果要你自己负责啰
A: CPU在每个指令做完之后如果允许中断就会检查是否有任何中断请求,
使用 16MHz 的CPU每个 tick (或称 cycle)是0.0000000625 秒,
每个基本指令大约两三个 tick (cycle), 也就是大约0.0000002 秒以内;
即使是比较花时间的中断处理,
从进入 ISR (23 ticks)到离开 ISR (19 ticks)(假设 ISR内都不做事),
也才大约 42 ticks大约 0.0000027 秒 = 2.7 micro seconds,
( 参考 http://gammon.com.au/interrupts )
这么短的时间要同时发生两个中断请求的机率不高 !
但既然说机率不高就表示总是有机会"同时发生" !
这时 CPU 会优先处理优先权(Priority)比较高的中断请求,
也就是说 CPU 对于各种中断请求的处理是有优先级的,
原则上 timer2 的中断请求比 timer1 的中断请求优先,
而 timer1 的中断请求又比 timer0 的中断请求优先,
还有, 计数器 Overflow 的中断请求又比计数器 CTC 的中断请求优先。
详细请看 ATmega328 datasheet:
http://www.atmel.com/Images/doc8161.pdf
或看这:
http://gammon.com.au/interrupts#reply0
如果你想使用外部中断, 可以参看这:
http://www.bristolwatch.com/arduino/arduino_irq.htm
以及看看奈何大神写的这篇有趣文章:
http://www.arduino.cn/thread-2421-1-1.html
A: Prescaler 是一个整数,
用来把原先给 CPU 的频率(Clock)除频之后给 timer 使用;
各个 timer 的 Prescaler 各自可以设定不同的整数:
timer0 可以设 1, 8, 64, 256, 1024
timer1 可以设 1, 8, 64, 256, 1024
timer2 可以设 1, 8, 32, 64, 128, 256,1024
请看这 datasheet:
http://www.atmel.com/Images/doc8161.pdf
(a)timer1 see P.137 Table 15-5
(b)timer2 see p.162 Table 17-9
©timer0 see p.110 Table 14-9
** 我故意把 timer0 写在第三项,
因为通常我们不能乱改timer0的 Prescaler !
Arduino开机后设 timer0 的 Prescaler 为64,
这 timer0 控制 millis( ), micros( ), 以及 delay( ),
在 16MHz 的clock下, 使得 timer0 Overflow 时间为 1.024 ms(每数256次),
所以每隔1.024ms执行一次中断程序 SIGNAL(TIMER0_OVF_vect);
如果你改了Prescaler则这三个 function 都会变不准确 !
(不过 delayMicroseconds( )不受影响, 因它不是靠中断处理!)
More Reference:
http://letsmakerobots.com/node/28278
你可以参考这篇里面的讨论:
http://forum.arduino.cc/index.php?topic=102430.0
只能帮你到这啰
有人来信问说设定了 0.1ms 产生一次中断,
并在中断程序内只有做:
v = analogRead( );
++n;
tot += v;
其中 v, n, tot 都是全局变量(Global variable), 都是 volatile,
然后发现似乎时间很不准确 ?!
问说为什么 ?
这个是当然会有问题了 !
因为根据官网说明,
每次 analogRead( ) 大约要 100us = 0.1ms,
我自己实际测则每秒只能 analogRead( ) 大约 8920次, 每次超过 0.11ms
这样你把中断设 0.1ms 来一次,
那不是摆明了一定来不及 !?
不过, 如果你要加快 analogRead( ), 请看以下我写的这篇:
如何加快analogRead速度提高采样率Sampling Rate?
http://www.arduino.cn/thread-12569-1-2.html
void stopT1( ) {
TIMSK1 &= ( ~ (1 << OCIE1A) ); // 禁止 CTC for chanel A
}
void resumeT1( ) {
TIMSK1 |= (1 << OCIE1A); // enable CTC for TIMER1_COMPA_vect
}
这是您在这篇帖子下写的关闭停止CTC中断和恢复CTC中断的句子。
但我在使用时发现这两句并没有起到作用,我本意是想使得pin9(与timer1相关联的引脚)停止输出方波(设置了CTC模式输出一定频率的方波)。
结果我把这个stopT1()函数放到我语句中,发现连接PIN9的示波器并没有变化,仍然在输出方波。
是否是我错误地理解了您的CTC中断和恢复CTC中断的意思。
在您要把OCIE1A位取0时,这样写不知道是否可以呢 ?TIMSK1 = ~ (1 << OCIE1A)
我看您在前面还加了个位取0,这是必要的吗?
因为按照一些人赋1的习惯直接就用
TIMSK1 = _BV(OCIE1A)
直接就把OCIE1A位赋1了,
#define _BV(bit) (1 << (bit))
还麻烦您不吝赐教。
答:
TIMSK1 = ~ (1 << OCIE1A)这个操作的结果会把OCIE1A置0,但也会把剩下的其他7位都置1。
楼主的 TIMSK1 &= ( ~ (1 << OCIE1A) )是把OCIE1A位置0,其他7位保持原样。