楼主: boboy911
22 0

STM32F103(HAL 库)外部中断完整示例 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-12-24
最后登录
2018-12-24

楼主
boboy911 发表于 2025-12-3 16:50:24 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

一、硬件设计说明

本示例基于“按键触发外部中断”机制,实现 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

同时调整按键电平逻辑判断条件,确保行为一致。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:STM oversampling Volatile instance Sampling

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2025-12-5 17:36