STM32F103C8T6 基于寄存器的四灯流水灯实验报告
目录
一、摘要
二、实验任务与目标
2.1 实验任务
2.2 学习目标
三、实验器材与安全注意事项
3.1 实验器材
3.2 硬件连接说明
四、实验环境与工程建立
4.1 软件环境
4.2 创建 Keil 工程
4.3 添加启动汇编文件
4.4 新建 main.c
4.5 配置工程选项
五、寄存器地址与 GPIO 原理
六、程序实现与逐段说明
6.1 寄存器地址宏定义
6.2 软件延时函数
6.3 main() 初始化部分
6.4 while(1) 流水灯逻辑
七、编译、下载与调试过程
八、实验现象
九、实验结果分析
十、实验总结
一、摘要
本实验以 STM32F103C8T6 最小系统核心板为实验对象,使用 Keil MDK-5 搭建一个不依赖 HAL 库和标准外设库的裸寄存器工程。实验在面包板上连接红、蓝、绿三只外接 LED,并利用核心板上的 PC13 板载 LED,共设置四个流水灯输出端口。程序通过直接访问 STM32F103C8T6 的寄存器地址,先使用 RCC_APB2ENR 开启 GPIOA、GPIOB 和 GPIOC 的外设时钟,再使用 GPIOx_CRL 或 GPIOx_CRH 将指定引脚配置为通用推挽输出,最后使用 BSRR 和 BRR 控制各引脚输出高、低电平。
程序采用软件循环延时,每隔约 1 s 点亮其中一盏 LED,并关闭其余三盏 LED,依次实现“红灯—蓝灯—板载灯—绿灯”的四灯循环效果。通过本实验,可以从底层理解 STM32 的存储器映射、RCC 时钟使能、GPIO 引脚模式配置、GPIO 输出锁存以及 LED 硬件接法对亮灭逻辑的影响,同时熟悉 Keil 工程创建、启动文件添加、HEX 文件生成、ST-Link 下载和基础故障排查流程。
二、实验任务与目标
2.1 实验任务
- 使用 PA1 控制红色外接 LED。
- 使用 PB1 控制蓝色外接 LED。
- 使用 PC14 控制绿色外接 LED。
- 使用 PC13 控制核心板上的板载 LED。
- 让四只 LED 按固定顺序循环点亮,单灯保持时间约为 1 s。
- 使用寄存器直接完成 GPIO 的时钟使能、引脚模式配置和输出电平控制。
2.2 学习目标
通过本实验,理解以下内容:
- STM32 外设寄存器的存储器映射方式;
- RCC_APB2ENR 外设时钟使能寄存器的作用;
- GPIOx_CRL 和 GPIOx_CRH 的使用范围以及 4 位引脚配置结构;
CNF[1:0]和MODE[1:0]对 GPIO 工作模式和输出速率的控制;- BSRR 和 BRR 对 GPIO 输出高、低电平的控制方式;
- 外接 LED 高电平点亮与板载 LED 低电平点亮之间的差异;
- 裸寄存器工程中启动汇编文件、
volatile和位操作的作用; - 软件延时、下载器连接和硬件调试的基本方法。
三、实验器材与安全注意事项
3.1 实验器材
- STM32F103C8T6 最小系统核心板;
- 面包板;
- 红色、蓝色、绿色 LED 各一只;
- LED 限流电阻;
- 杜邦线;
- USB 线;
- ST-Link 或其他支持 SWD 的下载器;
- 安装有 Keil MDK-5 和 STM32F1 器件支持包的计算机。
3.2 硬件连接说明
三个外接 LED 均应串联限流电阻后再连接到 GPIO 引脚,所有器件的地线必须连接在一起。GPIO 的工作电压为 3.3 V,不能将 5 V 电源直接接入 STM32 的普通 GPIO 引脚,否则可能超过芯片引脚的允许电压范围,造成芯片损坏或工作异常。
外接 LED 的具体连接关系如下:
| GPIO 引脚 | 连接对象 | 颜色 | 亮灯有效电平 |
|---|---|---|---|
| PA1 | 外接 LED | 红色 | 高电平 |
| PB1 | 外接 LED | 蓝色 | 高电平 |
| PC14 | 外接 LED | 绿色 | 高电平 |
| PC13 | 核心板板载 LED | 板载灯 | 低电平 |
外接 LED 的阳极通过限流电阻连接到 GPIO 时,GPIO 输出高电平可以形成电流通路,LED 点亮;核心板上的 PC13 板载 LED 通常一端接 3.3 V,另一端接 PC13,因此 PC13 输出低电平时才会形成电流通路,板载 LED 点亮。也就是说,PC13 的软件控制逻辑和三个外接 LED 相反。
四、实验环境与工程建立
4.1 软件环境
- 操作系统:Windows;
- 开发环境:Keil MDK-5 / µVision5;
- 芯片型号:STM32F103C8T6;
- 下载调试器:ST-Link;

