STM32的位段操作

开始STM32的学习!在学校的时候粗略学过STM32,但是很多外设没去涉猎。
STM32应该是现如今用得最火的单片机,作为一个嵌入式开发者还是很有必要去学习下。不论是硬件基础,还是软件角度来看标准库的设计技巧,都是值得研究研究。行吧,作为STM32学习记录的第一篇文章,说下我学习的参考资料:

刘平《深入浅出玩转51单片机》
《Cortex-M3权威指南(中文).pdf》
《STM32中文参考手册_V10.pdf》
正点原子《STM32不完全手册_库函数版本_V3.1.pdf》
秉火《零死角玩转STM32—F103指南者.pdf

第一篇文章写位段操作。

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

0x2000 0000 ~ 0x200f ffff (SRAM区中的最低1MB)
0x4000 0000 ~ 0x400f ffff (片上外设区中的最低1MB,已覆盖了全部的片上外设的寄存器)

这两个1MB的空间可以像普通RAM一样操作外(修改内容时用读-改-写),它们还有自己的位带别名区,位带别名区把这1MB的空间的每一位膨胀为一个32位的字。确切的说,这个字就是一个地址,当操作这个地址时,就可以达到操作这个位带区某个位的目的。
在位带区中,每个比特位都映射到别名地址区的一个地址,注意,这只是只有LSB有效的字(最低一位有效的字)。当一个别名地址被访问时,会把该地址转换为为位带操作。

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

AliasAddr = 0x42000000 + ((A - 0x40000000) * 8 + n) * 4
          = 0x42000000 + (A - 0x40000000) * 32 + n * 4

上式中,4表示一个字4个字节,8表示一个字节8个比特。

一开始,我对n(0<=n<=7)很不理解,既然n表示位序号,为什么不是0<=n<=31呢?其实是我忽略了“所在字节”四个字,也就是说在位带区中,不是以一个寄存器一个寄存器为分隔单元,而是以一个字节一个字节来分隔单元的。
STM32的位段操作_第1张图片
(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

如下图:
STM32的位段操作_第2张图片

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

AliasAddr = 0x22000000 + ((A - 0x20000000) * 8 + n) * 4 
          = 0x22000000 + (A - 0x20000000) * 32 + n * 4

只是对位带基地址和位带别名区基地址做了改变即可。

再举个例子吧:
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) (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;

位段操作,使得1MB的SRAM就有32MB的对应别名区空间,1位膨胀到32位,但效率更高,(在中断的时候)具有更安全的作用。

你可能感兴趣的:(STM32单片机)