首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

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灯中例程的改写,通过位映射空间来控制管脚的输出,获取管脚的电平状态。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1