4.2 创建 Keil 工程
-
打开 Keil µVision5,点击菜单栏中的
Project,选择New µVision Project。

-
选择工程存放的本地文件夹,输入工程名称并保存。Keil 会生成工程文件,主要文件扩展名为
.uvprojx。
-
在器件选择窗口中依次选择
STMicroelectronics→STM32F1 Series→STM32F103,最后选择STM32F103C8并点击OK。

4.3 添加启动汇编文件
STM32 上电或复位后并不会直接从 C 语言的 main() 函数开始执行,而是先从复位入口执行启动代码。因此,裸机工程必须添加与芯片容量匹配的启动汇编文件。本实验使用 startup_stm32f10x_md.s。
在工程窗口中右键 Source Group 1,选择 Add Existing Files to Group 'Source Group 1',然后找到并添加 startup_stm32f10x_md.s。


文件名中的 md 表示中容量(medium-density)器件。STM32F103C8T6 属于 STM32F1 系列的中容量产品,因此应选择对应的中容量启动文件,而不是高容量或低容量启动文件。
启动文件主要完成以下工作:
- 设置复位后的初始栈指针;
- 分配和设置栈空间、堆空间;
- 建立中断向量表,使异常和中断能够跳转到正确的处理函数;
- 执行复位入口代码,完成进入 C 运行环境前的底层准备;
- 最终跳转到 C 语言的
main()函数。
如果启动文件没有被加入工程,或者选择了与芯片容量不匹配的启动文件,程序可能无法正确完成复位启动,严重时将无法正常进入 main() 函数。
4.4 新建 main.c
点击 File → New 新建空白文本文件,将文件保存为 main.c,并保存到工程目录中。

保存后,将 main.c 添加到工程的 Source Group 1 分组下。后续的寄存器地址宏定义、软件延时函数、GPIO 初始化代码和四灯流水逻辑均写在这个文件中。

4.5 配置工程选项
点击工具栏中的魔术棒图标 Options for Target,检查以下设置:
- Target 标签页:确认目标器件为
STM32F103C8。 - Output 标签页:勾选
Create HEX File。编译和链接成功后,Keil 会生成.hex文件,之后可以使用 ST-Link 将该文件下载到芯片 Flash 中。 - Debug 标签页:选择
ST-Link Debugger。点击Settings,确认使用 SWD 下载和调试模式,并检查下载接口、复位方式等参数。


五、寄存器地址与 GPIO 原理
本节专门解释寄存器的功能、地址来源和位偏移计算。
5.1 存储器映射的基本概念
STM32 的外设寄存器并不是普通的 C 语言变量,而是被映射到固定的内存地址。芯片手册会规定每个外设的基地址以及该外设内部每个寄存器的偏移地址。程序只要向指定地址读写数据,就可以直接访问对应的硬件寄存器。
本实验用到的外设基地址如下:
| 外设 | 基地址 |
|---|---|
| RCC | 0x40021000 |
| GPIOA | 0x40010800 |
| GPIOB | 0x40010C00 |
| GPIOC | 0x40011000 |


