21971| 3
|
[ESP32系列教程] ESP32 Arduino教程:定时器中断 |
测试是在DFRobot的ESP-WROOM-32设备上进行的,该设备集成在ESP32 FireBeetle板上。 引言 本文主要解释在使用Arduino内核的ESP32平台上如何配置定时器中断。下文代码基于Arduino核心库里面的这个例子,非常建议你好好看看这个例子(https://github.com/espressif/ard ... mer/RepeatTimer.ino )。 本教程将会介绍如何对定时器进行配置以周期性地产生中断,以及如何对中断进行处理。 有关外部中断的教程:ESP32 Arduino教程:外部中断 中已经见过这种计数器的用法,因为ISR应该尽可能快地运行,所以不应执行过长的操作(比如向串口写入数据)。因此,在实现中断处理代码时,最好让ISR仅对中断进行响应,然后把实际的处理(可能包含时间较长的操作)交给主循环来做。
设置函数 在设置函数中,首先需要打开一个串行连接,以便后面将程序结果输出到Arduino IDE串口监测器。
然后,调用timerBegin函数以对定时器进行初始化,这个函数会返回一个指向hw_timer_t结构类型的指针,这个指针正是我们在上一节声明的定时器全局变量之一。 该函数有三个输入参数,分别是我们要使用的定时器编号(0到3,对应全部4个硬件定时器)、预分频器数值以及一个用于表示计数器向上(真)或向下(假)计数的标志。 在本例中,我们将使用第一个定时器,最后一个参数设为真(表示计数器向上计数)。 关于预分频器,我们在引言部分说过,ESP32计数器使用的基频信号通常是80 MHz(仅就FireBeetle开发板而言)。这个数等于80 000 000 Hz,这就意味着基频信号将使定时器计数器每秒递增80 000 000次。 尽管我们可以基于这个数值进行计算,进而对计数器值进行设置以产生中断,但是我们将利用预分频器来简化设置。如果我们将这个数值除以80(也就是说使用80作为预分频器的数值),那么就能得到一个1 MHz的频率,这就意味着定时器计数器将会每秒递增1 000 000次。 将上述数值取倒数,可知计数器将会每一微秒递增一次。所以,如果预分频器值为80,那么当我们调用函数对计数器值进行设置以产生中断时,我们所指定的数值就是以微秒为单位。
但是在启用定时器之前,我们还需要将其绑定到一个处理函数,以便对产生的中断做出响应。该操作可通过调用timerAttachInterrupt函数来完成。 该函数共有三个参数,分别是一个指向已初始化定时器的指针(保存在我们的全局变量中)、中断处理函数的地址以及一个表示中断触发类型是边沿(真)还是电平(假)的标志。有关边沿和电平触发中断的区别可参见此处:https://electronics.stackexchange.com/questions/21886/what-does-edge-triggered-and-level-triggered-mean 。 在调用函数时,我们将把定时器全局变量作为第一个输入参数,一个叫做onTimer的函数(稍后介绍)地址作为第二个参数,第三个参数赋值为真(表示中断是边沿触发类型)。
接下来,使用timerAlarmWrite函数指定触发定时器中断的计数器值。该函数的第一个参数是定时器指针,第二个参数是触发中断的计数器值,第三个参数是一个表示定时器在产生中断时是否重新加载的标志。 所以,在调用函数时,我们同样把定时器全局变量作为第一个输入参数,第三个参数设置为真(表示计数器将自动重新加载),这样就能周期性地产生中断。 关于第二个参数,不要忘了我们之前对预分频器的设置,中断应在数微秒后产生。因此,对本例而言,假如我们想要每秒产生一个中断,那么该值就是1 000 000微秒(等于1秒)。 重要说明:请注意,只有在将预分频器值设为80时,此处指定的时间单位才是微秒。如果使用其他预分频器数值,那就需要重新计算计数值。
在设置函数最后,通过调用timerAlarmEnable函数即可启用定时器,函数调用时的输入参数就是我们的定时器变量。
设置函数的最终代码如下所示。
主循环 如前文所述,在ISR对中断做出响应之后,真正的定时器中断处理操作其实是在主循环中。为简单起见,我们仅通过轮询对中断计数器的数值进行检验。但是更有效的处理方式是,使用一个信号量将主循环锁定,然后在ISR中将其解锁。这也是原始示例中所使用的方法:https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Timer/RepeatTimer/RepeatTimer.ino 。 首先,我们会检查interruptCounter变量是不是大于零,如果大于零,那么就进入中断处理代码。在中断处理代码中,首先会递减计数器的数值,表示已经对中断进行了响应和处理。 由于该变量由主循环和ISR所共享,所以必须在portENTER_CRITICAL和portEXIT_CRITICAL宏指定的关键代码段内处理。两次函数调用所使用的输入参数都是portMUX_TYPE全局变量的地址。
实际的中断处理操作只是对计数器(计数器数值表示自程序运行以来所发生的中断总数)进行递减,并将计数器值输出到串口。完整的主循环代码如下所示,其中已经包含了该函数调用。
ISR代码 中断服务程序必须是一个返回void(空)且没有输入参数的函数。 本例中,中断服务程序非常简单,仅对中断计数器进行递增,以告知主循环发生了一次中断。代码位于portENTER_CRITICAL和portEXIT_CRITICAL宏指定的关键代码段内。两次函数调用所使用的输入参数都是先前声明的portMUX_TYPE全局变量的地址。 更新:为使编译器将代码分配到IRAM内,中断处理程序应该具有 IRAM_ATTR属性。而且,根据IDF文档的说明(参见此处),中断处理程序只能调用同样位于IRAM内的函数。感谢Manuato指出这一点。 该函数的完整代码如下所示。
最终代码 周期计数器中断程序的最终源代码如下所示。
测试代码 将程序上传到您的ESP32开发板并打开Arduino IDE串口监测器,即可对代码进行测试。输出结果如图1所示,相关消息会周期性地显示出来(每秒显示一条消息)。 图1 - 定时器中断程序的输出。 注:本文作者是Nuno Santos,他是一位和蔼可亲的电子和计算机工程师,住在葡萄牙里斯本 (Lisbon)。 他写了200多篇有关ESP32、ESP8266的有用的教程和项目。 查看更多ESP32/ESP8266教程和项目,请点击 : ESP32教程汇总贴 英文版教程 : ESP32 tutorial |
© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed