最近在学习HAL库的过程中,遇到了一些疑问,通过Goto发现,所有对GPIO引脚状态的设置函数均是对相关寄存器进行操作,这里只探讨我所遇见的疑问,请各位大佬指点。这里需要记在以下两点重要知识点,方便理解。
- 所有配置GPIO数据的库函数,均是设置ODR,BSRR,BRR 寄存器直接来控制引脚输出状态。
- ODR、BSRR、BRR寄存器均为32位。
主要参考以下大佬的博客:
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。