Arduino - 中断
Arduino - 理解中断
Section titled “Arduino - 理解中断”在微控制器编程中,中断是一种信号,它会暂时暂停处理器当前的任务,以响应一个重要事件并执行一段特殊的代码。一旦特殊代码执行完毕,处理器会准确地从中断前的位置恢复其原始任务。
想象一下您正在读一本书(您的主 loop() 函数),然后门铃响了(中断事件)。您放上书签,去开门(中断服务程序),然后回到您的书,从书签所在的位置继续阅读。如果没有门铃,您就不得不每隔几秒钟停下来检查门,这效率很低。中断通过让事件通知您来解决这个问题。
为何使用中断?
Section titled “为何使用中断?”中断的主要优势是响应性。微控制器可以执行长时间任务(例如管理显示屏或计算数据),同时仍能立即响应关键事件,例如:
- 用户按下按钮。
- 传感器数据到达。
- 旋转编码器的信号。
- 定时器达到特定值。
如果没有中断,您将不得不在主循环中不断轮询(检查)这些输入的状态,这可能会很慢,并且如果循环繁忙,则可能导致事件丢失。
Arduino 上的外部中断
Section titled “Arduino 上的外部中断”本教程重点介绍外部中断,它由特定数字引脚上的电压变化触发。大多数 Arduino 开发板只有数量有限的专用中断引脚。
| 开发板 | 中断引脚 |
|---|---|
| Arduino UNO, Nano, Mini | 2, 3 |
| Arduino Mega 2560 | 2, 3, 18, 19, 20, 21 |
| Arduino MKR 和 Zero 系列 | 所有数字引脚都可使用。 |
| 树莓派 Pico (RP2040) | 所有 GPIO 引脚都可使用。 |
您可以使用 attachInterrupt() 函数配置中断。现代且推荐的语法是:
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);-
digitalPinToInterrupt(pin):一个有用的函数,它将开发板的数字引脚号(例如2)转换为其特定的内部中断号。使用此函数可使您的代码在不同的 Arduino 开发板之间具有可移植性。 -
ISR:特殊函数——中断服务程序——的名称,当中断被触发时将调用此函数。此函数不得接受任何参数,也不得返回任何值(void myISR())。 -
mode:定义何种信号变化触发中断: -
LOW:当引脚保持低电平时触发。(谨慎使用,因为它可能重复触发)。 -
CHANGE:当引脚值改变时触发(从低电平到高电平或从高电平到低电平)。 -
RISING:当引脚值从低电平变为高电平时触发。 -
FALLING:当引脚值从高电平变为低电平时触发。这对于带上拉电阻的按钮按下非常常见。
中断服务程序(ISR)最佳实践
Section titled “中断服务程序(ISR)最佳实践”ISR 是特殊的,并有严格的规则:
- 保持它们简短快速。 ISR 应该只做所需的最少工作,例如设置一个标志或更新一个变量,然后退出。
- 将共享变量声明为
volatile。 如果一个变量在 ISR 和主循环中都被使用,您必须使用volatile关键字声明它(例如,volatile bool eventOccurred = false;)。这会告诉编译器变量的值可以随时改变,从而防止可能导致错误的优化。 - 避免使用
delay()和Serial函数。 需要定时器(如delay())或其他中断(如Serial.print())的函数在 ISR 内部表现不可靠,应避免使用。请在主循环中进行打印和延迟操作。
示例:使用中断切换 LED
Section titled “示例:使用中断切换 LED”此草图演示了中断如何立即响应按钮按下以切换 LED,即使主循环因长时间延迟而“繁忙”时也能做到。
/* 中断示例:响应式 LED 切换
此草图演示了中断如何提供即时响应性。 即使主循环被长时间延迟阻塞,按下引脚 2 上的按钮也会立即切换内置 LED。
电路: - 一个按钮连接在引脚 2 和 GND 之间。*/
// 引脚定义const int interruptPin = 2;const int ledPin = LED_BUILTIN;
// 此变量在 ISR 和主循环之间共享,因此必须是 volatile。volatile bool ledState = LOW;
void setup() { // 将 LED 引脚设置为输出 pinMode(ledPin, OUTPUT);
// 将中断引脚设置为带上拉电阻的输入模式 pinMode(interruptPin, INPUT_PULLUP);
// 附加中断。 // digitalPinToInterrupt() 使代码可移植。 // 我们在下降沿(当按钮按下时)触发。 attachInterrupt(digitalPinToInterrupt(interruptPin), toggleLed, FALLING);
Serial.begin(9600); Serial.println("系统准备就绪。请按下按钮。");}
void loop() { // 主循环可以执行其他任务。 // 这里我们模拟一个长时间的阻塞任务。 Serial.println("主循环正在忙碌..."); delay(5000);}
// --- 中断服务程序 (ISR) ---// 当按钮按下时,此函数会自动被调用。// 它必须简短、快速,并且是无参数的 void 返回类型。void toggleLed() { // ISR 应该只做最少的工作。这里,我们只是翻转状态。 ledState = !ledState; digitalWrite(ledPin, ledState);}常见错误和调试
Section titled “常见错误和调试”- 忘记
volatile:如果您的共享变量没有正确更新,您很可能忘记将其声明为volatile。 - 过长的 ISR:如果您的程序在中断触发时冻结或行为异常,您的 ISR 可能做了太多工作。将
Serial调用、delay()或复杂计算移到主loop()中,并且仅使用 ISR 来设置一个标志。 - 按钮抖动:机械按钮在按下时可能会发生物理“抖动”,在几微秒内产生多个高/低电平转换。这可能导致您的 ISR 在一次按压中多次触发。这称为“抖动”。解决方案包括在电路中添加一个小电容(硬件去抖)或添加逻辑来忽略快速触发(软件去抖)。