AVR开发 Arduino方法(三) 定时/计数器子系统

  Arduino UNO R3的主处理器ATMega328P拥有3个定时/计数器,它们分别是Timer0Timer1Timer2;它们都通过对来自内部或外部的脉冲信号进行计数的方式完成基本的定时/计数功能以及一些其他的功能。

  Timer0Timer28位定时/计时器,Timer116位定时/计数器;下面以Timer2为例讨论定时/计数器子系统的典型应用,这些内容同样适用于Timer0Timer1

1. 精准延时

  在前面的例子中,已经使用了一些与精准延时相关的Arduino库函数:

 

  delay(ms):延迟一段时间

  ms:延迟的时长,单位是毫秒

 

  请注意,上面的Arduino库函数使用了Timer0的中断,因此不要在任何中断服务程序中调用它,否则程序可能无法正常工作。

  如果需要在延迟的同时,Arduino可以执行其他程序,这需要借助第三方库MsTimer2;你可以在http://playground.arduino.cc/Main/MsTimer2下载到它,把下载到的MsTimer2.zip解压到Arduino IDE安装目录下的libraries文件夹里就可以使用了,它包含了一个示例:

 1 // FlashLed.ino
 2 #include <MsTimer2.h>
 3 
 4 void flash() {
 5   static boolean output = HIGH;
 6   
 7   digitalWrite(13, output);
 8   output = !output;
 9 }
10 
11 void setup() {
12   pinMode(13, OUTPUT);
13 
14   MsTimer2::set(500, flash);
15   MsTimer2::start();
16 }
17 
18 void loop() {
19 }

   这个示例的效果与Blink示例基本相同,MsTimer2库提供了3个库函数:

 

  MsTimer2::set(interval, function):每隔一段时间执行指定函数

  interval:间隔时长,单位是毫秒

  function:指定执行函数的名称

  MsTimer2::start():开始每隔一段时间执行指定函数

  MsTimer2::stop():结束每隔一段时间执行指定函数

 

  Timer22个控制寄存器:TCCR2ATCCR2B,它们的结构如下图所示:

COM2A1

COM2A0

COM2B1

COM2B0

 

 

WGM21

WGM20

 

FOC2A

FOC2B

 

 

WGM22

CS22

CS21

CS20

其中WGM2[2:0]位用于设置模式,CS2[2:0]位用于设置时钟源,如下表所示:

WGM2[2:0]

模式

计数上限

 

CS2[2:0]

时钟源

000

正常

0xFF

 

000

001

相位校正脉宽调制

0xFF

 

001

系统时钟

010

比较匹配时清零

OCR2A

 

010

系统时钟8分频

011

快速脉宽调制

0xFF

 

011

系统时钟32分频

100

(保留)

 

 

100

系统时钟64分频

101

相位校正脉宽调制

OCR2A

 

101

系统时钟128分频

110

(保留)

 

 

110

系统时钟256分频

111

快速脉宽调制

OCR2A

 

111

系统时钟1024分频

 其余位暂且设置为0

  精准延时可以采用正常模式。正常模式下,Timer2不断从0x00计数到0xFF;当定时计数器寄存器TCNT2每一次返回0x00时,若Timer2中断屏蔽寄存器TIMSK2的溢出中断使能位TOIE21,则产生中断,它的结构如下图所示:

 

 

 

 

 

OCIE0B

OCIE0A

TOIE0

   通过直接访问寄存器改写以上程序为:

 1 // FlashLed_reg.ino
 2 void flash() {
 3   PORTB ^= (1 << PB5);
 4 }
 5 void setup() {
 6   DDRB |= (1 << PB5);
 7 
 8   // 正常模式,系统时钟256分频,计数初值为6
 9   TCCR2A = 0x00;
10   TCCR2B = 0x06;
11   TCNT2 = 0x06;
12   
13   TIMSK2 |= (1 << TOIE2);
14   sei();
15 }
16 
17 void loop() {
18 }
19 
20 ISR(TIMER2_OVF_vect) {
21   static volatile int iTimes = 0;
22 
23   // 中断125次为500毫秒
24   if (++iTimes == 125) {
25     flash();
26     iTimes = 0;
27   }
28   // 重新赋初值6
29   TCNT2 = 0x06;
30 }

  程序中的中断125次为500毫秒是这样计算出来的:Arduino UNO R3开发板使用16MHz的系统时钟,Timer2使用系统时钟256分频,每记256-6=250个脉冲溢出一次,则每秒溢出16000000÷256÷250=250次,因此每溢出125次为500毫秒。

2. 脉宽调制

  脉宽调制的一个典型应用是控制直流电机速度。将直流电机两极分别连接到直流电源的正负两极上,电机会以最快速度运行;要调整电机速度,一个很容易想到的方法是调整直流电源的功率,但在数字系统中还有一个更简单的方式:使用高低电平宽度不一样的脉冲信号快速开关直流电机;因为惯性的作用,电机不会以最快的速度运行。一般来说,高电平在一个脉冲周期中所占宽度更宽时,直流电机速度越快;我们把高电平所占一个脉冲周期的宽度称为占空比。

  如图所示连接电路,11PB3/OC2A)引脚通过三极管间接控制直流电机:

  用Arduino库函数输出一个脉宽调制信号十分简单,下面的示例使得直流电机由慢到快,又由快到慢反复运行:

 1 // FadingMotor.ino
 2 int motor = 11;
 3 int speed = 0;
 4 int fadeAmount = 5;
 5 
 6 void setup() {
 7   pinMode(motor, OUTPUT);
 8 }
 9 
