news 2026/9/30 9:49:32

实验三实验报告

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
实验三实验报告

实验三 HAL 库 + 按键外部中断控制流水灯暂停 / 恢复

项目内容
课程名称嵌入式系统原理及应用
实验名称HAL 库 + 按键中断方式控制流水灯
主控芯片STM32F103C8T6(ARM Cortex-M3,72 MHz)
硬件平台STM32 最小系统核心板 + 面包板 + 红 / 绿 / 蓝 LED
开发环境STM32CubeMX 6.x + Keil µVision5 + ST-Link 下载器
编程方式STM32 HAL 库(Hardware Abstraction Layer)

一、实验目的

  1. 掌握 STM32CubeMX 图形化工具创建 HAL 库工程的方法,理解 HAL 库工程的目录结构与初始化流程。
  2. 掌握 STM32 GPIO 的 HAL 库配置方法(推挽输出、上拉 / 下拉输入)。
  3. 理解并掌握EXTI 外部中断 / 事件控制器的工作原理,学会使用外部中断方式响应按键。
  4. 理解 NVIC 中断优先级管理,掌握中断服务函数与 HAL 库中断回调函数(HAL_GPIO_EXTI_Callback)的编写方法。
  5. 学会使用全局标志位与非阻塞计时(HAL_GetTick)实现流水灯"暂停 / 恢复"的状态控制。
  6. 掌握机械按键的软件消抖方法。

二、实验器材

序号器材数量
1STM32F103C8T6 最小系统核心板1 块
2ST-Link V2 仿真下载器1 个
3面包板及面包板跳线(杜邦线)若干
4红、绿、蓝 LED(各串 220 Ω 限流电阻)共 3 个
5USB 数据线、计算机(安装 CubeMX、Keil5)1 套

说明:本次实验未使用独立轻触按键,而是用一根杜邦线将按键引脚与 GND 手动短接 / 断开来模拟按键的按下与松开,原理与轻触按键完全相同(详见第六节)。


三、实验原理

3.1 引脚分配

核心板出厂已焊接一个板载用户 LED(接PC13,本板为蓝色,低电平点亮);面包板上外接红、绿、蓝 3 个 LED;PA0 作为按键输入引脚。

功能引脚GPIO 模式有效电平说明
板载用户 LEDPC13推挽输出,2 MHz低电平点亮出厂焊接,本板发蓝光
外接红灯PA4推挽输出,10 MHz低电平点亮串 220 Ω 电阻
外接绿灯PB5推挽输出,10 MHz低电平点亮串 220 Ω 电阻
外接蓝灯PC14推挽输出,2 MHz低电平点亮串 220 Ω 电阻
按键 KEYPA0上拉输入,EXTI0 下降沿触发短接到 GND杜邦线模拟

流水顺序:PC13(板载蓝)→ PA4(红)→ PB5(绿)→ PC14(外接蓝),循环往复,每个灯点亮 1 s,严格不重叠。

补充:核心板上还有一个红色电源指示灯(PWR),通电后常亮,它由硬件直接接 3.3 V,不参与程序控制。所以在实验视频中会看到一个红色小灯始终亮着,属于正常现象。

3.2 GPIO 输出原理

GPIO(通用输入输出端口)每个引脚可由程序独立配置为输入或输出。本实验 LED 引脚配置为通用推挽输出(GPIO_MODE_OUTPUT_PP):输出寄存器写 0 时引脚输出低电平(0 V),写 1 时输出高电平(3.3 V)。LED 负极(经限流电阻)接引脚、正极接 3.3 V 时为"低电平点亮",故输出 0 点亮、输出 1 熄灭。

3.3 EXTI 外部中断原理

STM32F103 的 EXTI(外部中断 / 事件控制器)共有 20 条外部中断 / 事件线,其中 EXTI0 ~ EXTI15 供 GPIO 使用。所有端口的同一编号引脚共用一条 EXTI 线,例如 PA0、PB0、PC0……共用 EXTI0,具体由复用功能 I/O 配置寄存器AFIO_EXTICR选择(本实验使用 PA0,对应 EXTI0)。

