Arduino开发实例-Arduino中断详解

Arduino中断详解

文章目录

本文是关于Arduino中断的文章。中断是一种让Arduino在特定事件发生时执行特定代码的功能。中断可以让Arduino在后台执行一些重要的任务,也可以让Arduino在低功耗模式下被唤醒。中断的使用需要注意一些细节和注意事项,本文将介绍中断的基本概念、使用方法和示例。

1、什么是中断?

中断是一种让Arduino在特定事件发生时暂停当前的程序,跳转到另一个预定义的函数(称为中断服务程序或ISR),执行完该函数后再返回原来的程序继续执行的功能。这种功能可以让Arduino在不影响主程序的情况下,及时响应一些外部或内部的信号,比如按钮按下、传感器触发、定时器溢出等。

2、如何使用中断?

要使用中断,首先需要确定哪些引脚可以作为中断源,即哪些引脚可以检测到电平变化或边沿变化,并触发中断。不同型号的Arduino有不同数量和位置的中断源引脚,通常用INTx来表示,其中x是一个数字。例如,Arduino Uno有两个中断源引脚,分别是INT0(对应数字引脚2)和INT1(对应数字引脚3)。Arduino Mega有六个中断源引脚,分别是INT0(对应数字引脚2)、INT1(对应数字引脚3)、INT2(对应数字引脚21)、INT3(对应数字引脚20)、INT4(对应数字引脚19)和INT5(对应数字引脚18)。具体的中断源引脚可以参考各型号Arduino的官方文档或数据手册。

其次,需要确定中断触发模式,即当中断源引脚上出现什么样的电平变化或边沿变化时,才会触发中断。Arduino支持四种中断触发模式,分别是:

  • LOW:当中断源引脚上出现低电平时触发
  • CHANGE:当中断源引脚上出现任何电平变化时触发
  • RISING:当中断源引脚上出现上升沿(从低电平变为高电平)时触发
  • FALLING:当中断源引脚上出现下降沿(从高电平变为低电平)时触发

不同的应用场景可能需要不同的中断触发模式,例如,如果要检测一个按钮是否被按下,可以使用CHANGE模式;如果要检测一个传感器是否输出了高电平信号,可以使用RISING模式;如果要检测一个定时器是否溢出了,可以使用FALLING模式等。

再次,需要定义一个ISR函数,即当中断被触发时要执行的代码。ISR函数必须符合以下要求:

  • ISR函数必须没有参数和返回值
  • ISR函数必须尽可能简短和快速
  • ISR函数不能使用延时函数或者阻塞函数
  • ISR函数不能调用其他ISR函数或者被其他ISR函数调用
  • ISR函数不能使用浮点数或者字符串等复杂数据类型
  • ISR函数不能修改或者依赖于主程序中被修改的变量,除非该变量被声明为volatile

最后,需要在setup()函数中使用attachInterrupt()函数来注册ISR函数,并指定要监听的中断源和触发模式。attachInterrupt()函数有三个参数:

  • interrupt:要监听的中断源编号,可以直接用INTx表示,也可以用digitalPinToInterrupt(pin)函数将数字引脚转

  • 换成中断源编号。例如,如果要监听数字引脚2上的中断,可以用INT0或者digitalPinToInterrupt(2)表示。

    • mode:要监听的中断触发模式,可以用LOW、CHANGE、RISING或者FALLING表示。
    • ISR:要注册的ISR函数的名称,不需要加括号。例如,如果要监听数字引脚2上的中断,当该引脚上出现上升沿时,执行名为blink的ISR函数,可以在setup()函数中写如下代码:
1
2
attachInterrupt(digitalPinToInterrupt(2), blink, RISING);

或者

1
2
attachInterrupt(INT0, blink, RISING);

其中,blink是一个自定义的ISR函数,可以在setup()函数之前或之后定义,例如:

1
2
3
4
void blink() {
digitalWrite(13, !digitalRead(13)); //切换数字引脚13上的LED状态
}

复制

注意,当使用attachInterrupt()函数注册ISR函数时,Arduino会自动将对应的中断源引脚设置为输入模式,并启用内部上拉电阻。如果不想使用内部上拉电阻,可以在setup()函数中使用pinMode()函数将中断源引脚设置为输入模式,并使用外部下拉电阻。

