文章目录
- 1. 使用TimerOne库定时做多件事
- 特别强调:
- 相关资料
- 如何使用硬件中断的 TimerOne (Timer1) 库,
- 疑难解答
- 2. 控制 timer1 定时器定时做多件事
- 3. TimerOne函数解析
如何使用Timer2库定时做多件事
1. 使用TimerOne库定时做多件事
使用TimerOne库(Timer1)定时做多件事
https://www.arduino.cn/thread-12441-1-1.html
(出处: Arduino中文社区)
特别强调:
请注意, 如果你使用了 Timer1 库 (TimerOne 库),
- 则 pin 9 和 pin 10 就不能再用做 PWM 输出了!
因为该 pin 9 和 pin 10 的 PWM 是靠 timer1 帮忙的! - 注意 Servo.h 库也是使用内部定时器 timer1,
所以, 用了 TimerOne 也不能再用 Servo.h 库了!
相关资料
- 不使用 Timer 库, 只用自带的 millis( ) 检查时间以便定时做某事或定时做两三件事:
http://www.arduino.cn/thread-12408-1-2.html - 如何每间隔几次才做某事: (避免太快做事)
http://www.arduino.cn/thread-12405-1-2.html - 如何使用 MsTimer2 库定时做很多事:
http://www.arduino.cn/thread-12435-1-1.html
如何使用硬件中断的 TimerOne (Timer1) 库,
MsTimer2 使用内部 timer2 定时器, 而 TimerOne 库当然是使用 timer1 定时器才会这样称呼 !
定时器 timer1 是个 16位的定时器, 而 timer2 和 timer0 都是8位的!
关于 TimerOne 库的相关资料在官方网站:
http://playground.arduino.cc/Code/Timer1
因为 TimerOne 库放在 Google.com, TimerOne Google Code下载,
GitHub上有效维护的TimerOne版本:https://github.com/PaulStoffregen/TimerOne
先来看看第一个从官方范例稍微改一下的简单范例:
// 使用 TimerOne Library -- by tsaiwn@ydu.edu.tw
// pin 13 LED 每秒闪烁 5 次持续6秒, 然后每秒闪烁 2.5 次持续6秒, ..
// 然后每秒闪烁 1 次持续5秒;灭灯 3 秒, 亮灯 5 秒; 然后 LOOP !
#include <TimerOne.h>
const long interval = 100 * 1000; // 100000 microseconds = 100 ms = 0.1 second
// (0.1 sec- or 10Hz => the led will blink 5 times per second;
// i.e., 5 cycles of on-and-off, per second; 每秒闪烁 5 次 )
/// Custom ISR Timer Routine
void ggyy( )
{
// Toggle LED
digitalWrite( 13, digitalRead( 13 ) ^ 1 ); // toggle 0, 1
}
void setup()
{
pinMode(13, OUTPUT); // Pin 13 has an LED
Timer1.initialize( interval ); // 初始化, interval 以 micro sec 为单位
Timer1.attachInterrupt( ggyy ); // attach the service routine here
}
void loop()
{
// 以下是故意用 delay( )
delay(6000);
Timer1.setPeriod( 2 * interval );
delay(6000);
Timer1.setPeriod( 5 * interval );
delay(5000);
Timer1.detachInterrupt(); // 停止闪烁
// Timer1.stop(); // 也可停用 Timer1
digitalWrite( 13, 0); // 灭灯 3 秒
delay(3000);
digitalWrite( 13, 1); // 亮灯 5 秒
delay(5000);
// Timer1.restart( ); // 如用 .stop( ); 可用这重新启动计时
Timer1.attachInterrupt( ggyy, interval); // 恢复每秒闪烁亮灭 5 次 (10Hz)
}
// Reference -- http://playground.arduino.cc/Code/Timer1
如同用 MsTimer2 库只能设定一件事, 使用 TimerOne 库也是只能设定一件要定时做的事, 后面的 attachInterrupt( ) 会盖掉前面的 attachInterrupt( );
后面的 setPeriod( ) 会盖掉前面的定时区间, 不论前次是用 setPeriod( ) 还是在 attachInterrupt( ) 顺便设定的 !
那如果要定时做两件事呢 ?
跟我们在讨论用 MsTimer2 库做多件事定时相似, 就是把原先定时做的事改为只负责计时并判定是否要分派小弟去做其他事即可!
好吧, 再把前次用 MsTimer2 库定时做两件事的范例拿来改,
以下也是要定时做两件事的范例, 但用 timer1 控制:
(A)每 250 ms 做一次 myJobOne : 闪烁 LED on pin 13
(B)每 250 ms 做一次 myJobTwo : 闪烁 LED on pin 8
请注意, 以下我故意设定每隔 0.5 ms 中断一次(即500micro seconds),
不是每隔 1 ms, 也不是每隔 0.1 ms! 虽然用 TimerOne 中断间隔可以用 micro second 为单位,
但是如果设定中断间隔太小有可能会因中断来得太快来不及处理定时的工作! !
还有, 以下在定时做的 ggyy( ) 内使用的变量 gy 已经改为全局变量(Global variable),
这样我们才能在 void loop( ) 内必要时把它归 0 重新计时 ! !
// 使用 TimerOne Library 定时做两件事 -- by tsaiwn@ydu.edu.tw
#include <TimerOne.h>
const int intA = 250*2; // *2 是因为 interval 为 0.5 个千分之一秒(0.5ms)
const int intB = 250*2; // 每 250 ms 做一次 myJobTwo
const int led2 = 8; // pin 8
const long interval = 500; // 500 microseconds (0.5 milli sec)
/// Custom ISR Timer Routine
static unsigned int gy = 0; // 这 gy 现在改为全局变量(Global variable)
void ggyy( ) { // 现在每 0.5 ms 会到这一次 !
++gy; // 注意: 这 gy 是全局变量(Global variable)
if( gy % intA == 0) myJobOne( ); // gy 除以 intA 的余数是 0
if( gy % intB == 0) myJobTwo( );
}
void setup() {
pinMode(13, OUTPUT); // Pin 13 has an LED
pinMode(led2, OUTPUT);
Timer1.initialize( interval ); // 初始化, interval 以 micro sec 为单位
Timer1.attachInterrupt( ggyy ); // attach the service routine here
}
void loop() { // Main code loop
// put your other code 其他代码放这, 以下是故意用 delay( )
delay(6123); // 故意
/// Timer1.detachInterrupt(); // 停止处理中断 (目前是注释掉)
Timer1.stop(); // 停用 Timer1
digitalWrite( 13, 0);
delay(3388);
digitalWrite( 13, HIGH); // HIGH 就是 1
gy = 0; // 注意, 故意重新计算, 这 gy 是全局变量(Global variable)
Timer1.restart( ); // 如用 .stop( ); 可用这重新启动计时
}
void myJobOne( ) {
// Toggle LED
digitalWrite( 13, digitalRead( 13 ) ^ 1 ); // toggle 0, 1
}
void myJobTwo( ) {
static int gy = 1; // 故意使得第一次会灭灯
gy = 1- gy; // toggle 0, 1
digitalWrite(led2, gy); // pin 8 LED
}
// Reference -- http://playground.arduino.cc/Code/Timer1
// 请注意, 如果你使用了 Timer1 库 (TimerOne 库),
// 则 pin 9 和 pin 10 就不能再用做 PWM 输出了!
// 因为该 pin 9 和 pin 10 的 PWM 是靠 timer1 帮忙的!
// 注意 Servo.h 库也是使用内部定时器 timer1,
// 所以, 用了 TimerOne 也不能再用 Servo.h 库了!
疑难解答
1:Q: 这范例显然每0.25秒都 “先” 做 myJobOne, 然后再做 myJobTwo, 并没有 “同时” 做啊?
A: 阿不然还能怎样 ? 上次讨论用 MsTimer2 就已经说过了 !
Arduino 的 CPU 只有一个, 又不是多核心(multi core), 怎可能真的"同时"做呢 ?
不过 Arduino 在 16MHz 频率之下每个指令大约0.7到 3 micro second, 如果做了二十个指令也才大约 0.05 ms (milli second), 进入 ISR( )与离开 ISR( )总计大约要 3 micro seconds,
每个 C 语言的指令大约要花 0.7 到 3 micro seconds, 就是说两个工作前后差不到0.1个千分之一秒(0.1 ms), 感觉还是 “同时” 做啦 !
如果你认为应该优先处理 myJobTwo, 那就把该两句检查 gy 的 if 前后对调即可 !
2. : 例中 intA 和 intB 可不可以设不一样呢?
A: 当然可以啊 !
你可以把 intB 改为 500 或 1000 自己测试看看 !
3.: 那可否一件事用 TimerOne, 另一件事用 MsTimer2 来定时做 ?
A: 当然可以 !
不过这样, 其实仍然不可能真的 “同时做” 两件事 ! 如果这时 timer1 的中断与 timer2 的中断真的同时发生, CPU 会优先处理 timer2 的中断, 这时 timer1 的中断进入排队状态, 要等把 timer2 中断的事情做好才会处理 timer1 的中断! !
关于中断(interrupt)的详细说明可以参考:
地址:http://gammon.com.au/interrupts
4: 那如果要设定为定时做三件事呢 ?
ㄟ … 阿这个看完上面例子你应该就会了啊 !
只要多用个类似 intA 与 intB 的 intC 就可以仿照写出了!
还是不会的就请看我前面那篇讨论用 MsTimer2 定时做多件事:
http://www.arduino.cn/thread-12435-1-1.html
5: 那可不可以把内部 timer0 定时器也拿来定时做事 ?
A: 当然可以 !
不过, 因为 millis( ) 和 delay( ) 也都靠 timer0 帮忙, 如果你改变了 timer0 的 Prescaler, 会使得 millis( ) 和 delay( )都不准确 !
还有, 要注意的是: 由于 timer0负责帮忙 pin 5 和 pin 6 的 PWM 输出以及帮忙 millis( ) 与 delay( ) 的计时工作,
所以 pin 5 和 pin 6 的 PWM 输出有时会有些微的误差(大约 1 到 8), 尤其输出很小值时误差比率会很大 !
例如 analogWrite(6, 0); 与 analogWrite(6, 6); 结果很可能相同 ! !
问答
Q: 如果 timer0, timer1, 和 timer2 的中断真的都同时发生会怎样?
根据 datasheet 描述, timer2 中断优先于 timer1, timer1又优先于 timer0,
所以会先处理 timer2 的中断, 然后处理 timer1 的中断, 最后才处理 timer0 的中断 !
6: 真的不能在中断程序内做 Serial.print( ) 和 Serial.println( ) 吗 ?
可是我在我的中断处理程序中用了 Serial.print( ) 好像也 OK 啊 ?
A: 那你运气不错 ,
其实, 应该是第一,你的中断来得不是非常快( 0.1 秒一次都算是相当慢的),
第二,你在中断处理程序中 Serial.print( ) 的数据并不多 !
说明:
由于 Serial.print( ) 和 Serial.println( ) 实际上是把数据搬到RAM内存中的输出缓存区(Output Buffer), 然后等待 I/O 中断来把数据搬走,
该缓存区只有 64 Bytes, 输出的数据太多有机会满溢出(必要时你可以找到源码偷改大一点! ) (P.S. 输入的缓存区也是预设(默认)为 64 Bytes, 如果数据来得太快也可找到源码偷改大一点! )
请注意
当在中断处理程序中所有的中断是被禁止的 (Arduino 共有25种中断), 所以该缓存区必须等到你的中断处理程序做完, 中断又被允许之后, 才可能被处理,
然而 I/O 相对 CPU 运算工作来讲是比较慢的工作, 如果你的中断来得太快, 快到来不及处理 I/O 的中断, 那就会出问题 !
在中断中使用print会出甚么问题呢 ?
- 有可能输出的部分数据不见了(还没真的送出就被盖掉),
- 甚至有可能使得 Arduino 的 CPU 当机(死机) !
7: 还有哪些要注意或限制的呢?
A: 主要是 Servo.h 库和 pin 9 与 pin 10也都是使用 timer1 定时器帮忙,
其他也是请看我前面那篇讨论用 MsTimer2 定时做多件事:
http://www.arduino.cn/thread-12435-1-1.html
更多关于中断(interrupt)的详细说明可以参考:
http://gammon.com.au/interrupts
http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/
http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/
http://www.avrbeginners.net/architecture/timers/timers.html
http://playground.arduino.cc/Code/Timer1
还有以下这三篇也很有用:
http://sphinx.mythic-beasts.com/~markt/ATmega-timers.html
http://maxembedded.com/2011/07/avr-timers-ctc-mode/
http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM
======================================
2. 控制 timer1 定时器定时做多件事
控制 timer1 定时器定时做多件事(教程, 设定timer1 定时器)
https://www.arduino.cn/thread-12445-1-1.html
(出处: Arduino中文社区)
5.1 相关资料
前面已经跟大家分享了使用硬件中断 MsTimer2库与 TimerOne 库定时做多件事:
http://www.arduino.cn/thread-12435-1-1.html
http://www.arduino.cn/thread-12441-1-1.html
现在再来分享自己控制 timer1 定时器, 每 0.1 ms (0.0001 秒)中断一次, 当然也是可以用来定时做多件事 !
请先测试以下范例再说 …
以下是对 Pin 13 上的 LED 做亮灭, 每秒闪烁一次: 亮0.5秒灭0.5秒 !
// 用 timer1 的中断 ISR( TIMER1_COMPA_vect ) 控制 LED 亮灭
// Prescaler(预分频器) 用 1024
volatile int ggyy = 1; // 使用这当 Flag 给 ISR 使用 !
int ledPin =13;
// For Prescaler == 1024
// 1 秒 / (16 000 000 / 1024) = 1/15625 = 0.000064 sec / per cycle
// 0.5 sec / 0.000064 sec -1 = 7812.5 -1 = 7812
// const int myTOP = 15624; // 大约 1 秒 when Prescaler == 1024
// 1.0 sec / 0.000064 sec -1 = 15625 -1 = 15624
const int myTOP = 7812; // 大约 0.5 秒 when Prescaler == 1024
//
/// For Prescaler == 64
/// 1 秒 / (16 000 000 / 64) = 1/250000 = 0.000004 sec
/// 0.1 sec / 0.000004 sec -1 = 25000 -1 = 24999
/// const int myTOP = 24999; // 0.1 sec when Prescaler == 64
/ Interrupt Service Routine for TIMER1 CTC on OCR1A as TOP
/// 注意以下名称是有意义的, 不可乱改 !
ISR(TIMER1_COMPA_vect)
{
digitalWrite(ledPin, ggyy); // ggyy 是 0 或 1
ggyy = 1 - ggyy; // 给下次进入 ISR 用
}
void setup( ) {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW); // turn Off the LED
cli(); // 禁止中断
TCCR1A = 0;
TCCR1B = 0;
TCCR1B |= (1<<WGM12); // CTC mode; Clear Timer on Compare
/// CS12, CS11, and CS10 这三个 bit 都是 1 表示使用 External clock
// ? TCCR1B |= (1<<CS11); // External clock ???
// ? External clock source on T1 pin. Clock on rising edge ?
// CS12 与 CS10 都是 1 表示 Prescaler 为 1024
// See http://www.engblaze.com/microcon ... -interrupts/#config
TCCR1B |= (1 << CS10) | (1 << CS12); // Prescaler == 1024
/// 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 loop() {
//... 做其他事
// if( ggyy == 1) ...
}
参考:
http://blog.oscarliang.net/arduino-timer-and-interrupt-tutorial/
关于 prescaler 请看这:
http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/#config
再补充一下
Prescaler 是用来除以 CPU 所用频率的频率以控制 timer 定时器,TCCR1B 的 CS12, CS11, and CS10 这三个 bit 控制 timer1 的 Prescaler,三个都是 1 表示使用 External clock, CS12 与 CS10 都是 1 表示 Prescaler 为 1024,这样每 count 一次用掉 1/(16*10^6 / 1024) == 6.4e-5 seconds, 所以 OCR1A= 7812; 则 7812 * 6.4e-5 seconds = 0.5 秒
,用 OCR1A=15624 则是 15624 * 6.4e-5 seconds = 1 秒
For more detail, see:
- http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/#config
- http://sphinx.mythic-beasts.com/~markt/ATmega-timers.html
- http://maxembedded.com/2011/07/avr-timers-ctc-mode/
- http://roboexperiments.com/beginners-guide-avr-atmega1632-timers-interrupts-part2/
程序看不太懂 ?
正常啦, 很多人都看不懂,看不懂照样可以用啦 ! 接下来看新版本,
这次 Prescaler 改设 64, 每 0.1 秒做一次中断处理 ISR(TIMER1_COMPA_vect)
// 控制 LED 亮灭, 每秒闪烁 5 次: 亮 0.1 秒灭 0.1 秒 ...
// Prescaler 用 64
volatile int ggyy = 1; // 使用这当 Flag 给 ISR 使用 !
int ledPin =13;
/// 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
const int myTOP = 24999; // 0.1 sec when Prescaler == 64
/ Interrupt Service Routine for TIMER1 CTC on OCR1A as TOP
/// 注意以下名称是有意义的, 不可乱改 !
ISR(TIMER1_COMPA_vect)
{
digitalWrite(ledPin, ggyy); // ggyy 是 0 或 1
ggyy = 1 - ggyy; // 给下次进入 ISR 用
}
void setup( ) {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW); // turn Off the LED
cli(); // 禁止中断
TCCR1A = 0;
TCCR1B = 0;
TCCR1B |= (1<<WGM12); // CTC mode; Clear Timer on Compare
/// CS12, CS11, and CS10 这三个 bit 都是 1 表示使用 External clock
// ? TCCR1B |= (1<<CS11); // External clock ???
// ? External clock source on T1 pin. Clock on rising edge ?
// CS12 与 CS10 都是 1 表示 Prescaler 为 1024
// See http://www.engblaze.com/microcon ... -interrupts/#config
// TCCR1B |= (1<<CS10) | (1<<CS12); // Prescaler == 1024
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 loop() {
//... 做其他事
// if( ggyy == 1) ...
}
看完这范例应该有点概念了吧 ?!
再看第三个版本, 这次只改 OCR1A 用的 myTOP, 但Prescaler仍用64,
因为 myTOP 改为 24, 每隔 0.0001 秒会做一次 ISR( )
// 控制 LED 亮灭, 每秒闪烁 5000 次: 亮 0.0001 秒灭 0.0001 秒 ...
// Prescaler 用 64
volatile int ggyy = 1; // 使用这当 Flag 给 ISR 使用 !
int ledPin =13;
/// 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
/// 注意以下名称是有意义的, 不可乱改 !
ISR(TIMER1_COMPA_vect)
{
digitalWrite(ledPin, ggyy); // ggyy 是 0 或 1
ggyy = 1 - ggyy; // 给下次进入 ISR 用
}
void setup( ) {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW); // turn Off the LED
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 loop() {
//... 做其他事
// if( ggyy == 1) ...
}
啥? 根本不会闪烁 ! ?
对啦, 每秒闪烁 5000 次,
Super Man 超人的眼睛也看不见啊 !
现在每 0.1 ms (0.0001 秒) 会做 ISR( ) 一次,
我们可以使用一个 int 或 long 变量计数, 达到某数值就做某事情 ! !
以下这第四个范例每 0.25秒闪烁 pin 13 的 LED,
且每 0.5 秒闪烁 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
/// 注意以下名称是有意义的, 不可乱改 !
ISR(TIMER1_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
setMyTimerOne( );
}
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 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(); // 允许中断
}
好啦, 有了上面这精准度 0.1 ms 做中断并定时做两件事myJobOne( ) 和 myJobTwo( )的范例,
应该很容易修改为定时做三件或更多事!
这次我故意用不一样的写法做计数与检查是否时间到要做myJob???()的时机 !
你可以对照之前使用 MsTimer2 库的写法:
http://www.arduino.cn/thread-12435-1-1.html
以及用 TimerOne 库的写法:
http://www.arduino.cn/thread-12441-1-1.html
请注意, 在前两篇中我们说过因为 Servo.h 库以及 pin 9 和 pin 10 的 PWM 都是使用 timer1 定时器,
所以, 自己控制 timer1 定时器之后, Servo.h 库就不能用了, 还有 pin 9 和 pin 10 的 PWM 也没有用了 ! !
更多关于中断(interrupt)的详细说明可以参考:
http://gammon.com.au/interrupts
http://maxembedded.com/2011/07/avr-timers-ctc-mode/
http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/
http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts
http://www.avrbeginners.net/architecture/timers/timers.html
http://playground.arduino.cc/Code/Timer1
还有以下这三篇也很有用:
http://sphinx.mythic-beasts.com/~markt/ATmega-timers.html
http://maxembedded.com/2011/07/avr-timers-ctc-mode/
http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM
3. TimerOne函数解析
关于Timer1
这个库是用来配置ATmega168/328中的16位硬件计时器。在芯片上有三个硬件计时器可用,通过不同方式的配置可以实现不同的功能。这个库的开发初衷是用来寻找一种方式快速并且简单地设置PWM周期或者频率,但是现在已经拥有了计时器溢出中断和一些其他的功能。它可以简单地与其他的计时器共同工作。
这个计时器的精度取决于你的芯片速度和频率。计时器1的时钟速度通过分频器或者因子来进行设定。这个分频率可以设定的值:1,8,64,256或者1024。
对于16MHz:
分频 | 每一次计数的时间 | 最大周期 |
---|---|---|
1 | 0.0625uS | 8.192mS |
8 | 0.5uS | 65.536mS |
64 | 4uS | 524.288mS |
256 | 16uS | 2097.152mS |
1024 | 64uS | 8388.608mS |
通常情况下:
最大周期=(分频)(1/频率)(217)
每次计数的时间=(分频)*(1/频率)
Timer3
特别注意:Timer1可以用在Mega上,但是无法支持OCR1A,OCR1B&OCR1C这三路输出引脚,它只支持A&B。OCR1A连接在Mega的11引脚,OCR1B连接在Mega的12引脚上。为了能够使用这三个引脚中的一个,我们需要通过一个指定引脚匹配。1引脚将匹配到11引脚上,2引脚将匹配到12引脚上(这些都是对于Mega)。目前Timer3仅仅用来在Mega上进行测试。
函数详解:
初始化函数:
Timer1.initialize(microseconds);
必须先调用这个函数,因为我们需要先指定计时器的频率(微妙),默认情况下是设定为1秒。
特别注意:在Arduino官网上写得是Note that this breaks analogWrite() for digital pins 9 and 10 on Arduino.
Timer1.setPeriod(microseconds);
在库被初始化后设定一个新的周期。这个库的最小周期(最高频率)支持1microsecond(1Mhz),最大周期为8388480微妙(8.3秒)。
特别注意:这个周期的设定将会改变中断和两个PWM输出的平率与duty cycles simultaneously(应该怎么翻译?)。
运行控制函数:
- 打开计时器(中断),从一个新的周期开始。
Timer1.start();
- 关闭计时器(中断)。
Timer1.stop();
- 重启计时器(中断),从一个新的周期开始。
Timer1.restart();
- 恢复运行被停止的计时器(中断),不会开始一个新的周期。
Timer1.resume();
PWM信号输出函数:
- 在指定的pin口产生一个PWM波形。
Timer1.pwm(pin,duty,period);
对于Timer1的输出pin口分别为PROTB pins 1和2,我们只能用这两个口,如果我们用其他的口将会被忽视。在Arduino上,他们分别是数字输出引脚9和10,所以用这两个参数也是可以正常运行的。对于Timer3的输出引脚是PORTE和对应Arduino Mega的2,3&5。占空比是指定为一个相当于10位的值(1024),也就是0到1023之间。
特别注意:
period是一个可选择的参数,如果我们输入了这个参数,那么函数将会以我们给出的参数(微妙)作为周期。
- 设定一个新的PWM,但是不会重新配置这个引脚。这个函数相对于pwm()要快一些,但是必须在pwm()被调用后才能使用。
Timer1.setPwmDuty(pin,duty);
- 停止使用引脚的PWM功能。之后这个引脚将会被释放,可以通过digitalWrite()进行控制。
Timer1.disablePwm(pin);
中断函数:
- 打开中断,每当计时器的周期停止时调用括号中的中断函数。
Timer1.attachInterrupt(function);
特别注意:
当你在一个高频率中使用一个复杂的中断函数时,CPU有可能不在进入主循环,此时你的程序将会“死机”,在设定中断周期时,要进行优化处理。
- 关闭中断函数。
Timer1.detachInterrrupt();