STM32的位段操作

第一篇文章写位段操作。

位操作就是可以读/写单独的一个比特位,在STM32中没有像51单片机的sbit来实行位定义,但是它可以通过位带别名区来实现。 
在STM32中有两个地方实现了位带操作,一个是SRAM区的最低1MB空间,另一个是外设区最低1MB空间。

0x2000 0000 ~ 0x200f ffff (SRAM区中的最低1MB)
0x4000 0000 ~ 0x400f ffff (片上外设区中的最低1MB,已覆盖了全部的片上外设的寄存器)
1
2
这两个1MB的空间可以像普通RAM一样操作外(修改内容时用读-改-写),它们还有自己的位带别名区,位带别名区把这1MB的空间的每一位膨胀为一个32位的字。确切的说,这个字就是一个地址,当操作这个地址时,就可以达到操作这个位带区某个位的目的。 
在位带区中,每个比特位都映射到别名地址区的一个地址,注意,这只是只有LSB有效的字(最低一位有效的字)。当一个别名地址被访问时,会把该地址转换为为位带操作。

对片上外设位带区的某个比特位,记它的所在字节的地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:

AliasAddr = 0x42000000 + ((A - 0x40000000) * 8 + n) * 4
          = 0x42000000 + (A - 0x40000000) * 32 + n * 4
1
2
上式中,4表示一个字4个字节,8表示一个字节8个比特。

一开始,我对n(0<=n<=7)很不理解,既然n表示位序号,为什么不是0<=n<=31呢?其实是我忽略了“所在字节”四个字,也就是说在位带区中,不是以一个寄存器一个寄存器为分隔单元,而是以一个字节一个字节来分隔单元的。 
 
(1) A - 0x40000000 = 当前字节偏离外设基地址的偏移字节数 
(2) 偏移字节数 * 8 = 偏移了多少位 
(3) 因为位带区每一位对应位带别名区的一个地址(4字节),而地址是以字节计算的,所以位带别名区对应偏移量最后一个的地址 = 偏移了多少位 * 4 
(4) n * 4 = 偏移量后面的n位对应位带别名区的地址

计算如下
位带区寄存器地址:0x40000000
0: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 0) * 4 = 0x42000000 + 0 = 42000000
1: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 1) * 4 = 0x42000000 + 0 = 42000004
2: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 2) * 4 = 0x42000000 + 0 = 42000008
3: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 3) * 4 = 0x42000000 + 0 = 4200000c
4: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 4) * 4 = 0x42000000 + 0 = 42000010
5: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 5) * 4 = 0x42000000 + 0 = 42000014
6: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 6) * 4 = 0x42000000 + 0 = 42000018
7: 0x42000000 + ((0x40000000 - 0x40000000) * 8 + 7) * 4 = 0x42000000 + 0 = 4200001c

位带区寄存器地址:0x40000008
0: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 0) * 4 = 0x42000000 + 0x8 = 42000020
1: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 1) * 4 = 0x42000000 + 0x104 = 42000024
2: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 2) * 4 = 0x42000000 + 0x108 = 42000028
3: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 3) * 4 = 0x42000000 + 0x10c = 4200002c
4: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 4) * 4 = 0x42000000 + 0x110 = 42000030
5: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 5) * 4 = 0x42000000 + 0x114 = 42000034
6: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 6) * 4 = 0x42000000 + 0x118 = 42000038
7: 0x42000000 + ((0x40000001 - 0x40000000) * 8 + 7) * 4 = 0x42000000 + 0x11c = 4200003c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
如下图: 


同理,对于SRAM位带区的某个比特位,记它所在字节地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:

AliasAddr = 0x22000000 + ((A - 0x20000000) * 8 + n) * 4 
          = 0x22000000 + (A - 0x20000000) * 32 + n * 4
1
2
只是对位带基地址和位带别名区基地址做了改变即可。

再举个例子吧: 
1) 往地址为0x40000001的位带区写入0x4466aadd (0b1000100011001101010101011011101) 
2) 读取地址为42000020的位带别名区得1 
读取地址为42000024的位带别名区得0 
读取地址为42000028的位带别名区得1 
3) 往地址为42000020的位带别名区写0, 往地址为42000024的位带别名区写1后,地址为0x40000001的位带区的数据为0b1000100011001101010101011011110,即0x4466AADE。

为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址 + 位序号”转换成别名地址。

//把位带区地址 + 位序号转换成位带别名去的宏
#define BITBAND(addr, bit_num) ((addr & 0xf0000000) + 0x02000000 + ((addr & 0x00ffffff) << 5) + (bit_num << 2))
1
2
1) (addr & 0xf0000000) + 0x02000000: 区分SRAM还是外设,如果是外设,结果为4,再加0x2000000就等于0x4200000,0x42000000就是外设别名位带区。 
如果是SRAM,结果为2,再加上0x2000000就等于0x22000000,0x22000000就是SRAM别名位带区。 
2)addr & 0x00ffffff:屏蔽了最高2位,相当于减去0x20000000或者0x40000000。因为位带区的有效范围是1M,即0x100000,这样子就做到了低6位有效。 
3) << 5:等价于乘以32 
4) << 2:等价于乘以4

运用该计算代码,位带操作示例代码为:

//通过位带区地址和位带区的目标位,找到位带别名区的地址
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2))

// 把一个地址强制转换成指针
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))

// 把位带别名区地址转换成指针
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))

//定义GPIOA的ODR寄存器
#define GPIOA_ODR_Addr (GPIOA_BASE + 12)

//设置GPIOA的引脚5为1,这是位操作
BIT_ADDR(GPIOA_ODR_Addr,5) = 1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
位段操作,使得1MB的SRAM就有32MB的对应别名区空间,1位膨胀到32位,但效率更高,(在中断的时候)具有更安全的作用。
--------------------- 

猜你喜欢

转载自blog.csdn.net/xiaobaixiongxiong/article/details/83715753
今日推荐