【STM32C5教程】(4)GPIO 外部中断实验

欢迎关注 youcans@xidian【嵌入式软件AI编程】专栏

【STM32C5教程】(1)STM32C5系列介绍

【STM32C5教程】(2)STM32CubeMX2 安装与STM32C5闪灯

【STM32C5教程】(3)GPIO 按键输入实验

【STM32C5教程】(4)GPIO 外部中断实验

1. 项目简介

1.1 实验目标

本实验在 NUCLEO-C542RC 开发板上,将用户按键 B1(PC13)配置为外部中断输入,按下按键触发中断,在中断回调中切换板载 LED(PA5)的状态,掌握 STM32C5 系列 EXTI 外部中断的配置与使用方法。

功能需求:

  • 按下用户按键(PC13)时触发外部中断
  • 在中断回调中切换板载 LED(PA5)状态
  • 消抖时间 20 ms

开发板和 MCU 型号: NUCLEO-C542RC 开发板,搭载 STM32C542RCT6 MCU。

1.2 实验条件

硬件:

  • NUCLEO-C542RC 开发板
  • 支持数据传输的 USB Type-C 线(用于连接 ST-LINK 接口)

软件:

  • STM32CubeMX2(用于引脚、时钟配置与代码生成)
  • VS Code + STM32Cube 插件(用于编译、烧录、调试)

2. 硬件说明

2.1 NUCLEO-C542RC 开发板

NUCLEO-C542RC 是 ST 基于 STM32C542RCT6 推出的 Nucleo-64 开发板。主要特性:

  • MCU:STM32C542RCT6,Arm Cortex-M33 内核,最高主频 144 MHz,256 KB Flash,64 KB RAM
  • 调试器:板载 STLINK-V3EC,支持大容量存储、虚拟串口和调试端口三种功能
  • 用户 LED:LD1,连接 PA5
  • 用户按键:B1,连接 PC13
  • 复位按键:B2
  • 扩展接口:Arduino Uno V3 和 ST morpho 连接器

本实验使用板载的 LD1 和 B1,无需额外外设。

2.2 引脚说明

引脚 别名 功能 方向 有效电平
PA5 LD1 板载用户 LED GPIO 输出 高电平点亮
PC13 B1 用户按键 EXTI 中断输入 按下为高电平,松开为低电平

PA5(LD1):配置为推挽输出,输出高电平时 LED 点亮,输出低电平时 LED 熄灭。复位后初始状态为低电平(LED 熄灭)。

PC13(B1) :配置为外部中断输入。按键按下时引脚为低电平,松开时为高电平。本实验采用上升沿触发 ,即中断发生在按键松开的瞬间。

3. STM32CubeMX2 工程配置

3.1 新建工程

本实验采用 MCU 方式创建工程,即以芯片型号 STM32C542RC 为起点,而非开发板方式。

  1. 进入 MCU 选择界面

    启动 STM32CubeMX2,在首页点击 MCU,进入芯片型号选择列表。

  2. 搜索并选中芯片

    在 MCU name 输入框中输入 STM32C542RC(或完整型号 STM32C542RCT6),从筛选出的列表中选择对应型号。

  1. 填写项目信息
    输入项目名称 GPIO_EXTI 和项目保存路径。项目名称避免使用特殊字符,路径建议使用纯英文目录。

3.2 基础配置

工程创建后,软件自动进入 Pinout 视图。时钟配置保持默认即可 :CubeMX2 创建工程后的默认系统主频为 48 MHz,时钟源为 HSI 分频,本实验无需修改。

注意:本工程的系统时钟为 48 MHz,这是 CubeMX2 的默认配置,不影响 GPIO 功能。

3.3 GPIO 配置

