代码基于Arduino核心库的教程:ESP32内核在ESP32上配置计时器中断

本文的目的是解释如何使用 Arduino 内核在 ESP32 上配置定时器中断。

测试是在 DFRobot 的 ESP-WROOM-32 设备上进行的,该设备集成在 ESP32 FireBeetle 板上。

介绍

本文主要讲解如何在 ESP32 平台上使用 Arduino 内核配置定时器中断。以下代码基于 Arduino 核心库中的这个示例,我强烈建议您好好看看这个示例(… mer/RepeatTimer.ino )。

本教程将向您展示如何配置定时器以定期产生中断什么是定时器中断函数,以及如何处理中断。

关于外部中断的教程:ESP32 Arduino Tutorial: External Interrupts 这种计数器的使用已经见过,因为 ISR 应该尽可能快地运行,所以它不应该执行长时间的操作(比如向串口写入数据) )。因此,在实现中断处理代码时,最好让ISR只响应中断,然后将实际处理(可能涉及较长的操作)留给主循环。

  1. ]portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
复制代码

设置功能

在 setup 函数中,首先需要打开一个串口,以便将程序结果输出到 Arduino IDE 串口监视器。

  1. Serial.begin(115200);
复制代码

然后,调用该函数来初始化定时器,该函数返回一个指向 hw_timer_t 结构类型的指针,它是我们在上一节中声明的定时器全局变量之一。

该函数接受三个输入参数,分别是我们要使用的定时器编号(0到3,对应所有4个硬件定时器),预分频器值,以及一个指示计数器是向上(true)还是向下(false)的值)。) 计数标志。

在此示例中,我们将使用第一个计时器,并将最后一个参数设置为 true(表示计数器向上计数)。

关于预分频器,我们在介绍中说过,ESP32 计数器使用的基带信号通常为 80 MHz(仅适用于 FireBeetle 板)。这个数字等于 80 000 000 Hz,这意味着基频信号将每秒增加定时器计数器 80 000 000 次。

虽然我们可以根据这个值进行计算,然后设置计数器值来产生中断,但我们将使用预分频器来简化设置。如果我们将此值除以 80(即使用 80 作为预分频器值),我们得到 1 MHz 的频率,这意味着定时器计数器将每秒递增 1,000,000 次。

取上述值的倒数表明计数器将每微秒递增一次。所以,如果预分频器的值为80,那么当我们调用函数设置计数器值来产生中断时,我们指定的值是以微秒为单位的。

  1. timer = timerBegin(0, 80, true);
复制代码

但是在启用定时器之前,我们还需要将它绑定到一个处理函数,以响应产生的中断。这个操作可以通过调用来完成。

这个函数有三个参数,一个指向初始化定时器的指针(存储在我们的全局变量中),中断处理程序的地址,以及一个指示中断触发类型是边沿(true)还是电平(false)的值。标识。边缘和电平触发中断之间的区别可以在这里找到:.

调用函数时,我们将timer全局变量作为第一个入参,一个名为onTimer的函数的地址(后面介绍)作为第二个参数,第三个参数赋值为true(表示中断是边缘触发类型))。

  1. timerAttachInterrupt(timer, &onTimer, true);
复制代码

接下来,使用触发定时器中断的指定计数器值。该函数的第一个参数是定时器指针,第二个参数是触发中断的计数器值,第三个参数是一个标志位,表示在产生中断时是否重新加载定时器。

所以,在调用函数的时候,我们也将timer全局变量作为第一个入参传入,第三个参数设置为true(意思是计数器会自动重新加载),这样就可以周期性的产生中断了。

关于第二个参数,别忘了我们之前对预分频器的设置,应该是几微秒后产生中断。所以,对于这个例子,如果我们想每秒产生一个中断,那么这个值就是 1 000 000 微秒(相当于 1 秒)。

重要提示:注意这里指定的时间单位只有在预分频器值设置为80时才为微秒。如果使用其他预分频器值,则需要重新计算计数值。

  1. timerAlarmWrite(timer, 1000000, true);
复制代码

在setup函数的最后,可以通过调用t来开启定时器,函数调用的入参就是我们的定时器变量。

  1. timerAlarmEnable(timer);
复制代码

设置函数的最终代码如下所示。

  1. void setup() {
  2.   Serial.begin(115200);
  3.   timer = timerBegin(0, 80, true);
  4.   timerAttachInterrupt(timer, &onTimer, true);
  5.   timerAlarmWrite(timer, 1000000, true);
  6.   timerAlarmEnable(timer);
  7. }
