文章目录
- 实验目的
- 实验环境
- 一、实验任务 1:寄存器直接操作方式实现流水灯
- 1.1 程序设计思路
- 1.2 关键寄存器地址与参数
- 1.3 寄存器版完整代码
- 二、实验任务 2:标准外设库(SPL)方式实现流水灯
- 2.1 工程创建与库文件添加详细过程
- 2.2 标准库完整代码(包含 PC13 板载 LED)
- 2.3 软件延时周期分析
- 2.4 Keil 逻辑分析仪仿真结果分析
- 三、实验任务 3(拓展)HAL 库 + 外部中断按键控制流水灯暂停恢复
- 实验现象
- 实验问题与分析
- 实验总结
实验目的
- 掌握 STM32F103C8T6 GPIO 寄存器底层编程,理解 GPIO 相关寄存器功能与地址。
- 掌握 STM32 标准外设库(SPL)工程搭建流程,学会添加 CMSIS、标准库源文件。
- 实现 GPIO 端口控制 LED 流水闪烁,将板载 PC13 指示灯加入流水灯序列。
- 掌握软件循环延时原理,使用 Keil 逻辑分析仪软件仿真观察 GPIO 输出波形,分析时序。
- 理解寄存器编程与标准库编程两种开发方式的区别。
实验环境
- 硬件:STM32F103C8T6 最小系统板、LED 若干、ST‑Link 下载器
- 软件:Keil MDK‑ARM5,STM32F10x_StdPeriph_Lib_V3.5.0 标准固件库
- 仿真:Keil Logic Analyzer 逻辑分析仪软件仿真
一、实验任务 1:寄存器直接操作方式实现流水灯
1.1 程序设计思路
- 开启 APB2 外设时钟:GPIOA、GPIOC 挂载在 APB2 总线上,开启 RCC APB2 使能位,给端口提供工作时钟。
- GPIO 配置寄存器:
CRL:端口低八位模式配置寄存器,配置推挽输出模式(0011),最大输出速度 50MHz。ODR:端口输出数据寄存器,写 0 输出低电平,写 1 输出高电平,控制 LED 亮灭。
- 板载 LED 为PC13,C8T6 最小系统板 PC13LED 一般是低电平点亮。
- 写软件循环延时函数,空循环消耗 CPU 时间实现毫秒延时。
- 循环遍历 PA0‑PA4、PC13,依次拉低点亮 LED,延时 1s,拉高熄灭,实现轮询流水闪烁。
1.2 关键寄存器地址与参数
STM32F103 寄存器基地址:
表格
| 寄存器 | 基地址 | 说明 |
|---|---|---|
| RCC 基地址 | 0x40021000 | 复位时钟控制单元 |
| RCC‑APB2ENR | 0x40021018 | APB2 外设时钟使能寄存器Bit2:GPIOA 时钟;Bit4:GPIOC 时钟 |
| GPIOA 基地址 | 0x40010800 | GPIOA 寄存器起始地址 |
| GPIOA‑CRL | 0x40010800 | PA0‑PA7 模式配置寄存器 |
| GPIOA‑ODR | 0x4001080C | GPIOA 输出寄存器 |
| GPIOC 基地址 | 0x40011000 | GPIOC 寄存器起始地址 |
| GPIOC‑CRH | 0x40011004 | PC8‑PC15 模式配置寄存器(PC13 在此寄存器) |
| GPIOC‑ODR | 0x4001100C | GPIOC 输出寄存器 |
模式说明:通用推挽输出
MODE=11(50MHz) CNF=00,配置值0x3。
1.3 寄存器版完整代码
#include "stm32f10x.h" /** * @brief 软件ms延时,72MHz主频 */ void Delay_ms(uint32_t ms) { uint32_t i,j; for(i = 0; i < ms; i++) for(j = 0; j < 12000; j++); } int main(void) { //1.开启GPIOA、GPIOC时钟 RCC_APB2ENR *(volatile unsigned int *)0x40021018 |= (1<<2); //开启GPIOA时钟 *(volatile unsigned int *)0x40021018 |= (1<<4); //开启GPIOC时钟 //2.配置PA0‑PA4为推挽输出50MHz CRL寄存器 *(volatile unsigned int *)0x40010800 &= ~0x000FFFFF; *(volatile unsigned int *)0x40010800 |= 0x00033333; //3.配置PC13推挽输出50MHz,CRH寄存器 PC13对应bit20‑23 *(volatile unsigned int *)0x40011004 &= ~(0xF << 20); *(volatile unsigned int *)0x40011004 |= (0x3 << 20); //全部LED初始熄灭:PA0‑PA4置1;PC13置1(板载LED高电平熄灭) *(volatile unsigned int *)0x4001080C |= (0x1F); *(volatile unsigned int *)0x4001100C |= (1<<13); //LED引脚掩码数组:PA0,PA1,PA2,PA3,PA4,PC13 uint32_t led_mask[] = {1<<0,1<<1,1<<2,1<<3,1<<4, 1U<<13}; while(1) { for(uint8_t i = 0; i < 6; i++) { if(i <5) { //PA口LED:置0点亮 *(volatile unsigned int *)0x4001080C &= ~led_mask[i]; Delay_ms(1000); *(volatile unsigned int *)0x4001080C |= led_mask[i]; } else { //PC13板载LED:置0点亮 *(volatile unsigned int *)0x4001100C &= ~led_mask[i]; Delay_ms(1000); *(volatile unsigned int *)0x4001100C |= led_mask[i]; } } } }二、实验任务 2:标准外设库(SPL)方式实现流水灯
2.1 工程创建与库文件添加详细过程
- 新建工程目录
在桌面英文路径新建总工程文件夹,内部新建子文件夹:
Project:存放 keil 工程文件,.uvprojxCMSIS:存放内核启动文件StdPeriph_Driver:标准外设库源码 inc、srcUser:main.c、stm32f10x_it.c、stm32f10x_conf.hOutput:输出 hex、编译中间文件
- 复制 CMSIS 内核文件
从官方库Libraries\CMSIS\CM3\CoreSupport复制core_cm3.c core_cm3.h;
从DeviceSupport\ST\STM32F10x复制system_stm32f10x.c/h;
启动文件:Libraries\CMSIS\startup\arm\startup_stm32f10x_md.s(C8T6 Flash64K 中等密度 md 版本)复制进 CMSIS。 - 复制标准外设库驱动
Libraries\STM32F10x_StdPeriph_Driver下inc、src整个拷贝;添加misc.c/h。 - 复制模板配置文件
官方模板Project\STM32F10x_StdPeriph_Template复制stm32f10x_conf.h、stm32f10x_it.c/h放入 User 文件夹。 - Keil 工程分组创建
CMSIS组:core_cm3.c、system_stm32f10x.c、startup_stm32f10x_md.sStdPeriph组:各个外设.c源文件(gpio、rcc、misc)User组:main.c、stm32f10x_it.c
- 配置头文件包含路径
魔术棒→C/C++,添加 include 搜索路径:./CMSIS``./StdPeriph_Driver/inc``./User - 预定义宏:
STM32F10X_MD,USE_STDPERIPH_DRIVER - 芯片选型:
STM32F103C8;Output 勾选生成.hex文件。
2.2 标准库完整代码(包含 PC13 板载 LED)
2.3 软件延时周期分析
软件延时依靠 CPU 空循环实现,延时精度受编译器优化等级影响。
实验设置‑O0零优化,72MHz 下内层循环 12000 次大致消耗 1ms;开启优化后循环会被编译器优化删除,延时失效。
缺点:软件延时占用 CPU,无法做别的任务;不精准,是粗略延时。
2.4 Keil 逻辑分析仪仿真结果分析
- Debug 设置选择
Use Simulator软件仿真;逻辑分析仪添加信号:PORTA.0~PORTA.4,设置显示类型Bit位模式。 - 全速运行仿真,采集 GPIO 端口方波波形。
- 仿真现象:
- 波形高低电平变化顺序与流水灯逻辑完全符合,引脚依次拉低、延时、拉高。
- ⚠️受精简版 Keil 仿真 DLL 缺失影响,软件仿真内部时钟和真实 72MHz 不一致,仿真工具显示的时间轴周期不等于实物 1s。
- 实物下载到开发板,72MHz 系统时钟下,闪烁周期为 1 秒。
仿真价值:不需要硬件电路板,提前验证 GPIO 输出逻辑是否正确,排查代码逻辑错误。
三、实验任务 3(拓展)HAL 库 + 外部中断按键控制流水灯暂停恢复
简要写进报告,可直接编译
功能:按键触发外部中断,翻转运行状态,控制流水灯暂停 / 继续
#include "stm32f1xx_hal.h" GPIO_InitTypeDef GPIO_InitStruct; EXTI_HandleTypeDef hexti; uint8_t run_flag = 1; //1运行,0暂停 //中断回调函数 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == GPIO_PIN_0) { run_flag = !run_flag; } } int main(void) { HAL_Init(); SystemClock_Config(); //LED初始化省略 //PA0按键下降沿外部中断 __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_NVIC_EnableIRQ(EXTI0_IRQn); HAL_NVIC_SetPriority(EXTI0_IRQn,2,2); while(1) { if(run_flag ==1) { //流水灯执行代码 } } }实验现象
- 寄存器版本:下载到 STM32 最小系统板,PA0‑PA4 外接 LED、PC13 板载 LED,依次点亮,每个灯亮 1s,循环往复。
- 标准库版本:现象和寄存器版本完全一致。
- 逻辑分析仪仿真:软件仿真可以看到对应 GPIO 引脚周期性高低电平方波,电平变换顺序符合流水灯逻辑。
- 拓展 HAL 中断实验:按下按键,流水灯暂停;再次按下,恢复流水闪烁。
实验问题与分析
- 上电 LED 全亮
原因:GPIO 复位默认电平,初始化完成之后需要手动 SetBits,把 LED 全部置为熄灭状态。
解决:初始化结束后执行GPIO_SetBits,全部引脚输出高电平。
- 软件仿真时间和实物时间不一致
原因:Keil 精简版缺少 STM32 仿真外设 DLL,无法设置 72M 仿真时钟。
解决:仿真只看逻辑顺序,时间以实物开发板为准。
- PC13 板载 LED 电平相反
最小系统板 PC13LED 是共阳极,输出低电平点亮,高电平熄灭,和普通 PA 口 LED 逻辑保持一致。
- 软件延时函数失效
编译器优化等级调高,空 for 循环会被直接优化删除。解决:调试保持‑O0 优化;或者使用 HAL 库 HAL_Delay(SysTick 定时器硬件延时)。
实验总结
- 寄存器编程直接操作内存地址,执行效率高,但可读性差;标准库对寄存器封装,代码可读性强,开发效率高。
- GPIO 使用前必须开启对应总线时钟,否则端口不工作。
- PC13 板载 LED 可以当作普通 GPIO 使用,注意硬件点亮电平。
- 软件循环延时属于阻塞延时,精度有限,适合简单实验;实际产品建议定时器中断延时。
- Keil 逻辑分析仪软件仿真,不用硬件即可验证 IO 输出逻辑,可以快速定位代码逻辑 bug。