2、基础知识:中断的概念、中断向量表、中断优先级、中断嵌套与临界区

TinyML 设备通常运行在资源极度受限的微控制器(MCU)上,主频可能只有几十 MHz,RAM 以 KB 计,Flash 以 MB 计。在这样的环境下,事件驱动的实时响应能力几乎完全依赖中断系统。无论是按键唤醒、传感器数据就绪、定时器溢出,还是无线通信帧到达,中断都是连接“硬件事件”与“软件处理”的核心桥梁。理解中断的概念、向量表、优先级、嵌套与临界区,是设计低功耗、高实时性 TinyML 应用(如关键词唤醒、异常声音检测、振动分类)的前提。本章将系统梳理这些基础概念,并结合 Cortex-M 系列 MCU 与典型 TinyML 场景进行说明。

中断系统核心概念关系图 事件源 GPIO 按键/传感器中断 定时器溢出/比较匹配 UART/SPI/I2C 外设 中断控制器 NVIC (Cortex-M) 中断向量表查找 优先级仲裁 中断向量表 存储 ISR 入口地址 按中断号索引 VTOR 寄存器定位 触发请求 查表跳转 中断服务程序 ISR 保存现场/恢复现场 快速处理/置标志位 优先级与嵌套 抢占优先级 子优先级 高优先级抢占低优先级 决定执行顺序 临界区 关中断/开中断 保护共享数据 避免竞态条件 访问共享资源 主程序(后台循环) 推理任务/低优先级处理 置标志位/唤醒

2.1 中断的核心概念与在 TinyML 中的角色

中断(Interrupt)是一种硬件机制,允许外设或内部事件异步地暂停 CPU 当前正在执行的指令流,跳转到一个预先定义的服务程序(ISR, Interrupt Service Routine)中执行,处理完毕后自动返回被中断的现场继续执行。与轮询(Polling)方式相比,中断不需要 CPU 持续检查事件状态,从而大幅降低功耗并提高响应实时性。

在 TinyML 场景中,中断的角色尤为关键:

  • 事件唤醒:MCU 在睡眠模式下,通过外部中断(如按键、加速度计 INT 引脚)或 RTC 定时中断唤醒,执行一次推理后再次进入睡眠,实现电池供电的长期待机。
  • 数据采集同步:音频采样(I2S/PDM 接口)、IMU 数据流(SPI/I2C)通常依赖 DMA 完成数据搬运,而 DMA 传输完成中断通知 CPU 处理数据块,避免 CPU 逐字节搬运。
  • 推理任务边界划分:中断服务程序只负责“事件检测”和“数据就绪标志”的设置,而耗时的神经网络推理(如 MFCC 特征提取 + CNN 分类)放在主循环或低优先级任务中执行,防止阻塞中断响应。
核心原则:中断服务程序应保持“短小精悍”——只做必要的寄存器操作、数据拷贝和标志位设置,耗时操作(如浮点矩阵运算、打印日志)一律移出 ISR。这不仅是良好的编程习惯,更是 TinyML 系统实时性和低功耗的基石。

2.2 中断向量表:从事件到处理函数的跳转地图

中断向量表(Vector Table)是一段存储在 Flash 或 RAM 起始区域的内存表,每个表项对应一个中断源,存放该中断的服务函数入口地址。当 CPU 响应中断时,硬件自动根据中断号从向量表中取出目标地址并跳转执行。

以 ARM Cortex-M4 为例,向量表布局如下:

偏移地址 向量内容 说明
0x0000_0000 初始 SP 值 栈顶地址,复位后加载到 MSP
0x0000_0004 Reset_Handler 复位中断入口
0x0000_0008 NMI_Handler 不可屏蔽中断
0x0000_000C HardFault_Handler 硬件错误处理
0x0000_0040 EXTI0_IRQHandler 外部中断 0(GPIO 唤醒常用)
0x0000_0058 TIM2_IRQHandler 定时器 2 中断(周期性采样)

