Arduino 系列:Arduino Nano 系列 (基于 ATmega328P)_(8).ArduinoNano定时器和中断
Arduino Nano 定时器和中断
定时器概述
定时器是微控制器(MCU)中非常重要的一个外设,它用于生成精确的时间延迟、测量时间间隔以及产生周期性的事件。Arduino Nano 系列(基于 ATmega328P)配备了多个定时器,每个定时器都有不同的功能和用途。这些定时器可以用于各种应用场景,例如:
生成 PWM 信号
测量脉冲宽度
生成定时中断
精确延时
ATmega328P 具有三个定时器,分别是:
定时器0:8位定时器/计数器
定时器1:16位定时器/计数器
定时器2:8位定时器/计数器
每个定时器都有多个工作模式,包括普通模式、CTC(清除定时器比较)模式、快速 PWM 模式和相位正确 PWM 模式。这些模式的选择取决于具体的应用需求。
定时器0
基本原理
定时器0 是一个8位定时器,它可以通过预分频器来控制计数速度。定时器0 的工作模式可以通过 TCCR0A
和 TCCR0B
寄存器来设置。定时器0 的计数器寄存器是 TCNT0
,比较寄存器是 OCR0A
和 OCR0B
。
工作模式
-
普通模式:计数器从0计数到255,然后重新从0开始。
-
CTC模式:计数器从0计数到
OCR0A
的值,然后清零。 -
快速 PWM 模式:计数器从0计数到255,然后重新从0开始。输出信号在计数器达到
OCR0A
或OCR0B
时改变状态。 -
相位正确 PWM 模式:计数器从0计数到255,然后从255计数回0。输出信号在计数器达到
OCR0A
或OCR0B
时改变状态。
示例代码:使用定时器0生成1秒延时
// 使用定时器0生成1秒延时
#define F_CPU 16000000UL // 定义CPU频率为16MHz
#include <avr/io.h>
#include <util/delay.h>
void setup() {
// 设置定时器0为CTC模式
TCCR0A = 0; // 清零寄存器A
TCCR0B = 0; // 清零寄存器B
TCCR0B |= (1 << WGM02); // 设置为CTC模式
TCCR0B |= (1 << CS02) | (1 << CS00); // 设置预分频器为1024
// 设置比较值
OCR0A = 244; // 设置比较值为244 (16000000 / 1024 / 256 - 1 = 244)
// 启用定时器0的比较中断
TIMSK0 |= (1 << OCIE0A);
// 初始化中断标志
TCNT0 = 0;
// 设置LED引脚
DDRB |= (1 << DDB5); // 设置引脚5为输出
// 允许全局中断
sei();
}
void loop() {
// 主循环中不执行任何操作
}
// 定时器0比较中断处理函数
ISR(TIMER0_COMPA_vect) {
// 切换LED状态
PORTB ^= (1 << PB5); // 切换引脚5的状态
TCNT0 = 0; // 重置计数器
}
代码解释
-
设置定时器0为CTC模式:通过设置
TCCR0B
寄存器的WGM02
位。 -
设置预分频器:通过设置
TCCR0B
寄存器的CS02
和CS00
位,选择1024的预分频器。 -
设置比较值:通过设置
OCR0A
寄存器的值,使定时器在达到该值时产生中断。 -
启用定时器0的比较中断:通过设置
TIMSK0
寄存器的OCIE0A
位。 -
初始化中断标志:通过设置
TCNT0
寄存器的值,使其从0开始计数。 -
设置LED引脚:通过设置
DDRB
寄存器的DDB5
位,使引脚5为输出。 -
允许全局中断:通过调用
sei()
函数,允许全局中断。 -
定时器0比较中断处理函数:在
ISR(TIMER0_COMPA_vect)
中,切换引脚5的状态,并重置计数器。
定时器1
基本原理
定时器1 是一个16位定时器,它可以提供更高的计数精度和更大的计数范围。定时器1 的工作模式可以通过 TCCR1A
和 TCCR1B
寄存器来设置。定时器1 的计数器寄存器是 TCNT1
,比较寄存器是 OCR1A
和 OCR1B
。
工作模式
-
普通模式:计数器从0计数到65535,然后重新从0开始。
-
CTC模式:计数器从0计数到
OCR1A
的值,然后清零。 -
快速 PWM 模式:计数器从0计数到65535,然后重新从0开始。输出信号在计数器达到
OCR1A
或OCR1B
时改变状态。 -
相位正确 PWM 模式:计数器从0计数到65535,然后从65535计数回0。输出信号在计数器达到
OCR1A
或OCR1B
时改变状态。
示例代码:使用定时器1生成1秒延时
// 使用定时器1生成1秒延时
#define F_CPU 16000000UL // 定义CPU频率为16MHz
#include <avr/io.h>
#include <util/delay.h>
void setup() {
// 设置定时器1为CTC模式
TCCR1A = 0; // 清零寄存器A
TCCR1B = 0; // 清零寄存器B
TCCR1B |= (1 << WGM12); // 设置为CTC模式
TCCR1B |= (1 << CS12) | (1 << CS10); // 设置预分频器为1024
// 设置比较值
OCR1A = 15624; // 设置比较值为15624 (16000000 / 1024 / 1 - 1 = 15624)
// 启用定时器1的比较中断
TIMSK1 |= (1 << OCIE1A);
// 初始化中断标志
TCNT1 = 0;
// 设置LED引脚
DDRB |= (1 << DDB1); // 设置引脚1为输出
// 允许全局中断
sei();
}
void loop() {
// 主循环中不执行任何操作
}
// 定时器1比较中断处理函数
ISR(TIMER1_COMPA_vect) {
// 切换LED状态
PORTB ^= (1 << PB1); // 切换引脚1的状态
TCNT1 = 0; // 重置计数器
}
代码解释
-
设置定时器1为CTC模式:通过设置
TCCR1B
寄存器的WGM12
位。 -
设置预分频器:通过设置
TCCR1B
寄存器的CS12
和CS10
位,选择1024的预分频器。 -
设置比较值:通过设置
OCR1A
寄存器的值,使定时器在达到该值时产生中断。 -
启用定时器1的比较中断:通过设置
TIMSK1
寄存器的OCIE1A
位。 -
初始化中断标志:通过设置
TCNT1
寄存器的值,使其从0开始计数。 -
设置LED引脚:通过设置
DDRB
寄存器的DDB1
位,使引脚1为输出。 -
允许全局中断:通过调用
sei()
函数,允许全局中断。 -
定时器1比较中断处理函数:在
ISR(TIMER1_COMPA_vect)
中,切换引脚1的状态,并重置计数器。
定时器2
基本原理
定时器2 是一个8位定时器,类似于定时器0,但它可以提供更多的中断源。定时器2 的工作模式可以通过 TCCR2A
和 TCCR2B
寄存器来设置。定时器2 的计数器寄存器是 TCNT2
,比较寄存器是 OCR2A
和 OCR2B
。
工作模式
-
普通模式:计数器从0计数到255,然后重新从0开始。
-
CTC模式:计数器从0计数到
OCR2A
的值,然后清零。 -
快速 PWM 模式:计数器从0计数到255,然后重新从0开始。输出信号在计数器达到
OCR2A
或OCR2B
时改变状态。 -
相位正确 PWM 模式:计数器从0计数到255,然后从255计数回0。输出信号在计数器达到
OCR2A
或OCR2B
时改变状态。
示例代码:使用定时器2生成1秒延时
// 使用定时器2生成1秒延时
#define F_CPU 16000000UL // 定义CPU频率为16MHz
#include <avr/io.h>
#include <util/delay.h>
void setup() {
// 设置定时器2为CTC模式
TCCR2A = 0; // 清零寄存器A
TCCR2B = 0; // 清零寄存器B
TCCR2B |= (1 << WGM22); // 设置为CTC模式
TCCR2B |= (1 << CS22) | (1 << CS20); // 设置预分频器为1024
// 设置比较值
OCR2A = 244; // 设置比较值为244 (16000000 / 1024 / 256 - 1 = 244)
// 启用定时器2的比较中断
TIMSK2 |= (1 << OCIE2A);
// 初始化中断标志
TCNT2 = 0;
// 设置LED引脚
DDRD |= (1 << DDD6); // 设置引脚6为输出
// 允许全局中断
sei();
}
void loop() {
// 主循环中不执行任何操作
}
// 定时器2比较中断处理函数
ISR(TIMER2_COMPA_vect) {
// 切换LED状态
PORTD ^= (1 << PD6); // 切换引脚6的状态
TCNT2 = 0; // 重置计数器
}
代码解释
-
设置定时器2为CTC模式:通过设置
TCCR2B
寄存器的WGM22
位。 -
设置预分频器:通过设置
TCCR2B
寄存器的CS22
和CS20
位,选择1024的预分频器。 -
设置比较值:通过设置
OCR2A
寄存器的值,使定时器在达到该值时产生中断。 -
启用定时器2的比较中断:通过设置
TIMSK2
寄存器的OCIE2A
位。 -
初始化中断标志:通过设置
TCNT2
寄存器的值,使其从0开始计数。 -
设置LED引脚:通过设置
DDRD
寄存器的DDD6
位,使引脚6为输出。 -
允许全局中断:通过调用
sei()
函数,允许全局中断。 -
定时器2比较中断处理函数:在
ISR(TIMER2_COMPA_vect)
中,切换引脚6的状态,并重置计数器。
中断概述
中断是微控制器中用于处理外部或内部事件的一种机制。当某个事件发生时,微控制器会暂停当前的程序执行,转去执行中断服务例程(ISR),处理完中断后再返回到被中断的地方继续执行。Arduino Nano 系列(基于 ATmega328P)支持多种中断源,包括定时器中断、外部中断、ADC中断等。
中断源
-
定时器中断:包括定时器0、1、2的比较中断和溢出中断。
-
外部中断:包括 INT0 和 INT1,可以通过外部引脚触发。
-
ADC中断:当ADC转换完成时触发。
-
USART中断:当串行通信数据接收或发送完成时触发。
中断服务例程
中断服务例程(ISR)是中断发生时执行的代码块。在Arduino中,ISR可以通过 ISR()
宏来定义。例如,ISR(TIMER0_COMPA_vect)
是定时器0比较中断的ISR。
示例代码:外部中断
基本原理
外部中断可以通过设置 EICRA
寄存器来配置。Arduino Nano 提供了两个外部中断引脚:INT0
(引脚2)和 INT1
(引脚3)。这些引脚可以配置为上升沿、下降沿或任意电平变化触发中断。
示例代码:使用外部中断引脚2控制LED
void setup() {
// 设置引脚2为外部中断输入
EICRA = (1 << ISC00) | (1 << ISC01); // 上升沿触发
EIMSK = (1 << INT0); // 启用INT0中断
// 设置LED引脚
DDRB |= (1 << DDB5); // 设置引脚5为输出
// 允许全局中断
sei();
}
void loop() {
// 主循环中不执行任何操作
}
// 外部中断0的ISR
ISR(INT0_vect) {
// 切换LED状态
PORTB ^= (1 << PB5); // 切换引脚5的状态
}
代码解释
-
设置引脚2为外部中断输入:通过设置
EICRA
寄存器的ISC00
和ISC01
位,使引脚2在上升沿时触发中断。 -
启用INT0中断:通过设置
EIMSK
寄存器的INT0
位。 -
设置LED引脚:通过设置
DDRB
寄存器的DDB5
位,使引脚5为输出。 -
允许全局中断:通过调用
sei()
函数,允许全局中断。 -
外部中断0的ISR:在
ISR(INT0_vect)
中,切换引脚5的状态。
综合示例:使用定时器和外部中断控制LED
基本原理
在这个综合示例中,我们将使用定时器1生成1秒延时,同时使用外部中断引脚2来控制LED的状态。当外部中断引脚2检测到上升沿时,LED的状态将被切换。
示例代码
// 使用定时器1生成1秒延时,同时使用外部中断引脚2控制LED
#define F_CPU 16000000UL // 定义CPU频率为16MHz
#include <avr/io.h>
#include <util/delay.h>
// 定义LED引脚
#define LED_PIN PB5
void setup() {
// 设置定时器1为CTC模式
TCCR1A = 0; // 清零寄存器A
TCCR1B = 0; // 清零寄存器B
TCCR1B |= (1 << WGM12); // 设置为CTC模式
TCCR1B |= (1 << CS12) | (1 << CS10); // 设置预分频器为1024
// 设置比较值
OCR1A = 15624; // 设置比较值为15624 (16000000 / 1024 / 1 - 1 = 15624)
// 启用定时器1的比较中断
TIMSK1 |= (1 << OCIE1A);
// 设置引脚2为外部中断输入
EICRA = (1 << ISC00) | (1 << ISC01); // 上升沿触发
EIMSK = (1 << INT0); // 启用INT0中断
// 设置LED引脚
DDRB |= (1 << DDB5); // 设置引脚5为输出
// 允许全局中断
sei();
}
void loop() {
// 主循环中不执行任何操作
}
// 定时器1比较中断处理函数
ISR(TIMER1_COMPA_vect) {
// 切换LED状态
PORTB ^= (1 << LED_PIN); // 切换引脚5的状态
TCNT1 = 0; // 重置计数器
}
// 外部中断0的ISR
ISR(INT0_vect) {
// 切换LED状态
PORTB ^= (1 << LED_PIN); // 切换引脚5的状态
}
代码解释
-
设置定时器1为CTC模式:通过设置
TCCR1B
寄存器的WGM12
位,使定时器1工作在CTC模式。CTC模式下,定时器会在计数器达到OCR1A
的值时产生中断。 -
设置预分频器:通过设置
TCCR1B
寄存器的CS12
和CS10
位,选择1024的预分频器。预分频器的选择决定了定时器的计数速度。 -
设置比较值:通过设置
OCR1A
寄存器的值为15624,使定时器在1秒后产生中断。计算公式为:16000000 / 1024 / 1 - 1 = 15624
。 -
启用定时器1的比较中断:通过设置
TIMSK1
寄存器的OCIE1A
位,启用定时器1的比较中断。 -
初始化中断标志:通过设置
TCNT1
寄存器的值为0,使其从0开始计数。 -
设置引脚2为外部中断输入:通过设置
EICRA
寄存器的ISC00
和ISC01
位,使引脚2在上升沿时触发中断。 -
启用INT0中断:通过设置
EIMSK
寄存器的INT0
位,启用INT0中断。 -
设置LED引脚:通过设置
DDRB
寄存器的DDB5
位,使引脚5为输出。 -
允许全局中断:通过调用
sei()
函数,允许全局中断。 -
定时器1比较中断处理函数:在
ISR(TIMER1_COMPA_vect)
中,切换引脚5的状态,并重置计数器。 -
外部中断0的ISR:在
ISR(INT0_vect)
中,当引脚2检测到上升沿时,切换引脚5的状态。
运行结果
定时器1:每1秒切换一次引脚5上的LED状态,实现1秒的闪烁。
外部中断引脚2:当引脚2检测到上升沿时,立即切换引脚5上的LED状态。
通过这个综合示例,你可以看到如何同时使用定时器和外部中断来控制LED的状态。定时器用于产生周期性的事件,而外部中断则用于响应外部信号的变化。这种组合在许多实际应用中非常有用,例如定时采样和外部触发控制等。
扩展应用
-
定时数据采集:使用定时器定期触发ADC转换,采集传感器数据。
-
外部触发控制:使用外部中断引脚响应用户输入(如按钮),执行特定的操作。
-
多任务调度:结合多个定时器和中断,实现多任务的并行处理。
通过合理配置和使用定时器和中断,可以极大地提高Arduino项目的性能和灵活性。希望这个示例能帮助你更好地理解和应用这些功能。
作者:kkchenkx