Arduino与Proteus仿真实例-计时器延时仿真

计时器延时仿真

1、Arduino计时器介绍

1)什么是计时器

作为 Arduino 程序员,您肯定在不知情的情况下使用过定时器和中断。 那是因为所有底层硬件的东西都被已经封装好的 Arduino 函数隐藏了。 许多 Arduino 函数都使用定时器,例如时间函数:delay、millis、micros delayMicroseconds。 他们都在后台使用 Arduino 计时器。 其他功能如 PWM 模拟写入也使用定时器。 音调和 noTone 功能甚至伺服电机控制库也是如此。 那么,这个计时器是什么?

计时器是 Arduino 控制器中内置的一块硬件,根据型号的不同,它可以有不同数量的计时器。 例如,Arduino UNO 有 3 个定时器,Timer0、Tmer1 和 Timer2。 这就像一个时钟,可以用来测量时间事件。 定时器可以由一些特殊寄存器编程,就像编程时钟一样。

2)预分频器

可以把定时器想象成一个计数脉冲的计数器。 这个定时器由系统时钟提供,对于 Arduino UNO 来说,它可以是 16Mhz 的晶体时钟,或者根据引导加载程序也可以是一个 8MHz 的内部振荡器。 在定时器计数器和系统时钟之间,有另一个称为预标量的硬件。 这个预标量将来自系统时钟的脉冲数除以自定义的数字,可能是 8、64、256 等等,稍后将看到如何定义这个预标量分频数。

现在假设主系统时钟是 16Mhz 晶体,它将产生一个 16Mhz 方波信号。 每个脉冲为 62.5ns,对吗? 你把它提供给预标量,可以说设置为 8。因此,来自预标量的输出信号将慢 8 倍,即 500ns。 所以每8个来自系统时钟的脉冲,我们的定时器就会增加1。可以使用相关寄存器为每个定时器配置预分频器。

可以用这些定时器做什么?Arduino的定时器 0 和 2 是 8 位,这意味着它可以从 0 计数到 255;定时器 1 是 16 位,因此它可以从 0 计数到 65536。一旦定时器达到最大值,它将返回到 0 或根据所选模式开始减少。 所以基本上它会创建一个锯齿形曲线。 定时器从 0 开始增加它的值。当达到 255 时,如果是 8 位寄存器,我们回到 0 并再次增加。 这将创建一个三角形曲线,因此可以使用它来创建我们的中断。

3)计时器的中断服务

每个定时器可以产生一个或多个中断。 一种类型的中断是比较匹配。 我可以在不同的寄存器中写入一个值,当定时器值等于比较值时,它会触发中断。 例如,我们将比较匹配寄存器设置为 100,每次定时器 0 到达 100 时,都会产生中断。 另一种中断类型用于溢出。 在这种情况下,每次定时器溢出时都会触发中断,这意味着它从最大值返回到 0,在 8 位定时器的情况下,每次达到 255 时都会发生这种情况。 最后,我们有输入捕获中断, 对于 Arduino UNO,可以在计时器 1 上实现。在这种情况下,计时器可以将其值存储在不同的寄存器中,每次在 Arduino 引脚之一上发生外部事件时。

4)设置预分频器

设置预分频器时,需要更改的第一个寄存器是预标量。 为了控制定时器,Arduino有两个主要寄存器,TCCRA 和 TCCRB,每个寄存器都有定时器的编号。 所以对于定时器 1,有 TCCR1A 和 TCCR1B。 TCCRA 寄存器用于控制 PWM 模式,因此对于定时器 1,可以控制与引脚 9 和 10 上的 PWM 信号相关的 OC1A。在使用中断时,不需要这个寄存器,但需要设置它的所有位 为 0,因为 Arduino 默认将它们设置为 1。因此,将所有这些设置为 0 将禁用引脚 9 和 10 上的 PWM 信号。

接着,需要更改 TCCRB 寄存器。 在这里,我们只关心用于定义 prescaar 值的前 3 位。 如下表所示,使用 CS10、CS11 和 CS12 位,可以禁用预分频器或将其设置为 1,除以 8、64、256、1024 甚至使用外部时钟源。 对于定时器 0 和 2,必须使用 TCCR0B 和 TCCR2B 以及位 CS00、CS01、CS02 和位 CS20、CS21 和 CS22。

在这里插入图片描述

TCCRB 寄存器

在这里插入图片描述

设置示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
void setup() {  
TCCR1A = 0; // 重置整个TCCR1A寄存器
TCCR1B = 0; // 重置整个TCCR1B寄存器
TCCR1A |= B00000100; // 设置 CS12为1,得到预分频值= 256
TCNT1 = 0; // 设置Timer1的什为0
}

void loop() {
//your code here...
}

5)设置中断(比较匹配)

通过设置 TIMSK 寄存器可以激活比较匹配中断。如下表所示:

在这里插入图片描述

2、仿真电路原理图

在这里插入图片描述

在系统时钟为16MHz、预分频器为256时,创建500ms延时的定时器1的值计算公式如下:

1
2
3
4
Timer 1 speed = 16Mhz/256 = 62.5 Khz 
Pulse time = 1/62.5 Khz = 16us
Count up to = 500ms / 16us = 31250

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool LED_STATE = true;
int ledPin = 7;
void setup() {
pinMode(ledPin, OUTPUT); // 设置引脚为输出模式
cli(); // 停止中断直到完成设置
/\*第一步:重置控制寄存器以确保我们从禁用所有内容开始。\*/
TCCR1A = 0; //重置整个TCCR1A寄存器
TCCR1B = 0; //重置整个TCCR1B寄存器

/\*第二步:通过更改 CS10、CS12和CS12位将预标量设置为所需值. \*/
TCCR1B |= B00000100; // 设置 CS12为1,得到预分频值= 256

/\*第三步:我们在寄存器OCIE1A上启用比较匹配模式\*/
TIMSK1 |= B00000010; // 将OCIE1A设置为 1,启用比较匹配寄存器A

/\*第四步:将寄存器A的值设置为31250\*/
OCR1A = 31250; // 最后将比较寄存器A设置为这个值
sei(); // 启用中断
}

void loop() {
// put your main code here, to run repeatedly:
}

// 计时器中断服务函数
ISR(TIMER1_COMPA_vect){
TCNT1 = 0; // 将计时器设置回 0,以便为下一次中断重置
LED_STATE = !LED_STATE;
digitalWrite(ledPin,LED_STATE);
}

上面代码实现了每 500 毫秒中断一次。 每次触发中断,我们就去相关的ISR向量。 由于Arduino UNO有 3 个计时器,因此有 6 个 ISR 向量,每个计时器两个,它们具有以下名称:TIMER1_COMPA_vect

  • TIMER1_COMPB_vect
  • TIMER2_COMPA_vect
  • TIMER2_COMPB_vect
  • TIMER3_COMPA_vect
  • TIMER4_COMPB_vect

4、仿真结果

在这里插入图片描述

文章来源: https://iotsmart.blog.csdn.net/article/details/120842019