外部中断的完整信号通路如下:

按键动作(PA0 高电平 → 低电平) │ ▼ GPIO PA0 引脚 │ ▼ AFIO_EXTICR1 选择 EXTI0 来自 GPIOA(PA0) │ ▼ EXTI 触发方式寄存器(FTSR 下降沿选择)+ EXTI_IMR 中断不屏蔽 │ ▼ EXTI_PR 置位中断挂起标志(Pending) │ ▼ NVIC(嵌套向量中断控制器)的 EXTI0_IRQn 通道 │ ▼ 中断向量表 → EXTI0_IRQHandler() │ ▼ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0) // 判断并清除挂起标志 │ ▼ HAL_GPIO_EXTI_Callback(GPIO_PIN_0) // 用户回调:翻转暂停标志

PA0 配置为内部上拉 + 下降沿触发:不按键时上拉电阻把引脚拉为高电平;按键(短接 GND)时变为低电平,这个"高 → 低"的跳变(下降沿)即触发一次外部中断。

3.4 中断优先级(NVIC)

Cortex-M3 的 NVIC 支持中断优先级分组,每个中断可设置"抢占优先级"和"子优先级"。HAL 库在HAL_Init()中默认使用优先级分组 4(4 位全部为抢占优先级)。本实验通过

HAL_NVIC_SetPriority(EXTI0_IRQn,0,0);// 抢占优先级 0,子优先级 0HAL_NVIC_EnableIRQ(EXTI0_IRQn);// 在 NVIC 中使能 EXTI0 通道

使能 EXTI0 中断。注意:仅在 EXTI 外设中配置还不够,必须在 NVIC 中使能对应通道,中断才能进入 CPU。

3.5 暂停 / 恢复的程序设计(状态标志 + 非阻塞计时)

程序用一个全局标志位记录运行状态:

volatileuint8_tpause_flag=0;// 0 = 运行,1 = 暂停
  • 每来一次有效按键中断,回调函数把pause_flag翻转一次:0 → 1(暂停)、1 → 0(恢复)。
  • 主循环中判断该标志:
    • 运行态:用HAL_GetTick()获取系统毫秒数,采用非阻塞方式计时,每满 1000 ms 切换到下一个 LED;
    • 暂停态:不切换 LED,当前灯保持常亮(冻结),同时不断刷新计时基准,保证恢复后当前灯重新亮满 1 s 再切换。

为什么不用HAL_Delay(1000)阻塞延时?
HAL_Delay()是死等函数,在它等待的 1 s 内主循环无法执行其他逻辑,虽然中断仍能触发并翻转标志,但"暂停"动作要等当前这次延时结束后才会生效,存在最多 1 s 的滞后。改用HAL_GetTick()轮询比较时间,主循环始终在运行,按键一按下立即冻结,响应实时。

3.6 按键软件消抖

机械按键的金属触点在闭合 / 断开瞬间会发生数毫秒的机械抖动,导致一次按下被芯片识别为多个下降沿,引起标志位误翻转。本实验在中断回调中记录上一次有效按键的时刻,只有距上一次超过200 ms才认定为一次新按键:

if((now_tick-key_last_tick)>KEY_DEBOUNCE_MS){...}

从而滤除抖动干扰。


四、实验内容与步骤

4.1 使用 STM32CubeMX 创建 HAL 工程

(1)新建工程,选择芯片
打开 STM32CubeMX →File → New Project→ 在 MCU 选择器中输入并选择STM32F103C8Tx→ 点击 Start。

(2)配置调试接口(重要)
左侧System Core → SYS,将Debug设置为Serial Wire。这样 PA13(SWDIO)、PA14(SWCLK)保留为下载调试口;若不设置,程序下载后可能无法再次连接芯片。

