拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32 实验任务 2:标准外设库流水灯 + Keil 逻辑分析仪测波形

STM32 实验任务 2:标准外设库流水灯 + Keil 逻辑分析仪测波形

STM32 实验任务 2:标准外设库流水灯 + Keil 逻辑分析仪测波形

任务要求

在实验 1(寄存器方式流水灯)的基础上,改用STM32 标准外设库方式,使用 GPIOx 端口控制几个 LED 轮流闪烁,间隔 1 秒。要求:

  1. 写出工程项目创建、添加标准外设库文件(.c/.h)的详细过程;

  2. 用 Keil 软件仿真逻辑分析仪观察 GPIO 输出波形,分析时序是否正确、LED 闪烁周期实际为多少。


问 1:标准外设库工程怎么建?

1.1 准备工作:下载标准外设库

STM32F1 的标准外设库(STM32F10x_StdPeriph_Driver)从官方渠道下载后解压,里面Libraries目录有两大块:

  • CMSIS:芯片内核相关(core、设备头文件 stm32f10x.h、启动文件)

  • STM32F10x_StdPeriph_Driver:外设驱动(gpio、rcc、flash、usart 等,每个外设一个 .c + .h)

注意:本项目只用到了 GPIO、RCC、FLASH 三个外设,所以只把这三个驱动文件复制进工程,其他外设不添加,工程更干净。

1.2 创建工程文件夹

我在D:\Project\STM32Project\下创建工程目录:

stm32-led-stdlib/ ├── main.c ← 用户主程序 ├── stm32f10x\_conf.h ← 外设配置头文件(声明启用哪些外设) └── StdPeriph/ ← 标准外设库驱动文件 ├── stm32f10x\_gpio.c / .h ├── stm32f10x\_rcc.c / .h └── stm32f10x\_flash.c / .h

文件夹的作用:

  • StdPeriph/专门放标准外设库的驱动源码,和用户代码分开,结构清晰;

  • stm32f10x_conf.h是标准库的 “总开关”,它 include 哪些外设头文件,工程就能用哪些外设。

1.3 新建 Keil 工程

  1. Keil →Project → New uVision Project→ 定位到stm32-led-stdlib文件夹,工程名main→ 保存;

  2. 选择芯片STM32F103C8;

  3. 弹出 Manage Run-Time Environment 时,勾选:

  • CMSIS → CORE(提供内核头文件 core_cm3.h)

  • Device → Startup(提供启动文件和系统时钟初始化文件)

  • 点 OK。


1.4 添加源文件

左侧Source Group 1上右键 →Add Existing Files to Group...,添加 4 个文件:

  • main.c

  • StdPeriph\stm32f10x_gpio.c

  • StdPeriph\stm32f10x_rcc.c

  • StdPeriph\stm32f10x_flash.c

1.5 配置宏和头文件路径(重点!)

点魔术棒(Options for Target)→C/C++页:

配置项值说明
DefineUSE_STDPERIPH_DRIVER让 stm32f10x.h 自动包含 stm32f10x_conf.h
Include Paths.\;.\StdPeriph告诉编译器去哪找头文件
Language Cc99CMSIS 6.x 内核头文件要求

1.6 踩坑记录(编译报错排查)

坑 1:宏名写错 → 20 个错误

第一次编译报了一堆GPIO_InitTypeDef、GPIO_Pin_0未定义的错误。原因是:很多网络教程写的是USE_STDPERIPH_DEVICE,但我这台 Keil(STM32F1xx_DFP 2.4.0 包)里的 stm32f10x.h 实际要求的是:

\#ifdef USE\_STDPERIPH\_DRIVER   #include "stm32f10x\_conf.h" \#endif

宏名必须改成USE_STDPERIPH_DRIVER,标准库的所有定义才能被包含进来。

坑 2:conf.h 引用不存在的头文件

宏改对后又报stm32f10x_adc.h file not found。原因:官方模板stm32f10x_conf.h默认 include 了全部外设头文件(adc、can、dac……),而我工程里只添加了 gpio/rcc/flash 三个驱动。解决:把 conf.h 精简,只保留用到的三个:

\#include "stm32f10x\_flash.h" \#include "stm32f10x\_gpio.h" \#include "stm32f10x\_rcc.h"

编译通过,0 Error。

1.7 流水灯代码(标准外设库版)

