Arduino 入门学习笔记(六):外部中断实验
Arduino 入门学习笔记(六):外部中断实验
开发板:正点原子ESP32S3
例程源码在文章顶部可免费下载(审核中…)
1. 外部中断介绍
很多时候, 我们程序采集一个传感器的数据,采集后就要进行分析判断,若符合某个条件就会做出处理。为了随时根据传感器的变化做出反应,所以程序需要一直重复这个过程。 这种方式称为轮询,这种方式是最简单的。
但轮询有时候并不能很好完成一些实际场景的应用,比如我某个时刻按下按键,但这时候程序执行的是采集传感器数据的过程,这就意味着没有检测到按键按下的动作,此时该系统就成了无法正常响应的系统了。通过对该按键配置外部中断功能,这时候就能很好解决上述问题。
1.1 中断程序
外部中断是由外部设备发起请求的中断。每个中断对应一个中断程序,中断可以看作一段独立于主程序之外的程序,也称为中断回调函数。当中断被触发时,控制器会暂停当前正在运行的主程序,而跳转去运行中断程序。当中断程序运行完毕,则返回到先前主程序暂停的位置,继续运行主程序,如此便可达到实时响应处理事件的效果。中断程序运行示意图如下图所示:
1.2 ESP32-S3 的中断介绍
便于大家了解 ESP32-S3 芯片的中断知识,这里简单介绍一下。
ESP32-S3 有 99 个外部中断源, 但是 CPU0 或 CPU1 只能够处理 32 个中断。 ESP32-S3 将外部中断映射到 CPU0 或 CPU1 中断就需要用到中断矩阵。中断映射的过程如下图所示:
ESP32-S3 中断矩阵会将任一外部中断源单独分配到双核 CPU 的任一外部中断上, 以便在外设中断信号产生后,及时通知 CPU0 或 CPU1 进行处理。每个 CPU 都有 32 个中断号(0 ~ 31),其中包括 26 个外部中断, 6 个内部中断。
外部中断为外部中断源引发的中断,包括下面三种类型:
1) 电平触发类型中断:高电平触发,要求保持中断的电平状态直到 CPU 响应
2) 边沿触发类型中断:上升沿触发,此中断一旦产生, CPU 即可响应
3) NMI 中断:不可屏蔽中断,产生该中断时,表示系统发生了致命错误
内部中断为 CPU 内部自己产生的中断,包括以下三种类型:
1) 定时器中断:由内部定时器触发,可用于产生周期性的中断
2) 软件中断:软件写特殊寄存器时将触发此中断
3) 解析中断:用于性能监测和分析
ESP32-S3 的外部中断是很强大的,每个引脚都可设置成外部中断触发引脚。 但在大部分的Arduino 控制器上,并非所有引脚都有中断功能。只有少数带外部中断功能的引脚上, Arduino控制器才能捕获到该中断信号并做出反应。在这过程中, 难免需要通过中断引脚找中断的编号。
1.3 中断触发模式
ESP32-S3 的中断触发模式有 5 种, 即前面所提及的电平触发以及边沿触发, 如下表所示:
1.4 中断触发函数介绍
Arduino-esp32 库提供了两个中断函数, 一个用于对中断引脚进行初始化设置,另一个是关闭外部中断。
attachInterrupt 函数,该函数功能是指定中断引脚,并对中断引脚进行初始化设置。
void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode);
参数 pin 为要设置中断触发输入的引脚, ESP32-S3 所有引脚均可以配置为外部中断引脚。
参数 handler 为中断回调函数,当引脚中断触发时,会终止当前运行的程序,转而执行该程序。
参数 mode 为 5 种中断触发模式。
注意:中断回调函数不能有参数,且没有返回值。
假如不需要监测某个引脚的信号变化,可通过 detachInterrupt 函数关闭外部中断。
void detachInterrupt(uint8_t pin);
其中参数 pin 为已经设置中断触发输入的引脚。 在本例程中没有用到该函数。
另外,很多时候,我们会见到 attachInterrupt 函数的第一参数会使用 digitalPinToInterrupt(pin)函数,这里简单解释一下为什么?
其实前面也有所提及,在一些 Arduino 开发板中比如 Arduino Uno、 Leonardo,只有 2 和 3引脚有外部中断功能,而中断编号对应为 0 和 1。在 Arduino Uno 开发板中, attachInterrupt 函数第一个参数为中断编号,第二个参数为中断回调函数,第三个参数为触发模式,所以为了避免硬件引脚和中断编号,直接通过 digitalPinToInterrupt 函数解决。很多时候, attachInterrupt 函数的使用也采用以下方式。
attachInterrupt(digitalPinToInterrupt(pin), handler, mode);
ESP32-S3 的中断引脚跟中断编号一致,也可不必用 digitalPinToInterrupt 函数,但是你在程序中也没有问题
2. 硬件设计
2.1 例程功能
通过外部中断的方式让开发板上的 BOOT 独立按键控制 LED 灯翻转。
2.2 硬件资源
1) LED 灯:LED-IO1
2)独立按键:BOOT-IO0
2.3 原理图
独立按键硬件部分的原理图,如下图所示:
这里需要注意的是: BOOT 设计为采样到按键另一端的低电平为有效电平。
3. 软件设计
3.1 程序流程图
下面看看本实验的程序流程图:
3.2 程序解析
- exti 驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。 EXTI 驱动源码包括两个文件: exti.cpp 和 exti.h。
下面我们先解析exti.h的程序。 由硬件设计小节,我们知道KEY按键在硬件上连接到IO0,为了与按键实验进行区分, 我们做了下面的引脚定义。
/* 引脚定义 */
#define KEY_INT_PIN 0 /* 外部中断引脚 IO0 */
下面我们再解析 exti.cpp 的程序,这里有两个函数 exti_init 和 key_isr,其定义如下:
uint8_t led_state = 0; /* 决定灯亮灭状态变量 */
/**
* @brief 初始化外部中断相关 IO 口
* @param 无
* @retval 无
*/
void exti_init(void)
{
key_init(); /* KEY 初始化 */
attachInterrupt(digitalPinToInterrupt(KEY_INT_PIN), key_isr, FALLING);
/* 设置 KEY 引脚为中断引脚,下降沿触发 */
}
/**
* @brief KEY 外部中断回调函数
* @param 无
* @retval 无
*/
void key_isr(void)
{
delay(10);
if (KEY == 0)
{led_state =! led_state; /* 两种情况:从 0 变 1,从 1 变为 0 */}
}
exti_init 函数是初始化外部中断引脚,首先调用 key_init 设置 KEY 引脚为上拉输入模式,然后调用 attachInterrupt 函数设置中断回调函数和设置中断引脚为下降沿触发。
key_isr 函数就是中断引脚的中断回调函数。 当按键被按下时,出现了下降沿即高电平变为低电平时,这时候就会跳到该函数去执行。函数内部很简单, 首先调用 delay 函数进行按键消抖, 然后判断按键是否真的被按下。当按键是按下情况,就对全局变量 led_state 进行取反操作,即 led_state 就有两种情况 0 和 1,最终在 loop 函数中作为 LED(x)宏函数的参数 x 决定了 LED灯的状态。
- 03_exti.ino 代码
03_exti.ino 代码:
#include "led.h"
#include "exti.h"
/**
* @brief 当程序开始执行时,将调用 setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
led_init(); /* LED 初始化 */
exti_init(); /* 外部中断引脚初始化 */
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
LED(led_state); /* 灯的亮灭由 led_state 值决定,led_state 变化在 key_isr 函数中实现 */
}
在 setup 函数中,除了要调用 led_init 函数对 LED 灯进行初始化,还要调用 exti_init 函数对按键 KEY 进行初始化以及配置 KEY 所在引脚的下降沿触发中断功能。 接下来,在 loop 函数中, LED 的状态通过全局变量 led_state 决定,若 led_state 为 0,即运行 LED(0)代码,这时候LED 灯就会亮起;若 led_state 为 1,即运行 LED(1)代码,这时候 LED 灯就会熄灭。
当按键被按下时, 这时候出现了下降沿,就会触发中断,进入到中断回调函数中。在该函数中,会对全局变量 led_state 进行取反操作,执行完又回到 loop 函数暂停的地方继续往下执行。最终,实现的效果就是:通过按下按键, LED 灯的状态会进行翻转。
4. 下载验证
下载完之后, 通过 BOOT 按键来控制 LED 灯的亮灭状态。