零基础入门STM32:寄存器点亮绚丽流水灯
声明:
B站视频教程:
1.面包板使用教程:【还不知道面包板怎么用?保姆级解说让电子小白快速入门】https://www.bilibili.com/video/BV1Ji42197sp?vd_source=b7612118137a8a93cc53a24fd714989a
2.流水灯教程:【STM32入门教程-2023版 细致讲解 中文字幕】https://www.bilibili.com/video/BV1th411z7sn?p=6&vd_source=b7612118137a8a93cc53a24fd714989a
前置配置:
在开始点亮流水灯之前,您的电脑中应该先拥有一个Keil编译开发软件。后续所有的代码的撰写、编译、烧录都是基于这个软件开始的。软件的安装包、下载方式以及如何创建一个新文件请参考B站教程:【STM32入门教程-2023版 细致讲解 中文字幕】https://www.bilibili.com/video/BV1th411z7sn?p=3&vd_source=b7612118137a8a93cc53a24fd714989a
声明2.0:
本文档基于实际学习经验编写,涵盖面包板使用方法、STM32数据手册分析、代码撰写以及实物链接全流程,如果你作为一名0基础的新手小白,那这篇文章无疑是100%适合于你。
目录
前言
对于零基础想要入门嵌入式的大学生来说,STM32C8T6几乎是绕不开的入门单片机。它资源适中、资料丰富、价格亲民,既能帮助我们跳出传统51单片机的思维,熟悉ARM内核的开发思路,又不会因为外设过多而打击初学者的信心。很多同学入门STM32会先接触标准库或者HAL库开发,但库函数本质是对底层寄存器的封装。想要真正理解单片机硬件工作原理,了解底层架构,就需要直接操作寄存器。流水灯作为STM32的第一个实验,非常适合我们用寄存器的方式,亲自配置GPIO,从底层理解IO口的点亮与熄灭逻辑。
学习单片机的旅途中,大家一定绕不开一个可靠的调试工具——面包板。它因可拆卸、可二次利用等优点深受初学者喜爱。对比传统焊接,它上手更快、容错率高。因此,在开始讲解STM32前,我们先来介绍一下面包板的基本原理!
一、面包板的原理
面包板白色塑料外壳本身不导电。板子小孔下方藏有弹性磷青铜金属弹片。把元件引脚或者杜邦线插入小孔,金属弹片会紧紧夹住金属引脚,形成稳定的电气连接。同一组金属弹片对应的所有孔互相导通;不同金属弹片之间相互绝缘。

面包板分为两个区域:两侧电源轨、中间信号工作区。
- 两侧电源轨:一般标红色“
+”线,蓝色“-”线。用来统一给整个电路提供 VCC 和 GND。注意很多面包板电源轨是中间断开的,不是从头到尾一整条连通,需要用杜邦线把断开位置桥接。 - 中间工作区:中间有一条长长的隔离凹槽,把中间区域分成上下两半。同一列(纵向)的 5 个孔互相导通,凹槽上下两侧的孔完全断开,互不连通。DIP 直插芯片跨在凹槽上插入,芯片左右引脚就不会短路。
1. 基础使用方法
- 元件插入:电阻、LED 引脚,或者杜邦线,垂直插入小孔,保证金属引脚完全插入,接触到底部金属弹片。不要只插一半,容易出现接触不良,电路时通时断。
- 电路连接规则:想要两个器件引脚电气相连,就把它们插到同一纵向 5 孔组内。
举个例子:LED 阳极引脚插到第 10 列 A 孔,限流电阻一端插到第 10 列 C 孔,这两个引脚就自动连通。 - 芯片放置:直插芯片跨在中间凹槽上方,芯片左右两排引脚分别落在凹槽上下两个独立区域,引脚不会短路。
- 电源分配:把 STM32 开发板的 3.3V 接到面包板红色电源轨,GND 接到蓝色电源轨。后续所有器件需要供电时,直接就近从电源轨取电。
- 插拔规范:不要暴力弯折元件引脚,拔元件捏住引脚根部垂直向上拔出。接线时尽量断电,防止短路烧坏 STM32 芯片。
2. 常见误区
- ❌ 误区:同一行横向的孔互相连通。
✅ 事实:横向孔不导通,纵向 5 孔才连通。 - ❌ 误区:面包板侧边电源轨从头到尾是连通的。
✅ 事实:很多面包板电源轨中间断开,左右两半不导通,需要额外跳线。 - ❌ 误区:把 LED 两个引脚插在凹槽同一侧同一列,直接短路。
✅ 事实:LED 正负极要分开,串联限流电阻,再分别接到 GPIO 和 GND。
掌握面包板接线原理之后,我们就可以搭建 STM32C8T6 寄存器版流水灯的外围电路,通过配置 GPIO 底层寄存器,控制 LED的亮灭,实现流水灯效果。
二、流水灯实验原理
声明:
本节引用与参考: ST 官方《STM32F103x 中文参考手册》。
1. GPIO引脚内部硬件原理

