[PICO][Adv]中断向量表

Raspberry Pi Pico 2 中断向量表

在嵌入式编程中,中断是一种强大的机制,允许微控制器在执行主程序的同时,快速响应外部事件(如引脚电平变化、定时器溢出、数据接收等)。中断向量表是理解中断机制的关键概念之一。本文将详细介绍 Raspberry Pi Pico 2(RP2350,ARM Cortex-M33 架构)的中断向量表工作原理,并通过 Arduino 风格(基于 Arduino-Pico 核心)和 C/C++ 风格(基于官方 Pico SDK)的代码示例,帮助你掌握这一重要概念。


1. 什么是中断向量表?

中断向量表是一个存储中断服务程序(ISR,Interrupt Service Routine)入口地址的表格。当某个中断事件发生时,处理器会根据中断编号在向量表中找到对应的 ISR 地址,并跳转执行。向量表通常位于内存的起始位置(对于 Cortex-M 处理器,默认从地址 0x00000000 开始)。

在 Pico 2 中,中断向量表由硬件和 SDK 共同管理。开发者通常不需要直接修改向量表,而是通过 SDK 提供的函数(如 irq_set_exclusive_handler)或 Arduino 的 attachInterrupt 来注册中断处理函数。

1.1 Cortex-M 中断向量表的特点

  • 第一个条目:初始栈指针(MSP)值。
  • 第二个条目:复位向量(程序入口)。
  • 后续条目:各种异常和中断处理函数地址(如 NMI、HardFault、SVCall、PendSV、SysTick,以及外设中断如 TIMER_IRQ、UART_IRQ、GPIO_IRQ 等)。

Pico 2 的 RP2350 使用 ARMv8-M 架构,支持最多 240 个外设中断向量(IRQ0 到 IRQ239)。


2. 中断向量表的工作原理

当某个中断事件发生时,处理器会:

  1. 保存当前状态:将程序计数器(PC)、程序状态寄存器(PSR)等压入栈。
  2. 获取中断编号:根据中断源确定对应的向量表索引。
  3. 跳转:从向量表中读取 ISR 地址,并跳转执行。
  4. 执行 ISR:运行用户定义的中断处理代码。
  5. 返回:执行 BX LR 指令,恢复之前保存的状态,继续主程序。

以下是简化的中断处理流程图:

1
2
3
4
5
6
7
8
9
10
11
12
13
主程序执行

中断事件发生(例如 GPIO 引脚电平变化)

处理器自动保存上下文(压栈)

从向量表中读取对应 ISR 地址

执行中断服务程序(ISR)

恢复上下文(出栈)

继续主程序执行

3. 代码示例

3.1 Arduino 风格:使用 attachInterrupt

在 Arduino-Pico 核心中,你可以像传统 Arduino 一样使用 attachInterrupt 函数来注册外部中断。底层会自动将你的函数地址填入向量表(GPIO 中断条目)。

示例:按钮中断控制 LED 翻转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const int BUTTON_PIN = 0;   // 按钮连接到 GP0
const int LED_PIN = 25; // 板载 LED

volatile bool ledState = false; // 注意:volatile 用于中断中修改的变量

void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP); // 内部上拉,按下时 LOW

// 绑定中断:下降沿触发(按钮按下时)
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

Serial.begin(115200);
}

void loop() {
// 主程序可以做其他事情,LED 状态由中断异步修改
digitalWrite(LED_PIN, ledState);
// 其他任务...
}

void buttonISR() {
// 中断服务程序:尽量简短,只修改标志位
ledState = !ledState;
// 注意:不要在此使用 Serial.print() 或 delay()
}

提示:在 Arduino-Pico 核心中,attachInterrupt 支持 CHANGERISINGFALLINGLOWHIGH 触发模式。

3.2 C/C++ 风格:使用 Pico SDK

Pico SDK 提供了更底层的中断注册函数,如 gpio_set_irq_enabled_with_callbackirq_set_exclusive_handler

示例 1:使用 GPIO 回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "pico/stdlib.h"

#define BUTTON_PIN 0
#define LED_PIN 25

volatile bool led_state = false;

void gpio_callback(uint gpio, uint32_t events) {
// 检查是否为下降沿(按钮按下)
if (events & GPIO_IRQ_EDGE_FALL) {
led_state = !led_state;
gpio_put(LED_PIN, led_state);
}
}

int main() {
stdio_init_all();
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
gpio_init(BUTTON_PIN);
gpio_set_dir(BUTTON_PIN, GPIO_IN);
gpio_pull_up(BUTTON_PIN);

// 启用中断并设置回调
gpio_set_irq_enabled_with_callback(BUTTON_PIN, GPIO_IRQ_EDGE_FALL, true, &gpio_callback);

while (true) {
// 主循环可以做其他事情
tight_loop_contents();
}
return 0;
}

