摘要:本文介绍如何使用PCF8574模块触发中断并做响应的处理
在前面的程序中,要通过不断的读取PCF8574的引脚状态,来判断红外避障模块是不是检测到了障碍物。处理器一旦在忙于其他的事情,那就有可能发现状态变化不及时,从而影响做出正确的响应。在前面的避障小车中也是,loop()函数中除了需要不断的判断各个红外避障模块的状态外,还需要不断用超声波传感器测量前方障碍物的距离,这两件事情是轮换进行的,那么就有可能出现当忙于意见事情的时候,就耽误了另一件事情的处理,造成设备的响应不及时,从而可能导致运行是出现某些问题。
为了处理某些意外事件,处理器提供了一种特殊的处理机制——中断。中断是指处理器在运行过程中,出现某些意外情况需处理时,处理器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。中断提供了应用程序与现实世界中发生的事情之间的接口。假如我是负责开门的,那么我可以每隔一段时间就去看看是不是需要开门。我也可在门外安装一个门铃,当有人按门铃的时候我再去开门,没人的时候我该干什么干什么,可以看书、看电视等等。开完们之后,我可以回来继续干我自己的事情,而不需要等在那里。这种事情处理的方式就称为中断。中断通常用在处理意外事件或者特别紧急的事件处理中。
下面就来看一下如何在ESP32中使用PCF8574模块产生的中断来获得红外避障模块的状态变化。中断功能的使用通常有以下几步:
- 完成相关的初始化
- 绑定中断服务函数
- 设置触发外部中断的方式
中断服务程序(Interrupt Service Routines,简称ISR)就是当发生符合条件的外部事件后,CPU会暂停当前执行的程序,立刻跳转到中断服务程序并执行,因此要求中断服务程序是一种特殊的函数,它具有一些独特的规则。
- 中断服务程序不能有任何参数,并且它们也不应该返回任何内容。
- 中断服务程序应尽可能短且快,不要使用delay()、串口数据发送等耗时比较长的方法,因为它们会阻止正常的程序执行。
- 中断服务函数应该添加IRAM_ATTR描述属性。因为拥有IRAM_ATTR属性的函数,在编译后将会放置在ESP32的内部RAM中。否则,代码将会被保存到Flash中。而内部RAM比Flash要快得多。
因此我们通常在中断处理函数中,只进行一些耗时极短的操作,对于耗时较长的操作,通常通过改变变量的值来表示某种状态的改变。然后依据主程序的判断和调度,再来做具体的处理工作。
外部中断的触发方式是指当外部接入引脚的信号发生怎样的变化时,触发中断,调用中断服务函数。ESP32支持的中断触发方式有以下几种:
中断触发方式 | 说明 |
RISING | 上升沿触发(由低电平变高电平瞬间触发) |
FALLING | 下降沿触发(由高电平变低电平瞬间触发) |
CHANGE | 电平变化触发 |
LOW | 低电平触发 |
HIGH | 高电平触发 |
好了,这就是中断的基本知识,下一篇再来介绍如何使用中断捕获红外避障模块的状态变化。
在使用PCF8574芯片的中断引脚时,需要注意以下几个问题:
- PCF8574的中断(INT)引脚是漏极开路输出,因此在连接单片机输入引脚的时候,要使用上拉电阻。
- PCF8574的中断引脚平时是高电平,当检测到输入引脚外部输入信号发生电平转换的时候,INT会变为低电平(大约会滞后输入信号改变4us)在对PCF8574进行一次读写操作后,INT会恢复高电平。
- 在设置引脚输出状态之后不会产生中断,只有在无设置引脚输出状态或者最后的操作为读取引脚状态数据,才会产生中断。
- 内部通过指令改变引脚电平不会产生中断,只有引脚因为外部信号高低电平转换导致的引脚电平变化才会产生中断。
- 外部信号变化就会导致INT引脚发出中断信号。在这里不能设置上升沿还是下降沿或者高电平还是低电平触发中断信号的变化。
在这里,还采用上一篇文章中介绍的电路,唯一的区别是增加一根导线,将PCF8574的INT引脚(中断信号输出)连接到ESP32的P27引脚。
接下来打开Arduino IDE软件,编写代码。软件代码如下:
#include <Adafruit_PCF8574.h> Adafruit_PCF8574 pcf; #define PCF_LED 0 // on the GPIO expander! #define PCF_INFRARED 1 // on the GPIO expander! #define ARDUINO_IRQ 27 void setup() { Serial.begin(115200); if (!pcf.begin(0x20, &Wire)) { Serial.println("Couldn't find PCF8574"); while (1); } pcf.pinMode(PCF_INFRARED, INPUT); pcf.pinMode(PCF_LED, OUTPUT); // set up the interrupt pin on IRQ signal toggle pinMode(ARDUINO_IRQ, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(ARDUINO_IRQ), infrared_detect, CHANGE); } // 中断标识 volatile bool in_irq = false; // 当发生中断时调用 void infrared_detect(void) { in_irq = true; } void loop() { if(in_irq) { Serial.print("Interrupt:"); bool val = pcf.digitalRead(PCF_INFRARED); pcf.digitalWrite(PCF_LED, !val); Serial.println(pcf.digitalRead(PCF_INFRARED)); in_irq = false; } delay(100); // we do nothing here! } |
可以看到,在这个代码的初始化setup()方法中,增加了如下两句代码:
pinMode(ARDUINO_IRQ, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ARDUINO_IRQ), infrared_detect, FALLING);
第一句将连接PCF8574中断的引脚引脚设置为输入上拉模式。这个在之前已经讲解过了,PCF8574的INT引脚为漏极开路输出,因此,必须要有上拉电阻才能输出低电平。
attachInterrupt()用来绑定中断服务函数以及指定中断的方式。这里设置的是电平变为低电平时产生中断。因此,每当输入引脚电平发生变化的时候,就会产生中断信号。
这个主程序相对于上个程序的变化就是只在有中断信号产生后,才会读取PCF8574输入引脚的状态,并且输出控制LED的信号。减少了对PCF8574的访问,无疑节省了很多的时间,提高了主程序的工作效率,可以在主循环中安排更多的事情。
好了,有了PCF8574模块,你可以扩充的GPIO口,接入足够多的红外避障模块了。