在 TinyML 工程中,开发者通常使用 STM32CubeMX 或 Keil 启动文件预定义向量表,但需要理解两个关键操作:

  1. 向量表重定位:当使用 Bootloader + App 架构时,App 的向量表需通过 SCB->VTOR 寄存器重定位到 App 的起始 Flash 地址(如 0x08008000)。否则中断触发后会跳转到 Bootloader 的向量表,导致 App 崩溃。
  2. 中断服务函数命名:启动文件中的弱定义(Weak)函数需要用户在 C 代码中重新实现同名强函数。例如在 STM32 中,外部中断 0 的服务函数必须命名为 EXTI0_IRQHandler,否则链接时使用空函数,中断触发后程序跑飞。
// 向量表重定位示例(基于 CMSIS)
#define APP_FLASH_BASE  0x08008000U
void app_vector_table_init(void) {
    SCB->VTOR = APP_FLASH_BASE;
    __DSB();
    __ISB();
}

2.3 中断优先级:抢占与子优先级的协同

Cortex-M 内核使用 NVIC(嵌套向量中断控制器)管理中断优先级。每个中断源可配置抢占优先级(Preempt Priority)和子优先级(Sub Priority),两者组合成一个 4 位或 8 位的优先级数值(具体位数由芯片厂商决定,如 STM32F4 使用 4 位)。

优先级仲裁规则如下:

  • 抢占优先级决定是否可以打断当前正在执行的中断。数值越小,优先级越高。高抢占优先级的中断可以打断低抢占优先级的中断服务程序。
  • 子优先级仅在抢占优先级相同时起作用,用于决定多个同时挂起的中断的执行顺序。子优先级不会触发嵌套。
  • 如果两个中断的抢占优先级和子优先级都相同,则编号小的中断先执行(硬件决定)。

在 TinyML 系统中,合理的优先级分配策略至关重要:

中断源 抢占优先级 子优先级 设计理由
系统节拍定时器 (SysTick) 0(最高) 0 操作系统心跳,必须准时
音频 DMA 传输完成 1 0 音频数据实时性高,丢数据会产生爆音
外部唤醒按键 (EXTI) 2 0 用户交互响应,允许被音频打断
UART 接收 3 0 调试日志,可容忍延迟
经验提醒:在裸机 TinyML 程序中,如果不需要操作系统,SysTick 优先级可以适当降低。但务必保证音频/传感器数据采集中断的优先级高于任何可能长时间占用 CPU 的中断(如 Flash 写入中断),否则会导致数据丢失。

2.4 中断嵌套与临界区保护

中断嵌套是指当一个中断服务程序正在执行时,另一个更高优先级的中断到来,CPU 暂停当前 ISR,转而去执行新的高优先级 ISR,处理完毕后再返回原 ISR 继续执行。Cortex-M 硬件自动保存和恢复现场(压栈 R0-R3、R12、LR、PC、xPSR),无需软件干预,但开发者仍需注意共享资源的保护。

临界区(Critical Section)是指访问共享资源(如全局变量、外设寄存器、DMA 缓冲区)的代码段,在这段代码执行期间必须禁止中断或禁止高优先级中断,以防止数据竞争。在 TinyML 中,典型的临界区场景包括:

  • 主循环读取 ISR 中更新的数据就绪标志位。
  • 多个中断源(如定时器中断和外部中断)同时修改同一个传感器数据缓冲区。
  • 低功耗模式下,配置睡眠寄存器与使能唤醒中断之间的操作序列。

常用的临界区保护方法有两种:

  1. 关中断/开中断:使用 __disable_irq() 和 __enable_irq() 全局关闭中断。这种方法简单粗暴,但会阻塞所有中断,包括高优先级实时中断,应尽量缩短临界区执行时间。
  2. BASEPRI 寄存器屏蔽:Cortex-M3/M4 支持设置 BASEPRI 来屏蔽低于某个优先级的中断,而允许更高优先级中断继续响应。这种方法更精细,适合需要保护共享数据但又不希望完全关闭实时中断的场景。