5.2 RCC_APB2ENR:开启 GPIO 端口时钟
RCC 是复位和时钟控制模块。GPIOA、GPIOB、GPIOC 都属于 APB2 总线上的外设,因此需要通过 RCC_APB2ENR 开启它们的时钟。RCC_APB2ENR 的完整名称是 APB2 外设时钟使能寄存器。
STM32 为了降低功耗,许多片上外设在复位后默认关闭时钟。外设没有工作时钟时,其寄存器即使存在对应的地址,写入也不能使硬件正常工作。因此,GPIO 初始化的第一步不是配置引脚,而是先开启相应端口的时钟。
RCC 外设基地址为 0x40021000,而 RCC_APB2ENR 在 RCC 外设中的偏移地址为 0x18,所以:
RCC_APB2ENR 地址
= RCC 基地址 + 寄存器偏移
= 0x40021000 + 0x18
= 0x40021018

本实验需要使用的位如下:
| 位号 | 位名称 | 对应端口 | 置 1 后的作用 |
|---|---|---|---|
| bit2 | IOPAEN | GPIOA | 开启 GPIOA 时钟 |
| bit3 | IOPBEN | GPIOB | 开启 GPIOB 时钟 |
| bit4 | IOPCEN | GPIOC | 开启 GPIOC 时钟 |
对应的位操作是“按位或置 1”,这样只改变目标位,不会破坏寄存器中其他外设的时钟配置。
5.3 GPIOx_CRL:配置 0~7 号引脚
GPIOx_CRL 是 GPIO 端口配置低寄存器,负责配置端口的 0~7 号引脚。这里的 x 可以是 A、B、C 等端口,例如 GPIOA_CRL 表示 GPIOA 的配置低寄存器。
每个引脚占用连续的 4 个 bit,结构为:
CNF[1:0] + MODE[1:0]
其中:
CNF[1:0]用于选择引脚的输入、输出、复用或模拟等功能类型;MODE[1:0]用于选择输入模式或输出速度;- 引脚
n在 CRL 中的 4 位字段起始偏移为n × 4。
本实验采用通用推挽输出、最高 2 MHz 的输出速度:
CNF = 00:通用推挽输出;MODE = 10:输出速度最高 2 MHz;- 拼接得到
0010b,十六进制为0x2。
PA1 和 PB1 都是 1 号引脚,因此它们在各自端口的 CRL 中都占用 bit4~bit7:
n = 1
n × 4 = 1 × 4 = 4
占用范围:bit4~bit7

5.4 GPIOx_CRH:配置 8~15 号引脚
GPIOx_CRH 是 GPIO 端口配置高寄存器,功能与 CRL 相同,但负责配置 8~15 号引脚。引脚编号为 n 时,在 CRH 中的字段起始偏移为:
(n - 8) × 4
本实验中 PC13 和 PC14 都属于 8~15 号引脚,因此必须使用 GPIOC_CRH:
-
PC13:
(13 - 8) × 4 = 5 × 4 = 20所以 PC13 占用 CRH 的 bit20~bit23。
-
PC14:
(14 - 8) × 4 = 6 × 4 = 24所以 PC14 占用 CRH 的 bit24~bit27。
PC13 和 PC14 使用 CRH 而不是 CRL,是由引脚编号所在的范围决定的。如果错误地把 13 号或 14 号引脚配置到 CRL 中,实际对应的引脚模式不会被正确修改。

