硬件部分
在硬件上,中断的本质是:一根物理导线(或内部总线)上的电平跳变(信号),这根线直接连接到 CPU 核心的一个特殊输入引脚上。
1. 信号源(源头):一根“拉扯”的线
中断在硬件上,首先表现为一个电平的变化(GPIO 引脚或内部外设的标志位)。
外部中断(如 PC4/PC5):你在原理图上看到的
INT1_Accel就是一根实实在在的铜导线,一头连着 BMI088 芯片,一头连着 STM32 的 PC4 引脚。表现形式:当 BMI088 数据准备好时,它会把这根线的电压从0V(低电平)强行拉到 3.3V(高电平)(上升沿),或者从 3.3V 拉到 0V(下降沿)。这个“电压跳变”就是硬件中断信号。
内部中断(如 UART 接收):虽然没有外部引脚,但 UART 外设内部有一根逻辑信号线。当它收到一个字节时,内部的移位寄存器满标志位会从 0 变为 1,这个内部的“电平跳变”同样会触发中断。
2. 中断控制器(NVIC):一个“门卫”与“总机”
这根信号线并不能直接打断 CPU,它必须先经过一个管家,叫做NVIC(嵌套向量中断控制器)。这是一个独立的硬件电路模块。
表现形式:在 NVIC 内部,针对每个中断源(如 USART3、TIM1、EXTI0)都有一个对应的“中断使能寄存器位”。
物理动作:当信号线电平跳变时,NVIC 会检查对应的寄存器位是否被你写入了
1(即你之前在 CubeMX 里勾选的“Enable”)。如果没使能:NVIC 直接无视这个电平变化(屏蔽)。
如果已使能:NVIC 会立即向 CPU 核心发送一个中断请求信号(一个特定的高电平脉冲)。
3. CPU 核心(Cortex-M):一个“中断响应引脚”
ARM Cortex-M 核心内部有一个专门用来接收中断请求的硬件逻辑接口。
表现形式:当 CPU 正在执行主程序(比如跑
while(1))时,它内部有一个指令流水线在跑。物理动作:当 NVIC 的中断请求脉冲过来时,CPU 的硬件控制单元(不是软件)会在当前指令执行完毕后,立即暂停指令流水线的取指动作。它不再去 Flash 里读取下一条
for循环指令,而是强制去读取一个名为向量表的特殊内存地址。
4. 向量表(硬件跳转):一个“固定地址映射”
这是硬件层面的自动跳转。
表现形式:STM32 内存的最开头(地址
0x00000000附近)存放着一张固定的表格。比如规定:地址
0x00000040存放着USART3_IRQHandler函数的入口地址。
物理动作:CPU 硬件会自动将PC(程序计数器)指针强行设置为向量表中对应中断的地址,然后开始执行你的 C 代码(
USART3_IRQHandler函数里的汇编指令)。
5. 总结一下硬件流程(时序感):
外设引脚(或内部信号线)从 0V 跳到 3.3V(硬件表现形式)。
NVIC 看到电平变化,匹配使能位,向 CPU 发送中断请求脉冲(硬件表现形式)。
CPU 执行完当前汇编指令,不再取下一条指令(硬件行为),而是去读取向量表(硬件表现形式)。
CPU 硬件将 PC 寄存器设置为
HAL_UART_IRQHandler的地址。
所以我们常说“进入中断”,实际上是指:CPU 的取指单元被硬件强制切换了数据来源——从 Flash 中取主程序的指令,切换为从向量表中取中断服务函数的入口地址。
另一方面,中断是 CPU 的“应急响应机制”或“优先级抢占机制”。
两种中断事件(触发源)的区别:
| 中断类型 | 触发时机 | 硬件本质 | 适用场景 |
|---|---|---|---|
| 接收中断 (RXNE) | 来一个字节,触发一次。当 UART 收到 1 个字节,且 CPU 还没来得及读走时触发。 | 接收数据寄存器(DR)非空。 | 未知长度数据,需要逐字节解析(如 AT 指令)。 |
| 空闲中断 (IDLE) | 收到一帧数据后,触发一次。当 UART 收到数据,然后总线停止接收并保持高电平超过一个字节的时间时触发。 | 检测到 RX 引脚持续 1 个字节时间的空闲态。 | 已知数据帧格式(如 18 字节的 DBUS),且想减少中断次数。 |
有关中断函数的篇幅
一. 流程图
硬件触发中断
↓
USART3_IRQHandler ← 硬件中断向量表直接调用的函数
↓ ( 由 CubeMX 生成)
HAL_UART_IRQHandler ← HAL 库的通用中断处理函数
↓
HAL_UART_RxCpltCallback ← 用户重写的回调函数(在特定事件发生时被调用)
二. 逐个解释
1.USART3_IRQHandler(中断服务函数,ISR)
它是什么:由 CubeMX 在
stm32f4xx_it.c中自动生成的硬件中断入口函数。谁调用它:当 USART3 产生任何中断(接收、发送、空闲、错误等)时,CPU 硬件会自动跳转到这个函数。
你的任务:你可以在其中添加自定义的、需要立即响应的代码。
void USART3_IRQHandler(void) { // 你自己添加的空闲中断处理 if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE) != RESET) { // 处理一帧数据收完的逻辑 } // 然后调用 HAL 库的通用处理函数 HAL_UART_IRQHandler(&huart3); }2.HAL_UART_IRQHandler(HAL 通用中断处理)
它是什么:HAL 库提供的一个通用中断分发函数,负责判断具体是哪种中断(RXNE、TXE、IDLE、错误等),然后调用对应的回调函数。
谁调用它:由
USART3_IRQHandler调用。你的任务:通常不需要修改它,它是 HAL 库内部逻辑。
3.HAL_UART_RxCpltCallback(接收完成回调)
它是什么:HAL 库定义的一个弱(weak)回调函数,当 DMA 或中断方式接收完成(收到指定数量的字节)时,被
HAL_UART_IRQHandler调用。你的任务:在用户代码中重写这个函数,在其中处理接收完成后的数据。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART3) { // 这里处理一帧数据收完后的逻辑 } }
4. 总结图
硬件事件(中断源) 硬件中断入口 HAL 库处理层 用户代码层 ─────────────────────────────────────────────────────────────────────────────────────── 1. 收到 1 个字节 (RXNE) ─┐ ├──> USART3_IRQHandler ──> HAL_UART_IRQHandler ──> 2. 总线空闲 (IDLE) ──────┘ HAL_UART_RxCpltCallback
结尾。