外部中断(External Interrupt,EXTI)是 MCU 响应片外引脚上电平变化的一种机制。CPU 不需要不停地去"轮询"某个引脚的状态,而是把检测工作交给硬件:当指定引脚上出现上升沿、下降沿或电平跳变时,硬件自动触发一个中断信号,暂停当前主程序,转而执行对应的中断服务函数(ISR),处理完再回到原来被打断的位置继续执行。
对 CW32F030 而言,外部中断最大的价值有三点:
一是实时性好:引脚一旦变化就能立刻被感知,不必受主循环周期影响;
二是省电:CPU 可以进入 Sleep/DeepSleep 等低功耗模式,等中断事件唤醒后再工作;
三是解耦:把"事件发生"和"事件处理"分开,主循环可以专注于别的任务。
二、CW32F030 外部中断的整体结构
外部中断从引脚到内核,大致要经过四级:GPIO 引脚 → AFIO 中断线映射 → EXTI 触发检测与屏蔽 → NVIC → Cortex-M0+ 内核。理解这条链路,配置时就不容易漏步骤。
CW32F030 的 GPIO 引脚编号 PA0~PA15、PB0~PB15 等,按"引脚号"被分成 EXTI0~EXTI15 共 16 根中断线。也就是说,所有端口的 0 号脚(PA0、PB0、PC0……)共享 EXTI0,所有端口的 1 号脚共享 EXTI1,以此类推。同一根中断线在同一时刻只能选择其中一个端口的引脚作为输入,这一点很关键——它意味着 PA0 和 PB0 不能同时各自独立地触发 EXTI0,必须二选一。
EXTI 控制器本身负责两件事:根据配置决定是上升沿、下降沿还是双边沿触发;提供中断屏蔽位(IMR)和挂起标志位(PR),决定某根线是否允许把中断请求送给 NVIC。而真正决定"这个中断请求能不能打断 CPU、打断时优先级谁高谁低"的,是内核外设 NVIC(嵌套向量中断控制器)。可以把 EXTI 理解成"负责检测和上报事件的传感器",NVIC 则是"负责最终裁决和派发的调度器"。
在中断向量层面,Cortex-M0+ 向量表把 16 根 EXTI 线合并成了几个中断号:常见的分组是 EXTI0~EXTI1 共用一个中断服务函数、EXTI2~EXTI3 共用一个、EXTI4~EXTI15 共用一个。这意味着写中断服务函数时,函数体内往往需要用 if 判断当前到底是哪一根线触发的,再分别处理,并只清除对应那根线的挂起标志。三、典型配置流程
配置一个外部中断,通常按下面的顺序走,少一步都可能出现"引脚动了但不进中断"的情况。
1.开启相关外设时钟
CW32F030 的 GPIO、AFIO 都挂在一定的总线上,使用之前必须先开时钟。常见做法是使能对应 GPIO 端口的时钟,同时确保复用功能(AFIO/SYSCFG)相关时钟也已打开。很多人调试时"怎么都进不了中断",最后发现就是忘了开端口时钟,引脚根本没工作。2. 把 GPIO 配置为输入(或对应模式)
根据电路选择浮空输入、上拉输入或下拉输入。例如按键一端接地、一端接引脚,通常配上拉输入,平时为高电平,按下为低电平,于是下降沿(或低电平)表示按下。推挽/开漏这些输出模式与中断无关,不要配错。3. 通过 AFIO 把引脚映射到对应 EXTI 线
这一步就是告诉硬件:"EXTI0 这根线,这次我要接的是 PA0,而不是 PB0"。它对应参考手册里外部中断线选择寄存器(EXTICR)。如果这一步没配对,即使引脚电平在变,EXTI 也收不到正确的信号。4. 配置 EXTI 触发方式与屏蔽
选择上升沿触发、下降沿触发还是双边沿触发,并把对应中断线的中断屏蔽位打开(允许该线把请求送到 NVIC)。按键这类机械开关一般选下降沿;编码器 A/B 相、脉冲输入这类需要双向计数的场合,常用双边沿触发。5. 配置并使能 NVIC
在 NVIC 中打开对应的 EXTI 中断号,并设置抢占优先级。Cortex-M0+ 的优先级位数较少(通常为 2~4 位),同一优先级下中断不能互相抢占,设计时要想清楚谁更紧急。这一步漏掉,是"EXTI 标志都置位了但 CPU 就是不进 ISR"的最常见原因。
6. 编写中断服务函数
在启动文件或库约定的中断服务函数名里写处理逻辑,函数体内先判断是哪根线触发,做完真正的工作后,必须手动清除挂起标志位。如果不清除,中断退出后会立刻再次进入,程序看起来就像"卡死"或在中断里反复打转。
GPIO口初始化:void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__RCC_GPIOA_CLK_ENABLE();
__RCC_GPIOB_CLK_ENABLE();
__disable_irq();
NVIC_EnableIRQ(GPIOB_IRQn);
__enable_irq();
GPIO_InitStruct.IT = GPIO_IT_FALLING; //KEY_UP
GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP;
GPIO_InitStruct.Pins = GPIO_PIN_13;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.IT = GPIO_IT_NONE; //LED1 LED2
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pins = GPIO_PIN_7|GPIO_PIN_8;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
GPIO_WritePin(CW_GPIOA,GPIO_PIN_7|GPIO_PIN_8,GPIO_Pin_RESET);
}按键回调函数:void GPIOB_IRQHandler(void)
{
/* USER CODE BEGIN */
if (CW_GPIOB->ISR_f.PIN13) //pB13 test interrupt
{
CW_GPIOB->ICR_f.PIN13 = 0; // 清中断标志位
PA07_TOG();
}
/* USER CODE END */
}四、典型应用场景
按键与用户交互。这是最经典的用法。按键按下产生一个下降沿,中断里只是把"有按键按下"这个事件记下来,真正的消抖、短按/长按判断、业务处理放到主循环或状态机里去做,而不是在中断里直接 delay。
外部传感器告警。比如温湿度超阈值、红外人体感应、震动传感器、烟雾报警等,这些信号平时不变,一旦变化需要 MCU 尽快响应,用外部中断比轮询省电也更及时。
脉冲与编码器计数。旋转编码器的 A/B 相、流量表的脉冲输出、外部方波测量等,可以用双边沿中断配合定时器/计数器,捕捉边沿时刻,再换算成转速、频率或流量。需要注意这类信号频率较高,中断里的处理必须尽量短。
低功耗唤醒。CW32F030 进入 Sleep/DeepSleep 后,CPU 基本停止工作,只有外部中断、RTC 等事件能把它唤醒。把按键、RTC 闹钟、外部数据准备好信号接到中断线上,就可以做到大部分时间睡眠、事件到来才醒来,非常适合电池供电的设备。
与通信外设配合的"准备好"信号。某些外部芯片(如屏幕、传感器、SD 卡)会用一根中断/忙线通知 MCU"数据准备好了"或"我空闲了",MCU 收到后再去读数据,避免一直轮询。
五、小结
CW32F030 的外部中断本质上就是一条"引脚变化 → EXTI 检测上报 → NVIC 裁决 → CPU 跳转执行 ISR"的硬件链路。掌握它的关键不在背寄存器,而在记住这条链路上每一环都要配齐:时钟、GPIO 模式、AFIO 线映射、EXTI 触发沿、NVIC 使能与优先级、ISR 里清标志。调试时按这条链路从后往前查(先看 ISR 进没进,再看 EXTI 标志,再看 AFIO 映射和时钟),大多数问题都能很快定位。把"事件上报"和"业务处理"分开——中断里只做最轻的事,重活交给主循环——写出的中断代码才稳、才好维护。
我要赚赏金
