一、实验目的
1. 在实验一(HAL库四灯流水灯)的基础上,掌握使用STM32标准外设库(Standard Peripheral Library,SPL)控制GPIO端口实现LED流水灯的方法。
2. 掌握在Keil5(MDK-ARM)中手动建立工程、添加STM32标准外设库文件(.c、.h)的完整过程。
3. 理解软件循环延时实现LED亮灭周期的原理,并分析其准确周期。
4. 对比标准外设库与HAL库两种开发方式的异同。
二、实验器材
| 器材 | 说明 |
|---|---|
| STM32F103最小系统板 | Cortex-M3内核,72MHz主频,自带PC13板载LED |
| ST-Link下载器 | 程序烧录与调试 |
| LED发光二极管 × 3 | 红、绿、蓝各一只(外部LED) |
| 面包板及杜邦线 | 电路连接 |
| 220Ω限流电阻 × 3 | 保护外部LED和GPIO引脚 |
| PC电脑 + Keil5(MDK-ARM)+ 标准外设库 | 开发环境 |
三、实验原理
3.1 标准外设库概述
STM32标准外设库(SPL)是ST官方提供的一套底层固件库,将寄存器操作封装成函数,例如GPIO_Init()配置引脚、RCC_APB2PeriphClockCmd()使能时钟。相比HAL库,标准外设库更贴近寄存器、代码简洁,是Keil5下经典的STM32开发方式。
3.2 GPIO控制原理
标准外设库中,引脚配置通过GPIO_InitTypeDef结构体与GPIO_Init()完成;电平控制通过GPIO_SetBits()(高电平)和GPIO_ResetBits()(低电平)实现。本实验沿用实验一的四个LED及电平逻辑:PC13板载LED低电平点亮,PC14、PA4、PB1三个外部LED高电平点亮。
四、实验步骤
(一)在Keil5中创建工程并添加标准外设库文件
1. 下载STM32F10x标准外设库(如STM32F10x_StdPeriph_Lib_V3.5.0),解压后记录Libraries目录位置,其中包含CMSIS与STM32F10x_StdPeriph_Driver两个子目录。
2. 启动Keil5(μVision),点击菜单 Project → New μVision Project,选择工程保存路径并命名(如LEDflow_SPL),保存。
3. 在弹窗中选择芯片型号:ARM → STMicroelectronics → STM32F1xx → STM32F103 → 选择STM32F103RCT6(或C8T6),点击OK。
4. 系统提示"Copy STM32 Startup Code to Project Folder?(是否拷贝启动文件)“,选择"是”,工程自动添加启动文件startup_stm32f10x_hd.s。
5. 在左侧Project窗口右键Target1,选择Manage Project Items,建立如下分组并添加库文件:
LEDflow_SPL(工程) ├─ User/# main.c、stm32f10x_it.c ├─ StdPeriph_Driver/│ ├─ stm32f10x_gpio.c │ ├─ stm32f10x_rcc.c │ └─ stm32f10x_flash.c ├─ Startup/# startup_stm32f10x_hd.s └─ CMSIS/├─ core_cm3.c └─ system_stm32f10x.c6. 添加头文件路径:点击工程按钮 Options for Target(或Alt+F7),切换到 C/C++ 选项卡,在 Include Paths 中依次添加:
CMSIS STM32F10x_StdPeriph_Driver/inc User7. 在 C/C++ 选项卡的 Define 框填入宏定义:USE_STDPERIPH_DRIVER, STM32F10X_HD(大容量芯片用HD,中容量用MD)。
8. 配置下载调试器:切换到 Debug 选项卡,选择 ST-Link Debugger,再进入 Flash Download 勾选 Reset and Run,便于下载后自动运行。
9. 将编写好的main.c放入User分组,点击编译(F7),无误后点击下载(F8)烧录到开发板。
(二)编写主程序
main.c中使用标准外设库函数完成时钟使能、GPIO配置与四灯流水控制,代码如下:
#include"stm32f10x.h"voidRCC_Configuration(void);voidGPIO_Configuration(void);voidDelay(__IOuint32_tnCount);intmain(void){RCC_Configuration();GPIO_Configuration();while(1){// 1. PC13板载LED亮,其余灭GPIO_ResetBits(GPIOC,GPIO_Pin_13);GPIO_SetBits(GPIOC,GPIO_Pin_14);GPIO_SetBits(GPIOA,GPIO_Pin_4);GPIO_SetBits(GPIOB,GPIO_Pin_1);Delay(0x00100000);// 2. PC14蓝灯亮,其余灭GPIO_SetBits(GPIOC,GPIO_Pin_13);GPIO_SetBits(GPIOC,GPIO_Pin_14);GPIO_SetBits(GPIOA,GPIO_Pin_4);GPIO_SetBits(GPIOB,GPIO_Pin_1);Delay(0x00100000);// 3. PA4红灯亮,其余灭GPIO_SetBits(GPIOC,GPIO_Pin_13);GPIO_ResetBits(GPIOC,GPIO_Pin_14);GPIO_SetBits(GPIOA,GPIO_Pin_4);GPIO_SetBits(GPIOB,GPIO_Pin_1);Delay(0x00100000);// 4. PB1绿灯亮,其余灭GPIO_SetBits(GPIOC,GPIO_Pin_13);GPIO_ResetBits(GPIOC,GPIO_Pin_14);GPIO_ResetBits(GPIOA,GPIO_Pin_4);GPIO_SetBits(GPIOB,GPIO_Pin_1);Delay(0x00100000);}}voidRCC_Configuration(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);}voidGPIO_Configuration(void){GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;GPIO_Init(GPIOC,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;GPIO_Init(GPIOC,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;GPIO_Init(GPIOB,&GPIO_InitStructure);// 上电全部熄灭GPIO_SetBits(GPIOC,GPIO_Pin_13);GPIO_ResetBits(GPIOC,GPIO_Pin_14);GPIO_ResetBits(GPIOA,GPIO_Pin_4);GPIO_ResetBits(GPIOB,GPIO_Pin_1);}voidDelay(__IOuint32_tnCount){for(;nCount!=0;nCount--);}五、软件循环延时周期分析
LED的亮灭周期通过软件空循环Delay()实现:
voidDelay(__IOuint32_tnCount){for(;nCount!=0;nCount--);}该函数的核心是一条空for循环,每一轮循环执行的耗时取决于以下三个因素:
| 因素 | 说明 |
|---|---|
| 系统主频 | 本实验为72MHz,一个CPU时钟周期约1/72MHz ≈ 13.9ns |
| 编译优化等级 | Keil中默认-O0不优化时循环完整保留;优化等级高时可能被优化掉导致延时失效 |
| 汇编指令数 | -O0下每轮约3~4条指令(加载、减法、比较、跳转),共约4个CPU周期 |
因此在72MHz、Keil默认-O0条件下,每一轮循环耗时的近似值为:
单轮耗时 ≈4×13.9ns ≈55ns由此可估算延时:延时 ≈ nCount × 55ns。例如Delay(0x00100000)(1048576次)约延时57ms;要接近1秒,nCount应约为18×10^6(约0x1100000)。
结论:软件循环延时周期的准确值不是固定常数,它依赖主频、编译优化等级和编译器版本,只能给出量级估算。若要获得精确、稳定的1秒延时,应改用SysTick定时器(调用SysTick_Config())或硬件定时器实现。
六、实验现象
上电后,四个LED按照以下顺序循环点亮,每一盏灯点亮约0.8~1秒(由Delay()循环次数决定):
PC13板载LED亮 → PC14蓝灯亮 → PA4红灯亮 → PB1绿灯亮 → 循环。
任意时刻只有一个LED点亮,实现亮灯依次流动的流水灯效果。
使用逻辑分析仪观测DBG_PA0、DBG_PA1、DBG_PA2三个LED引脚,得到如图1所示的时序波形:三个通道输出周期性方波,波形之间存在固定的时序偏移,印证了四灯按顺序依次点亮的流水灯时序。
七、实验中遇到的问题及解决方法
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 编译报错找不到头文件 | 未在Include Paths中添加头文件路径 | 在Options for Target的C/C++中配置Include Paths |
| GPIO无效或时钟异常 | 未使能GPIO端口时钟 | 调用RCC_APB2PeriphClockCmd()使能对应端口时钟 |
| 条件编译报错 | 缺少宏定义 | 在C/C++的Define中添加USE_STDPERIPH_DRIVER与STM32F10X_HD |
| 延时周期不准 | 空循环延时依赖主频与优化等级 | 改用SysTick或硬件定时器实现精确延时 |
八、实验总结
本次实验在实验一的基础上,改用STM32标准外设库,通过Keil5完成了四灯流水灯实验。通过手动添加标准外设库文件,掌握了Keil5工程的建立、分组管理、头文件路径配置与宏定义设置方法;理解了标准外设库与HAL库在GPIO配置和电平控制上的差异。同时通过软件循环延时实验,认识到空循环延时周期的估算方法及其不精确、依赖编译环境的特性,为后续使用定时器实现精确延时的学习打下基础。