\#include "stm32f10x.h" void delay\_ms(unsigned int ms) // 软件延时 { &#x20; volatile unsigned int i, j; &#x20; for (i = 0; i < ms; i++) &#x20; for (j = 0; j < 5000; j++) {} } int main(void) { &#x20; GPIO\_InitTypeDef GPIO\_InitStructure; &#x20; // 1. 使能 GPIOA/B/C 时钟 &#x20; RCC\_APB2PeriphClockCmd(RCC\_APB2Periph\_GPIOA | RCC\_APB2Periph\_GPIOB | &#x20; RCC\_APB2Periph\_GPIOC, ENABLE); &#x20; // 2. PA0 红灯 &#x20; GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_0; &#x20; GPIO\_InitStructure.GPIO\_Mode = GPIO\_Mode\_Out\_PP; &#x20; GPIO\_InitStructure.GPIO\_Speed = GPIO\_Speed\_50MHz; &#x20; GPIO\_Init(GPIOA, \&GPIO\_InitStructure); &#x20; // 3. PB0 绿灯(复用同一结构体) &#x20; GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_0; &#x20; GPIO\_Init(GPIOB, \&GPIO\_InitStructure); &#x20; // 4. PC14 蓝灯(PC13/14/15 低速引脚,2MHz) &#x20; GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_14; &#x20; GPIO\_InitStructure.GPIO\_Speed = GPIO\_Speed\_2MHz; &#x20; GPIO\_Init(GPIOC, \&GPIO\_InitStructure); &#x20; // 5. PC13 板载灯(低电平点亮) &#x20; GPIO\_InitStructure.GPIO\_Pin = GPIO\_Pin\_13; &#x20; GPIO\_Init(GPIOC, \&GPIO\_InitStructure); &#x20; // 6. 初始全灭 &#x20; GPIO\_ResetBits(GPIOA, GPIO\_Pin\_0); &#x20; GPIO\_ResetBits(GPIOB, GPIO\_Pin\_0); &#x20; GPIO\_ResetBits(GPIOC, GPIO\_Pin\_14); &#x20; GPIO\_SetBits(GPIOC, GPIO\_Pin\_13); &#x20; while (1) { &#x20; GPIO\_SetBits(GPIOA, GPIO\_Pin\_0); delay\_ms(1000); &#x20; GPIO\_ResetBits(GPIOA, GPIO\_Pin\_0); &#x20; GPIO\_SetBits(GPIOB, GPIO\_Pin\_0); delay\_ms(1000); &#x20; GPIO\_ResetBits(GPIOB, GPIO\_Pin\_0); &#x20; GPIO\_SetBits(GPIOC, GPIO\_Pin\_14); delay\_ms(1000); &#x20; GPIO\_ResetBits(GPIOC, GPIO\_Pin\_14); &#x20; GPIO\_ResetBits(GPIOC, GPIO\_Pin\_13); delay\_ms(1000); &#x20; GPIO\_SetBits(GPIOC, GPIO\_Pin\_13); &#x20; } }

相比实验 1 的寄存器操作,标准外设库把所有寄存器操作封装成了函数(GPIO_Init、GPIO_SetBits),可读性明显更好。


问 2:软件延时的 “1 秒” 到底准不准?

2.1 思路

LED 亮 / 灭靠delay_ms(1000)软件循环延时。它到底是精确 1 秒,还是存在误差?没有示波器的情况下,可以用Keil 软件仿真的逻辑分析仪直接观察 GPIO 波形、测量时间。

2.2 仿真配置步骤

  1. Target 页设置晶振:Xtal (MHz) 设为8.0(STM32F103C8T6 外部晶振 8MHz)。这一步很重要,仿真时间计算依赖它。我的 Keil 工程 Xtal 显示<undefined>改不动,直接修改工程文件main.uvprojx里的CLOCK(12000000)为CLOCK(8000000)解决;

  2. Debug 页:勾选Use Simulator+Run to main();Dialog DLL 填DARMSTM.DLL,Parameter 填-pSTM32F103C8;

  3. 点Ctrl+F5进入仿真;

  4. 菜单View → Analysis Windows → Logic Analyzer打开逻辑分析仪;

  5. 菜单View → Symbols Window,找到GPIOA_ODR,拖入逻辑分析仪窗口;

  6. 点逻辑分析仪里的Setup:

  • Display Type 选Bit(按位显示)

  • And Mask 填0x00000001(只看 bit0 = PA0)

  • 点 Close;

  1. 按F5全速运行,观察波形。

2.3 波形分析

PA0 是红灯的控制脚,波形如下(绿色方波):

从波形可以清楚看到:

  • 高电平段(红灯亮):0.879936s ≈0.88s

  • 低电平段(红灯灭,其他三灯轮流亮):约 3 秒

  • 完整周期(两个上升沿之间):3.519741s

验证自洽性:3.519741 ÷ 4 = 0.879935 ≈ 0.879936,正好等于单灯点亮时间—— 说明四盏灯确实是轮流各亮约 0.88s,程序逻辑和波形完全对应。

2.4 结论:软件延时准确吗?

以下是波形图图片数据记录


结论:不准确。实测数据如下:

项目理论值实测值误差
单灯点亮时间1.000 s0.879936 s约 12%
PA0 完整周期4.000 s3.519741 s约 12%

误差原因分析:

  1. 软件延时本质是 “数指令周期”:delay_ms里 5000 这个参数是按经验估的循环次数,而每条循环指令实际占几个时钟周期、volatile 变量访问的内存开销,都和估算有偏差;

  2. 编译优化等级(-O0)也影响循环开销;

  3. 所以软件延时的 “1 秒” 只能做到 “大约 1 秒”,误差在 10% 左右完全正常。

如果想精确到 1 秒,应该改用硬件定时器:SysTick 系统节拍定时器或 TIM 通用定时器(实验任务 3 的 HAL 库方案正是用 SysTick 实现精确延时,可以对比)。


总结

这个实验把 STM32 的开发方式从 “寄存器直接操作” 推进到了 “标准外设库函数调用”,工程从建文件夹、复制库文件、配置宏、到解决两个编译坑(宏名、conf.h),完整走了一遍标准外设库工程的搭建流程。更重要的是,通过 Keil 逻辑分析仪把 “软件延时到底准不准” 这个看不见的问题变成了看得见的波形和数据:实测 0.88s 与理论 1s 的 12% 误差,让我真正理解了为什么嵌入式开发里讲究 “能用定时器就别用软件延时”。

返回列表