关于STMF4系列芯片GPIO的ODR、BSRR、BRR寄存器

最近在学习HAL库的过程中,遇到了一些疑问,通过Goto发现,所有对GPIO引脚状态的设置函数均是对相关寄存器进行操作,这里只探讨我所遇见的疑问,请各位大佬指点。这里需要记在以下两点重要知识点,方便理解。

  1. 所有配置GPIO数据的库函数,均是设置ODR,BSRR,BRR 寄存器直接来控制引脚输出状态。
  2. ODR、BSRR、BRR寄存器均为32位。

主要参考以下大佬的博客:

STM32F4不同版本的库中BSRR的设置差异简述_stm32f407 bsrr-CSDN博客

GPIO 配置之ODR, BSRR, BRR 详解 - prayer521 - 博客园 (cnblogs.com)

GPIO 配置之ODR, BSRR, BRR 详解_gpiox->brr-CSDN博客

BSRR代码

void SCL_OutPut(iic_bus_t *Bus, uint16_t Value)
{
    if(Value)
    {
        Bus ->IIC_SCL_PORT->BSRR |= Bus->IIC_SCL_PIN;
    }
    else
    {
        Bus->IIC_SCL_PORT->BSRR = (uint32_t)Bus->IIC_SCL_PIN << 16U;
    }
}

此代码中,通过直接操作BSRR寄存器即可实现IIC发送一位数据,对我这种常年调库的菜鸡来说,一下干到寄存器直接懵逼,这个寄存器是干什么的,为什么对他操作,能实现相关GPIO端口Pin的高低电平转换,于是带着疑问,Goto了一下这个寄存器,发现他是在stm32f4xx_hal_gpio.h里面定义的一个结构体成员:

  GPIO结构体定义

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

ODR寄存器

手册全称为:端口输出数据寄存器

ODR寄存器可读可写:既能控制管脚为高电平,也能控制管脚为低电平。

ODR寄存器是一个可读可写的寄存器,对相应位写1,即可置GPIOx某一PIN为1,对相应位写0,即可置GPIOx某一PIN为0。通过GPIO端口寄存器描述可发现,ODR寄存器前16位保留,只有后16位有效,即可实现对相应位写1或0,改变GPIOx的高低电平。

BSRR寄存器

手册全称为:端口设置/清除寄存器

BSRR 只写寄存器:既能控制管脚为高电平,也能控制管脚为低电平。

BSRR有一个特点,就是Set比Reset的级别高,

就是说同一个bit又做Set又做Reset,最后结果是Set

对寄存器高 16bit 写1 对应管脚为低电平,对寄存器低16bit写1对应管脚为高电平。写 0 ,无动作。

这里我们来看相应的手册寄存器描述,便于更好理解

不同于ODR寄存器,BSRR寄存器32位全部使用。

高16位用于清除端口x的状态,写1可将对应端口置0,写0,无效果。

低16位用于设置端口x的状态,写1即可将对应端口置1,写0不产生影响。

BRR寄存器

手册全称为:端口位清除寄存器

BRR 只写寄存器:只能改变管脚状态为低电平,对寄存器 管脚对于位写 1 相应管脚会为低电平。写 0 无动作。 

 查阅官方手册,其高16位被保留,只有低16位有效,在官方手册里被称为端口位清除寄存器。

 ODR、BSR、BRR区别

看到这里,想必你也跟我一样产生了疑问,既然ODR能把BSR、BRR寄存器的活干完,还造这两个娃干嘛,这不闲的吗?这里可以去看看这个佬的博客:

GPIO 配置之ODR, BSRR, BRR 详解 - prayer521 - 博客园 (cnblogs.com)

1.既然ODR 能控制管脚高低电平为什么还需要BSRR和SRR寄存器?
2.既然BSRR能实现BRR的全部功能,为什么还需要SRR寄存器?

作为一个菜鸡,我相信ST公司这么大一定有他的大道理,我查阅了部分博客发现原来是这样: 

对于问题 1 ------ 意法半导体给的答案是---

“This way, there is no risk that an IRQ occurs between the read and the modify access.”
什么意思呢?就就是你用BSRR和BRR去改变管脚状态的时候,没有被中断打断的风险。也就不需要关闭中断。

关闭中断明显会延迟或丢失一事件的捕获,所以控制GPIO的状态最好还是用SBRR和BRR

 对于问题 2 ------- 个人经验判断意法半导体仅仅是为了程序员操作方便估计做么做的。

因为BSRR的 低 16bsts 恰好是set操作,而高16bit是 reset 操作 而BRR 低 16bits 是reset 操作。

