当前位置: 首页 > news >正文

中断、MsTimer2、Ticker、多任务功能详解

Arduino中断

CPU执行时原本是按程序指令一条一条向下顺序执行的。 但如果此时发生了某一事件B请求CPU迅速去处理(中断发生),CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务). 待CPU将事件B处理完毕后, 再回到原来被中断的地方继续执行程序(中断返回),这一过程称为中断 。

Arduino有两种形式的中断:

● 外部输入

● 引脚状态变化

1.外部中断(INT0、INT1)

1. 本质与特点

  • 硬件专属中断引脚:ATmega168/328 芯片仅支持 2 个外部中断通道,分别命名为 INT0INT1
  • 对应物理引脚:这两个中断通道固定映射到 Arduino 开发板的引脚 2(INT0)和引脚 3(INT1),不可更改。
  • 触发方式:通过attachInterrupt()函数配置,可选择以下触发模式:
    • LOW(低电平)
    • CHANGE(电平变化)
    • RISING(上升沿)
    • FALLING(下降沿)
    • HIGH:高电平触发(仅适用于 Arduino Due)。

2. 适用场景

  • 需要对特定引脚的状态变化进行高优先级响应(如紧急按钮、高速脉冲计数)。
  • 优势:中断响应速度极快,因为硬件直接支持。

2.引脚变化中断(Pin Change Interrupts)

1. 本质与特点

  • 全局中断机制:ATmega168/328 芯片支持3 组引脚变化中断(PCINT0、PCINT1、PCINT2),每组对应多个物理引脚:
    • PCINT0 组:引脚 0~7(对应 Arduino 引脚 D0~D7)。
    • PCINT1 组:引脚 8~13(对应 Arduino 引脚 D8~D13)。
    • PCINT2 组:引脚 14~20(仅适用于 ATmega328P,对应 Arduino 引脚 A0~A5 和未定义引脚)。
  • 关键特性:
    • 任何引脚均可触发:只要属于某一组 PCINT,该组内的所有引脚状态变化都会触发中断
    • 无法指定单个引脚:只能按组配置中断,无法单独监听某个引脚(需在中断服务程序中自行判断具体是哪根引脚变化)。

2. 触发方式

  • 仅支持电平变化触发:只要引脚电平发生变化(上升沿或下降沿),该组中断就会被触发。
  • 不支持高 / 低电平持续触发:无法像外部中断一样配置为 LOWHIGH 模式。

