使用中断(Interrupt)来简化Arduino代码 - 是对实时事件做出反应的简单方法!
中断
事实证明,所有Arduinos都内置了一个很好的(并未充分利用的)机制,非常适合监控实时事件。这种机制称为中断。中断的工作是确保处理器快速响应重要事件。当检测到某个信号时,中断会打断处理器正在做的任何事情,然后执行一些代码,用于对一些外部事件进行反馈。一旦该代码处理完成后,处理器就会回到原来的状态,好像什么也没发生!
令人敬畏的是,它使得系统可以快速有效地对软件中不易预测的重要事件做出反应。最重要的是,它可以让你的处理器在等待某个事件出现时做其他事情。
按钮中断
让我们从一个简单的例子开始 - 使用中断来监控一个按键按下。首先,我们将看一下以前的sketch - “Button”示例,所有Arduino中都包含。 (您可以在“Examples”示意图中找到它。查看“File > Examples > Digital > Button”。)
const int buttonPin = 2; // the number of the pushbutton pin
const int ledPin = 13; // the number of the LED pin
// variables will change:
int buttonState = 0; // variable for reading the pushbutton status
void setup() {
// initialize the LED pin as an output:
pinMode(ledPin, OUTPUT);
// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT);
}
void loop() {
// read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);
// check if the pushbutton is pressed.
// if it is, the buttonState is HIGH:
if (buttonState == HIGH) {
// turn LED on:
digitalWrite(ledPin, HIGH);
}
else {
// turn LED off:
digitalWrite(ledPin, LOW);
}
}复制代码
这里的代码并不令人惊奇 - 程序做的所有工作是,一遍又一遍的运行`loop()`,然后读取`buttonPin`的值。假设你想在`loop()`中做一些除读取引脚之外的其他事情。现在就能体现中断的好处了。我们不用一直检测该引脚,而是将监视该引脚的工作转移到中断,并释放`loop()`来做我们在此期间需要做的事情!新代码如下所示:
const int buttonPin = 2; // the number of the pushbutton pin
const int ledPin = 13; // the number of the LED pin
// variables will change:
volatile int buttonState = 0; // variable for reading the pushbutton status
void setup() {
// initialize the LED pin as an output:
pinMode(ledPin, OUTPUT);
// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT);
// Attach an interrupt to the ISR vector
attachInterrupt(0, pin_ISR, CHANGE);
}
void loop() {
// Nothing here!
}
void pin_ISR() {
buttonState = digitalRead(buttonPin);
digitalWrite(ledPin, buttonState);
}复制代码
循环和中断模式
你会发现这里有一些变化。第一个,也是最明显的一个是`loop()`不包含任何指令!之前由`if / else`语句做的所有工作现在由一个新函数`pin_ISR()`处理。这种类型的函数称为_interrupt service routine_ - 它的工作是快速运行并处理中断并让处理器返回主程序(即`loop()`的内容)。编写中断服务程序时需要考虑一些重要事项,您可以在上面的代码中看到:
● ISR应该简短且易读。不能离开主循环太久!
● 没有输入变量或返回值。必须对全局变量进行所有更改。
你可能想知道 - 我们怎么知道中断何时运行?触发它的是什么? `setup()`例程中的第三个函数是为整个系统设置中断的。这个函数`attachInterrupt()`有三个参数:
1. 中断向量,确定哪个引脚可以产生中断。这不是引脚本身的编号 - 它实际上是对内存中Arduino处理器必须查看是否发生中断的位置的引用。该向量中的给定空间对应于特定的外部引脚,并非所有引脚都可以产生中断!在Arduino Uno上,引脚2和3能够产生中断,它们分别对应于中断向量0和1。有关哪些引脚可用作中断引脚的列表,请查看有关`attachInterrupt()`上的Arduino文档。
2. 中断服务程序的函数名称 - 它确定满足中断条件时运行的代码。
3. 中断模式,确定哪个引脚操作触发中断。 Arduino Uno支持四种中断模式:
*`RISING`,在引脚的上升沿触发中断,
*'FALLING`,在引脚的下降沿触发中断,
*`CHANGE`,响应中断引脚值的任何变化,
*`LOW`,在引脚为数字低电平时触发。
回过来看一下代码 - 我们设置`attachInterrupt()`设置我们监视中断向量0 /引脚2,使用`pin_ISR()`响应中断,并在我们看到任何变化时调用`pin_ISR()`引脚2上的状态。
Volatile关键字
还有一点需要指出 - 我们的ISR使用变量`buttonState`来存储引脚状态。查看`buttonState`的定义 - 而不是类型`int`,我们将其定义为`volatile int`类型。这是什么情况? `volatile`是一个应用于变量的C关键字。这意味着该变量的值不完全在程序的控制范围内。它反映了`buttonState`的值可以改变,并改变程序本身无法预测的东西。
`volatile`关键字做的另一个有用的事情是保护我们免受任何意外的编译器优化。事实证明,编译器除了将源代码转换为机器可执行文件外,还有一些目的。他们的任务之一是从机器代码中修剪未使用的源代码变量。由于变量`buttonState`不是在`loop()`或`setup()`函数中直接使用或调用的,因此存在编译器可能将其作为未使用的变量删除的风险。显然,这是错误的 - 我们需要变量! `volatile`关键字的另一个作用是,告诉编译器不要优化并保留该变量 - 这不是一个错误!
(旁白:从代码中优化未使用的变量是编译器的一个特性,而不是错误。人们偶尔会在源代码中留下未使用的变量,这会占用内存。如果您正在编写C程序,这不是什么大问题。一台计算机,有几千兆字节的RAM。然而,在Arduino上,RAM是有限的,你不想浪费任何东西!即使是计算机上的C程序也会这样做,有大量的系统内存可用。为什么?同样的,人们清理露营地的原因 - 最好不要留下垃圾!)
结束语
中断是一种简单的方法,可以使您的系统对时间敏感的任务更具响应性。它们还有一个额外的好处,就是可以释放主`loop()`来专注于系统中的一些主要任务。 (我发现当我使用它时,这会使我的代码更有条理 - 更容易看到代码的主要代码是什么,而中断处理周期性事件。)此处显示的示例几乎是最多的使用中断的基本情况 - 您可以使用它们来读取I2C设备、发送或接收无线数据,甚至启动或停止电机。