10 void loop() {
11   analogWrite(motor, speed);
12 
13   speed = speed + fadeAmount;
14   if (speed <= 0 || speed >= 255) {
15     fadeAmount = -fadeAmount;
16   }
17 
18   delay(30);
19 }

  与脉宽调制相关的Arduino库函数有:

 

  analogWrite(pin, value):在指定引脚上输出一个指定占空比脉宽调制信号

  pin:指定引脚

  value:脉宽调制信号的占空比;00%255100%

 

  Timer2拥有2输出比较寄存器OCR2AOCR2B,它们通过与TCNT2寄存器发生比较匹配时对引脚置位、清零或取反来完成脉宽调制信号的输出。TCCR2A寄存器中的COM2A[1:0]位用于设置OCR2A寄存器发生比较匹配时的行为,如下表所示:

COM2A[1:0]

行为

00

正常的端口操作,OC2A未连接

01

WGM22=0,正常的端口操作,OC2A未连接

WGM22=1,发生比较匹配时OC2A取反

10

发生比较匹配时OC2A清零,计数到下限时OC2A置位

11

发生比较匹配时OC2A置位,计数到下限时OC2A清零

  通过直接访问寄存器改写以上程序为:

 1 // FadingMotor_reg.ino
 2 int speed = 0;
 3 int fadeAmount = 5;
 4 
 5 void setup() {
 6   DDRB |= (1 << PB3);
 7 
 8   // 快速脉宽调制,OC2A比较匹配置位,下限清零,系统时钟
 9   TCCR2A = 0x83;
10   TCCR2B = 0x01;
11 }
12 
13 void loop() {
14   OCR2A = speed;
15 
16   speed = speed + fadeAmount;
17   if (speed <= 0 || speed >= 255) {
18     fadeAmount = -fadeAmount;
19   }
20 
21   delay(30);
22 }

3. 输入捕获*

  输入捕获用来计算外部输入信号的周期,它是Timer1的特有功能,Arduino官网提供了一个输入捕获的示例:

 1 // PulseIn.ino
 2 const int pin = 8;
 3 unsigned long duration;
 4 
 5 void setup() {
 6   pinMode(pin, INPUT);
 7 }
 8 
 9 void loop() {
10   duration = pulseIn(pin, HIGH);
11 }

  与输入捕获相关的Arduino库函数有:

 

  pulseIn(pin, value):计算指定引脚输入外部信号的周期

  pin:指定引脚

  value:捕获脉冲的类型,LOW(低电平,0V)或HIGH(高电平,5V

  函数返回外部输入信号的周期,是一个unsigned long类型的整数,单位为微秒

 

  Timer1的控制寄存器与Timer2的不尽相同,请参阅ATMega328P芯片手册16-bit Timer/Counter1 with PWM章的Register Description小节进行设置。通过直接访问寄存器改写以上程序为:

 1 // PulseIn_reg.ino
 2 unsigned long duration;
 3 unsigned long timer1_pulse_in();
 4 
 5 void setup() {
 6   DDRB &= ~(1 << PB0);
 7   PORTB &= ~(1 << PB0);
 8 
 9   // 正常模式,系统时钟8分频
10   // 噪声抑制关闭,下降沿触发输入捕获
11   TCCR1A = 0x00;
12   TCCR1B = 0x42;
13   TCCR1C = 0x00;
14   TIMSK1 |= (1 << ICIE1) | (1 << TOIE1);
15   sei();
16 }
17 
18 void loop() {
19   duration = timer1_pulse_in();
20 }
21 
22 volatile int iOvf = 0;
23 volatile bool isCap = false;
24 volatile uint16_t iCap = 0;
25 volatile uint16_t iLastCap = 0;
26 
27 unsigned long timer1_pulse_in() {
28   unsigned long duration;
29 
30   while (!isCap);
31   duration = 0xffff - iCap 
32       + 0xffff * (iOvf - 1) 
33       + iLastCap;
34 
35   isCap = false;
36   return duration;
37 }
38 
39 ISR(TIMER1_CAP_vect) {
40   iCap = iLastCap;
41   iLastCap = (ICR1H << 8) | ICR1L;
42   
43   isCap = true;
44 }
45 
46 ISR(TIMER1_OVF_vect) {
47   iOvf += 1;
48 }

  事实上,本章仅仅讨论了定时/计数器子系统的典型应用,这仅仅是定时/计数器子系统强大功能的冰山一角,由于篇幅关系没有提及的部分,另请参阅ATMega328P芯片手册。 

posted @ 2017-09-14 10:20  Lets_Blu  阅读(6866)  评论(0编辑  收藏  举报