(3)配置外部高速时钟
System Core → RCC,将High Speed Clock (HSE)选择为Crystal/Ceramic Resonator(使用板载 8 MHz 外部晶振)。此时引脚图中 PD0 / PD1 自动变为 RCC_OSC_IN / RCC_OSC_OUT。

(4)配置引脚
在右侧芯片引脚图上依次点击:

  • PA4、PB5、PC13、PC14 → 选择GPIO_Output;
  • PA0 → 选择GPIO_EXTI0。

(5)配置 GPIO 详细参数
进入System Core → GPIO:

  • 选中 PA0:GPIO mode 选择External Interrupt Mode with Falling edge trigger detection(下降沿触发);GPIO pull-up / pull-down 选择Pull-up(内部上拉)。
  • 切换到NVIC Settings标签页,勾选EXTI line0 interrupt → Enabled。
  • 分别选中 4 个输出引脚:GPIO output type 选Push Pull,PA4 / PB5 速度选 Low(10 MHz),PC13 / PC14 速度选 Medium(2 MHz),并将GPIO output level 设为 High(保证上电即熄灭)。

(6)配置时钟树
切换到Clock Configuration标签页,在HCLK输入框直接填入72并回车,软件自动配置:HSE 8 MHz → PLL × 9 → SYSCLK = 72 MHz;AHB = 72 MHz,APB1 = 36 MHz,APB2 = 72 MHz。

(7)设置工程并生成代码
进入Project Manager:

  • Project Name 填写FlowLED_HAL;Project Location 自行选择;Toolchain / IDE 选择MDK-ARM V5;
  • 左侧Code Generator中勾选Copy only the necessary library files,并选择Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral;
  • 点击右上角GENERATE CODE,生成工程。

生成后的工程主要结构:

FlowLED_HAL/ ├── Core/ │ ├── Inc/ (main.h, gpio.h, stm32f1xx_hal_conf.h, stm32f1xx_it.h) │ └── Src/ (main.c, gpio.c, stm32f1xx_it.c, stm32f1xx_hal_msp.c, system_stm32f1xx.c) ├── Drivers/ (CMSIS 与 STM32F1xx_HAL_Driver 库文件) └── MDK-ARM/ (FlowLED_HAL.uvprojx Keil 工程)

4.2 硬件连接

  1. 将核心板插入面包板,用 ST-Link 的 SWDIO、SWCLK、GND、3.3 V 与核心板对应引脚连接,ST-Link 经 USB 接计算机。
  2. 外接 LED 接线(低电平点亮方式):LED 正极 → 3.3 V;负极 → 220 Ω 限流电阻 → 对应 GPIO 引脚:
    • 红灯负极 → PA4;绿灯负极 → PB5;蓝灯负极 → PC14。
  3. 将核心板 GND 与面包板接地排("−"排)连通。
  4. 取一根杜邦线:一端(母头)接PA0,另一端悬空,用于点插面包板 GND 排模拟按键。

4.3 编写应用代码

在 Keil 中打开工程,将 LED 控制函数、中断回调及主循环逻辑写入main.c对应的USER CODE区域(完整代码见第五节)。

4.4 编译与下载

  1. 点击Build(F7)编译工程,结果为0 Error(s)。
  2. 点击魔术棒Options for Target → Debug,右侧选择ST-Link Debugger→ Settings,确认 SW 模式下能读到芯片 ID;在Flash Download中勾选Reset and Run。
  3. 点击Download(F8)下载程序,复位后观察现象。

五、核心程序代码(HAL 库)

5.1 main.c