3. 如何使用?

  • 需手动配置寄存器:Arduino 核心库未直接提供attachPinChangeInterrupt()之类的函数,需通过底层寄存器操作实现。

    • 示例代码(监听引脚 4 的电平变化):

      volatile bool pin4Changed = false;void setup() {pinMode(4, INPUT_PULLUP); // 假设使用上拉电阻// 使能 PCINT0 组中断(引脚 0~7 属于 PCINT0)PCMSK0 |= (1 << PCINT4); // 使能引脚 4 的中断掩码PCICR |= (1 << PCIE0);   // 使能 PCINT0 组的中断sei();                   // 开启全局中断
      }// 引脚变化中断服务程序(需通过 ISR 关键字声明组别)
      ISR(PCINT0_vect) {if (digitalRead(4) == LOW) { // 假设下降沿触发pin4Changed = true;}
      }void loop() {if (pin4Changed) {// 处理引脚 4 的变化事件pin4Changed = false;}
      }
      

总结:

1.两种中断的核心区别

特性外部中断(INT0/INT1)引脚变化中断(PCINT)
可配置引脚仅引脚 2(INT0)、3(INT1)任意引脚(按组划分)
触发模式支持 LOW/CHANGE/RISING/FALLING仅支持 CHANGE(电平变化)
响应速度更快(硬件直接处理)稍慢(需软件判断具体引脚)
Arduino 库支持直接通过 attachInterrupt()需手动操作寄存器
适用场景单个引脚的高速响应多引脚监听(如键盘矩阵、传感器阵列)

2.为什么需要引脚变化中断?

当需要监控多个引脚的状态变化(如同时检测多个按钮),但又不想占用稀缺的外部中断引脚时,引脚变化中断非常有用。例如:

  • 键盘矩阵的行 / 列引脚监控。
  • 多个传感器的状态变化检测(如红外对管阵列)。

注意:由于引脚变化中断是按组触发的,每次中断触发时需在服务程序中遍历组内所有引脚,判断具体是哪个引脚发生了变化,这会增加软件开销。因此,外部中断更适合对实时性要求极高的单个引脚场景,而引脚变化中断适用于多引脚但实时性要求稍低的场景。

外部中断 attachInterrupt()

外部中断是外部干扰出现时发生的系统中断。干扰可能来自用户或网络中的其他硬件设备。

ATmega168 / 328上有两个外部中断引脚,称为INT0和INT1。 INT0和INT1分别映射到引脚2和3。

核心函数

attachInterrupt()

void attachInterrupt(digitalPinToInterrupt(GPIO), ISR, mode);
void attachInterrupt(0, ISR, mode);//UNO 0 => GPIO2

参数:

ISR 对应函数

[!IMPORTANT]

ESP8266 在处理中断时,由于闪存读取的延迟问题,可能会让中断响应变得不稳定。为了保证中断能够及时响应,就需要把中断函数放到 RAM 里执行,此时就需要使用ICACHE_RAM_ATTR前缀。

普通 Arduino 板(如 Uno、Mega 等)普通的 Arduino 板(基于 AVR 架构)并不需要ICACHE_RAM_ATTR前缀。在这些平台上,中断函数的定义较为简单,像之前给出的代码示例,直接定义中断服务函数就行

//中断处理函数,ESP8266 这类特定芯片平台
ICACHE_RAM_ATTR void dataReadyISR() {if (LoadCell.update()) {newDataReady = 1;}
}//普通 Arduino 板
void dataReadyISR() {if (LoadCell.update()) {newDataReady = 1;}
}
  • 中断服务程序的函数名,必须是 无参数、无返回值 的函数(如 void handleInterrupt())。

  • 通常ISR需要越短小精悍越好!另外如果您的代码中有多个ISR函数,那么每次Arduino只能运行一个ISR函数,其它ISR函数只有在当前的ISR函数执行结束以后,才能按照其优先级别顺序执行。

  • ISR函数中 delay()millis()Serial.print() 等依赖硬件定时器的函数无法正常运行。 delayMicroseconds() 不需要任何计数器就可以运行(delayMicroseconds() 会直接根据 CPU 的时钟周期来进行计数,通过执行一定数量的空指令来达到指定的延迟时间), 运行是不会受到影响。

  • 一般情况下,ISR函数与主程序之间传递数据是依靠全局变量来实现的。为了确保全局变量在ISR函数中可以正常的工作,应该将可能被ISR函数中使用的全局变量声明为volatile类型。

volatile – 易变变量

volatile这个关键字是变量修饰符,常用在变量类型的前面,该指令指示编译器从RAM而非存储寄存器(程序存储和操作变量的内存区域)中读取变量。ISR(中断服务程序)中所涉及的变量需要被声明为volatile易变变量。

mode触发方式

  • LOW:低电平触发。
  • CHANGE:电平变化(上升或下降沿)触发。
  • RISING:上升沿(从低到高)触发。
  • FALLING:下降沿(从高到低)触发。
  • HIGH:高电平触发(仅适用于 Arduino Due)。

[!IMPORTANT]

长时间运行(> 1ms)的中断任务将导致程序不稳定或崩溃。如果中断被长时间运行的中断阻塞,WiFi和核心的其他部分可能会变得不稳定。如果有很多事情要做,可以设置一个全局易变变量,主循环loop()中检查来进行变量的值,进而运行需要长时间工作的程序。

内存操作是危险的,应该在中断中避免。尽量减少对new或malloc的调用。因为如果内存被分割,可能需要很长的运行时间。出于同样的原因,realloc和free绝对不能被调用,也不能使用任何调用free或realloc本身的例程或对象。这意味着使用String、std:string 、std::vector和其他可调整大小的连续内存的类时必须非常小心(确保不改变字符串,不添加vector元素,等等)。

detachInterrupt()

  • 功能:禁用指定中断。
  • 参数:中断号(如 digitalPinToInterrupt(pin) 的返回值)。

interrupts()noInterrupts()

  • interrupts():启用全局中断。
  • noInterrupts():禁用全局中断(慎用,可能影响系统功能)。

中断引脚

http://www.lqws.cn/news/129871.html

相关文章:

  • 麒麟+ARM架构安装mysql8的操作指南
  • 大数据分析07 数据链接
  • 第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
  • Shopify 主题开发:促销活动页面专属设计思路
  • 告别延迟,拥抱速度:存储加速仿真应用的解决方案【1】
  • DexUMI:以人手为通用操作界面,实现灵巧操作
  • 激活函数和归一化、正则化
  • Unstructured.io 文件 Extract 方案概述
  • redis集群和哨兵的区别
  • MySQL 索引:为使用 B+树作为索引数据结构,而非 B树、哈希表或二叉树?
  • Python 解释器安装全攻略(适用于 Linux / Windows / macOS)
  • Spring AI 项目实战(五):Spring Boot + AI + DeepSeek + Redis 实现聊天应用上下文记忆功能(附完整源码)
  • VR博物馆推动现代数字化科技博物馆
  • 基于 ShardingSphere + Seata 的最终一致性事务完整示例实现
  • 思维力三阶 · 序章:从认知碎片到系统思维——点亮内心的“认知操作系统”蓝图
  • 佰力博科技与您探讨半导体电阻测试的基本原理
  • UE5 创建了一个C++类,现在我还有一个蓝图类,我想将编写的C++类中包含的功能加入到这个蓝图类里面,如何做到
  • Redis中的setIfAbsent方法和execute
  • 使用cursor 编辑器开发 Vue项目,配置ESlint自动修复脚本,解决代码不规范引起的报错无法运行项目问题
  • Flutter如何支持原生View
  • node 进程管理工具 pm2 的详细说明 —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录 7
  • excel从不同的excel表匹配数据
  • 采用 Docker GPU 部署的 Ubuntu 或者 windows 桌面环境
  • centos 9/ubuntu 一次性的定时关机
  • Python训练第四十四天
  • 【EasyExcel】导出时添加页眉页脚
  • 【Oracle】存储过程
  • Oracle实用参考(13)——Oracle for Linux静默安装(1)
  • Delphi中实现批量插入数据
  • oracle从表B更新拼接字段到表A