(1)PA5 配置为输出

  1. 在 Pinout 视图中找到 PA5 引脚。
  2. 右键点击 PA5,选择 Assign signal → GPIO。
  3. 再次右键点击 PA5,选择 Configure GPIO,进入 GPIO 详细配置界面。
  4. 将 PA5 的 Mode 设置为 Output。
  5. 保持默认参数:Output level 为 Low,Output mode 为 Push Pull,Pull-up/Pull-down 为 No pull-up and no pull-down,Maximum output speed 为 Low。
  6. 在 User Label 输入框中填入 LD1 。
    设置别名后,CubeMX2 会自动生成宏定义 LD1_PORT 和 LD1_PIN。

(2)PC13 配置为外部中断

  1. 在 Pinout 视图中找到 PC13 引脚。
  2. 右键点击 PC13,选择 Assign signal → GPIO。
  3. 再次右键点击 PC13,选择 Configure GPIO,进入 GPIO 详细配置界面。
  4. 将 PC13 的 Mode 设置为 Interrupt。
  5. 将 Trigger detection 设置为 Rising edge(上升沿触发)。
  6. 将 Interrupt 设置为 Enabled 启用中断,CubeMX2 会自动选择 EXTI13。
  7. 将 Pull-up/Pull-down 设置为 No pull-up and no pull-down。PC13 外部已有下拉电阻 R13,内部无需再设上下拉,避免与外部电阻形成分压。
  8. 在 User Label 输入框中填入 B1。

3.4 项目配置

  1. 点击 Project Settings,进入工程设置页面。
  2. 在 IDE Format 下拉选项中选择 CMake。
  3. 在 CMake Toolchain 下拉选项中选择 GCC。
  4. 确认配置无误后,点击 Generate Code。CubeMX2 会执行配置检查,如果没有错误,开始在指定路径下生成 CMake 工程文件。
  5. 生成完成后,项目路径下会出现标准 CMake 工程结构,包含 Core/、Drivers/、CMakeLists.txt 等。

4. 编程实现

4.1 实现思路

采用 "EXTI 边沿中断 + 时间戳消抖 + 回调中翻转 LED" 的方案:

复制代码
用户操作 B1(PC13)
    │
    ├─ 按下 → 引脚由低变高 ──────┐
    │                           │ 硬件 EXTI13 检测到上升沿
    │                           ▼
    │                  EXTI13_IRQHandler()
    │                           │
    │                           ▼
    │                  HAL_EXTI_IRQHandler()
    │                           │
    │                           ▼
    │                  HAL_EXTI_TriggerCallback()
    │                           │
    │                   ┌───────┴────────┐
    │                   │ 距上次响应 < 20ms? │
    │                   └───┬───────────┬───┘
    │                       │ 是         │ 否 = 有效触发
    │                       ▼            ▼
    │                   视为抖动      更新 last_tick
    │                   忽略          翻转 LED
    │
    └─ 松开 → 引脚由高变低 → 下降沿,不配置触发,无中断

与轮询方案的区别 :轮询版在主循环 while(1) 中不停地读引脚判断是否按下;本实验用硬件 EXTI 边沿产生中断,由中断回调响应,主循环保持空闲。

中断中不能用 HAL_Delay() :阻塞延时会导致中断服务程序长时间占用 CPU,影响系统实时性。本实验改用 HAL_GetTick() 时间戳比较实现非阻塞消抖。

各步骤使用的 HAL 函数:

步骤 HAL 函数 作用
中断入口 HAL_EXTI_IRQHandler() 清中断标志,调用触发回调
读当前时间 HAL_GetTick() 获取系统毫秒节拍,用于消抖计时
翻转 LED HAL_GPIO_TogglePin(LD1_PORT, LD1_PIN) 切换 PA5 输出电平

4.2 关键代码

在 VS Code 中打开生成的 CMake 工程,找到 Core/Src/main.c,添加以下代码。

(1)文件头部添加宏定义与变量

c 复制代码
/* Private define ------------------------------------------------------------*/
#define DEBOUNCE_TIME_MS  20U  /* 消抖时间(毫秒) */

/* Private variables ---------------------------------------------------------*/
static uint32_t last_tick = 0U;   /* 上一次有效按键触发时的系统时间戳,用于消抖 */