/* USER CODE BEGIN Header *//** * @file main.c * @brief 实验三 HAL库 + 按键外部中断(EXTI) 控制流水灯暂停/恢复 * LED : 板载PC13(蓝)、红PA4、绿PB5、蓝PC14,均为低电平点亮 * 按键 : PA0,内部上拉,短接GND产生下降沿 -> EXTI0 *//* USER CODE END Header */#include"main.h"#include"gpio.h"/* USER CODE BEGIN PD *//* ---------- LED 引脚定义 ---------- */#defineLED_ONBOARD_PINGPIO_PIN_13/* 板载 LED */#defineLED_ONBOARD_PORTGPIOC#defineLED_RED_PINGPIO_PIN_4/* 红灯 */#defineLED_RED_PORTGPIOA#defineLED_GREEN_PINGPIO_PIN_5/* 绿灯 */#defineLED_GREEN_PORTGPIOB#defineLED_BLUE_PINGPIO_PIN_14/* 蓝灯 */#defineLED_BLUE_PORTGPIOC/* ---------- 按键引脚 ---------- */#defineKEY_PINGPIO_PIN_0/* PA0 -> EXTI0 */#defineKEY_PORTGPIOA/* ---------- 时序参数 ---------- */#defineLED_STEP_MS1000U/* 每灯亮 1 s */#defineKEY_DEBOUNCE_MS200U/* 消抖 200 ms */#defineLED_NUM4U/* USER CODE END PD *//* USER CODE BEGIN PV *//* * pause_flag:0=运行,1=暂停。 * 中断中改写、主循环中读取,必须加 volatile。 */volatileuint8_tpause_flag=0;staticuint8_tled_index=0;/* 当前点亮的灯序号 0~3 *//* USER CODE END PV *//* USER CODE BEGIN PFP */staticvoidLED_All_Off(void);staticvoidLED_Show(uint8_tindex);/* USER CODE END PFP *//* USER CODE BEGIN 0 *//* 全部熄灭:低电平点亮,写高电平熄灭 */staticvoidLED_All_Off(void){HAL_GPIO_WritePin(LED_ONBOARD_PORT,LED_ONBOARD_PIN,GPIO_PIN_SET);HAL_GPIO_WritePin(LED_RED_PORT,LED_RED_PIN,GPIO_PIN_SET);HAL_GPIO_WritePin(LED_GREEN_PORT,LED_GREEN_PIN,GPIO_PIN_SET);HAL_GPIO_WritePin(LED_BLUE_PORT,LED_BLUE_PIN,GPIO_PIN_SET);}/* 只点亮序号为 index 的灯,其余熄灭,保证不重叠 */staticvoidLED_Show(uint8_tindex){LED_All_Off();switch(index){case0:/* 板载 PC13 */HAL_GPIO_WritePin(LED_ONBOARD_PORT,LED_ONBOARD_PIN,GPIO_PIN_RESET);break;case1:/* 红 PA4 */HAL_GPIO_WritePin(LED_RED_PORT,LED_RED_PIN,GPIO_PIN_RESET);break;case2:/* 绿 PB5 */HAL_GPIO_WritePin(LED_GREEN_PORT,LED_GREEN_PIN,GPIO_PIN_RESET);break;case3:/* 蓝 PC14 */HAL_GPIO_WritePin(LED_BLUE_PORT,LED_BLUE_PIN,GPIO_PIN_RESET);break;default:break;}}/* * EXTI 中断回调函数(重写 HAL 弱定义函数)。 * 调用链:EXTI0_IRQHandler -> HAL_GPIO_EXTI_IRQHandler -> 本回调 */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){staticuint32_tkey_last_tick=0;uint32_tnow_tick;if(GPIO_Pin==KEY_PIN){now_tick=HAL_GetTick();if((now_tick-key_last_tick)>KEY_DEBOUNCE_MS)/* 消抖 */{key_last_tick=now_tick;pause_flag=!pause_flag;/* 暂停/恢复翻转 */}}}/* USER CODE END 0 */intmain(void){/* USER CODE BEGIN 1 */uint32_tstep_tick;/* USER CODE END 1 */HAL_Init();/* 初始化 HAL、Flash、SysTick(1ms) */SystemClock_Config();/* 配置系统时钟 72 MHz */MX_GPIO_Init();/* 初始化 GPIO(在 gpio.c 中) *//* USER CODE BEGIN 2 */LED_All_Off();/* 上电全灭 */LED_Show(led_index);/* 先亮第 0 个:板载 PC13 */step_tick=HAL_GetTick();/* USER CODE END 2 */while(1){/* USER CODE BEGIN 3 */if(pause_flag==0)/* 运行态 */{/* 非阻塞计时:满 1 s 切换下一个灯 */if((HAL_GetTick()-step_tick)>=LED_STEP_MS){step_tick=HAL_GetTick();led_index++;if(led_index>=LED_NUM){led_index=0;}LED_Show(led_index);}}else/* 暂停态 */{/* 冻结当前灯;刷新基准,恢复后重新计满 1 s */step_tick=HAL_GetTick();}/* USER CODE END 3 */}}