另外,如果不想再监听某个中断源引脚上的中断,可以在loop()函数中或者其他地方使用detachInterrupt()函数来取消注册ISR函数。detachInterrupt()函数只有一个参数:

+ interrupt:要取消监听的中断源编号,可以直接用INTx表示,也可以用digitalPinToInterrupt(pin)函数将数字引脚转换成中断源编号。例如,如果要取消监听数字引脚2上的中断,可以在loop()函数中或者其他地方写如下代码:
1
2
detachInterrupt(digitalPinToInterrupt(2));

或者

1
2
detachInterrupt(INT0);

3、中断的示例

下面给出一些使用中断的示例,以供参考。

3.1 ### 示例一:检测按钮按下

这个示例演示了如何使用中断来检测一个按钮是否被按下,并在串口监视器上打印出按下次数。假设按钮连接在数字引脚2和GND之间,当按钮按下时,数字引脚2会变为低电平。因此,我们可以使用CHANGE模式来监听数字引脚2上的电平变化,并在ISR函数中增加一个计数变量。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
volatile int count = 0; //定义一个计数变量,并声明为volatile

void setup() {
Serial.begin(9600); //初始化串口通信
pinMode(2, INPUT_PULLUP); //将数字引脚2设置为输入模式,并启用内部上拉电阻
attachInterrupt(digitalPinToInterrupt(2), buttonPressed, CHANGE); //注册ISR函数,并指定中断源和触发模式
}

void loop() {
Serial.println(count); //在串口监视器上打印计数变量的值
delay(1000); //延时1秒
}

void buttonPressed() {
count++; //每次触发中断时,计数变量加一
}

运行这个程序后,在串口监视器上可以看到每次按下按钮时,计数变量的值会增加一。

3.2示例二:控制LED闪烁

这个示例演示了如何使用中断来控制一个LED的闪烁频率。假设LED连接在数字引脚13和GND之间,我们可以使用定时器溢出产生的中断来切换LED的状态。Arduino Uno有三个定时器(Timer0、Timer1和Timer2),每个定时器都有自己的寄存器和控制位。这里我们使用Timer1作为例子。Timer1是一个16位定时器,它有两个比较匹配寄存器(OCR1A和OCR1B),分别对应两个比这个示例演示了如何使用中断来控制一个LED的闪烁频率。假设LED连接在数字引脚13和GND之间,我们可以使用定时器溢出产生的中断来切换LED的状态。Arduino Uno有三个定时器(Timer0、Timer1和Timer2),每个定时器都有自己的寄存器和控制位。这里我们使用Timer1作为例子。Timer1是一个16位定时器,它有两个比较匹配寄存器(OCR1A和OCR1B),分别对应两个比较匹配中断(TIMER1_COMPA_vect和TIMER1_COMPB_vect)。我们可以通过设置这些寄存器和控制位,来调节定时器的工作模式、预分频系数、比较匹配值等,从而改变中断的触发频率。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
volatile bool ledState = false; //定义一个LED状态变量,并声明为volatile

void setup() {
pinMode(13, OUTPUT); //将数字引脚13设置为输出模式
noInterrupts(); //关闭全局中断,以便设置定时器
TCCR1A = 0; //将定时器1的控制寄存器A清零
TCCR1B = 0; //将定时器1的控制寄存器B清零
TCNT1 = 0; //将定时器1的计数寄存器清零
OCR1A = 31250; //将定时器1的比较匹配寄存器A设置为31250,即每秒触发一次中断
TCCR1B |= (1 << WGM12); //将定时器1的工作模式设置为CTC(Clear Timer on Compare match)
TCCR1B |= (1 << CS12); //将定时器1的预分频系数设置为256
TIMSK1 |= (1 << OCIE1A); //开启定时器1的比较匹配中断A
interrupts(); //开启全局中断
}

void loop() {
//主程序不需要做任何事情,只需等待中断触发
}

ISR(TIMER1_COMPA_vect) { //定义ISR函数,当定时器1的比较匹配中断A触发时执行
ledState = !ledState; //切换LED状态变量的值
digitalWrite(13, ledState); //根据LED状态变量的值,控制数字引脚13上的LED状态
}