打开参考手册 GPIO 章节,可以看到上面这张 IO 口内部原理图。STM32 的 GPIO 引脚内部包含保护二极管、输入施密特触发器、输出锁存器、推挽 MOS 管驱动电路。
流水灯实验我们选用推挽输出模式。推挽输出内部有 PMOS 和 NMOS 两个管子:
- 写
1:上方 PMOS 导通,引脚输出 3.3V 高电平 - 写
0:下方 NMOS 导通,引脚输出 0V 低电平
推挽输出驱动能力强,非常适合驱动 LED。
我们硬件电路设计:PA0~PA7 共 8 个 GPIO 引脚,每个引脚串联 220Ω 限流电阻,再接 LED 阳极;LED 阴极统一接 GND。当 GPIO 输出高电平,LED 点亮;输出低电平,LED 熄灭。通过程序依次轮流拉高 PA0~PA7 引脚电平,加上延时,就形成流水灯效果。
2. 核心寄存器
参考手册里,GPIO 外设挂载在 APB2 总线上,要操作 GPIO,第一步必须开启 RCC 时钟。下面是本次实验用到的全部寄存器。
- RCC_APB2ENR 时钟使能寄存器
手册描述:APB2 外设时钟使能寄存器。第 2 位 IOPAEN,置 1 就开启 GPIOA 端口时钟。外设默认时钟关闭,不开启时钟,GPIO 寄存器全部无法读写。
代码:RCC->APB2ENR |= (1 << 2); - GPIOA_CRL 端口配置低寄存器