5.2 gpio.c 中的初始化要点

MX_GPIO_Init()由 CubeMX 生成,关键内容如下(注意初始输出电平必须为高,否则上电灯全亮):

voidMX_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStruct={0};/* 使能 GPIOC / GPIOA / GPIOB 等端口时钟 */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();/* LED 初始输出高电平(SET):低电平点亮,保证上电全灭 */HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13|GPIO_PIN_14,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);/* PC13 / PC14:推挽输出 */GPIO_InitStruct.Pin=GPIO_PIN_13|GPIO_PIN_14;GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull=GPIO_NOPULL;GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOC,&GPIO_InitStruct);/* 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);/* PA4、PB5:推挽输出 *//* ...(结构同上,略)... *//* NVIC:设置并使能 EXTI0 中断通道 */HAL_NVIC_SetPriority(EXTI0_IRQn,0,0);HAL_NVIC_EnableIRQ(EXTI0_IRQn);}

5.3 stm32f1xx_it.c 中的中断入口

/* EXTI0 中断服务函数,由 CubeMX 自动生成 */voidEXTI0_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);/* 清挂起标志并调用回调函数 */}

六、实验现象

程序下载运行后,面包板上的红、绿、蓝 LED 与板载蓝色 LED(PC13)按PC13 → PA4 → PB5 → PC14的顺序轮流点亮,每个灯亮约 1 s,同一时刻只有一个灯亮。用杜邦线将 PA0 与面包板 GND 排短接一次:

  • 第一次短接:流水灯立即停止,当前点亮的 LED 保持常亮(冻结),进入暂停状态;
  • 第二次短接:流水灯从被冻结的灯开始,重新按原顺序继续流动,恢复运行。

实验全过程录像已转换为 GIF 动图(如下)。图中可清楚看到:核心板上红色电源灯始终常亮;板载蓝灯(PC13)与面包板红、绿、蓝灯轮流点亮;


七、实验中遇到的问题及解决方法

序号问题现象原因分析解决方法
1没有独立轻触按键手头器材缺少微动开关按键本质是"按下时把引脚与 GND 导通",改用一根杜邦线,一端接 PA0,需要时将另一端插入面包板 GND 排点接一下再拔出,即可模拟一次完整按键,中断正常触发
2上电瞬间 4 个 LED 同时亮CubeMX 中输出引脚初始电平默认为 Low(低电平点亮)在 gpio.c 中将初始输出电平改为GPIO_PIN_SET(高电平),或在 CubeMX 中把 GPIO output level 设为 High
3短接一次引脚,灯连续翻转多次杜邦线接触瞬间存在抖动,产生多个下降沿在中断回调中加入 200 ms 软件消抖,只接受间隔足够大的触发
4担心用HAL_Delay时暂停不及时阻塞延时期间主循环空转,标志位虽翻转但 LED 不立即冻结改用HAL_GetTick()非阻塞计时,主循环持续运行,暂停实时生效
5视频中红色小灯一直亮,误以为程序异常该红灯是核心板硬件电源指示灯(PWR),通电常亮属正常现象,它不连接任何受控 GPIO,与程序无关

