基于STM32F103C8T6的寄存器和标准库的LED流水灯零基础操作
STM32F103C8T6是一款基于ARM Cortex-M3内核的高性价比微控制器,具有72MHz主频、64KB Flash和丰富外设资源。本文介绍其基于寄存器和标准库的流水灯实现方法,实现LED流水灯效果,展示了STM32底层硬件控制原理。使用Keil的软件仿真逻辑分析仪功能观察管脚的时序波形,更方便动态跟踪调试和定位代码故障点。该方案适合嵌入式入门学习,为后续外设开发奠定基础。
目录
前言
1. STM32F103C8T6的介绍
STM32F103C8T6是STMicroelectronics公司基于ARM Cortex-M3内核设计的一款高性能32位微控制器,属于STM32F1系列的"增强型"主流产品。它以其优异的性能、丰富的外设资源和极高的性价比,在嵌入式领域得到了广泛应用,常被称为"蓝色药丸"(Blue Pill)开发板的核心芯片。
核心架构与性能:
- 内核: ARM Cortex-M3,最高主频可达72MHz
- 存储器: 64KB Flash程序存储器,20KB SRAM
- 供电: 2.0V-3.6V工作电压
丰富的外设资源:
| 外设类型 | 具体模块 | 功能描述 |
|---|---|---|
| 时钟控制 | RCC | 复位和时钟控制 |
| GPIO | GPIOA-GPIOG | 通用输入输出端口 |
| 中断系统 | NVIC | 嵌套向量中断控制器 |
| EXTI | 外部中断/事件控制器 | |
| 定时器 | TIM1-TIM4 | 高级/通用定时器 |
| SysTick | 系统滴答定时器 | |
| 通信接口 | USART1-USART3 | 同步/异步串行通信 |
| SPI1-SPI2 | 串行外设接口 | |
| I2C1-I2C2 | 集成电路总线 | |
| 模拟外设 | ADC1-ADC2 | 模数转换器(12位) |
| DAC | 数模转换器 | |
| 存储控制 | DMA | 直接存储器访问 |
| SDIO | SD卡接口 | |
| 系统外设 | PWR | 电源控制 |
| BKP | 备份寄存器 | |
| IWDG | 独立看门狗 | |
| WWDG | 窗口看门狗 | |
| CRC | CRC校验计算单元 |
其他特性:
- 支持JTAG和SWD调试接口
- 37个快速I/O端口,大部分兼容5V电平
- 2个12位ADC,10个转换通道
- 7通道DMA控制器
- 80个通用I/O引脚
- 丰富的定时器和通信接口配置


该芯片广泛应用于工业控制、消费电子、物联网设备、电机驱动等领域,是学习STM32开发的理想入门芯片。
2. 准备工作
- 若干LED小灯
- 若干杜邦线
- 一块STM32F103C8T6最小系统板
- 一块面包板
- 标准外设库(这里从江协科技STM32F1C8T6入门教程资料获取)
- 选择控制的引脚:寄存器选择A5,B9以及C13引脚;标准库选择PB5,PB6,PB7,PB8,PB9引脚。
一、基于寄存器的流水灯实现
1. 实现逻辑
①延时函数设计
这里使用的是江协科技延时函数,核心代码如下:
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
因为STM32系统时钟是72MHZ,即1微秒滴答72次,那么定xus微妙就让定时器数72*xus次。
这里的x00000005则做三件事情:选择内核时钟;关闭中断;启动定时器。
这里的0x0000004则是:选择内核时钟;关闭终端;关闭定时器。
这里的while循环则是阻塞式延时,CPU不能执行其它工作。
明白核心代码,我们就能些毫秒延时函数如下:
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
②GPIO时钟启用
- 时钟系统的核心地位
在基于ARM Cortex-M内核的STM32微控制器中,时钟系统(Clock System) 是驱动所有外设模块工作的基础。其重要性体现在:
-
同步与控制:时钟信号为数字电路提供工作节拍,确保内部数以亿计的晶体管有序、同步地工作。没有时钟,处理器和外设将无法执行指令和操作。
-
功耗管理:现代MCU采用复杂的时钟树结构,允许独立地开启或关闭不同外设的时钟。这是实现低功耗运行的关键机制。当一个外设不使用时,关闭其时钟可显著降低动态功耗。
-
性能调节:通过编程时钟预分频器(Prescaler)、锁相环(PLL)等,可以灵活配置内核和外设的工作频率,在性能和功耗之间取得平衡。
- 外设总线与时钟使能
STM32采用AMBA(Advanced Microcontroller Bus Architecture)总线架构。不同外设挂接在不同的总线上,而时钟则沿总线分发。GPIO端口通常挂接在APB2(Advanced Peripheral Bus 2) 总线上,这是STM32F1系列中速度最快的外设总线。要使能外设时钟需要通过配置复位和时钟控制(RCC, Reset and Clock Control) 模块中的特定寄存器位,打开连接在该外设与总线之间的时钟门控(Clock Gating)开关。只有当时钟信号送达该外设时,其寄存器才能被读写,其功能逻辑才能正常运作。
在STM32F103中,控制GPIOA、GPIOB、GPIOC等端口时钟的寄存器是RCC->APB2ENR(APB2 Peripheral Clock Enable Register)。其地址为RCC地址+对应的地址偏移即0x40021000+18=0x40021018

