定时器中断

上一节我们介绍了 ESP32 的外部中断的使用,本节课介绍 ESP32 的定时器功能。

实验原理

定时器,顾名思义就是用来计时的,我们常常会设置计时或闹钟,然后时间到了就告诉我们要做什么。ESP32 也是这样,通过定时器可以完成各种预设好的任务。ESP32 定时器到达指定时间后也会产生中断,然后在回调函数内执行所需功能,这个和外部中断类似。

ESP32 内置 4 个 64-bit 通用定时器。每个定时器包含一个 16-bit 预分频器和一个 64-bit 可自动重新加载向上/向下计数器。

使用计时器的好处,是实现类似并行处理的功能,也就是一个应用里只能有一个 while True,两个以上都是不可以的,但是如果使用定时器,就可以同时运行多个 while True

硬件电路设计

物料清单(BOM 表):

材料名称 数量
直插式 LED 2
1kΩ 电阻 2
杜邦线(跳线) 若干
面包板 1

LED 的正极接开发板的 D2、D4 引脚,并串联一个电阻,负极接 GND,如下图:

注意

一定要接电阻,不然会由于电流过大,烧坏 LED。

软件程序设计

1. 硬件定时器

在 ESP32 Arduino 开发环境中,可以使用以下几个库函数来配置和操作硬件定时器(Timer):

  1. void timerBegin(timer_num_t timer_num, uint32_t divider, bool count_up):初始化硬件定时器,参数说明:

  2. timer_num:定时器编号,可选值为 0-3 等。

  3. divider:定时器的分频系数,用于设置定时器的时钟频率。较大的分频系数将降低定时器的时钟频率。可以根据需要选择合适的值,一般设置为 80 即可;
  4. count_up:指定定时器是否为向上计数模式。设置为 true 表示向上计数,设置为 false 表示向下计数。

  5. timerAttachInterrupt(hw_timer_t *timer, void (*isr)(void *), void *arg, int intr_type):用于将中断处理函数与特定的定时器关联起来,参数含义如下:

  6. timer;定时器指针;

  7. isr: 中断处理函数。
  8. arg: 传递给中断处理函数的参数。
  9. intr_type: 中断类型,可选值为 ture(边沿触发)或 false(电平触发)。

  10. timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload):用于设置定时器的计数值,即定时器触发的时间间隔,参数含义如下:

  11. timer:定时器指针;

  12. alarm_value: 定时器的计数值,即触发时间间隔;
  13. autoreload: 是否自动重载计数值,可选值为 true(自动重载)或 false(单次触发)。

  14. timerAlarmEnable(hw_timer_t *timer):用于启动定时器,使其开始计数;

  15. timerAlarmDisable(hw_timer_t *timer):用于禁用定时器,停止计数;
  16. timerGetAutoReload(hw_timer_t *timer):获取定时器是否自动重新加载;
  17. timerAlarmRead(hw_timer_t *timer):获取定时器计数器报警值;
  18. timerStart(hw_timer_t *timer):计数器开始计数;
  19. timerStop(hw_timer_t *timer):计数器停止计数;
  20. timerRestart(hw_timer_t *timer):计数器重新开始计数,从 0 开始;
  21. timerStarted(hw_timer_t *timer):计数器是否开始计数。

以上是一些常用的硬件定时器相关的库函数,你可以根据自己的需求和定时器的特性,调用适当的函数来配置和操作硬件定时器。请参考 ESP32 的官方文档和相关库的文档,以获取更详细的信息。

使用硬件定时器的基本步骤如下:

  1. 初始化定时器:使用 timerBegin() 函数初始化所需的硬件定时器;
  2. 注册中断处理函数:使用 timerAttachInterrupt() 函数将中断处理函数与定时器关联起来;
  3. 设置定时器模式:使用 timerAlarmWrite(),设置触发一次,还是周期性触发;
  4. 启动定时器:使用 timerAlarmEnable() 函数启动定时器,使其开始计数。

因此,我们的代码可以这么写:

#define LED   2
#define LED_ONCE  4

hw_timer_t *timer = NULL;
hw_timer_t *timer_once=NULL;


// 定时器中断处理函数
void timer_interrupt(){
  digitalWrite(LED, !digitalRead(LED));
}

void timer_once_interrupt() {
  digitalWrite(LED_ONCE, !digitalRead(LED_ONCE));
}

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(LED_ONCE, OUTPUT);

  // 初始化定时器
  timer = timerBegin(0,80,true);
  timer_once = timerBegin(1, 80, true);

  // 配置定时器
  timerAttachInterrupt(timer,timer_interrupt,true);
  timerAttachInterrupt(timer_once, timer_once_interrupt, true);

  // 定时模式,单位us,只触发一次
  timerAlarmWrite(timer,1000000,true); 
  timerAlarmWrite(timer_once, 3000000, false);
  // 启动定时器
  timerAlarmEnable(timer); 
  timerAlarmEnable(timer_once);

}

void loop() {
}

2. 软件定时器

使用软件计时器的时候,我们需要用到 ESP32 内置的库 TickerTicker 是 ESP32 Arduino 内置的一个定时器库,这个库用于规定时间后调用函数。

接着我们来看看 Ticker 库的一些方法

  • detach():停止 Ticker;
  • active():Ticker 是否激活状态,True 表示启用;
  • once(n, callback,arg):n 秒后只执行一次 callback 函数,arg 表示回调函数的参数(不写表示没有);
  • once_ms(n, callback,arg):n 毫秒后只执行一次 callback 函数,arg 表示回调函数的参数(不写表示没有);
  • attach(n, callback, arg):每隔 n 秒周期性执行;
  • attach_ms(n, callback, arg):每隔 n 毫秒周期性执行;

::: warning

不建议使用 Ticker 回调函数来阻塞 IO 操作(网络、串口、文件);可以在 Ticker 回调函数中设置一个标记,在 loop 函数中检测这个标记;

:::


#include <Ticker.h>

#define LED 4
#define LED_ONCE 2


// 定义定时器对象
Ticker timer;
Ticker timer_once;


// 定义定时器中断回调函数
void toggle(int pin) {
  digitalWrite(pin, !digitalRead(pin));
}


void setup() {
  pinMode(LED, OUTPUT);
  pinMode(LED_ONCE, OUTPUT);

  // 配置周期性定时器
  timer.attach(0.5, toggle, LED);
  // 配置一次性定时器
  timer_once.once(3, toggle, LED_ONCE);
}

void loop() {

}