/* Private functions prototype -----------------------------------------------*/
static void handle_key_press(void);

(2)主函数

c 复制代码
int main(void)
{
  if (mx_system_init() != SYSTEM_OK)
  {
    return (-1);
  }
  else
  {
    /* 记录上电时刻,作为消抖时间戳的初始化基准 */
    last_tick = HAL_GetTick();

    /* 主循环:空闲等待;按键事件由 EXTI13 中断触发处理 */
    while (1)
    {
    }
  }
}

(3)按键处理函数

c 复制代码
/**
  * brief:  按键有效按下时的处理函数。
  * retval: 无
  */
static void handle_key_press(void)
{
  /* 切换板载 LED(PA5)状态:每次有效按下翻转一次 */
  HAL_GPIO_TogglePin(LD1_PORT, LD1_PIN);
}

(4)EXTI 触发回调函数

c 复制代码
/**
  * brief:  EXTI 触发回调(覆盖默认实现)。
  *         当 PC13(B1 用户按键)产生上升沿中断时被调用。
  *         PC13 外部下拉、空闲为低、按下为高,一次上升沿对应一次按下动作。
  * param:  hexti   EXTI 句柄
  * param:  trigger 触发沿(本工程仅配置了上升沿)
  * retval: 无
  */
void HAL_EXTI_TriggerCallback(hal_exti_handle_t *hexti, hal_exti_trigger_t trigger)
{
  uint32_t tick;

  /* 仅处理上升沿触发(本工程只有 PC13/B1 配置了 EXTI 上升沿) */
  if (trigger != HAL_EXTI_TRIGGER_RISING)
  {
    return;
  }

  tick = HAL_GetTick();

  /* 基于时基的软件消抖:间隔小于消抖时间则视为抖动,忽略本次触发 */
  if ((tick - last_tick) < DEBOUNCE_TIME_MS)
  {
    return;
  }

  /* 更新最后一次有效触发时刻,作为下一次消抖比较基准 */
  last_tick = tick;

  /* 执行有效按键动作:切换板载 LED */
  handle_key_press();

  /* 避免未使用形参的编译警告 */
  STM32_UNUSED(hexti);
}

代码说明:

  • LD1_PORT、LD1_PIN 两个宏由 CubeMX2 生成在 generated/hal/mx_gpio_default.h,通过 main.h 链入,在 main.c 中可直接使用。
  • 回调函数 HAL_EXTI_TriggerCallback() 是 HAL 驱动中已经声明好的回调入口。用户在自己的源文件中重写这个同名函数,中断发生时 HAL 就会自动调用它,无需修改驱动源码。
  • 回调中判断 trigger != HAL_EXTI_TRIGGER_RISING 后提前返回。当前工程只配置了上升沿,这个判断是防御性写法------如果将来同一个工程里还有其他触发边沿的 EXTI 配置,这段代码可以保证只响应上升沿。
  • 消抖使用 HAL_GetTick() 获取当前毫秒时刻,与上次有效触发时刻比较。时间差 >= 20ms 才判定为有效触发,并更新时间戳。这是非阻塞消抖,不会阻塞中断。
  • STM32_UNUSED(hexti) 用于避免未使用形参的编译告警(工程开启了 -Wall)。

5. 编译烧录与运行测试

5.1 编译与烧录

  1. 打开工程 :用 VS Code 打开 CubeMX2 生成的 CMake 工程文件夹(如 GPIO_EXTI_cmake)。
  2. 编译 :点击 VS Code 底部状态栏的 Build 按钮,或使用命令面板执行 CMake 构建。编译完成后,终端应输出 Build finished with exit code 0,无 error。
  3. 连接开发板:用支持数据传输的 USB Type-C 线连接 NUCLEO-C542RC 的 ST-LINK USB 口(CN1)。
  4. 确认设备识别 :打开设备管理器,在"端口 (COM 和 LPT)"下应能看到 STLink COMx。
  5. 烧录:点击 VS Code 中的运行按钮,将固件下载到开发板。

