当前位置: 首页 > news >正文

【GPIO】从STM32F103入门GPIO寄存器

STM32 GPIO寄存器详解与操作对比

核心理论:每个GPIO端口(A-E)由7个寄存器控制,每组寄存器控制特定功能。下面按寄存器类型详细对比标准库和寄存器操作。


一、端口配置寄存器:GPIOx_CRL/CRH

功能:控制引脚工作模式(输入/输出/复用)和输出速度
位结构

  • GPIOx_CRL:控制Pin0-Pin7(低8位)
  • GPIOx_CRH:控制Pin8-Pin15(高8位)
  • 每4位控制1个引脚(共16引脚 × 4bit = 64位)
    CRL/CRH结构
场景1:配置PA2为浮空输入

标准库写法:

// 1.使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 2.配置引脚
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_2;
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio);

寄存器写法:

// 1.使能时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;// 2.配置CRL寄存器
GPIOA->CRL &= ~(0x0F << 8);  // 清空位[11:8]
GPIOA->CRL |=  (0x04 << 8);  // CNF=01(浮空输入), MODE=00(输入)

场景2:配置PA2为推挽输出(50MHz)

标准库写法:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_2;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);

寄存器写法:

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;GPIOA->CRL &= ~(0x0F << 8);  // 清空原有配置
GPIOA->CRL |=  (0x03 << 8);  // CNF=00(推挽), MODE=11(50MHz)

二、输入数据寄存器:GPIOx_IDR

功能:读取引脚当前电平状态(只读)
位结构:低16位对应引脚电平(0/1)
注意:该寄存器只能以16位的形式读出
IDR结构

场景:读取PA2电平

标准库写法:

uint8_t value = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2);

寄存器写法:

// 方法1:位与操作(推荐)
uint8_t value = (GPIOA->IDR & GPIO_IDR_IDR2) ? 1 : 0;// 方法2:移位法
uint8_t value = (GPIOA->IDR >> 2) & 0x01;

三、输出数据寄存器:GPIOx_ODR

功能:控制输出电平 + 配置上拉/下拉电阻
位结构:低16位控制输出电平
ODR结构

场景1:设置PA2输出高电平

标准库写法:

GPIO_SetBits(GPIOA, GPIO_Pin_2);

寄存器写法:

GPIOA->ODR |= GPIO_ODR_ODR2;  // 直接置位ODR对应位

场景2:配置PA2为上拉输入

标准库写法:

GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_2;
gpio.GPIO_Mode = GPIO_Mode_IPU;  // 内置上拉配置
GPIO_Init(GPIOA, &gpio);

寄存器写法:

// 配置CRL:CNF2=10(上拉输入), MODE2=00(输入)
GPIOA->CRL &= ~(GPIO_CRL_CNF2 | GPIO_CRL_MODE2); // 清除原有配置
GPIOA->CRL |= GPIO_CRL_CNF2_1;                   // CNF2[1]=1, CNF2[0]=0 → 10
// 或直接操作寄存器地址:
// *(volatile uint32_t*)0x40010800 &= ~(0x0F << 8); // 清除位[11:8]
// *(volatile uint32_t*)0x40010800 |= (0x08 << 8);   // 设置0x8(二进制1000)// 启用上拉电阻
GPIOA->ODR |= GPIO_ODR_ODR2;  // ODR对应位置1
// 或直接操作寄存器地址:*(volatile uint32_t*)0x4001080C |= (1 << 2);

在这里插入图片描述

四、位设置寄存器:GPIOx_BSRR

功能:原子操作输出电平(避免ODR直接操作冲突)
位结构

  • 低16位:置位引脚(1→高电平)
  • 高16位:复位引脚(1→低电平)
    BSRR结构
场景:PA2输出高电平+PA3输出低电平

标准库写法:

GPIO_SetBits(GPIOA, GPIO_Pin_2);
GPIO_ResetBits(GPIOA, GPIO_Pin_3);

寄存器写法:

// 单条指令完成双操作
GPIOA->BSRR = GPIO_BSRR_BS2 | GPIO_BSRR_BR3;

五、位清除寄存器:GPIOx_BRR

功能:快速清除输出电平(专用于低电平输出)
位结构:低16位控制清零操作
BRR结构

场景:设置PA2输出低电平

标准库写法:

GPIO_ResetBits(GPIOA, GPIO_Pin_2);

寄存器写法:

// 专用清零寄存器
GPIOA->BRR = GPIO_BRR_BR2;  

六、锁定寄存器:GPIOx_LCKR

功能:锁定CRL/CRH配置防止意外修改
锁定序列:写1→写0→写1→读0→读1
LCKR结构

场景:锁定PA2配置

标准库写法:

GPIO_PinLockConfig(GPIOA, GPIO_Pin_2);

寄存器写法:

