欢迎关注 youcans@xidian【嵌入式软件AI编程】专栏
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 为起点,而非开发板方式。
-
进入 MCU 选择界面
启动 STM32CubeMX2,在首页点击 MCU,进入芯片型号选择列表。
-
搜索并选中芯片
在 MCU name 输入框中输入 STM32C542RC(或完整型号 STM32C542RCT6),从筛选出的列表中选择对应型号。

- 填写项目信息
输入项目名称 GPIO_EXTI 和项目保存路径。项目名称避免使用特殊字符,路径建议使用纯英文目录。
3.2 基础配置
工程创建后,软件自动进入 Pinout 视图。时钟配置保持默认即可 :CubeMX2 创建工程后的默认系统主频为 48 MHz,时钟源为 HSI 分频,本实验无需修改。
注意:本工程的系统时钟为 48 MHz,这是 CubeMX2 的默认配置,不影响 GPIO 功能。
3.3 GPIO 配置
(1)PA5 配置为输出
- 在 Pinout 视图中找到 PA5 引脚。
- 右键点击 PA5,选择 Assign signal → GPIO。
- 再次右键点击 PA5,选择 Configure GPIO,进入 GPIO 详细配置界面。
- 将 PA5 的 Mode 设置为 Output。
- 保持默认参数:Output level 为 Low,Output mode 为 Push Pull,Pull-up/Pull-down 为 No pull-up and no pull-down,Maximum output speed 为 Low。
- 在 User Label 输入框中填入 LD1 。
设置别名后,CubeMX2 会自动生成宏定义LD1_PORT和LD1_PIN。
(2)PC13 配置为外部中断
- 在 Pinout 视图中找到 PC13 引脚。
- 右键点击 PC13,选择 Assign signal → GPIO。
- 再次右键点击 PC13,选择 Configure GPIO,进入 GPIO 详细配置界面。
- 将 PC13 的 Mode 设置为 Interrupt。
- 将 Trigger detection 设置为 Rising edge(上升沿触发)。
- 将 Interrupt 设置为 Enabled 启用中断,CubeMX2 会自动选择 EXTI13。
- 将 Pull-up/Pull-down 设置为 No pull-up and no pull-down。PC13 外部已有下拉电阻 R13,内部无需再设上下拉,避免与外部电阻形成分压。
- 在 User Label 输入框中填入 B1。



3.4 项目配置
- 点击 Project Settings,进入工程设置页面。
- 在 IDE Format 下拉选项中选择 CMake。
- 在 CMake Toolchain 下拉选项中选择 GCC。
- 确认配置无误后,点击 Generate Code。CubeMX2 会执行配置检查,如果没有错误,开始在指定路径下生成 CMake 工程文件。
- 生成完成后,项目路径下会出现标准 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 编译与烧录
- 打开工程 :用 VS Code 打开 CubeMX2 生成的 CMake 工程文件夹(如
GPIO_EXTI_cmake)。 - 编译 :点击 VS Code 底部状态栏的 Build 按钮,或使用命令面板执行 CMake 构建。编译完成后,终端应输出
Build finished with exit code 0,无 error。 - 连接开发板:用支持数据传输的 USB Type-C 线连接 NUCLEO-C542RC 的 ST-LINK USB 口(CN1)。
- 确认设备识别 :打开设备管理器,在"端口 (COM 和 LPT)"下应能看到 STLink COMx。
- 烧录:点击 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编程】专栏
转发必须注明原文链接:
Copyright by ai4mcu@qq.com 2026
Crated:2026-10