上图知道GPIOA\B\C的使能位则代码实现如下
RCC_APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
③引脚配置
GPIO(General-Purpose Input/Output)即通用输入输出端口,是微控制器与外部世界交互的最基本外设。其强大之处在于每个引脚均可通过软件独立配置为多种工作模式。在STM32F1系列中,每个GPIO端口(如GPIOA, GPIOB等)由以下两个32位寄存器控制其工作模式:
- GPIOx_CRL (Port Configuration Register Low):配置引脚 0-7 的模式和速度
- GPIOx_CRH (Port Configuration Register High):配置引脚 8-15 的模式和速度
每个引脚由寄存器中的 4个位(CNFy[1:0] 和 MODEy[1:0]) 进行控制,其中 y 表示引脚编号。
GPIO引脚主要通过 CNF (Configuration) 和 MODE 位的组合来配置,具体模式如下表所示:
| 模式类型 | CNF[1:0] | MODE[1:0] | 功能描述 | 典型应用 |
|---|---|---|---|---|
| 通用推挽输出 | 00 | 01 / 10 / 11 | 输出0: 引脚输出低电平 输出1: 引脚输出高电平 |
驱动LED、蜂鸣器、继电器等 |
| 通用开漏输出 | 01 | 01 / 10 / 11 | 输出0: 引脚输出低电平 输出1: 引脚高阻态(需外部上拉) |
I2C通信、电平转换 |
| 复用功能推挽 | 10 | 01 / 10 / 11 | 由片上外设(如SPI, USART)控制输出 | 用作SPI_MOSI, USART_TX等 |
| 复用功能开漏 | 11 | 01 / 10 / 11 | 由片上外设控制开漏输出 | I2C_SDA, I2C_SCL等 |
| 浮空输入 | 01 | 00 | 完全高阻抗,电平不确定 | 按键检测、数字信号读取 |
| 上拉输入 | 10 | 00 | 内部连接上拉电阻至VDD | 按键检测,默认高电平 |
| 下拉输入 | 11 | 00 | 内部连接下拉电阻至VSS | 按键检测,默认低电平 |
| 模拟输入 | 00 | 00 | 引脚连接至ADC/DAC,禁用施密特触发器 | 连接传感器,ADC采样 |
输出速度配置 (MODE位)
00: 输入模式 (复位状态)01: 输出模式,最大速度 10 MHz10: 输出模式,最大速度 2 MHz11: 输出模式,最大速度 50 MHz
GPIOA\B\C的引脚配置地址分别为:GPIOA:0x40010800+0=0x40010800GPIOB:0x40010c00+4=0x40010c04GPIOC:0x40011000+4=0x40011004