// 锁定序列实现
GPIOA->LCKR = GPIO_LCKR_LCK2;    // 选择锁定PA2
GPIOA->LCKR |= GPIO_LCKR_LCKK;   // Step1: LCKK=1
GPIOA->LCKR &= ~GPIO_LCKR_LCKK;  // Step2: LCKK=0
GPIOA->LCKR |= GPIO_LCKR_LCKK;   // Step3: LCKK=1
volatile uint32_t tmp = GPIOA->LCKR;  // Step4: 读LCKK(应为0)
tmp = GPIOA->LCKR;               // Step5: 读LCKK(应为1)

关键区别总结

  1. 配置效率

    • 标准库:封装性好,但存在函数调用开销
    • 寄存器:直接操作硬件,效率更高
  2. 多引脚操作

    • 标准库:需多次调用函数
    GPIO_SetBits(GPIOA, GPIO_Pin_0);
    GPIO_ResetBits(GPIOA, GPIO_Pin_1);
    
    • 寄存器:单条指令完成
    GPIOA->BSRR = GPIO_BSRR_BS0 | GPIO_BSRR_BR1;
    
  3. 代码可读性

    • 标准库:函数名自解释(GPIO_Mode_IN_FLOATING
    • 寄存器:需查阅手册理解位含义(CNF=01, MODE=00
  4. 安全性

    • 标准库:内置参数检查
    • 寄存器:直接操作硬件,需开发者保证正确性

扩展:✅位操作分析GPIOA_CRL &= ~(0xF << 8);

// 正确操作
GPIOA_CRL &= ~(0xF << 8);  // 等价于 GPIOA_CRL &= 0xFFFFF0FF;
操作二进制表示(32位)作用范围
0xF0000 0000 0000 1111
0xF << 80000 0000 1111 0000 0000 0000位[11:8]区域
~(0xF << 8)1111 1111 0000 1111 1111 1111掩码(取反后)
最终效果仅清空位[11:8]PA2专属区域

关键区别图示:
假设原始CRL值:0x12345678

目标:仅清除PA2配置(位[11:8])✅ 正确操作:
原始值:0001 0010 0011 0100 0101 0110 0111 1000
掩码:  1111 1111 1111 1111 0000 1111 1111 1111 (0xFFFFF0FF)
结果:  0001 0010 0011 0100 0000 0110 0111 1000 → 仅位[11:8]清零❌ 错误操作:
原始值:0001 0010 0011 0100 0101 0110 0111 1000
掩码:  0000 0000 0000 0000 0000 0000 0000 0000 (全0)
结果:  0000 0000 0000 0000 0000 0000 0000 0000 → 整个寄存器清零!

最佳实践建议:

// 推荐写法(可读性更高):
#define PA2_CLEAR_MASK  (0xF << 8)   // 定义清除掩码
GPIOA_CRL &= ~PA2_CLEAR_MASK;// 或直接使用十六进制:
GPIOA_CRL &= ~0x00000F00;  // 0x00000F00 = (0xF << 8)

永远记住:在嵌入式寄存器操作中,清零特定区域必须使用位掩码+取反,直接赋0会导致整个寄存器被意外清除!这是嵌入式开发中最常见的错误之一。


GPIO操作中移位运算的风险与BSRR/BRR解决方案

问题本质:直接操作ODR的风险
当直接使用移位运算操作GPIOx_ODR寄存器时,主要存在两个问题:

  1. 非原子操作:读-改-写过程可能被中断打断
  2. 位覆盖风险:移位操作可能意外改变其他引脚状态

风险代码示例

// 危险操作:使用移位设置PA2输出高电平
GPIOA->ODR = (1 << 2);  // 将1左移2位后赋值给ODR// 等效操作:
// 假设原始ODR = 0xFFFF (所有引脚高电平)
// 操作后ODR = 0x0004 (仅PA2高电平,其他全低)

移位操作风险详解

场景:同时控制PA2和PA3

// 目标:PA2输出高,PA3输出低// 错误实现:
GPIOA->ODR = (1 << 2);  // 设置PA2高
GPIOA->ODR = (0 << 3);  // 设置PA3低 → 实际清除了PA2!// 实际效果:
// 第一条指令后:ODR = 0000 0000 0000 0100
// 第二条指令后:ODR = 0000 0000 0000 0000 (PA2也被清除)

位运算分析

// 看似正确的错误写法:
GPIOA->ODR |= (1 << 2);  // 设置PA2高
GPIOA->ODR &= ~(1 << 3); // 清除PA3低// 风险点:
// 1. 非原子操作:两条指令间可能被中断打断
// 2. 若PA2和PA3都需要改变,需要执行两次寄存器访问
// 3. 当多个任务操作同一GPIO端口时可能冲突

BSRR/BRR寄存器的解决方案

BSRR寄存器工作原理
BSRR结构

  • 低16位 (BSy):置位操作 (1→高电平)
  • 高16位 (BRy):复位操作 (1→低电平)
  • 关键特性:写0的位不影响当前状态

安全实现方案

// 原子操作:同时设置PA2高+PA3低
GPIOA->BSRR = (1 << 2) | (1 << (16 + 3)); // 位运算分解:
//  低16位: 0000 0000 0000 0100 (设置PA2)
//  高16位: 0000 0000 0000 1000 (清除PA3)
//  合并值: 0x00040008

BRR寄存器补充

// 专用清零寄存器 (等效BSRR高16位)
GPIOA->BRR = (1 << 3);  // 清除PA3// 等效于:
GPIOA->BSRR = (1 << (16 + 3));

对比实验

测试场景
控制开发板上两个LED:

  • LED1 (PA2):高电平点亮
  • LED2 (PA3):低电平点亮

危险代码(ODR移位)

while(1) {// 尝试同时点亮两个LEDGPIOA->ODR = (1 << 2);    // PA2高 (点亮LED1)GPIOA->ODR &= ~(1 << 3);  // PA3低 (点亮LED2)// 实际效果:LED1短暂亮后熄灭// 原因:第二行清除了PA2
}

安全代码(BSRR)

while(1) {// 原子操作同时控制两个LEDGPIOA->BSRR = (1 << 2) | (1 << (16 + 3)); // LED1亮(PA2高) + LED2亮(PA3低)// 延时后关闭delay_ms(500);GPIOA->BSRR = (1 << (16 + 2)) | (1 << 3); // LED1灭(PA2低) + LED2灭(PA3高)
}

位运算原理图解

ODR直接操作风险

初始状态:PA0-PA15全高 (ODR=0xFFFF)
目标:设置PA2高,保持其他位不变错误操作:ODR = (1 << 2) → 二进制 0000 0100结果:PA2高,但其他所有引脚被强制置低!

BSRR安全操作

初始状态:任意
操作:BSRR = (1 << 2) | (1 << 19) 位分解:[31:16] BR: 0000 0000 0000 1000 (清除PA3)[15:0]  BS: 0000 0000 0000 0100 (设置PA2)效果:仅修改PA2和PA3,其他引脚保持不变

使用原则总结

  1. 设置单个引脚高电平

    // 推荐
    GPIOx->BSRR = (1 << Pin);// 避免
    GPIOx->ODR |= (1 << Pin);
    
  2. 设置单个引脚低电平

    // 推荐
    GPIOx->BRR = (1 << Pin);
    // 或
    GPIOx->BSRR = (1 << (16 + Pin));
    
  3. 同时设置多个引脚

    // 原子操作
    GPIOx->BSRR = (1 << PinA) | (1 << (16 + PinB));
    
  4. 切换引脚状态

    // 最优方案
    GPIOx->ODR ^= (1 << Pin);  // 异或操作切换状态// 替代方案(两条指令)
    if(GPIOx->ODR & (1 << Pin)) GPIOx->BRR = (1 << Pin);
    elseGPIOx->BSRR = (1 << Pin);
    

关键结论:BSRR/BRR寄存器通过"只影响目标位"的设计,从根本上解决了ODR直接操作时的位覆盖问题,同时提供原子操作保证,是多引脚控制场景的最佳选择。

http://www.xdnf.cn/news/1126243.html

相关文章:

  • 153.在 Vue 3 中使用 OpenLayers + Cesium 实现 2D/3D 地图切换效果
  • 淘宝扭蛋机小程序开发:重构电商娱乐化体验的新范式
  • Kruskal重构树
  • Linux操作系统从入门到实战(九)Linux开发工具(中)自动化构建-make/Makefile知识讲解
  • 12.6 Google黑科技GShard:6000亿参数MoE模型如何突破显存限制?
  • 导出内存溢出案例分析
  • 学习秒杀系统-实现秒杀功能(商品列表,商品详情,基本秒杀功能实现,订单详情)
  • JavaScript认识+JQuery的依赖引用
  • ethers.js-8-bigNmber和callstatic模拟
  • 2025年最新香港站群服务器租用价格参考
  • 探索阿里云ESA:开启边缘安全加速新时代
  • 基于Ruoyi和PostgreSQL的统一POI分类后台管理实战
  • 论文阅读:arxiv 2025 A Survey on Data Contamination for Large Language Models
  • 从12kW到800V,AI服务器电源架构变革下,功率器件如何解题?
  • redisson 设置了过期时间,会自动续期吗
  • 【网络安全】大型语言模型(LLMs)及其应用的红队演练指南
  • 经典排序算法之希尔排序
  • docker 方式gost代理搭建以及代理链实施
  • HTTP常见误区
  • 具身智能零碎知识点(六):VAE 核心解密:重参数化技巧(Reparameterization Trick)到底在干啥?
  • 第二章 OB 存储引擎高级技术
  • JavaScript进阶篇——第四章 解构赋值(完全版)
  • IT岗位任职资格体系及发展通道——研发岗位任职资格标准体系
  • 进程探秘:从 PCB 到 fork 的核心原理之旅
  • 从零开始的云计算生活——第三十二天,四面楚歌,HAProxy负载均衡
  • 测试tcpdump,分析tcp协议
  • JAVA学习笔记 使用notepad++开发JAVA-003
  • Bootstrap-HTML(七)Bootstrap在线图标的引用方法
  • SELinux 详细解析
  • 【安卓笔记】RxJava之flatMap的使用