5.5 配置寄存器的安全修改方式
修改某一个引脚的 4 位配置时,不能直接使用 = 给整个 CRL 或 CRH 赋值。直接整体赋值可能覆盖同一端口中其他引脚原有的配置。
规范做法是分两步:
- 使用
&= ~(0xF << offset)将目标引脚对应的 4 位清零; - 使用
|= (0x2 << offset)将新的0x2配置写入目标位置。
例如 PA1 的偏移为 4:
GPIOA_CRL &= ~(0xF << 4); /* 先清除 PA1 的 4 位配置 */
GPIOA_CRL |= (0x2 << 4); /* 再写入通用推挽输出 2 MHz */
0xF 的二进制形式是 1111。左移 4 位后,它正好覆盖 PA1 的 bit4~bit7;取反后再与 CRL 相与,可以只清除 PA1 的配置位而不改变其他位。
5.6 BSRR 和 BRR:控制 GPIO 输出电平
BSRR 和 BRR 是 GPIO 专门用于控制输出电平的寄存器。它们有一个非常重要的特点:写入 0 不产生作用,只有写入 1 的位才会执行对应操作。
5.6.1 BSRR 端口置位寄存器
BSRR 的地址偏移为 0x10。
- 低 16 位写 1:将对应 GPIO 引脚输出置为高电平;
- 低 16 位写 0:不产生任何作用;
- 高 16 位可以用于复位对应引脚。
例如:
GPIOA_BSRR = (1 << 1); /* PA1 输出高电平 */
5.6.2 BRR 端口复位寄存器
BRR 的地址偏移为 0x14。
- 低 16 位写 1:将对应 GPIO 引脚输出置为低电平;
- 低 16 位写 0:不产生任何作用。
例如:
GPIOA_BRR = (1 << 1); /* PA1 输出低电平 */
本实验的输出逻辑可以总结为:
| LED | 点亮操作 | 熄灭操作 |
|---|---|---|
| PA1 外接红灯 | GPIOA_BSRR = (1 << 1) | GPIOA_BRR = (1 << 1) |
| PB1 外接蓝灯 | GPIOB_BSRR = (1 << 1) | GPIOB_BRR = (1 << 1) |
| PC14 外接绿灯 | GPIOC_BSRR = (1 << 14) | GPIOC_BRR = (1 << 14) |
| PC13 板载灯 | GPIOC_BRR = (1 << 13) | GPIOC_BSRR = (1 << 13) |
BSRR 和 BRR 只会修改 GPIO 的输出锁存电平,电平不会因为延时结束而自动恢复。如果某一步使用 BSRR 将 PC14 置为高电平,后续程序不再向 GPIOC_BRR 写入 bit14,那么 PC14 就会一直保持高电平,外接 LED 也会一直处于点亮状态。
这正是实验中排查 PC14 外接 LED 常亮问题的关键:如果流水循环的下一轮没有明确执行 GPIOC_BRR = (1 << 14),PC14 就不会自动变低。修正程序后,每个流水阶段都同时写出四个引脚的目标状态,保证上一个阶段的灯被明确关闭。

