前言

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时钟启用
  1. 时钟系统的核心地位
    在基于ARM Cortex-M内核的STM32微控制器中,时钟系统(Clock System) 是驱动所有外设模块工作的基础。其重要性体现在:
  • 同步与控制:时钟信号为数字电路提供工作节拍,确保内部数以亿计的晶体管有序、同步地工作。没有时钟,处理器和外设将无法执行指令和操作。

  • 功耗管理:现代MCU采用复杂的时钟树结构,允许独立地开启或关闭不同外设的时钟。这是实现低功耗运行的关键机制。当一个外设不使用时,关闭其时钟可显著降低动态功耗。

  • 性能调节:通过编程时钟预分频器(Prescaler)、锁相环(PLL)等,可以灵活配置内核和外设的工作频率,在性能和功耗之间取得平衡。

  1. 外设总线与时钟使能
    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 MHz
  • 10: 输出模式,最大速度 2 MHz
  • 11: 输出模式,最大速度 50 MHz

GPIOA\B\C的引脚配置地址分别为:
GPIOA:0x40010800+0=0x40010800
GPIOB:0x40010c00+4=0x40010c04
GPIOC: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=0x4001080C
GPIOB:0x40010c00+=C=0x40010c0C
GPIOC: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\CoreSupport
    D:\STM32F1C8T6入门教程资料\固件库\固件库\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm
    D:\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.hit.cit.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流水灯

Logo

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

更多推荐