STM32的位映射空间
在先前的文章中,我们通过定义片上外设结构体、起始地址宏、 和片上外设指针达到了访问GPIO寄存器的目的,并最终驱动了LED灯。在定义gpio_regs_t结构体时,我们使用了两个C语言中比较高级的用法: 结构体位域和联合体(union)。使得我们可以直接通过类似如下代码的方式控制F端口上的第9个引脚输出高电平。
GPIOF->ODR.bits.pin9 = 1;
上面的这种访问方式看似是直接访问了寄存器中的某一位,实际上编译器在背后为我们做了很多事情。
假设我们有一个32位的变量uint32 a;
,我们想修改它的第2位(从0开始计数),并保证其它各位不变。
那么在C语言中的一个标准做法就是先读取该变量(用一个临时变量保存),再修改该临时变量,最后把修改后的结果写给原来的变量,如下。
这个过程我们称为读-改-写。
uint32 tmp = a;
tmp |= (0x01 << 2); // tmp &= ^(0x01 << 2);
a = tmp;
如果我们查看GPIOF->ODR.bits.pin9
的定义就会看到uint32 pin9 : 1;
所表达的意义就是对pin9的访问实际上就是访问一个uint32的变量的1位。编译器在背后未必会按照这种读改写的方式进行编译,
但一定会有位操作的过程,整个过程还是有点繁琐的。
"读-改-写"的过程很繁琐,而我们对于按位访问片上外设寄存器的需求却是很强烈的。为了提高处理器和代码效率, Cortex-M4中引入了一种称为位映射空间(Bit-Banding)的技术(一些资料中称它为位带技术,我只是觉得位带一词比较费解, 所以就造了一个位映射空间的名词)。
所谓的位映射空间是指,对于地址空间中的某一段连续空间A中的每一个bit, 在一段连续空间B中都有唯一一个字(32bit)与之对应。地址空间A就是地址空间B的位映射空间(bit-band region), 对B中的每个字的访问都会被映射到A中的一个位(bit)上,地址空间B被称为地址空间A的别名空间(bit-band bias)。 需要特别说明的是,虽然在别名空间中是以字(32位)的形式映射到位映射空间的,但别名空间中的每个字只有最低位有效。
在Cortex-M4中,一共有两段位映射空间,分别是SRAM和Peripheral起始的1MB空间,如下表1所示。
表1 Cortex-M4的为映射空间
位映射空间 | 别名空间 | 描述 |
---|---|---|
0x20000000 - 0x200FFFFF | 0x22000000 - 0x23FFFFFF | SRAM区中的位映射空间 |
0x40000000 - 0x400FFFFF | 0x42000000 - 0x43FFFFFF | Peripheral区中的位映射空间 |
下图1中为SRAM区的位映射空间示意图。我们可以通过0x23FFFFE0访问0x200FFFFF的最低位,也可以通过0x23FFFFFC访问0x200FFFFF的最高位。 假设我们想访问位映射空间中\(byte\_offset\)的第\(bit\_number\)位,我们可以通过如下公式得到别名空间中的地址\(bias\_addr\): $$ \begin{matrix} bias\_offset & = & (byte\_offset \times 32) + (bit\_number \times 4) \\ bias\_addr & = & bias\_base\_addr + bias\_offset \end{matrix} $$
图1 SRAM位映射空间示意图 |
参考正点原子的例程,我们定义如下的宏,用于计算别名空间地址、获取指向别名的指针:
#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))
通过定义如下关于GPIO输出数据寄存器的别名映射关系的宏,
我们就可以像这样PIout(6) = 1
控制I端口的第6个引脚输出高电平了。
#define GPIOI_ODR_Addr (GPIOI_BASE+20) //0x40022014
#define PIout(n) BIT_ADDR(GPIOI_ODR_Addr,n) //输出
这里的代码是对外部中断控制LED灯中例程的改写,通过位映射空间来控制管脚的输出,获取管脚的电平状态。