4. 中断系统入门:中断向量表、中断控制器、中断优先级与嵌套
好,咱们今天聊聊中断。说实话,中断是嵌入式系统的灵魂。没有中断,你的CPU就得像个傻子一样轮询,浪费大量算力。我刚开始做Zephyr开发时,对中断的理解也就停留在“打断当前任务”这个层面。直到有一次,一个外设的中断响应慢了,导致数据丢包,我才真正开始深入研究中断的底层机制。
说白了,中断就是硬件给CPU发的一个信号:“嘿,别忙了,先处理我的事!” 而CPU收到信号后,会暂停当前工作,跳到一个固定的地方去执行中断服务程序(ISR)。这个“固定的地方”,就是中断向量表。
4.1 中断向量表:CPU的“紧急通讯录”
中断向量表,你可以把它想象成一本通讯录。每个中断源都有一个唯一的编号,叫中断号。CPU根据这个中断号,去向量表里找到对应的ISR入口地址。
在ARM Cortex-M系列处理器上,向量表通常放在Flash的起始地址(0x00000000)。第一个字是栈顶指针,第二个字就是复位向量。后面的依次是各种异常和中断的入口地址。
Zephyr里怎么配置?
Zephyr在启动时,会把中断向量表从Flash复制到RAM,然后让CPU指向RAM中的向量表。这样做的好处是,你可以在运行时动态修改中断处理函数。我个人习惯在设备树里定义中断,然后通过IRQ_CONNECT宏来注册ISR。
// 一个典型的Zephyr中断连接示例
#define MY_UART_IRQ 42
#define MY_UART_PRIORITY 2
void my_uart_isr(const void *arg) {
// 处理UART中断
uint32_t status = uart_irq_update(DEVICE_DT_GET(DT_NODELABEL(uart0)));
if (status & UART_IRQ_FLAG_RX) {
// 读取数据
}
}
// 在初始化代码中
IRQ_CONNECT(MY_UART_IRQ, MY_UART_PRIORITY, my_uart_isr, NULL, 0);
irq_enable(MY_UART_IRQ);
4.2 中断控制器:中断的“交通警察”
中断控制器,在ARM Cortex-M上叫NVIC(Nested Vectored Interrupt Controller)。它的职责很简单:管理所有中断的使能、挂起、优先级,以及决定哪个中断先被CPU处理。
你想想看,如果同时来了10个中断,CPU该先处理谁?这就是中断控制器的工作。它会根据优先级,选出最高优先级的中断,然后告诉CPU:“先处理这个!”
NVIC的核心功能:
- 使能/禁止中断: 通过
irq_enable()和irq_disable()控制。 - 挂起/清除挂起: 当中断条件满足但CPU无法立即响应时,中断会被“挂起”。
- 优先级管理: 每个中断都有独立的优先级寄存器。
在Zephyr里,你不需要直接操作NVIC寄存器。驱动层已经封装好了。但理解底层机制,对排查问题很有帮助。比如,你发现某个中断不响应,可以检查一下是不是被意外屏蔽了,或者挂起位没有清除。
4.3 中断优先级:谁更重要?
中断优先级,说白了就是给中断排个队。优先级高的中断,可以打断优先级低的中断。在ARM Cortex-M上,优先级数值越小,优先级越高(0是最高优先级)。
Zephyr的优先级配置:
Zephyr支持两种优先级模式:
- 固定优先级: 每个中断的优先级在编译时就确定,运行时不能改。
- 动态优先级: 可以在运行时通过
irq_set_priority()修改。
我个人建议,除非有特殊需求,否则用固定优先级就够了。动态优先级容易引入竞态条件,调试起来很头疼。
| 优先级值 | 含义 | 典型用途 |
|---|---|---|
| 0 | 最高优先级 | 系统时钟、紧急错误 |
| 1-3 | 高优先级 | 实时通信、DMA完成 |
| 4-7 | 中等优先级 | 外设数据收发 |
| 8-15 | 低优先级 | 按键扫描、非关键事件 |
嗯,这里要注意:优先级分配要留有余地。别把所有中断都设成最高优先级,否则低优先级中断永远得不到响应。我在一个项目中,把UART中断设成了优先级0,结果它频繁打断低优先级的定时器中断,导致定时器不准。
4.4 中断嵌套:中断里还有中断
中断嵌套,就是高优先级中断可以打断低优先级中断的执行。ARM Cortex-M的NVIC天生支持嵌套,不需要软件干预。
嵌套的流程:
- CPU正在执行低优先级ISR。
- 高优先级中断触发。
- NVIC判断新中断优先级更高,通知CPU。
- CPU自动保存当前ISR的上下文(压栈),然后跳转到高优先级ISR。
- 高优先级ISR执行完毕,CPU恢复上下文,继续执行低优先级ISR。
听起来很完美,对吧?但实际项目中,嵌套会带来两个问题:
- 栈溢出: 每次嵌套都要压栈,如果嵌套层数太多,栈空间可能不够。Zephyr默认给每个线程分配了栈,但ISR用的是系统栈,要小心。
- 优先级反转: 低优先级ISR持有某个资源,高优先级ISR也要用这个资源,结果高优先级被阻塞。这在中级篇会详细讲。
在Zephyr里,你可以通过irq_lock()和irq_unlock()来控制嵌套。在ISR开头调用irq_lock(),可以临时屏蔽所有中断,防止嵌套。但注意,这会增加中断延迟。
void my_isr(const void *arg) {
// 防止嵌套
unsigned int key = irq_lock();
// 处理中断
// ...
// 恢复中断状态
irq_unlock(key);
}
好了,中断的入门知识就这些。记住三点:向量表是入口,NVIC是调度员,优先级决定了谁先谁后。下一节,我们会深入中断延迟的测量和优化,到时候你会看到,这些基础概念有多重要。