5.2 运行与测试

烧录完成后,程序自动开始运行。依次验证以下功能:

测试 1:初始状态

复位开发板(按下黑色复位按键 B2),观察 LD1。复位后 LD1 应保持熄灭状态,说明 GPIO 初始化正确,空闲时没有误触发。

测试 2:按下触发

按下 B1,观察 LD1 是否翻转。由于配置为上升沿触发,中断发生在按下的瞬间:按下时 LD1 立即翻转,松开时 LD1 状态不变。反复测试几次,确认每次按下都能可靠触发翻转。

测试 3:消抖验证

快速连续按下并松开 B1 多次,观察 LED 响应。如果消抖正常,每次有效触发只翻转一次,不会因按键机械抖动产生多次翻转。

测试结果:

  • 复位后 LD1 保持熄灭,无按键时不会误触发
  • 按下 B1 瞬间,LD1 翻转一次
  • 松开 B1 时,LD1 状态不变
  • 快速连按无重复误翻转,20 ms 消抖生效
  • 编译、烧录、运行均正常

功能正常。

6. 小结

本实验通过外部中断方式实现了按键控制 LED 状态翻转,掌握了 STM32C5 EXTI 外部中断的配置与使用方法。

核心逻辑可以概括为:"EXTI 边沿触发 → 中断服务 → 回调消抖 → 翻转 LED"。

有两点需要注意:

第一,触发边沿的选择。 本实验采用上升沿触发,中断发生在按键按下瞬间。PC13 外部下拉,空闲为低,按下为高,所以上升沿正好对应按下动作。如果希望松开瞬间触发,应改为下降沿(Falling edge)。

第二,中断回调中不能用 HAL_Delay()。 阻塞式延时会导致中断服务程序长时间占用 CPU,影响系统实时性。本实验改用 HAL_GetTick() 时间戳比较实现非阻塞消抖,这是中断中处理消抖的标准做法。

与上一篇轮询方案相比,中断方案由硬件边沿触发响应、主循环保持空闲,而轮询方案需要主循环持续读取引脚状态,这是两者最本质的技术差异。

版权声明:

欢迎关注 youcans@xidian【嵌入式软件AI编程】专栏

转发必须注明原文链接:

【STM32C5教程】(4)GPIO 外部中断实验

Copyright by ai4mcu@qq.com 2026

Crated:2026-10

相关推荐
万物智能信息科技1 小时前
人体感应芯片PIR模块驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
驱动开发·嵌入式硬件·华为·开源·harmonyos
深圳市恒锐丰科技杨生2 小时前
# CSM9118|芯晞微电 7.5V/1.5A 单通道 H 桥电机 / 线圈驱动
嵌入式硬件·硬件工程
硅基手札2 小时前
【I2C 技术系列 09】I2C 传输模式:轮询 / 中断 / DMA
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
Zw-awa4 小时前
让机器回答“我在哪”:激光雷达与 SLAM
stm32
启友玩AI4 小时前
微安级唤醒、毫安级识别:基于 CI3422 的 AI 眼镜语音交互开发实战
c语言·人工智能·科技·嵌入式硬件·ai·ar·语音识别
wuyk5554 小时前
样章-第10讲-看门狗异常恢复与卡机排查方法论
stm32·单片机·嵌入式硬件·重构
开发笔记-阿牛4 小时前
AM收音机蓝牙二合一设计复盘
人工智能·单片机·嵌入式硬件·音视频·音频
sunoo-2294 小时前
嵌入式Linux驱动学习笔记(第3-6天):从misc封装、platform总线到设备树、中断与单总线外设实战
linux·arm开发·驱动开发·笔记·单片机·学习
半杯咖啡半行码18 小时前
STM32之ARM通用汇编指令详解:从指令格式到寻址方式
汇编·arm开发·stm32