要点:找到需要带操作的寄存器的地址,并把它转换为位带别名区地址。
1. 什么是位带?
通过普通的加载、存储指令来对单一的比特进行读写。例如下面就可以对P0^1口进行置位和复位。
P0^1 = 1;
P0^1 = 0;
在stm32开发过程中,如果使用库函数开发可以不使用位带操作。可以使用如下代码:
GPIO_SetBits(GPIO_IIC,IIC_SCL_Pin);
GPIO_SetBits(GPIO_IIC,IIC_SDA_Pin);
2. 位带别名区
在CM3中有两个区实现了位带。一个是SRAM区的最低1MB范围(0x2000_0000‐0x200F_FFFF ),第二个则是片内外设区的最低1MB范围(0x4000_0000‐0x400F_FFFF)。
这两个区中的地址除了可以像普通的RAM一样使用外,它们还有自己的“位带别名区”,位带别名区把每个比特膨胀成一个32位的字。
当通过位带别名区访问这些字的时候,就可以达到访问原始比特的目的。
(此处详见《Cortex-M3权威指南》)
1 Byte = 8 bit
1 KB = 1024 Byte
1 MB = 1024 KB
位带区(1MB): 支持位带操作的地址区
位带别名区(32MB): 对别名地址的访问最终作用到位带区的访问上(注:这中途有一个地址映射过程 )
LSB:最低有效位
在位带区中,每个比特都映射到别名地址区的一个字(一个字包含4个字节,也就是32比特,32位)——这是只有LSB有效的字。
当一个别名地址被访问时,会先把该地址变换成位带地址。对于读操作,读取位带地址中的一个字,再把需要的位右移到LSB,并把LSB返回。对于写操作,把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”过程。
对 SRAM 位带区的某个比特,记它所在字节地址为 A,位序号为n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr = 0x22000000+((A-0x20000000)*8+n)*4 = 0x22000000+(A-0x20000000)*32+n*4
对于片上外设位带区:
AliasAddr = 0x42000000+((A-0x40000000)*8+n)*4 = 0x42000000+(A-0x20000000)*32+n*4
SRAM区中的位带地址映射
位带区 | 等效的别名地址 |
---|---|
0x20000000.0 | 0x22000000.0 |
0x20000000.1 | 0x22000004.0 |
0x20000000.2 | 0x22000008.0 |
… | … |
0x20000000.31 | 0x2200007C.0 |
0x20000004.0 | 0x22000080.0 |
3. 工程实例
//将位带地址+序号转换为别名地址的宏。
//此处左移5就等价于乘于32,左移2就等价于乘于2.
#define BITBAND(addr,bitnum) ((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
//设置输入输出方向寄存器后给GPIOA的1口置位和复位只需要如下代码所示,这样再对IO操作是不是就很简单了。
PAout(1)=1;
PAout(1)=0;