位操作就是可以单独的对一个比特位读和写,这个在51 单片机中非常常见。51 单片机中通过关键字sbit 来实现位定义,STM32 没有这样的关键字,而是通过访问位带别名区来实现。
在STM32 中,有两个地方实现了位带,一个是SRAM 区的最低1MB 空间,令一个是外设区最低1MB 空间。这两个1MB 的空间除了可以像正常的RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这1MB 的空间的每一个位膨胀成一个32 位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。
图1 STM32 位带示意图
外设位带区
外设外带区的地址为:0X40000000~0X40100000,大小为1MB,这1MB 的大小在103系列大/中/小容量型号的单片机中包含了片上外设的全部寄存器,这些寄存器的地址为:0X40000000~0X40029FFF 。外设位带区经过膨胀后的位带别名区地址为:0X42000000~0X43FFFFFF,这个地址仍然在CM3 片上外设的地址空间中。在103 系列大/中小容量型号的单片机里面,0X40030000~0X4FFFFFFF 属于保留地址,膨胀后的32MB位带别名区刚好就落到这个地址范围内,不会跟片上外设的其他寄存器地址重合。
STM32 的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器比特位的效果,这比51 单片机强大很多。因为51 单片机里面并不是所有的寄存器都是可以比特位操作,有些寄存器还是得字节操作,比如SBUF。
虽然说全部寄存器都可以实现比特操作,但我们在实际项目中并不会这么做,甚至不会这么做。有时候为了特定的项目需要,比如需要频繁的操作很多IO 口,这个时候我们可以考虑把IO 相关的寄存器实现比特操作。
SRAM位带区
SRAM的位带区的地址为:0X2000 0000~X2010 0000,大小为1MB,经过膨胀后的位带别名区地址为:0X2200 0000~0X23FF FFFF,大小为32MB。操作SRAM 的比特位这个用得很少。
位带区和位带别名区地址转换
位带区的一个比特位经过膨胀之后,虽然变大到4 个字节,但是还是LSB 才有效。有人会问这不是浪费空间吗,要知道STM32 的系统总线是32 位的,按照4 个字节访问的时候是最快的,所以膨胀成4 个字节来访问是最高效的。
我们可以通过指针的形式访问位带别名区地址从而达到操作位带区比特位的效果。那这两个地址直接如何转换,我们简单介绍一下。
1. 外设位带别名区地址
对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
1 AliasAddr= =0x42000000+ (A-0x40000000)*8*4 +n*4
0X42000000 是外设位带别名区的起始地址,0x40000000 是外设位带区的起始地址,(A-0x40000000)表示该比特前面有多少个字节,一个字节有8 位,所以*8,一个位膨胀后是4 个字节,所以*4,n 表示该比特在A 地址的序号,因为一个位经过膨胀后是四个字节,所以也*4。
2. SRAM位带别名区地址
对于SRAM 位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
1 AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4
公式分析同上。
3. 统一公式
为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址+位序号”转换成别名区地址统一成一个宏。
1 // 把“位带地址+位序号”转换成别名地址的宏
2 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr &0x00FFFFFF)<<5)+(bitnum<<2))
addr & 0xF0000000 是为了区别SRAM还是外设,实际效果就是取出4 或者2,如果是外设,则取出的是4,+0X02000000 之后就等于0X42000000,0X42000000 是外设别名区的起始地址。如果是SRAM,则取出的是2,+0X02000000 之后就等于0X22000000,0X22000000 是SRAM别名区的起始地址。
addr & 0x00FFFFFF 屏蔽了高三位,相当于减去0X20000000 或者0X40000000,但是为什么是屏蔽高三位?因为外设的最高地址是:0X2010 0000,跟起始地址0X20000000 相减的时候,总是低5 位才有效,所以干脆就把高三位屏蔽掉来达到减去起始地址的效果,具体屏蔽掉多少位跟最高地址有关。SRAM 同理分析即可。<<5 相当于*8*4,<<2 相当于*4,这两个我们在上面分析过。
最后我们就可以通过指针的形式操作这些位带别名区地址,最终实现位带区的比特位操作。
GPIO 位带操作
外设的位带区,覆盖了全部的片上外设的寄存器,我们可以通过宏为每个寄存器的位都定义一个位带别名地址,从而实现位操作。但这个在实际项目中不是很现实,也很少人会这么做,我们在这里仅仅演示下GPIO中ODR 和IDR 这两个寄存器的位操作。
从手册中我们可以知道ODR 和IDR 这两个寄存器对应GPIO 基址的偏移是12 和8,我们先实现这两个寄存器的地址映射,其中GPIOx_BASE 在库函数里面有定义。
1. GPIO 寄存器映射
代码清单1 GPIO ODR和IDR寄存器映射
现在我们就可以用位操作的方法来控制GPIO 的输入和输出了,其中宏参数n 表示具体是哪一个IO 口,n(0,1,2...16)。这里面包含了端口A~G ,并不是每个单片机型号都有这么多端口,使用这部分代码时,要查看你的单片机型号,如果是64pin 的则最多只能使用到C 端口。
2. GPIO 位操作
代码清单2 GPIO输入输出位操作
3. 主函数
该工程我们直接从LED-库函数 操作移植过来,有关LED GPIO 初始化和软件延时等函数我们直接用,修改的是控制GPIO 输出的部分改成了位操作。该实验我们让IO 口输出高低电平来控制LED 的亮灭,负逻辑点亮。具体使用哪一个IO 和点亮方式由硬件平台决定。
代码清单3 main函数