这篇文章是GPIO寄存器操作的延伸,如果对GPIO寄存器的操作还不是很了解的可以去看这篇文章
学习寄存器——GPIO(根据手册用结构体封装寄存器)_gpio寄存器-CSDN博客
1.问题背景
我们以前在学GPIO的时候,只是简单的把GPIO口置1/0,就可以实现灯的亮灭,而到了一些比较复杂的项目中,有的时候你还没赋值呢,但有新的任务抢占/发生中断,然后刚好这个任务是进行IO口的翻转。
这个时候,翻转就是拿旧的数据来反转,最后导致实现的结果预期完全相反。
这个时候,STM32给予了一些特殊的寄存器供我们使用,我们先来看一些可以操控IO口的寄存器
2.各寄存器定义
首先我们先查看STM32F103的数据手册中对这些寄存器的定义
2.1 ODR寄存器
2.2 BSRR寄存器
2.3 BRR寄存器
2.4 对各寄存器进行分析
接下来,我将会从特点->推断->结论的步骤来对各寄存器进行层层分析。
这几个寄存器有什么特点呢?
我在上面的图片里面圈了一些关键词,在BRR和BSR中,都出现了“不影响”这个词,而ODR寄存器就没有这个词了。
而且我们还能发现,BSRR,BRR的功能都是对ODR寄存器进行设置的,你可以认为是对ODR寄存器的封装。
我们可以从上面的特点得出一些推断:
1.BSRR和BSR实现的功能都差不多,而BSR有的功能BSRR也有,BSR没有的功能BSR也有,因此BSR是BSRR的子集(毕竟BSRR的高16位实现的就是BSR的功能)。事实上在STM32F4系列芯片已经把BSR寄存器去掉了。因此本章也只会着重将BSRR寄存器。
2.ODR寄存器输入1就是高电平,输入0就是低电平,并没有像BSRR寄存器一样有保护机制(不产生影响)
因此我们根据推断得到一些结论:
1.ODR寄存器在更改某一个GPIO口的电平的时候,我们需要对其他位进行保护。
2.BSRR/BRR寄存器只有在高电平的时候允许设置位,低电平的时候不改变/不影响ODR寄存器本身的值。
那这个跟原子性有什么关联呢?这就引出我们的第二个概念:原子性
3.原子性
原子性指的是这个性质是原子的,而这个性质这么实现呢?
答案是原子操作。
原子操作单拎出来可以再出一篇文章了,为了文章不太过臃肿,我们这边就简单的说一下原子操作。
原子操作就是在执行一项任务(关键数据运算/修改寄存器的值)的时候,任何事情/任务都不能打扰它。
如果要详细的了解原子操作,可以看我这篇文章。
根据ARM手册,分析ARM架构中,原子操作的软硬件实现的底层原理-CSDN博客
我们在修改IO口(例如让LED灯亮起来)的时候,我们自然希望等到IO口完全改成功后才被其他任务获取,否则这个数据是不能被获取的。
在这情况下,BSRR/BSR寄存器就可以实现我们的需求
3.1 BSRR/BSR寄存器的原子性
在操作ODR寄存器实现对单个GPIO口的操作之时,我们一般都会使用到下面语句
GPIOA->ODR |= GPIO_Pin_6;
在C语言层面看是一个单独的语句,但实际上它包括了
- 读取当前ODR寄存器的值;
- 执行按位或运算;
- 以及将结果写回ODR寄存器;
- 这三个步骤。
由于这涉及多个CPU指令,所以并不是原子操作,尤其是在有中断的情况下可能会导致意想不到的行为。
且就算把或运算去掉了,他也不是原子操作。
因为原子操作不是单纯的只是一个指令,而是需要特殊硬件的配合。
而这个被赋予特殊硬件的寄存器,就是BSRR/BSR寄存器。
BSRR寄存器和ODR寄存器实现原子操作的方式代码对比。
① 使用 ODR(需保护)
// 需要关中断保护
__disable_irq(); // 关闭中断
GPIOA->ODR |= GPIO_Pin_6; // 设置 PA6 为高电平
__enable_irq(); // 恢复中断
② 使用 BSRR(无需保护)
GPIOA->BSRR = GPIO_Pin_6; // 原子操作,直接设置 PA6 为高电平
那么既然有BSRR寄存器了,我们为什么还会存在ODR寄存器?
在HAL库的源码中就说明了这一点。
4.ODR,BSRR寄存器的意义以及HAL库对各寄存器的使用
HAL库的GPIO位设置的函数是通过BSRR寄存器设置的,这样有效保障了HAL库的代码运行。
而GPIO的位翻转函数是通过ODR寄存器+异或赋值来实现的,下面是代码示例
// 翻转PA6引脚
GPIOA->ODR ^= GPIO_Pin_6;
因为翻转函数大部分都是使用频率很高的,例如LED灯的高频率闪烁。
这个时候你用原子操作,那你的中断之类的重要任务运行效率就降低了。
且异或+ODR对翻转的功能很友好,因为异或运算规则是:当两个位不同时,结果为1;相同时,结果为0。举个栗子
1^1=0 0^1=1 //结果必翻转
0^0=0 1^0=1 //结果不改变
我们只需要将其他位异或0,就可以不改变其他位的值。
5. 总结
特性 |
ODR |
BSRR |
操作类型 |
读-改-写(非原子) |
单次原子操作 |
是否影响其他引脚 |
是(需谨慎处理) |
否(仅修改目标引脚) |
是否需保护 |
是(多任务/中断环境) |
否(原子操作天然安全) |
代码复杂度 |
较高(需读取和写回) |
简单(直接写入) |
性能 |
较低(多次操作) |
高(单次操作) |