简单地说GPIOx_BSRR的高16位称作清除寄存器,而GPIOx_BSRR的低16位称作设置寄存器。

另一个寄存器GPIOx_BRR只有低16位有效,与GPIOx_BSRR的高16位具有相同功能。

举个例子说明如何使用这两个寄存器和所体现的优势。

例如GPIOE的16个IO都被设置成输出,而每次操作仅需要

改变低8位的数据而保持高8位不变,假设新的8位数据在变量Newdata中,

这个要求可以通过操作这两个寄存器实现,STM32的固件库中有两个函数

GPIO_SetBits()和GPIO_ResetBits()使用了这两个寄存器操作端口。

 BSRR寄存器应用

回到开头的代码,就不难理解为什么能实现其逻辑功能了

void SCL_OutPut(iic_bus_t *Bus, uint16_t Value)
{
    if(Value)
    {
        Bus ->IIC_SCL_PORT->BSRR |= Bus->IIC_SCL_PIN;
    }
    else
    {
        Bus->IIC_SCL_PORT->BSRR = (uint32_t)Bus->IIC_SCL_PIN << 16U;
    }
}

 这里Bus是一个iic_buis_t类型的结构体变量,其结构体定义如下:

typedef struct
{
    GPIO_TypeDef *IIC_SDA_PORT; 
    GPIO_TypeDef *IIC_SCL_PORT; 
    uint16_t IIC_SDA_PIN;       
    uint16_t IIC_SCL_PIN;       
    // void (*CLK_ENABLE)(void);
} iic_bus_t;

 对应端口PIN置1

Bus ->IIC_SCL_PORT->BSRR |= Bus->IIC_SCL_PIN;

上面代码实现的功能即为将相应的GPIO端口置1,这里IIC_SCl_PIN为形参,由用户创建结构体变量时赋值,实际上还是GPIO端口的PIN ,在gpio.h文件中可以看到相关PIN的定义

#define GPIO_PIN_0                 ((uint16_t)0x0001)  /* Pin 0 selected    */
#define GPIO_PIN_1                 ((uint16_t)0x0002)  /* Pin 1 selected    */
#define GPIO_PIN_2                 ((uint16_t)0x0004)  /* Pin 2 selected    */
#define GPIO_PIN_3                 ((uint16_t)0x0008)  /* Pin 3 selected    */
#define GPIO_PIN_4                 ((uint16_t)0x0010)  /* Pin 4 selected    */
#define GPIO_PIN_5                 ((uint16_t)0x0020)  /* Pin 5 selected    */
#define GPIO_PIN_6                 ((uint16_t)0x0040)  /* Pin 6 selected    */
#define GPIO_PIN_7                 ((uint16_t)0x0080)  /* Pin 7 selected    */
#define GPIO_PIN_8                 ((uint16_t)0x0100)  /* Pin 8 selected    */
#define GPIO_PIN_9                 ((uint16_t)0x0200)  /* Pin 9 selected    */
#define GPIO_PIN_10                ((uint16_t)0x0400)  /* Pin 10 selected   */
#define GPIO_PIN_11                ((uint16_t)0x0800)  /* Pin 11 selected   */
#define GPIO_PIN_12                ((uint16_t)0x1000)  /* Pin 12 selected   */
#define GPIO_PIN_13                ((uint16_t)0x2000)  /* Pin 13 selected   */
#define GPIO_PIN_14                ((uint16_t)0x4000)  /* Pin 14 selected   */
#define GPIO_PIN_15                ((uint16_t)0x8000)  /* Pin 15 selected   */
#define GPIO_PIN_All               ((uint16_t)0xFFFF)  /* All pins selected */

如果不理解,这里以端口PA0为例讲解一下

代码原为:

Bus ->IIC_SCL_PORT->BSRR |= Bus->IIC_SCL_PIN;

将IIC_SCL_PIN换为GPIO_PIN_0,即0x0001,将IIC_SCL_PORT换成GPIOA,可得

Bus ->GPIOA ->BSRR =| Bus ->0x0001; 

 这应该就看明白了,将BSSR低16位或上0x0001,即可实现对应端口PA0变为高电平。

对应端口PIN置0 

 Bus->IIC_SCL_PORT->BSRR = (uint32_t)Bus->IIC_SCL_PIN << 16U;

还记得前面讲过的,ODR、BSRR、BRR寄存器均为32位寄存器,其中ODR、BRR寄存器高16位被保留,而BSRR寄存器的高16位可以用来清除相应GPIO端口PIN的高电平状态。 

在上一步的代码中,将PIN左移16位即可,变成高16位对应的端口PIN,就可实现对应端口清0。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yuan园园

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值