- CRL 负责 PA0~PA7 的模式配置,每个引脚占用 4 个 bit:2 位 CNF 配置模式类型、2 位 MODE 配置输出速度。
推挽输出,50MHz 对应的配置:CNF=00,MODE=11,也就是 4bit 二进制0011。PA0~PA7 一共 8 个引脚,全部设为推挽输出,所以GPIOA->CRL = 0x33333333; - GPIOA_BSRR 置位复位寄存器
BSRR 是 32 位寄存器,分为低 16 位 BS 和高 16 位 BR。原子操作,不会出现读改写冲突,寄存器版本开发优先使用。- 低 16 位 BS:写 1,对应引脚置高(点亮 LED);写 0 无效果
- 高 16 位 BR:写 1,对应引脚置低(熄灭 LED);写 0 无效果
相比 ODR 寄存器,BSRR 不需要读取寄存器原值,不会产生竞争。
3. 工作逻辑
- 开启 GPIOA 端口时钟,让 GPIOA 外设上电工作;
- 配置 PA0~PA7 全部为推挽输出,速度 50MHz;
- 程序循环:依次对 PA0、PA1……PA7 引脚通过 BSRR 置高点亮,软件延时一段时间;然后熄灭当前 LED,切换下一个;
- 不断循环,形成流水灯。
三、代码实现
开发环境:Keil MDK5;
芯片:STM32F103C8T6;
功能:PA0 ~ PA7 依次点亮,循环流水效果。
#include "stm32f10x.h"
#define RCC_BASE 0x40021000 // RCC外设基地址
#define RCC_APB2ENR (*(volatile unsigned long*)(RCC_BASE + 0x18)) // 时钟使能寄存器
// GPIOA寄存器
#define GPIOA_BASE 0x40010800 // GPIOA基地址
#define GPIOA_CRL (*(volatile unsigned long*)(GPIOA_BASE + 0x00)) // 低8位配置寄存器
#define GPIOA_ODR (*(volatile unsigned long*)(GPIOA_BASE + 0x0C)) // 输出数据寄存器
// GPIOB寄存器
#define GPIOB_BASE 0x40010C00 // GPIOB基地址
#define GPIOB_CRL (*(volatile unsigned long*)(GPIOB_BASE + 0x00))
#define GPIOB_ODR (*(volatile unsigned long*)(GPIOB_BASE + 0x0C))
// GPIOC寄存器(PC13和PC14属于高8位)
#define GPIOC_BASE 0x40011000 // GPIOC基地址
#define GPIOC_CRH (*(volatile unsigned long*)(GPIOC_BASE + 0x04)) // 高8位配置寄存器
#define GPIOC_ODR (*(volatile unsigned long*)(GPIOC_BASE + 0x0C))
// 低电平点亮,高电平熄灭
#define LED_RED_ON() GPIOA_ODR |= (1 << 0) // PA0 输出低电平 → 红灯亮
#define LED_RED_OFF() GPIOA_ODR &= ~(1 << 0) // PA0 输出高电平 → 红灯灭
#define LED_GREEN_ON() GPIOB_ODR |= (1 << 1) // PB1 输出低电平 → 绿灯亮
#define LED_GREEN_OFF() GPIOB_ODR &= ~(1 << 1) // PB1 输出高电平 → 绿灯灭
#define LED_BLUE_ON() GPIOC_ODR |= (1 << 14) // PC14 输出低电平 → 蓝灯亮
#define LED_BLUE_OFF() GPIOC_ODR &= ~(1 << 14) // PC14 输出高电平 → 蓝灯灭
#define LED_PC13_ON() GPIOC_ODR |= (1 << 13) // PC13 输出低电平 → 板载灯亮
#define LED_PC13_OFF() GPIOC_ODR &= ~(1 << 13) // PC13 输出高电平 → 板载灯灭
void delay_ms(unsigned int time) {
unsigned int i, j;
for(i = 0; i < time; i++)
for(j = 0; j < 8000; j++); // 经验值,可根据实际微调
}
int main(void)
{
RCC_APB2ENR |= (1 << 2) | (1 << 3) | (1 << 4);
GPIOA_CRL &= ~(0xF << (4*0));
GPIOA_CRL |= (0x1 << (4*0));
GPIOB_CRL &= ~(0xF << (4*1));
GPIOB_CRL |= (0x1 << (4*1)); // 设置为推挽输出
GPIOC_CRH &= ~((0xF << (4*5)) | (0xF << (4*6)));
GPIOC_CRH |= ((0x1 << (4*5)) | (0x1 << (4*6)));
// 初始化所有LED为熄灭状态
LED_RED_OFF(); // PA0 = 1
LED_GREEN_OFF(); // PB1 = 1
LED_BLUE_OFF(); // PC14 = 1
LED_PC13_OFF(); // PC13 = 1
while(1)
{
LED_RED_OFF();
LED_GREEN_OFF();
LED_BLUE_ON();
LED_PC13_OFF();
delay_ms(1000);
LED_BLUE_OFF();
LED_RED_ON();
LED_GREEN_OFF();
LED_PC13_OFF();
delay_ms(1000);
LED_RED_OFF();
LED_GREEN_ON();
LED_BLUE_OFF();
LED_PC13_OFF();
delay_ms(1000);
LED_RED_OFF();
LED_GREEN_OFF();
LED_BLUE_OFF();
LED_PC13_ON();
delay_ms(1000);
}
}
四、代码解析
1. 时钟开启
STM32 所有外设默认关闭时钟。外设不开启时钟,寄存器无法读写,IO 口不会工作。因此,每次写代码都要先打开时钟!要养成习惯!!!
RCC->APB2ENR |= (1 << 2);
2. GPIO配置CRL寄存器
GPIOA->CRL = 0x22222222;
每个引脚占用 4bit:CNF1 CNF0 MODE1 MODE0
- CNF=00:通用推挽输出
- MODE=11:输出最大速度 50MHz
4bit 组合0011等于十六进制0x3,8 个引脚连续设置,结果就是0x33333333。
上电之后 GPIO 默认是浮空输入模式,必须修改 CRL 寄存器,才能变成输出模式控制 LED。
3. 引脚电平控制与延时函数
GPIOA->BSRR = (1 << 0); // PA0置高,点亮LED
GPIOA->BSRR = (1 << (0 + 16));// PA0置低,熄灭LED
//延时函数
void Delay(void)
{
volatile int i, j;
for(i = 0; i < 500000; i++)
for(j = 0; j < 20; j++);
}
BSRR 是 32 位寄存器:
- bit0~bit15(低 16 位 BS):写 1 → 对应引脚输出高电平;写 0 无任何效果
- bit16~bit31(高 16 位 BR):写 1 → 对应引脚输出低电平;写 0 无任何效果
五、编译与下载说明
-
在 Keil 中选择芯片 STM32F103C8T6,配置下载器(ST-Link)。
-
编译无报错后下载程序到单片机。