我们需要先确定希望GPIO输出高低电平,因此要用通用推挽输出,输出速度配置我们选择2MHZ,则GPIOA-5、GPIOB-9、GPIOC-13代码如下
GPIOA_CRL&=0xFF0FFFFF; //设置位清零
GPIOA_CRL|=0X00200000; //PA5推挽输出,把第23、22、21、20位变为0010
GPIOB_CRH&=0xFFFFFF0F; //设置位清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOC_CRH&=0xFF0FFFFF; //设置位清零
GPIOC_CRH|=0x00200000; //PC13推挽输出,把第23、22、21、20变为0010


④引脚输出高低电平
GPIOx_ODR 是STM32 GPIO模块中一个最直接、最常用的寄存器。它的核心功能非常简单:当你将GPIO引脚配置为输出模式后,向这个寄存器的相应位写入0或1,就可以直接控制对应引脚输出低电平或高电平。
GPIOA\B\C的引脚输出地址分别为:GPIOA:0x40010800+C=0x4001080CGPIOB:0x40010c00+=C=0x40010c0CGPIOC:0x40011000+C=0x4001100C
则GPIOA-5、GPIOB-9、GPIOC-13代码编写如下
//控制灯亮灭
void A_LED_LIGHT()
{
GPIOA_ODR = 0x0<<5; //PA5低电平,红灯不亮
GPIOB_ODR = 0x1<<9; //PB9高电平,蓝灯亮
GPIOC_ODR = 0x1<<13; //PC13高电平,黄灯不亮
}
void B_LED_LIGHT()
{
GPIOA_ODR = 0x1<<5; ///PA5高电平,红灯亮
GPIOB_ODR = 0x0<<9; //PB9低电平,蓝灯不亮
GPIOC_ODR = 0x1<<13; //PC13高电平,黄灯不亮
}
void C_LED_LIGHT()
{
GPIOA_ODR = 0x0<<5; //PA5低电平,红灯不亮
GPIOB_ODR = 0x0<<9; //PB9低电平,蓝灯不亮
GPIOC_ODR = 0x0<<13; //PC13低电平,黄灯亮
}

2. 工程建立
- 新建工程(工程名为project)

- 选择芯片型号

- 在根目录下新增start文件,将江协科技STM32F1C8T6入门教程资料下载到
D盘进入下面俩个路径复制相关启动文件粘贴进去。D:\STM32F1C8T6入门教程资料\固件库\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupportD:\STM32F1C8T6入门教程资料\固件库\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\armD:\STM32F1C8T6入门教程资料\固件库\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x



- 新增user文件导入延时和新增main.c文件

- keil工程目录导入文件夹和必要文件

- 再点击魔术棒加入文件夹路径

这样工程建立成功
3. 编写代码
下面是mian.c的完整代码:
#include "stm32f10x.h"
#include "Delay.h"
//--------------APB2使能时钟寄存器(硬件地址映射)------------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)//控制GPIOA/B/C的时钟开关
//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)//配置GPIOA的0-7引脚,(PA5)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)//配置GPIOB的8-15引脚(PB9)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)//配置GPIOB的8-15引脚(PC13)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
//函数声明
void A_LED_LIGHT(void);
void B_LED_LIGHT(void);
void C_LED_LIGHT(void);
//控制灯亮灭
void A_LED_LIGHT()
{
GPIOA_ODR = 0x0<<5; //PA5低电平,红灯不亮
GPIOB_ODR = 0x1<<9; //PB9高电平,蓝灯亮
GPIOC_ODR = 0x1<<13; //PC13高电平,黄灯不亮
}
void B_LED_LIGHT()
{
GPIOA_ODR = 0x1<<5; ///PA5高电平,红灯亮
GPIOB_ODR = 0x0<<9; //PB9低电平,蓝灯不亮
GPIOC_ODR = 0x1<<13; //PC13高电平,黄灯不亮
}
void C_LED_LIGHT()
{
GPIOA_ODR = 0x0<<5; //PA5低电平,红灯不亮
GPIOB_ODR = 0x0<<9; //PB9低电平,蓝灯不亮
GPIOC_ODR = 0x0<<13; //PC13低电平,黄灯亮
}
//------------------------主函数--------------------------
int main(){
RCC_APB2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<4; //APB2-GPIOC外设时钟使能
GPIOA_CRL&=0xFF0FFFFF; //设置位清零
GPIOA_CRL|=0X00200000; //PA5推挽输出,把第23、22、21、20位变为0010
GPIOB_CRH&=0xFFFFFF0F; //设置位清零
GPIOB_CRH|=0x00000020; //PB9推挽输出,把第7、6、5、4变为0010
GPIOC_CRH&=0xFF0FFFFF; //设置位清零
GPIOC_CRH|=0x00200000; //PC13推挽输出,把第23、22、21、20变为0010
while(1)
{
A_LED_LIGHT();//红灯亮
Delay_ms(1000);
B_LED_LIGHT();//黄灯亮
Delay_ms(1000);
C_LED_LIGHT();//PC13口亮
Delay_ms(1000);
}
}
4. 测试
VID_20250921_150730
二、基于库函数的流水灯实现
1. 环境配置
-
在寄存器环境的基础上,新建
Library文件夹,用来放置标准外设库文件
-
下面俩个路径复制
.c,.h文件.c文件:D:\STM32F1C8T6入门教程资料\固件库\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src.H文件:D:\STM32F1C8T6入门教程资料\固件库\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc

-复制到Library文件夹内
-
进入下面路径复制
cenf.h、it.c、it.h文件复制到user文件夹下D:\STM32F1C8T6入门教程资料\固件库\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template

-
keil工程目录导入文件夹和必要文件

-
再点击魔术棒加入文件夹路径,并在Define栏目添加
USE_STDPERIPH_DRIVER字符串,以便正常使用标准外设库
2. 代码编写
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; //GPIO引脚,赋值为所有引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
while (1)
{
GPIO_Write(GPIOB, ~0x0020); //0000 0000 0010 0000,PB5引脚为低电平,其他引脚均为高电平
Delay_ms(1000);
GPIO_Write(GPIOB, ~0x0040); //0000 0000 0100 0000,PB6引脚为低电平,其他引脚均为高电平
Delay_ms(1000);
GPIO_Write(GPIOB, ~0x0080); //0000 0000 1000 0000,PB7引脚为低电平,其他引脚均为高电平
Delay_ms(1000);
GPIO_Write(GPIOB, ~0x0100); //0000 0000 0100 0000,PB8引脚为低电平,其他引脚均为高电平
Delay_ms(1000);
GPIO_Write(GPIOB, ~0x0200); //0000 0000 1000 0000,PB9引脚为低电平,其他引脚均为高电平
Delay_ms(1000);
}
}
3. 测试
VID_20250921_155031
三、Keil软件仿真逻辑分析仪功能分析
- 点击魔法棒,按照图示进行修改

- 点击调试按钮


- 点击setup

- 输入引脚进行监听,修改参数

- 运行



可以看到PA5、PB9、PC13引脚的周期大致为2秒,即LED点亮电平0.67秒,LED灭的电平1.33秒。修改代码中Delay函数的参数后再进行观测:
可以发现PA5、PB9、PC13引脚的周期大致为3秒,即LED点亮电平1秒,LED灭的电平2秒,则延时代码修改正确。
四、总结
通过本次基于STM32F103C8T6的LED流水灯实验,我深刻体会到寄存器开发和库函数开发各自的优势与局限。寄存器开发让我直接操作硬件,深入理解了STM32的时钟系统、GPIO配置和内存映射等底层机制,虽然代码效率高,但开发过程繁琐且可读性较差;而库函数开发通过封装良好的API大幅提升了开发效率,代码更易维护和移植,虽然会增加代码体积和执行开销。实践过程中,我还掌握了Keil逻辑分析仪的调试技巧,能够不依赖硬件进行波形分析和性能优化。这次实验不仅巩固了我的理论知识,更培养了我解决实际问题的能力,为后续嵌入式开发奠定了坚实基础。
五、参考资料
[1] :STM32入门教程
[2]:STM32入门教程资料
[3]:基于寄存器&标准外设库的LED流水灯
更多推荐



所有评论(0)