文章目录流水灯实验实验目的时钟说明本板实测任务 1寄存器方式控制流水灯间隔 1s1.1 程序设计思路1.2 寄存器方式代码1.4效果展示1.5 补充说明任务 2标准外设库方式控制 LED 轮流闪烁间隔 1s2.1 工程项目创建与标准外设库添加步骤 1获取标准外设库步骤 2在 Keil 中新建工程并建文件夹分组步骤 3向各组添加文件步骤 4配置魔术棒Options for Target步骤 5生成 hex 文件烧录用2.2 间隔周期分析2.2.1 本工程采用 TIM2 定时器周期可精确计算2.2.2 Keil 软件仿真逻辑分析仪观察波形2.3 标准外设库代码2.4 效果展示任务 3HAL 库 按键中断控制流水灯暂停/恢复3.1 总体思路3.2 CubeMX 配置3.3 关键代码3.3.1 按键与 LED 的 GPIO 初始化gpio.c 节选3.3.2 中断服务函数stm32f1xx_it.c3.3.3 主循环 main.c 节选3.4 运行效果实验结论参考资料流水灯实验实验平台STM32F103C8T6 最小系统核心板Blue Pill 面包板 LED开发环境Keil MDK5µVision5 STM32 标准外设库 / HAL 库实验目的掌握 STM32 GPIO 端口的寄存器结构与配置方法用寄存器方式控制 GPIO输出实现流水灯掌握STM32 标准外设库工程搭建流程用库函数方式控制 LED 轮流闪烁并通过Keil 软件仿真逻辑分析仪观察波形、测定闪烁周期掌握HAL 库 EXTI 外部中断方式用按键中断控制流水灯的暂停与恢复。时钟说明本板实测核心板虽焊接了 8MHz 外部晶振HSE但实测未正常起振。因此三个任务的程序均以内部 HSI 8MHz为系统时钟运行标准外设库的SystemInit()在 HSE 起振超时后会自动回退到 HSI不会死等。该事实直接决定了软件延时周期的实测结论见任务 2。任务 1寄存器方式控制流水灯间隔 1s1.1 程序设计思路上电复位默认HSI 8MHz所有 GPIO 为浮空输入打开外设时钟使能 GPIOA / GPIOB / GPIOC 的 APB2 时钟配置引脚模式把 PA4 / PB0 / PC15 配为推挽输出主循环黄灯亮 1s → 灭 → 绿灯亮 1s → 灭 → 蓝灯亮 1s → 灭 → 循环软件延时空循环消耗指令周期8MHz 下每轮循环约 0.5µs1.2 寄存器方式代码#defineGPIOA_BASE0x40010800#defineGPIOB_BASE0x40010C00#defineGPIOC_BASE0x40011000#defineGPIOA_CRL(*(unsignedint*)0x40010800)#defineGPIOB_CRL(*(unsignedint*)0x40010C00)#defineGPIOC_CRH(*(unsignedint*)0x40011004)#defineGPIOA_ODR(*(unsignedint*)0x4001080C)#defineGPIOB_ODR(*(unsignedint*)0x40010C0C)#defineGPIOC_ODR(*(unsignedint*)0x4001100C)#defineRCC_APB2ENR(*(unsignedint*)0x40021018)voidSystemInit(void);voidDelay_ms(volatileunsignedint);voidA_LED_LIGHT(void);voidB_LED_LIGHT(void);voidC_LED_LIGHT(void);voidSystemInit(){}voidDelay_ms(volatileunsignedintt){unsignedinti;while(t--)for(i0;i800;i);}voidA_LED_LIGHT(){GPIOA_ODR~(0x14);//PA4低电平GPIOB_ODR|(0x10);//PB0高电平GPIOC_ODR|(0x115);//PC15高电平}voidB_LED_LIGHT(){GPIOA_ODR|(0x14);//PA4高电平GPIOB_ODR~(0x10);//PB0低电平GPIOC_ODR|(0x115);//PC15高电平}voidC_LED_LIGHT(){GPIOA_ODR|(0x14);//PA4高电平GPIOB_ODR|(0x10);//PB0高电平GPIOC_ODR~(0x115);//PC15低电平}intmain(){// 开启时钟RCC_APB2ENR|(13);// 开启 GPIOB 时钟RCC_APB2ENR|(14);// 开启 GPIOC 时钟RCC_APB2ENR|(12);// 开启 GPIOA 时钟// 设置 GPIO 为推挽输出GPIOA_CRL0xfff0ffff;//设置位 清零GPIOA_CRL|0x00010000;//PA4推挽输出GPIOB_CRL0xfffffff0;//设置位 清零GPIOB_CRL|0x00000001;//PB0推挽输出GPIOC_CRH0x0fffffff;//设置位 清零GPIOC_CRH|0x10000000;//PC15推挽输出// 3个LED初始化为不亮即高点位GPIOA_ODR|(0x14);GPIOB_ODR|(0x10);GPIOC_ODR|(0x115);while(1){B_LED_LIGHT();Delay_ms(1000);C_LED_LIGHT();Delay_ms(1000);A_LED_LIGHT();Delay_ms(1000);}}1.4效果展示1.5 补充说明更多内容见作者上期内容任务 2标准外设库方式控制 LED 轮流闪烁间隔 1s2.1 工程项目创建与标准外设库添加步骤 1获取标准外设库从 ST 官网下载 STM32F10x 标准外设库解压后需要的关键文件步骤 2在 Keil 中新建工程并建文件夹分组Project → New µVision Project工程名如LED_StdPeriph芯片选STM32F103C8在弹出的 “Manage Run-Time Environment” 中直接点 OK不勾选 RTE 组件在Project窗口按组新建文件夹右键 Target 1 → Add GroupCORE内核与启动文件以及系统文件system_stm32f10x.cUSER用户代码main.c / led.c / delay.c / stm32f10x_it.c 等DRIVER标准外设库源文件步骤 3向各组添加文件分组添加的文件COREstartup_stm32f10x_md.s、system_stm32f10x.cDRIVERmisc.c、stm32f10x_gpio.c、stm32f10x_rcc.c用到定时器再加stm32f10x_tim.cUSER自己编写的main.c、led.c、delay.c、stm32f10x_it.c、对应.h头文件。步骤 4配置魔术棒Options for TargetC/C 页 → Define输入两个宏大小写敏感USE_STDPERIPH_DRIVER, STM32F10X_MDUSE_STDPERIPH_DRIVER启用标准外设库STM32F10X_MD声明芯片为中容量F103C8T6 属 MDC/C 页 → Include Paths添加头文件路径..\CORE; ..\SYSTEM; ..\DRIVER\inc; ..\USER按实际目录结构调整可用相对路径Debug 页仿真调试勾选Use Simulator用于任务 2.2 逻辑分析仪观测编译F70 Error / 0 Warning 即工程搭建成功。步骤 5生成 hex 文件烧录用Output 页 → Create HEX File勾选编译后在工程目录生成.hex用串口下载工具 FlyMcu烧录。2.2 间隔周期分析2.2.1 本工程采用 TIM2 定时器周期可精确计算TIM_TimeBaseStructure.TIM_Period999;/* 自动重装载值 ARR */TIM_TimeBaseStructure.TIM_Prescaler7999;/* 预分频值 PSC */计数频率 TIM2时钟 / (PSC1) 8MHz / 8000 1kHz每 1ms 计一个数 中断周期 (ARR1) × 1ms (9991) × 1ms 1000ms 1s板子实测约 1s/步HSE 未起振系统跑 HSI 8MHz与计算一致定时器计数不依赖指令条数周期精确稳定这是定时器方案优于软件延时的根本原因。2.2.2 Keil 软件仿真逻辑分析仪观察波形① 仿真配置关键参照工程实际已调通Options for Target → Debug 页 ☑ Use Simulator Dialog DLL ParameterDARMSTM.DLL -pSTM32F103C8 CPU DLL ParameterSARMCM3.DLL -REMAP若 Dialog DLL 仍为默认的 DCM.DLL运行会报violation at 0x40000000 : no write permission通用 DLL 无 STM32 外设模型。② 添加调试观察变量工程已内置在led.c中定义调试全局变量volatileuint16_tg_led_state0x000E;/* 与 led_current 同步供逻辑分析仪观察 */并在LED_RotateLeft()末尾执行g_led_state led_current;。之所以不用 GPIOA 宏而是用真实全局变量是因为逻辑分析仪不认宏定义只能观察真实符号。③ 添加信号并观察CtrlF5进入仿真 →View → Analysis Windows → Logic AnalyzerSetup…添加 4 个位表达式每个灯一条线避免 16 位变量的多余位叠加显示g_led_state 0x1 ← PA0 g_led_state 0x2 ← PA1 g_led_state 0x4 ← PA2 g_led_state 0x8 ← PA3Display Type 均选Bit工具栏Update Screen点亮F5全速运行足够时间 →Esc暂停 →All显示全部④ 实测结果本实验项目仿真实测板子实测LED 高/低电平转换周期约111ms/步约1s/步波形形态4 条线轮流变低、循环流水4 个 LED 轮流点亮时序状态正确亮灭交替、顺序正确正确为什么仿真≈111ms 而板子≈1sKeil 仿真器默认按72MHz模拟指令/外设时序假定 HSE 起振、PLL 到 72MHz而本板实际跑HSI 8MHz仿真计数频率 72MHz / 8000 9kHz → 999 / 9kHz ≈ 111ms/步 板子计数频率 8MHz / 8000 1kHz → 999 / 1kHz 999ms/步波形时序本身是正确的差异只来自仿真假设时钟 ≠ 板子实际时钟二者相差正好 9 倍2.3 标准外设库代码main.c主程序配置中断分组 → 初始化外设 → 主循环空转#includestm32f10x.h#includeled.h#includetim.hintmain(void){/* 0. 中断优先级分组2位抢占 2位子优先级必须在配置中断前设置 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);/* 1. 初始化外设LED(PA0~PA3) TIM2(1s更新中断) */LED_GPIO_Init();TIM2_Init();/* 2. 主循环空转流水灯的移位全部由 TIM2 中断驱动 */while(1){}}led.cLED 初始化 流水移位 调试变量#includeled.h#defineLED_ON_LEVEL0/* 低电平点亮 */staticuint16_tled_current0x000E;/* bit0~bit3 对应 PA0~PA3初值 1110bPA0 亮 */volatileuint16_tg_led_state0x000E;/* 调试变量供逻辑分析仪观察 */voidLED_GPIO_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);/* 开 GPIOA 时钟 */GPIO_InitStructure.GPIO_PinGPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;/* 推挽输出 */GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_Write(GPIOA,led_current);/* 初始只亮 PA0 */}voidLED_RotateLeft(void){if(led_current0x0007)/* 已移到最后一个灯 PA3 */{led_current0x000E;/* 回到 PA0循环 */}else{/* 左移把灭的位置推高|0x0001 补位0x000F 只留低4位 */led_current((led_current1)|0x0001)0x000F;}GPIO_Write(GPIOA,led_current);/* 写回端口 */g_led_stateled_current;/* 同步调试变量 */}tim.cTIM2 配置8MHz 下 1s 更新中断#includetim.hvoidTIM2_Init(void){TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);/* 开 TIM2 时钟 *//* 计数频率 8MHz/8000 1kHz中断周期 1000 × 1ms 1s */TIM_TimeBaseStructure.TIM_Period999;TIM_TimeBaseStructure.TIM_Prescaler7999;TIM_TimeBaseStructure.TIM_ClockDivisionTIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterModeTIM_CounterMode_Up;TIM_TimeBaseInit(TIM2,TIM_TimeBaseStructure);TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);/* 使能更新中断 */NVIC_InitStructure.NVIC_IRQChannelTIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority1;NVIC_InitStructure.NVIC_IRQChannelSubPriority0;NVIC_InitStructure.NVIC_IRQChannelCmdENABLE;NVIC_Init(NVIC_InitStructure);TIM_Cmd(TIM2,ENABLE);/* 启动定时器 */}stm32f10x_it.c中断服务函数节选voidTIM2_IRQHandler(void){if(TIM_GetITStatus(TIM2,TIM_IT_Update)!RESET){LED_RotateLeft();/* 每 1 秒移一位 → 流水 */TIM_ClearITPendingBit(TIM2,TIM_IT_Update);/* 必须清标志否则反复进中断 */}}2.4 效果展示任务 3HAL 库 按键中断控制流水灯暂停/恢复3.1 总体思路按键PB5按下 → 触发 EXTI外部中断下降沿→ 进入 EXTI9_5 中断 → HAL_GPIO_EXTI_IRQHandler(Pin5)→ 回调 HAL_GPIO_EXTI_Callback 中翻转运行标志 run_flag主循环中run_flag 1 → 流水继续run_flag 0 → 流水暂停保持当前状态3.2 CubeMX 配置配置项设置SYSDebug: Serial Wire保留 SWD 下载RCC不使用 HSE本板未起振Clock Source 保持 HSI主频 8MHzGPIO 输出PA0~PA3或 PB0~PB2→ Output Push PullGPIO 输入PB5→ GPIO_EXTI5上拉Pull-up下降沿FallingNVIC使能EXTI9_5_IRQn工程Toolchain MDK-ARM V53.3 关键代码3.3.1 按键与 LED 的 GPIO 初始化gpio.c 节选/* 使能 GPIOA/GPIOB 时钟PB5 属 GPIOB */__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();GPIO_InitTypeDef GPIO_InitStruct{0};/* --- LEDPA0~PA3 推挽输出 --- */GPIO_InitStruct.PinGPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;GPIO_InitStruct.ModeGPIO_MODE_OUTPUT_PP;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA,GPIO_InitStruct);/* --- 按键PB5 上拉输入 EXTI 下降沿中断 --- */GPIO_InitStruct.PinGPIO_PIN_5;GPIO_InitStruct.ModeGPIO_MODE_IT_FALLING;// 下降沿触发中断GPIO_InitStruct.PullGPIO_PULLUP;// 内部上拉松开高按下低HAL_GPIO_Init(GPIOB,GPIO_InitStruct);/* 使能 EXTI5 中断5~9 线共用 EXTI9_5_IRQn 向量 */HAL_NVIC_SetPriority(EXTI9_5_IRQn,2,0);HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);3.3.2 中断服务函数stm32f1xx_it.c/* EXTI 第 5 线中断入口5~9 线共用一个向量必须叫这个名字 */voidEXTI9_5_IRQHandler(void){/* 清除挂起位并调用 HAL 回调 */HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);}3.3.3 主循环 main.c 节选volatileuint8_trun_flag1;// 1运行 0暂停volatileuint32_tlast_key_time0;// 上次按键时刻防抖/* EXTI 回调按键按下时翻转运行标志 */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_PinGPIO_PIN_5){uint32_tnowHAL_GetTick();// 当前系统毫秒/* 防抖两次触发间隔必须 ≥50ms滤掉机械抖动产生的连续沿 */if(now-last_key_time50){run_flag!run_flag;// 翻转暂停 ↔ 继续last_key_timenow;// 更新上次触发时刻}}}/* 流水灯移位PA0~PA3低电平点亮共阳接法 */staticvoidLED_RotateLeft(void){staticuint16_tled0x000E;// 初值 1110bPA0 亮led(led1)|(led3);// 4 位循环左移HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3,(led0xF)?GPIO_PIN_RESET:GPIO_PIN_SET);}intmain(void){HAL_Init();SystemClock_Config();// HSI 8MHz无 PLL见 3.2 说明MX_GPIO_Init();// LED 按键 EXTI 初始化while(1){if(run_flag)// 运行状态才流水{LED_RotateLeft();// 移动一位HAL_Delay(1000);// 间隔 1s}/* 暂停时不做任何事灯保持当前状态 */}}3.4 运行效果上电流水灯每 1s 移一位循环流水按一下按键暂停当前灯保持点亮不再移动再按一下恢复继续流水防抖生效快速连按不会产生“一次按键翻转多次”的现象。实验结论寄存器方式下GPIO 操作的本质是先开时钟APB2ENR→ 再配模式CRL/CRH 的 MODECNF→ 通过 BSRR/BRR/ODR 输出高低电平。三个版本寄存器/标准库/HAL做的是同一件事只是封装层次不同。标准外设库工程需要手动添加启动文件、内核文件、外设驱动库并配置Define宏与Include Paths软件循环延时的周期取决于时钟频率与编译产物误差较大精确测量应使用逻辑分析仪或示波器——本实验仿真实测约 111ms/步源于仿真器按 72MHz 模拟而板子实际 8MHz差 9 倍波形时序本身正确。HAL 库 EXTI 中断方案中按键用上拉输入 下降沿触发通过EXTI9_5_IRQHandler → HAL_GPIO_EXTI_IRQHandler → HAL_GPIO_EXTI_Callback链路翻转运行标志配合 50ms 间隔防抖可稳定实现暂停/恢复。参考资料基于寄存器与基于固件库的 STM32 LED 流水灯CSDNhttps://blog.csdn.net/Xkccsdn147/article/details/133796943https://blog.csdn.net/qq_43279579/article/details/110320013STM32 标准外设库官方下载地址ST 官网https://www.st.com/zh/embedded-software/stm32-standard-peripheral-libraries.html
