实验任务2:标准外设库方式控制LED流水灯
1)工程项目创建与标准外设库文件添加详细过程
(1)新建工程文件夹结构
一、创建一个keil工程(寄存器版)
打开keil,点击Project,选择New uVision Project
选择 CPU 型号,根据开发板进行选择
在线添加库文件
用寄存器控制 STM32 时,不需要在线添加库文件,可以直接关掉。
添加文件
①添加已经存在文件
在新建的工程中添加启动文件(startup_stm32f10x_hd.s),该文件可以先到固件库中复制到此处startup_stm32f10x_hd.s。
②创建新文件
stm32f10x.h
手动新建,用于存放寄存器映射的代码,暂时为空。
main.c
手动新建,用于存放 main 函数,暂时为空。
配置魔术棒选项卡
①Target设置
②Output设置
③Listing设置
④Debug设置
⑤Utilities设置
⑥Debug Settings设置
(3)标准外设库方式LED流水灯代码
#include “stm32f10x.h”
#include “stm32f10x_gpio.h”
#include “stm32f10x_rcc.h”
/* 软件延时函数,约1秒 */
void Delay_ms(unsigned int ms)
{
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 7200; j++);
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能GPIOA、GPIOB、GPIOC时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); /* 配置PA0、PB9、PC13为推挽输出 */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_Init(GPIOC, &GPIO_InitStructure); /* 初始全部熄灭 */ GPIO_SetBits(GPIOA, GPIO_Pin_0); GPIO_SetBits(GPIOB, GPIO_Pin_9); GPIO_SetBits(GPIOC, GPIO_Pin_13); while(1) { /* PA0点亮 */ GPIO_ResetBits(GPIOA, GPIO_Pin_0); GPIO_SetBits(GPIOB, GPIO_Pin_9); GPIO_SetBits(GPIOC, GPIO_Pin_13); Delay_ms(1000); /* PB9点亮 */ GPIO_SetBits(GPIOA, GPIO_Pin_0); GPIO_ResetBits(GPIOB, GPIO_Pin_9); GPIO_SetBits(GPIOC, GPIO_Pin_13); Delay_ms(1000); /* PC13点亮 */ GPIO_SetBits(GPIOA, GPIO_Pin_0); GPIO_SetBits(GPIOB, GPIO_Pin_9); GPIO_ResetBits(GPIOC, GPIO_Pin_13); Delay_ms(1000); }2)LED亮/灭周期分析与Keil逻辑分析仪观察![]()
可以观察并计算得知
1. GPIO端口输出波形分析
从 Logic Analyzer 中可以看到三路输出波形(红、绿、蓝),分别对应代码中操作 GPIOA 的 PA0、PA1、PA2 端口。
- 波形特征:三路波形呈现顺序脉冲的形态,并非同时跳变。
- 代码逻辑对应:结合下方
main.c第71行GPIO_Write(GPIOA, led_pins[led_index]);以及注释001 -> 010 -> 100 -> 001可以断定:- 红色波形(通道0/PA0):表现为高电平(1),其余为低电平(0),对应
001。 - 绿色波形(通道1/PA1):表现为高电平(1),其余为低电平(0),对应
010。 - 蓝色波形(通道2/PA2):表现为高电平(1),其余为低电平(0),对应
100。 - 三者按照红 -> 绿 -> 蓝的顺序依次输出高电平,形成循环流水灯效果。
- 红色波形(通道0/PA0):表现为高电平(1),其余为低电平(0),对应
2. 时序状态正确性判断
时序状态完全正确。
- 逻辑正确:波形展示了
1->0,0->1的交替,完美执行了代码中依次递增led_index并查表输出的逻辑。 - 无重叠(No Overlap):从波形可以清晰看出,当一路(如红色)拉高时,另外两路必定处于低电平,未出现两个或三个LED同时点亮的情况。这严格符合代码注释中的要求
No overlap between ON states。
3. 高低电平转换周期(LED闪烁周期)计算
可以通过 Command 窗口底部的精准打印信息(UVA measured均为代码内部计时打印)来进行确切计算,这比肉眼观察示波器的时间轴更精确:
- 高电平持续时间:
PA0 HIGH = 0.99999994 s≈1.0 秒(即单个LED点亮的时间) - 低电平持续时间:
PA0 LOW = 1.99999903 s≈2.0 秒(即该LED熄灭、等待其他两个LED点亮的时间) - LED闪烁总周期:
PERIOD = 2.99999847 s≈3.0 秒(高电平 + 低电平 = 1 + 2 = 3秒)
结论:
代码逻辑正确,时序无重叠。系统以 1 秒为间隔顺序切换三路LED(每一路亮1秒、灭2秒),单个LED完整的闪烁周期为3秒。
实验任务3:HAL库+按键中断控制流水灯暂停/恢复
1)实验要求
改用HAL库控制流水灯,增加一个按键:第一次按下按键时暂停流水灯(当前LED保持点亮),再次按下按键时恢复运行,接着暂停前剩余时间继续计时。
2)HAL库按键中断配置
#include “stm32f1xx_hal.h”
#define KEY_PIN GPIO_PIN_11
#define KEY_PORT GPIOB
static void KEY_EXTI_Init(void)
{
GPIO_InitTypeDef gpio = {0};
__HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_AFIO_CLK_ENABLE(); gpio.Pin = KEY_PIN; gpio.Mode = GPIO_MODE_IT_FALLING; /* 下降沿触发 */ gpio.Pull = GPIO_PULLUP; /* 内部上拉 */ HAL_GPIO_Init(KEY_PORT, &gpio); /* 使能EXTI15_10中断线 */ HAL_NVIC_SetPriority(EXTI15_10_IRQn, 1U, 0U); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);4)中断处理与流水灯控制逻辑
采用“中断提交请求、主循环处理请求”的方法:中断函数只翻转一个状态标志,主循环检测标志后执行暂停/恢复逻辑。这样可以缩短中断执行时间,避免在中断中使用阻塞延时
/* 全局变量/
volatile uint8_t run_flag = 1; /1=运行,0=暂停 */
uint32_t last_tick = 0;
uint8_t led_index = 0;
/* 中断回调函数(由HAL_GPIO_EXTI_IRQHandler调用)/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == KEY_PIN)
{
run_flag = !run_flag; /翻转运行状态 */
}
}
/* LED控制函数/
void LED_Set(uint8_t index)
{
/先全部熄灭 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2, GPIO_PIN_RESET);
/* 点亮指定LED */ switch(index) { case 0: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); break; /* 蓝 */ case 1: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); break; /* 红 */ case 2: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); break; /* 绿 */ } /* 主循环 */ while (1) { if (run_flag) { uint32_t now = HAL_GetTick(); if (now - last_tick >= 1000) { last_tick = now; led_index = (led_index + 1) % 3; LED_Set(led_index); } } /* run_flag=0时,什么都不做,当前LED保持点亮 */ }5)中断处理流程说明
当按键按下时,PB11电平由高变低,产生下降沿,触发EXTI11中断:
硬件自动进入
EXTI15_10_IRQHandler()HAL库调用
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11)该函数清除中断标志位,然后调用弱定义的
HAL_GPIO_EXTI_Callback()重写的回调函数翻转
run_flag
流水灯hal控制
五、问题与改进
- 软件延时不准:实际项目应使用定时器中断或系统滴答定时器产生精确延时。
- 按键抖动:机械按键可能多次触发中断,可加 10~20ms 软件消抖或硬件 RC 滤波。
- PC13 板载 LED:低电平点亮,编程时注意逻辑反转。
- 逻辑分析仪使用:需勾选 Debug Information,添加信号时写完整名称如
GPIOA_ODR.0,否则可能显示 Unknown Signal。
六、心得体会
通过本次实验,掌握了 STM32 GPIO 的三种开发方式,理解了时钟使能、端口配置、电平控制、外部中断和软件仿真调试的基本流程。寄存器方式有助于深入理解硬件,标准库和 HAL 库则提高了开发效率。逻辑分析仪是验证时序的有效工具,按键中断实验进一步强化了“中断快进快出、主循环处理任务”的设计思想,为后续定时器、串口、RTOS 等复杂应用打下基础。
Information,添加信号时写完整名称如GPIOA_ODR.0,否则可能显示 Unknown Signal。