示例 2:自定义中断向量表条目(高级)

在极少数情况下,你可能需要直接修改向量表,例如在应用程序中重定向某个中断。Pico SDK 允许你在链接脚本中放置自定义向量表,或者使用 __attribute__((used))__VECTOR_TABLE 符号。

以下是一个简化的示例,展示如何覆盖 SysTick 中断处理函数(不推荐常规使用,仅供学习):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "pico/stdlib.h"

// 自定义 SysTick 中断处理函数
void SysTick_Handler(void) {
static int count = 0;
count++;
if (count % 1000 == 0) {
printf("SysTick tick\n");
}
}

int main() {
stdio_init_all();
// 设置 SysTick 每毫秒中断一次(示例,实际使用 SDK 的硬件定时器更佳)
// 注意:这里仅演示向量表覆盖,实际应使用 SDK 的 add_repeating_timer_ms
SysTick_Config(SystemCoreClock / 1000);

while (true) {
tight_loop_contents();
}
return 0;
}

注意:直接定义 SysTick_Handler 会覆盖 SDK 弱符号定义。SDK 中很多中断处理函数是弱符号(weak),你可以在应用程序中重新定义它们来接管中断。


4. 中断向量表在 Pico 2 中的位置

默认情况下,Pico 2 的程序从 Flash 地址 0x10000000 开始执行。中断向量表位于 Flash 起始处(0x10000000)。第一个字是初始栈指针,第二个字是复位向量(reset_handler)。SDK 提供的链接脚本会正确放置向量表。

如果你需要将向量表重定位到 RAM(例如为了动态修改中断处理函数),可以设置 SCB->VTOR 寄存器(Cortex-M 的向量表偏移寄存器)。Pico SDK 支持通过配置 PICO_RAM_VECTOR_TABLE=1 来将向量表复制到 RAM。


5. 中断服务程序的注意事项

要点 说明
保持简短 ISR 中不要执行耗时操作(如 delay()、复杂计算、串口打印)。推荐只设置标志位或修改简单变量。
使用 volatile 在中断和主循环间共享的变量必须声明为 volatile,防止编译器优化。
避免使用 malloc/free 动态内存分配不可重入,禁止在 ISR 中使用。
禁止长时间禁用中断 长时间禁用中断会延迟其他中断响应。
嵌套中断 Cortex-M 支持中断嵌套,高优先级可以打断低优先级。默认所有中断优先级相同,不嵌套。

6. 实际应用场景

中断向量表在许多实际应用中都非常有用:

场景 描述
按键检测 使用中断代替轮询,立即响应按键,CPU 可以进入低功耗模式。
传感器数据就绪 如加速度计、陀螺仪的 INT 引脚输出中断信号,微控制器立即读取数据。
通信接收 UART、SPI、I2C 外设的数据接收中断,确保数据不丢失。
定时器事件 精确的周期性任务(如电机控制、采样)可以使用定时器中断。

7. 总结

中断向量表是 Pico 2 中断机制的核心部分,它使得微控制器能够快速响应外部和内部事件。通过理解中断向量表的工作原理,并掌握如何使用 attachInterrupt(Arduino 风格)或 gpio_set_irq_enabled_with_callback(SDK 风格),你可以在项目中实现高效的中断处理。

特性 Arduino 风格 C/C++ 风格 (SDK)
注册中断 attachInterrupt() gpio_set_irq_enabled_with_callbackirq_set_exclusive_handler
支持中断源 外部引脚中断(有限引脚) 所有外设中断(GPIO、定时器、UART、SPI、I2C 等)
自定义向量表 不直接支持 可通过重定义弱符号或设置 VTOR 实现
适合场景 简单项目、快速原型 复杂项目、需要精细控制中断优先级

8. 附加资源与练习

  • 练习 1:修改 GPIO 中断示例,实现单击和双击检测(需要结合定时器状态机)。
  • 练习 2:使用定时器中断(add_alarm_in_ms)实现一个非阻塞的延时任务,并在主循环中闪烁 LED。
  • 练习 3:研究 Pico SDK 中的 hardware_irq 文档,编写一个 UART 接收中断程序,将接收到的字符存入环形缓冲区。
  • 资源

通过本文的学习,你应该对 Pico 2 的中断向量表有了深入的了解。希望你能在实际项目中灵活运用中断机制,提升程序的实时性和效率。


[PICO][Adv]中断向量表
https://ka5fxt.cn/2026/03/30/PICO-Adv-中断向量表/
发布于
2026年3月30日
许可协议