-
上电,可以看到 PA0~PA7 外接 LED 依次点亮循环。
六、接线图与效果展示
按照下图完成接线:

实物展示如gif图所示:

当然,我们也可以对代码进行拓展,让他依次点亮8颗LED灯。具体代码希望大家自行拓展。
按照下图完成接线:

实物展示如gif图所示:

七、常见故障排查
故障 1:LED 全部不亮
- 检查 RCC 时钟有没有开启,忘记开时钟 GPIO 完全无效。
- CRL 寄存器配置错误,引脚仍然处于输入模式。
- 硬件接线错误,LED 正负极接反、限流电阻漏接。
故障 2:LED 常亮,不会流水
- BSRR 高 16 位复位位写错,没有把引脚拉低。
- 延时函数循环次数不对,延时太短肉眼看不出效果。
故障 3:部分 LED 亮,部分不亮
- 面包板接线虚接,引脚接触不良。
- CRL 寄存器数值写错,个别引脚没有配置成输出模式。
总结
通过本次实验,小编带大家掌握了面包板的使用方式,了解了STM32基本的架构,知晓了代码的攥写规则,同时也通过实操完成了点亮流水灯的小实验,不知道各位读者实验有没有成功呢?
本次寄存器版流水灯实验,让我直观感受到STM32底层寄存器的工作逻辑。不再依赖库函数封装,亲手配置时钟与GPIO寄存器,也体会到寄存器每一位都对应硬件行为。调试过程中,面包板接线、寄存器配置失误都会直接导致实验失败,让我养成对照数据手册排查问题的习惯,为后续嵌入式学习打下基础。
希望这一次小实验能够让你深深地爱上单片机与嵌入式开发,并能够持之以恒,在这条道路上越走越远!
写在最后:
我情愿,才敢于,如此专注的欢愉!希望这次的博客,能带领未来的工程师们,在学习单片机与嵌入式的道路上越走越远!也希望大家能够不断突破自己,挑战自己,将学习转化为爱好,心甘情愿地去体验、去了解、去学习!相逢即是缘分,如果您愿意与我一同学习,请送给我一个宝贵的三连吧!
更多推荐


所有评论(0)