声明:
  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前,我们先来介绍一下面包板的基本原理!

一、面包板的原理

  面包板白色塑料外壳本身不导电。板子小孔下方藏有弹性磷青铜金属弹片。把元件引脚或者杜邦线插入小孔,金属弹片会紧紧夹住金属引脚,形成稳定的电气连接。同一组金属弹片对应的所有孔互相导通;不同金属弹片之间相互绝缘。

在这里插入图片描述

面包板分为两个区域:两侧电源轨、中间信号工作区。

  1. 两侧电源轨:一般标红色“+”线,蓝色“-”线。用来统一给整个电路提供 VCC 和 GND。注意很多面包板电源轨是中间断开的,不是从头到尾一整条连通,需要用杜邦线把断开位置桥接。
  2. 中间工作区:中间有一条长长的隔离凹槽,把中间区域分成上下两半。同一列(纵向)的 5 个孔互相导通,凹槽上下两侧的孔完全断开,互不连通。DIP 直插芯片跨在凹槽上插入,芯片左右引脚就不会短路。

1. 基础使用方法

  1. 元件插入:电阻、LED 引脚,或者杜邦线,垂直插入小孔,保证金属引脚完全插入,接触到底部金属弹片。不要只插一半,容易出现接触不良,电路时通时断。
  2. 电路连接规则:想要两个器件引脚电气相连,就把它们插到同一纵向 5 孔组内。
    举个例子:LED 阳极引脚插到第 10 列 A 孔,限流电阻一端插到第 10 列 C 孔,这两个引脚就自动连通。
  3. 芯片放置:直插芯片跨在中间凹槽上方,芯片左右两排引脚分别落在凹槽上下两个独立区域,引脚不会短路。
  4. 电源分配:把 STM32 开发板的 3.3V 接到面包板红色电源轨,GND 接到蓝色电源轨。后续所有器件需要供电时,直接就近从电源轨取电。
  5. 插拔规范:不要暴力弯折元件引脚,拔元件捏住引脚根部垂直向上拔出。接线时尽量断电,防止短路烧坏 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 时钟。下面是本次实验用到的全部寄存器。

  1. RCC_APB2ENR 时钟使能寄存器
    手册描述:APB2 外设时钟使能寄存器。第 2 位 IOPAEN,置 1 就开启 GPIOA 端口时钟。外设默认时钟关闭,不开启时钟,GPIO 寄存器全部无法读写。
    代码:RCC->APB2ENR |= (1 << 2);
  2. GPIOA_CRL 端口配置低寄存器
    在这里插入图片描述
  3. CRL 负责 PA0~PA7 的模式配置,每个引脚占用 4 个 bit:2 位 CNF 配置模式类型、2 位 MODE 配置输出速度。
    推挽输出,50MHz 对应的配置:CNF=00,MODE=11,也就是 4bit 二进制0011。PA0~PA7 一共 8 个引脚,全部设为推挽输出,所以GPIOA->CRL = 0x33333333;
  4. GPIOA_BSRR 置位复位寄存器
    BSRR 是 32 位寄存器,分为低 16 位 BS 和高 16 位 BR。原子操作,不会出现读改写冲突,寄存器版本开发优先使用。
    • 低 16 位 BS:写 1,对应引脚置高(点亮 LED);写 0 无效果
    • 高 16 位 BR:写 1,对应引脚置低(熄灭 LED);写 0 无效果

  相比 ODR 寄存器,BSRR 不需要读取寄存器原值,不会产生竞争。

3. 工作逻辑

  1. 开启 GPIOA 端口时钟,让 GPIOA 外设上电工作;
  2. 配置 PA0~PA7 全部为推挽输出,速度 50MHz;
  3. 程序循环:依次对 PA0、PA1……PA7 引脚通过 BSRR 置高点亮,软件延时一段时间;然后熄灭当前 LED,切换下一个;
  4. 不断循环,形成流水灯。

三、代码实现

开发环境: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 无任何效果

五、编译与下载说明

  1. 在 Keil 中选择芯片 STM32F103C8T6,配置下载器(ST-Link)。

  2. 编译无报错后下载程序到单片机。
    在这里插入图片描述

  3. 上电,可以看到 PA0~PA7 外接 LED 依次点亮循环。

六、接线图与效果展示

  按照下图完成接线:
在这里插入图片描述
  实物展示如gif图所示:
在这里插入图片描述

  当然,我们也可以对代码进行拓展,让他依次点亮8颗LED灯。具体代码希望大家自行拓展。
  按照下图完成接线:
在这里插入图片描述
  实物展示如gif图所示:
在这里插入图片描述

七、常见故障排查

故障 1:LED 全部不亮

  • 检查 RCC 时钟有没有开启,忘记开时钟 GPIO 完全无效。
  • CRL 寄存器配置错误,引脚仍然处于输入模式。
  • 硬件接线错误,LED 正负极接反、限流电阻漏接。

故障 2:LED 常亮,不会流水

  • BSRR 高 16 位复位位写错,没有把引脚拉低。
  • 延时函数循环次数不对,延时太短肉眼看不出效果。

故障 3:部分 LED 亮,部分不亮

  • 面包板接线虚接,引脚接触不良。
  • CRL 寄存器数值写错,个别引脚没有配置成输出模式。

总结
  通过本次实验,小编带大家掌握了面包板的使用方式,了解了STM32基本的架构,知晓了代码的攥写规则,同时也通过实操完成了点亮流水灯的小实验,不知道各位读者实验有没有成功呢?
  本次寄存器版流水灯实验,让我直观感受到STM32底层寄存器的工作逻辑。不再依赖库函数封装,亲手配置时钟与GPIO寄存器,也体会到寄存器每一位都对应硬件行为。调试过程中,面包板接线、寄存器配置失误都会直接导致实验失败,让我养成对照数据手册排查问题的习惯,为后续嵌入式学习打下基础。
  希望这一次小实验能够让你深深地爱上单片机与嵌入式开发,并能够持之以恒,在这条道路上越走越远!

写在最后:
  我情愿,才敢于,如此专注的欢愉!希望这次的博客,能带领未来的工程师们,在学习单片机与嵌入式的道路上越走越远!也希望大家能够不断突破自己,挑战自己,将学习转化为爱好,心甘情愿地去体验、去了解、去学习!相逢即是缘分,如果您愿意与我一同学习,请送给我一个宝贵的三连吧!

2026年9月20日 于重庆
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