运行这个程序后,可以看到数字引脚13上的LED每秒闪烁一次。如果想要改变闪烁频率,可以修改OCR1A的值,或者修改TCCR1B中的预分频系数。例如,如果想要让LED每半秒闪烁一次,可以将OCR1A设置为15625;如果想要让LED每两秒闪烁一次,可以将TCCR1B中的预分频系数设置为1024(即将CS12和CS10都置为1)。具体的计算方法可以参考Arduino官方文档或数据手册。

3.3示例三:测量脉冲宽度

这个示例演示了如何使用中断来测量一个脉冲信号的宽度。假设脉冲信号输入到数字引脚2上,我们可以使用RISING和FALLING模式来监听该引脚上的边沿变化,并在ISR函数中记录时间戳。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
volatile unsigned long startTime = 0; //定义一个开始时间变量,并声明为volatile
volatile unsigned long endTime = 0; //定义一个结束时间变量,并声明为volatile
volatile unsigned long pulseWidth = 0; //定义一个脉冲宽度变量,并声明为volatile

void setup() {
Serial.begin(9600); //初始化串口通信
pinMode(2, INPUT); //将数字引脚2设置为输入模式
attachInterrupt(digitalPinToInterrupt(2), pulseDetected, CHANGE); //注册ISR函数,并指定中断源和触发模式
}

void loop() {
Serial.println(pulseWidth); //在串口监视器上打印脉冲宽度变量的值
delay(1000); //延时1秒
}

void pulseDetected() {
if (digitalRead(2) == HIGH) { //如果数字引脚2上出现上升沿
startTime = micros(); //记录开始时间戳,单位为微秒
} else { //如果数字引脚2上出现下降沿
endTime = micros(); //记录结束时间戳,单位为微秒
pulseWidth = endTime - startTime; //计算脉冲宽度,单位为微秒
}
}

运行这个程序后,在串口监视器上可以看到每秒打印出一次脉冲宽度的值,单位为微秒。如果想要改变测量的精度,可以修改delay()函数的参数,或者使用其他的时间函数,比如millis()或者micros()。具体的使用方法可以参考Arduino官方文档或数据手册。

4、中断的注意事项

使用中断时,需要注意以下几点:

  • 中断是一种高优先级的功能,当中断被触发时,Arduino会暂停当前的程序,跳转到ISR函数,并在执行完ISR函数后再返回原来的程序继续执行。这意味着,在ISR函数执行期间,主程序和其他中断都会被阻塞,无法响应其他事件。因此,ISR函数必须尽可能简短和快速,避免使用延时函数或者阻塞函数,以免影响主程序和其他中断的正常运行。
  • 中断是一种不可预知的功能,当中断被触发时,Arduino会立即跳转到ISR函数,并在执行完ISR函数后再返回原来的程序继续执行。这意味着,在ISR函数执行期间,主程序中被修改的变量可能会出现不一致或者错误的情况。例如,如果主程序中有一个变量a,在某一时刻被赋值为10,在下一时刻被赋值为20,在这两个时刻之间,如果发生了中断,并且ISR函数中也使用了变量a,那么变量a的值可能会出现混乱。因此,ISR函数不能修改或者依赖于主程序中被修改的变量,除非该变量被声明为volatile。volatile是一种修饰符,它可以告诉编译器不要对该变量进行优化或者缓存,而是每次都从内存中读取或者写入该变量的值。这样可以保证该变量在主程序和ISR函数之间保持一致。
  • 中断是一种有限制的功能,并不是所有的引脚都可以作为中断源,也不是所有的事件都可以作为中断触发模式。不同型号的Arduino有不同数量和位置的中断源引脚,通常用INTx来表示,其中x是一个数字。Arduino支持四种中断触发模式,分别是LOW、CHANGE、RISING和FALLING。具体的中断源引脚和触发模式可以参考各型号Arduino的官方文档或数据手册。

5、总结

本文介绍了中断的基本概念、使用方法和示例。中断是一种让Arduino在特定事件发生时执行特定代码的功能。中断可以让Arduino在后台执行一些重要的任务,也可以让Arduino在低功耗模式下被唤醒。中断的使用需要注意一些细节和注意事项,以保证

文章来源: https://iotsmart.blog.csdn.net/article/details/131268764