一、硬件设计说明
本示例基于“按键触发外部中断”机制,实现 NVIC 优先级管理,并采用顶半部(快速响应)与底半部(延迟处理)相结合的中断处理架构。系统具备详细注释,结构清晰,便于移植至不同项目中。
按键配置:连接至 PA0 引脚,采用低电平触发方式,按键按下时 PA0 接地;
LED 指示灯:连接至 PB0,工作在推挽输出模式,用于直观显示中断事件的发生;
功能逻辑:按键动作引发外部中断,顶半部负责快速标记事件并完成消抖,底半部则通过定时器或回调机制执行 LED 翻转及串口打印等耗时操作。
#include "stm32f1xx_hal.h"
#include <stdio.h>
/* 全局变量:中断事件标记(用于顶/底半部分离) */
volatile uint8_t key_interrupt_flag = 0; // 中断触发标志(volatile防止编译器优化)
/* 函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void KEY_EXTI_Callback(void); // 底半部处理函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin); // 中断回调函数(顶半部)
/* 重定向printf到串口1(方便打印调试) */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
int main(void)
{
/* 初始化HAL库 */
HAL_Init();
/* 配置系统时钟(72MHz) */
SystemClock_Config();
/* 初始化GPIO(按键/LED)、串口1 */
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("STM32外部中断示例(顶+底半部)\r\n");
printf("按键(PA0)按下触发中断...\r\n");
/* 主循环:处理底半部耗时操作 */
while (1)
{
/* 检测中断标志,处理底半部(耗时操作) */
if(key_interrupt_flag)
{
/* 1. 翻转LED(模拟耗时操作1) */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
/* 2. 串口打印(模拟耗时操作2) */
printf("中断触发!LED状态:%s\r\n",
HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) ? "亮" : "灭");
/* 3. 清除标志位,避免重复处理 */
key_interrupt_flag = 0;
/* 延时(仅演示,实际产品中底半部尽量减少延时) */
HAL_Delay(500);
}
/* 主循环其他任务(示例:空操作) */
__NOP();
}
}
二、核心代码实现
1. 主文件头包含与全局变量定义(main.c)
包含必要的头文件,并声明用于中断同步的全局标志位,确保主循环与中断服务程序之间的通信可靠。
2. GPIO 初始化(含外部中断与 NVIC 配置)
初始化 PA0 为输入模式并启用上拉电阻,配置其为外部中断源;同时设置 EXTI 线路映射至 GPIOA 的第 0 通道。NVIC 中断控制器进行优先级分组设定,并使能相应中断线。
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能时钟:GPIOA/GPIOB + SYSCFG(外部中断需要) */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_AFIO_CLK_ENABLE();
/* ------------------- 按键PA0:外部中断配置 ------------------- */
/* 1. 配置PA0为上拉输入(按键未按下时为高电平) */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发(按键按下:高→低)
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 2. 配置NVIC中断优先级(关键!避免优先级倒置) */
/* 抢占优先级:1(数值越小优先级越高),响应优先级:0 */
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能EXTI0中断
/* ------------------- LED PB0:输出配置 ------------------- */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 默认关闭LED */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
}
3. 中断服务函数(ISR)与回调处理(顶半部 + 底半部)
在 EXTI0_IRQHandler 中实现顶半部逻辑:清除中断标志、执行软件消抖、设置事件触发标志。底半部处理则在主循环中检测该标志后调用对应函数,完成 LED 控制和串口输出。
/* ------------------- 1. 中断服务函数(ISR,顶半部) ------------------- */
/* EXTI0中断服务函数(PA0对应EXTI0):必须放在stm32f1xx_it.c或main.c */
void EXTI0_IRQHandler(void)
{
/* HAL库中断处理:清除中断标志位(核心!避免重复触发) */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
/* ------------------- 2. HAL库中断回调函数(顶半部核心逻辑) ------------------- */
/* 重写HAL_GPIO_EXTI_Callback:快速处理(禁止耗时操作) */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_0) // 判断是否为PA0触发
{
/* 顶半部:仅做2件事 → 1. 消抖 2. 标记事件 */
/* 1. 简单消抖(硬件消抖优先,软件消抖仅辅助) */
HAL_Delay(10); // 10ms消抖(注意:ISR中尽量短延时,优先硬件RC消抖)
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
/* 2. 设置中断标志位,交给主循环处理(底半部) */
key_interrupt_flag = 1;
}
/* 顶半部禁止:延时>10ms、串口打印、复杂运算! */
}
}
4. 串口调试接口初始化(可选)
配置 USART1(或其他串口),用于输出调试信息,辅助验证中断响应与处理流程。波特率通常设为 115200,数据格式为 8N1。
UART_HandleTypeDef huart1;
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
/* 串口底层初始化(GPIO映射) */
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/* PA9=TX,PA10=RX */
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
5. 系统时钟初始化(必需)
启用 HSE 外部晶振,配置 PLL 锁相环以达到系统所需主频(如 72MHz)。同时开启相关外设时钟(GPIOA、GPIOB、AFIO、USART 等),保障各模块正常运行。
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/* 配置HSE(8MHz晶振)→ PLL倍频到72MHz */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/* 配置系统时钟、AHB、APB分频 */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* 错误处理函数(可选) */
void Error_Handler(void)
{
__disable_irq();
while (1)
{
/* 闪烁LED提示错误 */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(200);
}
}
三、中断处理架构解析:顶半部与底半部协同机制
| 处理部分 | 主要逻辑 | 关键注意事项 |
|---|---|---|
| 顶半部(ISR) |
1. 清除 EXTI 中断挂起标志 2. 执行短暂延时进行软件消抖(建议 ≤10ms) 3. 设置全局事件标志位,通知底半部任务 |
1. 严禁执行耗时操作(如 HAL_Delay 超过 10ms 或串口发送阻塞) 2. 必须正确清除中断标志,防止重复进入中断 |
| 底半部(主循环) |
1. 检测全局标志位是否被置位 2. 若检测到事件,则翻转 PB0 上的 LED 状态 3. 通过串口发送提示信息,并加入适当延时模拟负载 |
1. 依赖 volatile 全局变量传递状态 2. 可安全执行长时间操作,不影响中断实时性 |
四、关键技术要点说明
NVIC 中断优先级配置策略
- 抢占优先级(Preemption Priority):数值越小,级别越高,能够打断正在执行的低抢占优先级中断;
- 响应优先级(Sub Priority):仅当抢占优先级相同时起作用,决定多个同级中断的执行顺序;
- 示例中配置如下:
HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0)
实际应用中需根据系统整体中断布局调整优先级,避免与定时器、DMA、串口中断发生冲突。
按键消抖处理方案
- 硬件消抖:推荐在按键两端并联 RC 滤波电路(典型值:10kΩ 电阻 + 100nF 电容),有效抑制机械抖动;
- 软件消抖:在 ISR 中加入短延时(如 5~10ms),再判断电平是否稳定。注意延时不宜过长,以免影响其他中断响应。
volatile 关键字的必要性
所有被中断修改、主循环读取的共享变量(如事件标志位)必须声明为 volatile,例如:
key_interrupt_flag
否则编译器可能因优化而缓存变量值,导致主循环无法察觉其变化,从而引发逻辑错误。
volatile
中断标志清除机制
使用 HAL 库开发时,调用 __HAL_GPIO_EXTI_CLEAR_IT() 后,库函数会自动清除 EXTI 的中断挂起位,无需手动操作。
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0)
若采用标准外设库,则需手动写入 EXTI_PR 寄存器对应位来清除挂起状态:
EXTI->PR
五、跨平台适配与扩展建议
- 适用于 STM32F4/F7/H7 系列芯片:只需更新系统时钟配置函数,并调整 GPIO 和 AFIO 时钟使能语句即可兼容,例如:
__HAL_RCC_GPIOA_CLK_ENABLE()
- 更换按键引脚(如改为 PB12):需同步修改 GPIO 初始化中的端口号与引脚号,并重新配置 AFIO 的 EXTI 映射关系:
GPIO_PIN_12
同时确认对应的 EXTI 中断向量名称(如从 EXTI0 改为 EXTI15_10):
EXTI15_10_IRQn
- 支持上升沿触发场景:若硬件设计为高电平有效(如按键释放触发),应将中断触发边沿由下降沿改为上升沿:
原配置:
GPIO_MODE_IT_FALLING
修改为:
GPIO_MODE_IT_RISING
同时调整按键电平逻辑判断条件,确保行为一致。


雷达卡


京公网安备 11010802022788号







