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 的工作模式可以通过 TCCR0ATCCR0B 寄存器来设置。定时器0 的计数器寄存器是 TCNT0,比较寄存器是 OCR0AOCR0B

    工作模式

    1. 普通模式:计数器从0计数到255,然后重新从0开始。

    2. CTC模式:计数器从0计数到 OCR0A 的值,然后清零。

    3. 快速 PWM 模式:计数器从0计数到255,然后重新从0开始。输出信号在计数器达到 OCR0AOCR0B 时改变状态。

    4. 相位正确 PWM 模式:计数器从0计数到255,然后从255计数回0。输出信号在计数器达到 OCR0AOCR0B 时改变状态。

    示例代码:使用定时器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; // 重置计数器
    
    }
    
    

    代码解释

    1. 设置定时器0为CTC模式:通过设置 TCCR0B 寄存器的 WGM02 位。

    2. 设置预分频器:通过设置 TCCR0B 寄存器的 CS02CS00 位,选择1024的预分频器。

    3. 设置比较值:通过设置 OCR0A 寄存器的值,使定时器在达到该值时产生中断。

    4. 启用定时器0的比较中断:通过设置 TIMSK0 寄存器的 OCIE0A 位。

    5. 初始化中断标志:通过设置 TCNT0 寄存器的值,使其从0开始计数。

    6. 设置LED引脚:通过设置 DDRB 寄存器的 DDB5 位,使引脚5为输出。

    7. 允许全局中断:通过调用 sei() 函数,允许全局中断。

    8. 定时器0比较中断处理函数:在 ISR(TIMER0_COMPA_vect) 中,切换引脚5的状态,并重置计数器。

    定时器1

    基本原理

    定时器1 是一个16位定时器,它可以提供更高的计数精度和更大的计数范围。定时器1 的工作模式可以通过 TCCR1ATCCR1B 寄存器来设置。定时器1 的计数器寄存器是 TCNT1,比较寄存器是 OCR1AOCR1B

    工作模式

    1. 普通模式:计数器从0计数到65535,然后重新从0开始。

    2. CTC模式:计数器从0计数到 OCR1A 的值,然后清零。

    3. 快速 PWM 模式:计数器从0计数到65535,然后重新从0开始。输出信号在计数器达到 OCR1AOCR1B 时改变状态。

    4. 相位正确 PWM 模式:计数器从0计数到65535,然后从65535计数回0。输出信号在计数器达到 OCR1AOCR1B 时改变状态。

    示例代码:使用定时器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. 设置定时器1为CTC模式:通过设置 TCCR1B 寄存器的 WGM12 位。

    2. 设置预分频器:通过设置 TCCR1B 寄存器的 CS12CS10 位,选择1024的预分频器。

    3. 设置比较值:通过设置 OCR1A 寄存器的值,使定时器在达到该值时产生中断。

    4. 启用定时器1的比较中断:通过设置 TIMSK1 寄存器的 OCIE1A 位。

    5. 初始化中断标志:通过设置 TCNT1 寄存器的值,使其从0开始计数。

    6. 设置LED引脚:通过设置 DDRB 寄存器的 DDB1 位,使引脚1为输出。

    7. 允许全局中断:通过调用 sei() 函数,允许全局中断。

    8. 定时器1比较中断处理函数:在 ISR(TIMER1_COMPA_vect) 中,切换引脚1的状态,并重置计数器。

    定时器2

    基本原理

    定时器2 是一个8位定时器,类似于定时器0,但它可以提供更多的中断源。定时器2 的工作模式可以通过 TCCR2ATCCR2B 寄存器来设置。定时器2 的计数器寄存器是 TCNT2,比较寄存器是 OCR2AOCR2B

    工作模式

    1. 普通模式:计数器从0计数到255,然后重新从0开始。

    2. CTC模式:计数器从0计数到 OCR2A 的值,然后清零。

    3. 快速 PWM 模式:计数器从0计数到255,然后重新从0开始。输出信号在计数器达到 OCR2AOCR2B 时改变状态。

    4. 相位正确 PWM 模式:计数器从0计数到255,然后从255计数回0。输出信号在计数器达到 OCR2AOCR2B 时改变状态。

    示例代码:使用定时器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; // 重置计数器
    
    }
    
    

    代码解释

    1. 设置定时器2为CTC模式:通过设置 TCCR2B 寄存器的 WGM22 位。

    2. 设置预分频器:通过设置 TCCR2B 寄存器的 CS22CS20 位,选择1024的预分频器。

    3. 设置比较值:通过设置 OCR2A 寄存器的值,使定时器在达到该值时产生中断。

    4. 启用定时器2的比较中断:通过设置 TIMSK2 寄存器的 OCIE2A 位。

    5. 初始化中断标志:通过设置 TCNT2 寄存器的值,使其从0开始计数。

    6. 设置LED引脚:通过设置 DDRD 寄存器的 DDD6 位,使引脚6为输出。

    7. 允许全局中断:通过调用 sei() 函数,允许全局中断。

    8. 定时器2比较中断处理函数:在 ISR(TIMER2_COMPA_vect) 中,切换引脚6的状态,并重置计数器。

    中断概述

    中断是微控制器中用于处理外部或内部事件的一种机制。当某个事件发生时,微控制器会暂停当前的程序执行,转去执行中断服务例程(ISR),处理完中断后再返回到被中断的地方继续执行。Arduino Nano 系列(基于 ATmega328P)支持多种中断源,包括定时器中断、外部中断、ADC中断等。

    中断源

    1. 定时器中断:包括定时器0、1、2的比较中断和溢出中断。

    2. 外部中断:包括 INT0 和 INT1,可以通过外部引脚触发。

    3. ADC中断:当ADC转换完成时触发。

    4. 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的状态
    
    }
    
    

    代码解释

    1. 设置引脚2为外部中断输入:通过设置 EICRA 寄存器的 ISC00ISC01 位,使引脚2在上升沿时触发中断。

    2. 启用INT0中断:通过设置 EIMSK 寄存器的 INT0 位。

    3. 设置LED引脚:通过设置 DDRB 寄存器的 DDB5 位,使引脚5为输出。

    4. 允许全局中断:通过调用 sei() 函数,允许全局中断。

    5. 外部中断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. 设置定时器1为CTC模式:通过设置 TCCR1B 寄存器的 WGM12 位,使定时器1工作在CTC模式。CTC模式下,定时器会在计数器达到 OCR1A 的值时产生中断。

    2. 设置预分频器:通过设置 TCCR1B 寄存器的 CS12CS10 位,选择1024的预分频器。预分频器的选择决定了定时器的计数速度。

    3. 设置比较值:通过设置 OCR1A 寄存器的值为15624,使定时器在1秒后产生中断。计算公式为:16000000 / 1024 / 1 - 1 = 15624

    4. 启用定时器1的比较中断:通过设置 TIMSK1 寄存器的 OCIE1A 位,启用定时器1的比较中断。

    5. 初始化中断标志:通过设置 TCNT1 寄存器的值为0,使其从0开始计数。

    6. 设置引脚2为外部中断输入:通过设置 EICRA 寄存器的 ISC00ISC01 位,使引脚2在上升沿时触发中断。

    7. 启用INT0中断:通过设置 EIMSK 寄存器的 INT0 位,启用INT0中断。

    8. 设置LED引脚:通过设置 DDRB 寄存器的 DDB5 位,使引脚5为输出。

    9. 允许全局中断:通过调用 sei() 函数,允许全局中断。

    10. 定时器1比较中断处理函数:在 ISR(TIMER1_COMPA_vect) 中,切换引脚5的状态,并重置计数器。

    11. 外部中断0的ISR:在 ISR(INT0_vect) 中,当引脚2检测到上升沿时,切换引脚5的状态。

    运行结果

  • 定时器1:每1秒切换一次引脚5上的LED状态,实现1秒的闪烁。

  • 外部中断引脚2:当引脚2检测到上升沿时,立即切换引脚5上的LED状态。

  • 通过这个综合示例,你可以看到如何同时使用定时器和外部中断来控制LED的状态。定时器用于产生周期性的事件,而外部中断则用于响应外部信号的变化。这种组合在许多实际应用中非常有用,例如定时采样和外部触发控制等。

    扩展应用

    1. 定时数据采集:使用定时器定期触发ADC转换,采集传感器数据。

    2. 外部触发控制:使用外部中断引脚响应用户输入(如按钮),执行特定的操作。

    3. 多任务调度:结合多个定时器和中断,实现多任务的并行处理。

    通过合理配置和使用定时器和中断,可以极大地提高Arduino项目的性能和灵活性。希望这个示例能帮助你更好地理解和应用这些功能。

    作者:kkchenkx

    物联沃分享整理
    物联沃-IOTWORD物联网 » Arduino 系列:Arduino Nano 系列 (基于 ATmega328P)_(8).ArduinoNano定时器和中断

    发表回复