六、程序实现与逐段说明
6.1 寄存器地址宏定义
#define RCC_APB2ENR (*(volatile unsigned int *)0x40021018)
#define GPIOA_CRL (*(volatile unsigned int *)0x40010800)
#define GPIOA_BSRR (*(volatile unsigned int *)0x40010810)
#define GPIOA_BRR (*(volatile unsigned int *)0x40010814)
#define GPIOB_CRL (*(volatile unsigned int *)0x40010C00)
#define GPIOB_BSRR (*(volatile unsigned int *)0x40010C10)
#define GPIOB_BRR (*(volatile unsigned int *)0x40010C14)
#define GPIOC_CRH (*(volatile unsigned int *)0x40011004)
#define GPIOC_BSRR (*(volatile unsigned int *)0x40011010)
#define GPIOC_BRR (*(volatile unsigned int *)0x40011014)
这些宏把芯片手册中的物理地址转换成容易阅读的名称。例如:
#define GPIOA_BSRR (*(volatile unsigned int *)0x40010810)
这句话可以分成四部分理解:
0x40010810是硬件手册规定的 GPIOA_BSRR 物理地址;(unsigned int *)将这个数字解释成指向无符号 32 位整数的指针;*对该地址进行解引用,使代码可以直接读写寄存器中的值;volatile告诉编译器该地址对应硬件寄存器,访问不能被当作普通变量缓存或删除,每一次读写都必须真正发生。
因此,后续写 GPIOA_BSRR = (1 << 1),本质上就是向硬件地址 0x40010810 写入数据。
6.2 软件延时函数
void delay_ms(unsigned int ms)
{
volatile unsigned int i, j;
for (i = 0; i < ms; i++)
{
for (j = 0; j < 8000; j++)
{
;
}
}
}
外层循环用于重复 ms 次,内层循环通过执行大量空操作消耗 CPU 时间。变量声明为 volatile,可以避免编译器认为内层空循环没有作用而将其直接优化删除。
这个函数只是近似延时函数,并不能严格保证每次延时都是 1 ms。实际延时时间与系统时钟频率、编译器优化等级、编译器生成的指令数量以及芯片运行状态有关。STM32F103 复位后的默认时钟通常与内部 8 MHz HSI 有关,但如果工程后续修改了系统时钟配置,原来的 8000 次循环也需要重新校准。本实验使用它的主要目的,是让流水灯效果能够被肉眼观察,而不是实现精确计时。
6.3 main() 初始化部分
int main(void)
{
/* 1. 开启 GPIOA、GPIOB、GPIOC 的 APB2 时钟 */
RCC_APB2ENR |= (1 << 2);
RCC_APB2ENR |= (1 << 3);
RCC_APB2ENR |= (1 << 4);
这里使用 |= 的原因是只将目标位设置为 1,同时保留 RCC_APB2ENR 中其他位原来的状态。三条语句分别开启 GPIOA、GPIOB、GPIOC 的时钟。
接下来配置 PA1、PB1、PC13 和 PC14:
GPIOA_CRL &= ~(0xF << 4);
GPIOA_CRL |= (0x2 << 4);
GPIOB_CRL &= ~(0xF << 4);
GPIOB_CRL |= (0x2 << 4);
GPIOC_CRH &= ~(0xF << 20);
GPIOC_CRH |= (0x2 << 20);
GPIOC_CRH &= ~(0xF << 24);
GPIOC_CRH |= (0x2 << 24);
PA1 和 PB1 是 1 号引脚,使用 CRL 的 bit4~bit7;PC13 和 PC14 是 8 号以上引脚,使用 CRH,偏移分别为 20 和 24。每个引脚的 4 位配置值都写为 0x2,对应 2 MHz 通用推挽输出。
配置完引脚模式后,先设置四个灯的初始熄灭状态:
GPIOA_BRR = (1 << 1); /* PA1 低电平,外接红灯灭 */
GPIOB_BRR = (1 << 1); /* PB1 低电平,外接蓝灯灭 */
GPIOC_BSRR = (1 << 13); /* PC13 高电平,板载灯灭 */
GPIOC_BRR = (1 << 14); /* PC14 低电平,外接绿灯灭 */
初始化输出电平的作用有两个:一是防止上电后 LED 出现不确定状态,二是让程序进入流水循环时拥有确定的初始条件。
6.4 while(1) 流水灯逻辑
while (1)
{
/* 第 1 盏:PA1 红灯 */
GPIOA_BSRR = (1 << 1);
GPIOB_BRR = (1 << 1);
GPIOC_BSRR = (1 << 13);
GPIOC_BRR = (1 << 14);
delay_ms(1000);
/* 第 2 盏:PB1 蓝灯 */
GPIOA_BRR = (1 << 1);
GPIOB_BSRR = (1 << 1);
GPIOC_BSRR = (1 << 13);
GPIOC_BRR = (1 << 14);
delay_ms(1000);
/* 第 3 盏:PC13 板载灯,低电平点亮 */
GPIOA_BRR = (1 << 1);
GPIOB_BRR = (1 << 1);
GPIOC_BRR = (1 << 13);
GPIOC_BRR = (1 << 14);
delay_ms(1000);
/* 第 4 盏:PC14 绿灯 */
GPIOA_BRR = (1 << 1);
GPIOB_BRR = (1 << 1);
GPIOC_BSRR = (1 << 13);
GPIOC_BSRR = (1 << 14);
delay_ms(1000);
}
}
四个阶段都明确写出四个 GPIO 的目标状态,而不是只操作当前需要点亮的引脚。这样做的好处是:无论上一个阶段的输出状态是什么,进入当前阶段后都能得到确定的“只有一盏灯亮”的结果。
while (1) 是单片机程序中的永久循环。程序完成时钟、GPIO 和初始电平配置后,不能像普通 PC 程序那样退出 main(),而是需要持续执行控制逻辑,所以流水灯代码放在无限循环中。
七、编译、下载与调试过程
7.1 编译工程
在 Keil 中执行编译和链接,检查编译窗口是否出现错误。如果工程选项中已经勾选 Create HEX File,编译成功后还会生成对应的 .hex 文件。