// 使用 BASEPRI 保护临界区(仅屏蔽优先级 <= 3 的中断)
uint32_t basepri = __get_BASEPRI();
__set_BASEPRI(3 << 4);  // 屏蔽优先级数值大于等于 3 的中断
// 临界区代码:修改共享缓冲区指针
g_audio_buf_ready = true;
__set_BASEPRI(basepri);  // 恢复原优先级屏蔽状态
风险提示:在临界区内严禁调用阻塞型函数(如 HAL_Delay()、printf()),否则会导致中断长时间无法响应,轻则数据丢失,重则触发看门狗复位。如果临界区代码需要等待外设操作完成,应重新设计为“标志位 + 主循环轮询”模式。

2.5 实战案例:音频唤醒词检测中的中断边界划分

以一个典型的“关键词唤醒”TinyML 设备为例,硬件平台为 STM32L4 系列 MCU(Cortex-M4F,80MHz),板载数字麦克风(PDM 接口)和加速度计。系统要求:待机功耗低于 10µA,唤醒后 100ms 内完成“是/否”二分类推理。

初始方案与问题:工程师最初将所有处理都放在主循环中:主循环轮询 PDM 数据寄存器,读取音频数据,执行 MFCC 特征提取,运行神经网络推理。实测待机电流高达 2.3mA,且 CPU 占用率超过 90%,无法满足低功耗要求。

中断边界重新划分:

模块 中断/任务 优先级 处理内容
PDM 数据采集 DMA 传输完成中断 抢占 1 将 16ms 音频块从 DMA 缓冲区拷贝到双缓冲区的另一侧,置位 audio_block_ready
加速度计唤醒 EXTI 外部中断 抢占 2 清除中断标志,置位 motion_detected,唤醒 MCU 从 Stop 模式
特征提取 + 推理 主循环(后台任务) — 检测到 audio_block_ready 后,执行 MFCC + CNN 推理,输出结果

关键实现细节:

  • DMA 使用双缓冲模式(Ping-Pong),当 DMA 正在填充缓冲区 A 时,CPU 处理缓冲区 B;DMA 传输完成中断只切换缓冲区指针并置标志位,耗时约 2µs。
  • 主循环检测到 audio_block_ready 后,先进入临界区(BASEPRI 屏蔽优先级 ≤2 的中断)将标志位清零并交换缓冲区指针,然后退出临界区执行特征提取。这样即使加速度计中断在推理过程中到来,也能及时响应。
  • 推理完成后,主循环调用 WFI(Wait For Interrupt)指令进入睡眠,直到下一个 DMA 完成中断或外部唤醒事件到来。

结果对比:优化后待机电流降至 6.8µA(满足 10µA 指标),唤醒响应时间 85ms(满足 100ms 要求),音频数据零丢失。CPU 有效利用率从 90% 降至 35%,剩余时间全部处于低功耗睡眠状态。

复盘经验:中断边界的本质是“时间关键性”的划分。DMA 传输完成中断是时间关键(数据不取走就会被覆盖),必须立即处理;而神经网络推理不是时间关键(可以延迟几十毫秒),应放在后台。通过合理设置优先级和临界区,既保证了实时性,又实现了极低功耗。

2.6 常见误区与总结

在 TinyML 中断设计中,开发者常犯以下错误:

  • ISR 中执行浮点运算:Cortex-M4F 虽然带 FPU,但浮点运算的寄存器保存/恢复开销较大,且可能触发额外的延迟。建议在 ISR 中只做整数运算和指针操作。
  • 忽视中断标志位清除:许多外设中断(如 EXTI、UART)在进入 ISR 后必须手动清除中断标志位,否则会反复触发中断,导致系统卡死。
  • 临界区粒度过大:有些开发者为了保护一个 32 位变量,将整个函数体都放入关中断区域,导致中断延迟不可接受。应使用原子操作(如 __LDREXW/__STREXW)或 BASEPRI 精细屏蔽。
  • 向量表未重定位:在带 Bootloader 的 OTA 升级方案中,App 启动后忘记设置 VTOR,导致所有中断跳转到错误地址,表现为“程序跑飞”或“中断无响应”。