从工程案例阅读《STM32F10xxx参考手册》和库函数——LED闪烁的底层逻辑

从工程案例阅读《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寄存器
  }
}
相关推荐
lzqrzpt17 小时前
LED驱动电源行业品牌格局与技术差异深度总结
python·单片机·嵌入式硬件
SteveSenna17 小时前
直流减速电机 + STM32 控制接线方案
单片机·嵌入式硬件
云栖梦泽18 小时前
Camera驱动开发与应用开发中的零拷贝与DMA
linux·驱动开发·嵌入式硬件
小心亦新21 小时前
STM32学习16--定时器输出比较2直流电机
stm32·嵌入式硬件·学习
危桥带雨21 小时前
Freertos——任务通知
stm32·单片机·嵌入式硬件·freertos
十月的皮皮1 天前
stm20260719-STM32F103RCT6_HAL库_三人抢答器
c语言·stm32·单片机·嵌入式硬件·stm32cubemx·hal库
HPT_Lt1 天前
告别传统二极管损耗!ZCC5050-1替代LM5050-1,冗余电源效率跃升新高度
嵌入式硬件
π同学1 天前
热插拔控制器 + 外置功率 MOSFET组合电路设计
嵌入式硬件·热插拔·mosfet