从编译窗口可以看到,工程生成了 LED.axf,并显示 0 Error(s), 0 Warning(s),说明本次编译和链接通过。
7.2 下载程序
使用 ST-Link 下载时,按照以下顺序操作:
- 将 ST-Link 连接到计算机;
- 将 ST-Link 的 SWDIO、SWCLK、3.3 V 和 GND 分别连接到核心板对应引脚;
- 在 Keil 中打开 Debug 或 Download 设置页面;
- 确认调试器选择为 ST-Link,下载接口为 SWD;
- 执行下载命令,将程序写入 STM32 的 Flash;
- 下载结束后复位核心板,观察四个 LED 是否按照预定顺序循环。

原始下载日志中其实先出现过 No ST-LINK detected 和 Flash Download failed,说明第一次下载时没有检测到 ST-Link 或调试链路没有建立成功。后续日志出现 Programming Done 和 Verify OK,说明重新连接或调整设置后,程序已经成功写入并完成校验。
这个过程说明,下载失败时不能直接判断为 C 语言程序错误,还应依次检查 ST-Link 是否连接、核心板是否供电、SWDIO 与 SWCLK 是否接反、GND 是否共地、3.3 V 是否存在以及 Keil 中调试器选项是否正确。
八、实验现象
程序成功下载并复位后,四个 LED 按照以下顺序循环:
PA1 红色外接 LED
↓ 约 1 s
PB1 蓝色外接 LED
↓ 约 1 s
PC13 板载 LED
↓ 约 1 s
PC14 绿色外接 LED
↓ 约 1 s
返回 PA1,重复循环
正常情况下,每个阶段只有一盏 LED 点亮,其余三盏熄灭。