八、实验总结

  1. 本次实验基于STM32CubeMX + HAL 库完成了 GPIO 输出与外部中断的配置,相比寄存器直接编程,HAL 库通过标准化的句柄、结构体和 API(如HAL_GPIO_Init、HAL_GPIO_WritePin、HAL_NVIC_EnableIRQ)大大简化了开发,CubeMX 图形化配置还能自动完成时钟树和初始化代码生成。
  2. 掌握了EXTI 外部中断的完整工作流程:GPIO 引脚 → AFIO 选择 → EXTI 边沿检测与挂起 → NVIC 使能 → 中断服务函数 → HAL 回调。明确了"外设中断使能"和"NVIC 通道使能"缺一不可。
  3. 学会了用volatile全局标志位在中断与主循环之间传递状态,用HAL_GetTick()非阻塞计时实现了流水灯的暂停与恢复,理解了阻塞式与非阻塞式程序结构在实时响应上的区别。
  4. 掌握了机械按键(含手动短接)的软件消抖方法。
  5. 在没有实体按键的条件下,通过分析"按键即引脚与 GND 的通断"这一本质,用杜邦线短接的方式完成了中断触发,加深了对外部中断电气原理的理解。
  6. 实验结果与设计完全一致:流水灯正常轮流点亮,短接 PA0 可实时暂停、再次短接可恢复,达到了实验预期目的。

附:本报告完整源代码工程位于G:\stm32project\project2\led2\,实验录像源文件为IMG_1356.MP4。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 9:45:58

桃桃上岗记:调教出一个 3D 具身交互智能体导购

文章目录桃桃上岗记:调教出一个 3D 具身交互智能体导购〇、开工前,先摆正工具0.1 先说清楚:桃桃不是"ASR → LLM → TTS"拼出来的0.2 可插拔:换掉一个模块,商城咨询不能散架第一关:人设关——她得…

作者头像 李华
网站建设 2026/9/30 9:45:35

多厂区分布式PLC数据采集上云,工业物联网平台集中管理方案

一、方案背景与行业痛点 当前工业制造领域,生产核心设备多以PLC为控制核心,涵盖流水线、机床、暖通、动力设备、仓储输送设备等各类工业装备。传统工业场景中,PLC设备数据多封闭在现场工控内网,仅依托本地组态、触摸屏实现现场查看…

作者头像 李华
网站建设 2026/9/30 9:45:30

Java面试必问:JVM内存模型与GC调优全解析

运行时数据区:线程私有的三块与共享的两块程序计数器、虚拟机栈、本地方法栈,随线程生灭。程序计数器记录字节码行号,是唯一不抛OOM的区域。虚拟机栈存栈帧,每个方法调用压入一个,局部变量表、操作数栈、动态链接、返回…

作者头像 李华
网站建设 2026/9/30 9:42:37

C# DLL混淆实战:从ConfuserEx配置到纵深防御

1. 为什么C# DLL必须做混淆——不是防破解,而是防“被读懂” 在C#生态里,把项目编译成DLL后直接扔进ILSpy、dnSpy甚至JustDecompile里点开,你看到的几乎就是原始代码的“镜像”:类名、方法名、字段名、逻辑分支、字符串常量……全…

作者头像 李华
网站建设 2026/9/30 9:42:11

CentOS7时区时间误差8小时的四层校准方案

1. 问题本质与真实场景还原“CentOS7等Linux系统时区时间不对,显示误差8小时”——这句话在运维一线几乎每天都会出现在监控告警、日志排查或客户支持工单里。它不是一句模糊的报错,而是一个明确的信号:系统时间基准已偏移,且偏移…

作者头像 李华
网站建设 2026/9/30 9:41:50

从零手写Ping程序:Winsock原始套接字与ICMP协议实战

简介:这份计算机网络课程设计报告面向高校计算机相关专业学生,聚焦用Winsock技术实现Ping应用程序这一典型网络编程课题,适合正在完成课设、需要理解套接字编程与ICMP协议原理的学习者参考。资源包内含1个doc文档,大小约333KB&…

作者头像 李华