2、基础知识:中断的概念、中断向量表、中断优先级、中断嵌套与临界区
TinyML 设备通常运行在资源极度受限的微控制器(MCU)上,主频可能只有几十 MHz,RAM 以 KB 计,Flash 以 MB 计。在这样的环境下,事件驱动的实时响应能力几乎完全依赖中断系统。无论是按键唤醒、传感器数据就绪、定时器溢出,还是无线通信帧到达,中断都是连接“硬件事件”与“软件处理”的核心桥梁。理解中断的概念、向量表、优先级、嵌套与临界区,是设计低功耗、高实时性 TinyML 应用(如关键词唤醒、异常声音检测、振动分类)的前提。本章将系统梳理这些基础概念,并结合 Cortex-M 系列 MCU 与典型 TinyML 场景进行说明。
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 分类)放在主循环或低优先级任务中执行,防止阻塞中断响应。
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 启动文件预定义向量表,但需要理解两个关键操作:
- 向量表重定位:当使用 Bootloader + App 架构时,App 的向量表需通过
SCB->VTOR寄存器重定位到 App 的起始 Flash 地址(如 0x08008000)。否则中断触发后会跳转到 Bootloader 的向量表,导致 App 崩溃。 - 中断服务函数命名:启动文件中的弱定义(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 | 调试日志,可容忍延迟 |
2.4 中断嵌套与临界区保护
中断嵌套是指当一个中断服务程序正在执行时,另一个更高优先级的中断到来,CPU 暂停当前 ISR,转而去执行新的高优先级 ISR,处理完毕后再返回原 ISR 继续执行。Cortex-M 硬件自动保存和恢复现场(压栈 R0-R3、R12、LR、PC、xPSR),无需软件干预,但开发者仍需注意共享资源的保护。
临界区(Critical Section)是指访问共享资源(如全局变量、外设寄存器、DMA 缓冲区)的代码段,在这段代码执行期间必须禁止中断或禁止高优先级中断,以防止数据竞争。在 TinyML 中,典型的临界区场景包括:
- 主循环读取 ISR 中更新的数据就绪标志位。
- 多个中断源(如定时器中断和外部中断)同时修改同一个传感器数据缓冲区。
- 低功耗模式下,配置睡眠寄存器与使能唤醒中断之间的操作序列。
常用的临界区保护方法有两种:
- 关中断/开中断:使用
__disable_irq()和__enable_irq()全局关闭中断。这种方法简单粗暴,但会阻塞所有中断,包括高优先级实时中断,应尽量缩短临界区执行时间。 - 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%,剩余时间全部处于低功耗睡眠状态。
2.6 常见误区与总结
在 TinyML 中断设计中,开发者常犯以下错误:
- ISR 中执行浮点运算:Cortex-M4F 虽然带 FPU,但浮点运算的寄存器保存/恢复开销较大,且可能触发额外的延迟。建议在 ISR 中只做整数运算和指针操作。
- 忽视中断标志位清除:许多外设中断(如 EXTI、UART)在进入 ISR 后必须手动清除中断标志位,否则会反复触发中断,导致系统卡死。
- 临界区粒度过大:有些开发者为了保护一个 32 位变量,将整个函数体都放入关中断区域,导致中断延迟不可接受。应使用原子操作(如
__LDREXW/__STREXW)或 BASEPRI 精细屏蔽。 - 向量表未重定位:在带 Bootloader 的 OTA 升级方案中,App 启动后忘记设置 VTOR,导致所有中断跳转到错误地址,表现为“程序跑飞”或“中断无响应”。