九、实验结果分析
9.1 为什么必须先开启 GPIO 时钟
GPIO 是连接在 APB2 总线上的片上外设。为了节省功耗,STM32 在复位后不会默认开启所有外设的时钟。只有将 RCC_APB2ENR 中对应端口的时钟使能位置 1,GPIO 外设才会得到工作时钟。
因此,如果没有执行:
RCC_APB2ENR |= (1 << 2);
RCC_APB2ENR |= (1 << 3);
RCC_APB2ENR |= (1 << 4);
后续对 GPIOA、GPIOB、GPIOC 的 CRL、CRH、BSRR 和 BRR 的访问就不能产生预期的引脚电平变化。这个步骤是 GPIO 初始化的前提。
9.2 为什么 PA1 和 PB1 使用 CRL
CRL 负责配置 0~7 号引脚。PA1 和 PB1 的引脚编号都是 1,属于这个范围,因此必须在各自端口的 CRL 中配置。每个引脚占 4 位,所以 1 号引脚从 bit4 开始,占用 bit4~bit7。
9.3 为什么 PC13 和 PC14 使用 CRH
CRH 负责配置 8~15 号引脚。PC13 和 PC14 的编号分别为 13 和 14,都超过 7,所以必须使用 GPIOC_CRH。PC13 对应 CRH 的 bit20~bit23,PC14 对应 bit24~bit27。
9.4 为什么 PC13 使用相反的控制逻辑
寄存器只负责让 GPIO 输出高电平或低电平,LED 是否点亮取决于 LED 的外部接线。三个外接 LED 的电路是高电平有效,而核心板 PC13 板载 LED 通常是 LED 接到 3.3 V、GPIO 负责下拉电流,因此 PC13 输出低电平时点亮。软件控制时必须结合硬件电路判断,不能认为所有 LED 都是高电平点亮。
9.5 为什么要使用限流电阻
LED 需要通过一定大小的电流才能点亮,但 LED 本身不能有效限制电流。如果不串联限流电阻,GPIO 输出高电平后电流可能过大,既可能损坏 LED,也可能超过 STM32 GPIO 的允许输出电流,导致芯片引脚损坏或可靠性下降。因此,外接 LED 必须串联合适阻值的限流电阻。
9.6 PC14 常亮问题的定位过程
实验调试过程中曾出现 PC14 外接 LED 常亮的现象。最初如果只观察现象,容易怀疑 PC14 的硬件接线、LED 极性或 CRH 配置错误。检查程序后发现,BSRR 和 BRR 都属于写入控制寄存器,只有写入 1 才会产生作用,GPIO 输出电平也不会在延时结束后自动恢复。
如果程序在某个阶段执行:
GPIOC_BSRR = (1 << 14);
PC14 就会被锁存为高电平。后续如果没有执行:
GPIOC_BRR = (1 << 14);
PC14 就会一直保持高电平,外接 LED 也会一直点亮。解决方法是在每个流水阶段都明确写出 PC14 的目标状态,并在进入下一轮前使用 BRR 将其拉低。这个问题说明,裸寄存器编程中不能只写“点亮当前灯”的语句,还要显式关闭上一阶段的灯。
9.7 软件延时的缺点
当前程序使用两层循环实现软件延时,优点是代码简单、容易理解,不需要额外配置定时器;缺点也很明显:
- 延时时间受系统时钟频率影响;
- 延时时间受编译器优化等级和生成指令影响;
- 不能保证严格精确的 1 s;
- 延时期间 CPU 一直处于忙等状态,无法高效执行其他任务;
- 如果以后加入按键、串口或其他实时任务,程序结构会受到限制。
9.8 代码和硬件方面的局限
本实验直接使用物理地址访问寄存器,能够帮助理解底层原理,但代码的可读性、可移植性和维护性不如使用库函数的工程。更换 LED 引脚时,需要同时修改寄存器宏、配置位偏移、亮灭逻辑和流水顺序。
此外,PC13~PC15 属于特殊用途引脚,输出驱动能力相对有限,适合连接指示灯等小负载,不适合直接驱动大功率器件。实际工程中,较大负载应通过三极管、MOSFET 或专用驱动芯片进行电流放大和隔离。
十、实验总结
本次 STM32F103C8T6 基于寄存器的四灯流水灯实验,完整经历了 Keil 工程创建、芯片型号选择、启动汇编文件添加、main.c 编写、工程选项配置、寄存器地址映射、GPIO 初始化、程序编译、ST-Link 下载和硬件现象验证等步骤。
首先,通过添加 startup_stm32f10x_md.s,认识到 STM32 程序并不是上电后直接进入 main(),而是需要启动文件完成栈初始化、中断向量表设置和复位入口处理。其次,通过直接操作 RCC_APB2ENR,理解了片上外设时钟默认关闭以及 GPIO 时钟使能的必要性。通过对 CRL 和 CRH 的配置,掌握了低寄存器负责 0~7 号引脚、高寄存器负责 8~15 号引脚的规律,以及每个引脚占用 4 个 bit、需要先清零再写入配置的寄存器操作规范。
再次,通过使用 BSRR 和 BRR 控制 LED,理解了“只有写 1 有效、写 0 无效”的特殊机制以及 GPIO 输出锁存特性。PC14 常亮问题说明,引脚电平不会因为程序进入延时或下一次循环而自动复位,程序必须明确写入相反的控制寄存器。PC13 板载 LED 与外接 LED 的亮灭逻辑不同,也说明软件输出必须结合实际硬件接法分析。
本实验还暴露出程序的局限:软件延时精度不高、CPU 在延时期间被完全占用、物理地址访问的可读性较差,并且部分特殊引脚的驱动能力有限。
总体而言,本实验使我从底层理解了 STM32 的存储器映射、RCC 时钟系统、GPIO 工作原理和 LED 控制逻辑,同时积累了下载器连接、程序排错和硬件现象分析经验,为后续学习定时器、中断、串口和 STM32 库函数开发打下了基础。
更多推荐


所有评论(0)