支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在 CM3 中,有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。
位带操作的概念其实 30 年前就有了,那还是8051单片机开创的先河,如今,CM3 将此能力进化,这里的位带操作是 8051 位寻址区的威力大幅加强版。
CM3 使用如下术语来表示位带存储的相关地址:
位带区 支持位带操作的地址区
位带别名 对别名地址的访问最终作用到位带区的访问上(这中途有一个地址映射过程)
在位带区中,每个比特都映射到别名地址区的一个字——这是只有 LSB 有效的字。当一个别名地址被访问时,会先把该地址变换成位带地址。对于读操作,读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回。对于写操作,把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”过程。
支持位带操作的两个内存区的范围是:
0x2000_0000‐0x200F_FFFF(SRAM 区中的最低 1MB)
0x4000_0000‐0x400F_FFFF(片上外设区中的最低1MB)
对 SRAM 位带区的某个比特,记它所在字节地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr=0x22000000+((A-0x20000000)*8+n)*4=0x22000000+(A-0x20000000)*32+n*4
对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr=0x42000000+((A-0x40000000)*8+n)*4=0x42000000+(A-0x40000000)*32+n*4
上式中,“*4”表示一个字为 4 个字节,“*8”表示一个字节中有 8 个比特。
举例:
位带操作的作用:
1、GPIO的管脚单独控制(常用,重要)
2、使用标志位时简化判断(理解,可以尝试使用)
3、多任务中,用于实现共享资源在任务间的“互锁”访问(不懂)
在C编译器中并没有直接支持位带操作,所以,C编译器并不知道同一块内存能够使用不同的地址来访问,也不知道对位带别名区的访问只对LSB有效。欲在C中使用位带操作,最简单的做法就是#define一个位带别名区的地址。
以设置GPIOA,bit2为例理解位带操作:
GPIOA基址 0x4001 0800
端口输入数据寄存器 0x4001 0800+0x8=0x4001 0808
端口输出数据寄存器 0x4001 0800+0xC=0x4001 080C
输入寄存器位带别名
Addr=0x4200 0000+((0x40010808-0x4000 0000)*32+2*4=0x4221 0108
输出寄存器位带别名
Addr=0x4200 0000+((0x4001080C-0x4000 0000)*32+2*4=0x4221 0188
#define PA2in ((volatileunsigned long *)(0x4221 0108))
#define PA2out ((volatile unsigned long *)(0x4221 0118))
*PA2out=1; //PA2输出1
为简化位带操作,将位带别名计算定义成一个宏:
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 +
((addr & 0xFFFFF) << 5)+(bitnum << 2))
将该地址转化成一个指针:
#define MEM_ADDR(addr) *((volatileunsigned long *)(addr))
实现位带操作的宏:
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
I/O口寄存器地址映射:
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C,PA输出寄存器
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808,PA输入寄存器
对单一I/O的操作
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
定义I/O口
#define LED1 PAout(2)
则实现点亮LED只需
LED1=1;or LED1=0;
注意:当使用位带功能时,要访问的变量必须用volatile来定义。因为C编译器并不知道同一个比特可以有两个地址。所以就要通过volatile,使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的考虑,在中途使用寄存器来操作数据的复本,直到最后才把复本写回——这会导致按不同的方式访问同一个位会得到不一致的结果。
网友评论