复制代码

主循环

前面说过,ISR响应中断后,真正的定时器中断处理其实是在主循环中。为简单起见,我们只通过轮询来检查中断计数器的值。但更有效的方法是用信号量锁定主循环,然后在 ISR 中解锁。这也是原始示例中使用的方法: .

首先,我们会检查interruptCounter变量是否大于零,如果大于零,则进入中断处理代码。在中断处理代码中,先将计数器的值递减什么是定时器中断函数,表示中断已经响应并处理完毕。

由于此变量由主循环和 ISR 共享,因此必须在 portENTER_CRITICAL 和 portEXIT_CRITICAL 宏指定的关键代码段内处理它。两个函数调用使用的输入参数是 portMUX_TYPE 全局变量的地址。

  1. if (interruptCounter > 0) {
  2.     portENTER_CRITICAL(&timerMux);
  3.     interruptCounter--;
  4.     portEXIT_CRITICAL(&timerMux);
  5.     // Interrupt handling code
  6.   }
复制代码

实际的中断处理操作只是将计数器递减(计数器值表示自程序运行以来发生的中断总数),并将计数器值输出到串口。完整的主循环代码如下所示,其中已经包含函数调用。

  1. void loop() {
  2.   if (interruptCounter > 0) {
  3.     portENTER_CRITICAL(&timerMux);
  4.     interruptCounter--;
  5.     portEXIT_CRITICAL(&timerMux);
  6.     totalInterruptCounter++;
  7.     Serial.print("An interrupt as occurred. Total number: ");
  8.     Serial.println(totalInterruptCounter);
  9.   }
  10. }
复制代码

ISR 代码

中断服务程序必须是一个返回 void 且没有输入参数的函数。

在这个例子中,中断服务程序非常简单,只是增加中断计数器以通知主循环发生了中断。该代码位于 portENTER_CRITICAL 和 portEXIT_CRITICAL 宏指定的关键代码段内。用于两个函数调用的输入参数是先前声明的 portMUX_TYPE 全局变量的地址。

更新:为了让编译器将代码分配到 IRAM,中断处理程序应该具有 IRAM_ATTR 属性。此外,根据 IDF 文档,中断处理程序只能调用同样位于 IRAM 中的函数。感谢 Manuato 指出这一点。

该函数的完整代码如下所示。

  1. void IRAM_ATTR onTimer() {
  2.   portENTER_CRITICAL_ISR(&timerMux);
  3.   interruptCounter++;
  4.   portEXIT_CRITICAL_ISR(&timerMux);
  5. }
复制代码

最终代码

周期计数器中断程序的最终源代码如下所示。

  1. volatile int interruptCounter;
  2. int totalInterruptCounter;
  3. hw_timer_t * timer = NULL;
  4. portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
  5. void IRAM_ATTR onTimer() {
  6.   portENTER_CRITICAL_ISR(&timerMux);
  7.   interruptCounter++;
  8.   portEXIT_CRITICAL_ISR(&timerMux);
  9. }
  10. void setup() {
  11.   Serial.begin(115200);
  12.   timer = timerBegin(0, 80, true);
  13.   timerAttachInterrupt(timer, &onTimer, true);
  14.   timerAlarmWrite(timer, 1000000, true);
  15.   timerAlarmEnable(timer);
  16. }
  17. void loop() {
  18.   if (interruptCounter > 0) {
  19.     portENTER_CRITICAL(&timerMux);
  20.     interruptCounter--;
  21.     portEXIT_CRITICAL(&timerMux);
  22.     totalInterruptCounter++;
  23.     Serial.print("An interrupt as occurred. Total number: ");
  24.     Serial.println(totalInterruptCounter);
  25.   }
  26. }
复制代码

测试代码

将程序上传到您的 ESP32 开发板并打开 Arduino IDE 串行监视器以测试代码。输出如图 1 所示,并定期显示相关消息(每秒一条消息)。

图片[1]-代码基于Arduino核心库的教程:ESP32内核在ESP32上配置计时器中断-老王博客

图 1 – 定时器中断程序的输出。

注意:本文作者是住在葡萄牙里斯本的和蔼可亲的电气和计算机工程师 Nuno Santos。

他撰写了 200 多个关于 ESP32、ESP8266 的有用教程和项目。

更多ESP32/ESP8266教程和项目,请点击:ESP32教程总结贴

英文版教程:ESP32教程

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论