从工程案例阅读《STM32F10xxx参考手册》和库函数------LED闪烁的底层逻辑
本文将结合实际的LED闪烁工程代码,带你逐行对照《STM32F10xxx参考手册》,理解每行代码背后的硬件原理。读完本文,你将学会如何通过阅读手册来深入理解STM32的底层工作机制。
完整代码展示:
c
#include "stm32f10x.h"
#include "delay.h" // 延时函数(可用SysTick实现)
int main(void)
{
/* 1. 开启GPIOA时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 2. GPIO初始化配置 */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PA0引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 50MHz速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 3. 主循环:LED闪烁 */
while (1)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 低电平 → LED熄灭
Delay_ms(500);
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 高电平 → LED点亮
Delay_ms(500);
}
}
一、整体工程流程与手册章节对照
LED闪烁工程流程 参考手册对应章节
│
├── 1. 开启GPIOA时钟 → 第7章:复位和时钟控制(RCC)
│ └── 7.3.7 APB2外设时钟使能寄存器(RCC_APB2ENR)
│
├── 2. 配置GPIO模式 → 第9章:通用I/O(GPIO)
│ ├── 9.2.1 CRL/CRH寄存器
│ └── 9.2.2 ODR寄存器
│
└── 3. 控制电平翻转 → 第9章:通用I/O(GPIO)
└── 9.2.5 BSRR寄存器
二、步骤1:开启GPIOA时钟
原代码
c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
①对应库函数stm32f10x_rcc.c :
RCC_APB2PeriphClockCmd

②对应库函数声明stm32f10x_rcc.h:
③手册对应内容
章节:第7章「复位和时钟控制(RCC)」→ 7.3.7节「APB2外设时钟使能寄存器(RCC_APB2ENR)」
手册原文 (部分):

注:16进制寄存器地址 :0x4002 1018(偏移量:0x18)


注:
开启端口A的时钟需要将位2置1,则二进制: 0000 0000 0000 0000 0000 0000 0000 0100
对应16进制:0x0000 0004
🔍 代码如何操作手册中的寄存器
RCC_APB2PeriphClockCmd() 函数内部
↓
RCC->APB2ENR |= RCC_APB2Periph; // RCC_APB2Periph_GPIOA = 0x00000004
↓
RCC_APB2ENR寄存器的位2 (IOPAEN) 从0变为1
↓
GPIOA模块的时钟门控打开
↓
GPIOA开始接收时钟信号,可以工作了!
核心要点:
手册7.3.7节明确告诉我们:GPIOA的时钟由RCC_APB2ENR寄存器的第2位 控制。复位值
0x0000 0000表示所有外设时钟默认 都是关闭的,这就是为什么使用任何外设前必须开启时钟。
三、步骤2:配置GPIO模式
📄 代码
c
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PA0引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 50MHz速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
对库函数GPIO_Init的介绍见附录1
1、 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
①对应库函数声明stm32f10x_rcc.h:


注释:这里的关键点在于枚举的默认赋值规则:第一个枚举值 GPIO_Speed_10MHz 被显式赋值为 1,后续的 GPIO_Speed_2MHz 会默认递增为 2,而 GPIO_Speed_50MHz 则默认递增为 3,即0x11。
②手册对应内容

注释:
先确认选取的引脚为A口的0引脚: GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PA0引脚
GPIO_Speed_50MHz = 0x03 (二进制: 0000 0011)
取低0~1位有效性,则MODE0取11,为输出模式,50MHz。
GPIO_Mode_Out_PP = 0x10 (二进制: 0001 0000)
取低2~3位有效性,则CNF0取00,为推挽输出。
四、步骤3:控制电平翻转
📄 代码
c
GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 输出低电平
Delay_ms(500);
GPIO_SetBits(GPIOA, GPIO_Pin_0); // 输出高电平
Delay_ms(500);
①对应库函数声明stm32f10x_rcc.h:
GPIO_ResetBits:端口位清除------低电平0

GPIO_SetBits:端口位设置------高电平1

GPIO口宏定义

② 手册对应内容
• 对BRR低16位写1,将ODR对应清0(输出低电平)

• 对BSRR低16位写1,将ODR对应位置1(输出高电平)
• 对BSRR高16位写1,将ODR对应位清0(输出低电平)
• 如果同时设置和清除同一个位,清除优先
附录1 对库函数GPIO_Init进行注释:
c
/**
* @brief 初始化GPIO外设
* @param GPIOx: 选择GPIO组,如GPIOA、GPIOB等
* @param GPIO_InitStruct: 指向配置结构体的指针
* @retval 无
*/
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
/* 定义局部变量 */
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/*------------------- 参数检查 -------------------*/
assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); // 检查GPIOx是否合法(如GPIOA-GPIOG)
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode)); // 检查模式参数是否合法
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); // 检查引脚参数是否合法
/*------------------- 1. 模式配置 -------------------*/
// 提取GPIO_Mode的低4位(CNF配置位),用于写入CRL/CRH寄存器
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
// 检查GPIO_Mode的高4位是否为0x10(判断是否为输出模式)
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* 检查速度参数是否合法(只有输出模式才需要速度) */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* 输出模式:将速度值合并到currentmode(占低2位) */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*------------------- 2. 配置低8位引脚(CRL寄存器) -------------------*/
// 检查GPIO_Pin是否包含低8位引脚(位0-7)
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL; // 读取当前CRL寄存器的值
// 循环遍历8个低引脚(pinpos = 0~7)
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos; // 计算当前引脚的掩码(1<<pinpos)
/* 检查该引脚是否被选中 */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos) // 如果该引脚需要配置
{
pos = pinpos << 2; // 计算4位组的起始位偏移(pinpos*4)
/* 清除该引脚对应的4位控制位 */
pinmask = ((uint32_t)0x0F) << pos; // 生成4位掩码(0xF << pos)
tmpreg &= ~pinmask; // 将对应4位清零
/* 写入新的配置值到对应4位 */
tmpreg |= (currentmode << pos); // 将配置值左移到位偏移位置
/* 处理上下拉输入的ODR初始值 */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) // 如果是下拉输入
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos); // 写BRR清0对应位(下拉)
}
else
{
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) // 如果是上拉输入
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos); // 写BSRR置1对应位(上拉)
}
}
}
}
GPIOx->CRL = tmpreg; // 将修改后的值写回CRL寄存器
}
/*------------------- 3. 配置高8位引脚(CRH寄存器) -------------------*/
// 检查GPIO_Pin是否包含高8位引脚(位8-15)
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH; // 读取当前CRH寄存器的值
// 循环遍历8个高引脚(pinpos = 0~7,对应引脚8~15)
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08)); // 计算引脚掩码(1<<(pinpos+8))
/* 检查该引脚是否被选中 */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos) // 如果该引脚需要配置
{
pos = pinpos << 2; // 计算4位组的起始位偏移(pinpos*4)
/* 清除该引脚对应的4位控制位 */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* 写入新的配置值到对应4位 */
tmpreg |= (currentmode << pos);
/* 处理上下拉输入的ODR初始值(引脚位置+8) */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) // 下拉输入
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08)); // 清0对应位
}
/* 处理上拉输入 */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) // 上拉输入
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08)); // 置1对应位
}
}
}
GPIOx->CRH = tmpreg; // 将修改后的值写回CRH寄存器
}
}