本帖最后由 zhihuizhou 于 2011-10-13 17:19 编辑
位操作是指单独操作某个数据中的某一位。在嵌入式程序设计中很常见,诸如设置标志位、判断一个字节某一位的值、取反一个字节的某一位等都需要通过位操作来完成。常见的51单片机可以通过***it关键字进行位定义从而实现位操作。但事实上绝大部分的单片机(比如被广泛使用的AVR单片机)是不支持位操作的,如此一来就必须通过程序的设计来实现位操作,最常见的操作如下: (1) a &=~(1 << 5); // 将变量 a 的第5位清0
(2) a |= (1 << 6); // 将变量 a 的第6位置1
(3) a ^=(1 << 2); // 将变量 a 的第2位取反 使用以上三句C代码进行位操作是一种很规范的方法,因为其没有使用任何非ANSI
C关键字,任何支持C编译的开发环境都支持这样的写法,这对程序的移植性有相当的好处。与此对应的,51单片机的keil uVision2开发环境所支持的***it关键字就是非ANSI C关键字,因此若在用户程序中使用了此关键字,则该程序无法移植到其他开发环境中,该程序也不具备移植到其他硬件平台(比如德仪的MSP430单片机)的基础。 有少许C代码编写经验的读者应该知道,上述3句代码都是“复合”语句。选(1)句为例:该语句首先进行移位操作,再将移位后的结果进行取反,然后读取出变量a的值并与取反的结果进行“与”运算,总结起来便是“读出——修改——写入”的过程。如此一来从表面上来看就至少经过了4条指令才完成该句代码的执行——从代码效率的角度上来说,这并不是很理想。 STM32所基于的ARM Cortex-M3内核引入了一种新颖的“位带”技术(英文称为Bit
Band),这种“位带”技术将部分其片内的部分称为“位带区”的存储区域和另外一部分称为“位带别名区”的区域映射起来。一个比较完整的描述是:Cortex-M3的内部存储空间有2个“位带区”,分别称为“SRAM位带区”和“外设存储位带区”,各自位于SRAM区和外设存储区各自最低的1MBit空间;并有对应的2个“位带别名区”,分别称为“SRAM位带别名区”和“外设存储位带别名区”,每个别名区大小为32MBit。“位带”技术将两个“位带区”的每一位分别映射带对应的“位带别名区”的一个“字”(即32位)的最低位上。图1展示了这种关系:
图1
图1中,左边的0x40000000表示“外设存储位带区”的起始地址,而右边的0x42000000则表示“外设存储位带别名区”的存储地址,0th Bit、1th Bit等表示从地址0x40000000依次往后的第0位,第1位等。右边的0x42000000表示STM32内部的“外设存储位带别名区”起始地址,而下面的0x42000000 – 0x420000010、0x42000010 – 0x420000020等则表示从地址0x42000000依次往后的第1个、第二个“字”空间。在此要注意到的是,STM32作为一款32位控制器,其数据总线当然是32位的,但其内部存储空间不仅支持32位存取,同时也支持8位(字节)、16位(半字)存取方式,因此其内部存储空间是按照最小存取长度(8位)来对齐的,以图1中的0x42000000 – 0x420000010为例,其存储空间的排列情况如下图2所示。假设想这段空间内写入数据0x12345678,则实际内容(假设是小端存储格式)如图3所示。
图2 图3
8位长度的对齐方式决定了用户通过应用程序操作存储空间的最小长度为8位,亦即1个字节。因此如果要单独对某一“位”进行操作,则必须使用上文中所讲述的办法。
但通过这种“位带”技术进行存储空间的映射后,可以很轻易快捷的实现位操作。当对“位带别名区”的某一个“字”空间的最低位进行清除操作时,则对应的“位带区”所对应的“位”即会被清除,反之当对“位带别名区”的某一个“字”空间的最低位进行置位操作时,,则对应的“位带区”所对应的“位”也会被置位。这样一来,前文所讲述的“读出——修改——写入”就变成了只有“写入”的过程,这是一种非常典型的空间换时间的做法。也许有读者会疑问,这样岂不是损失掉了2个32MBit的存储空间?答案是这部分存储空间是通过映射技术“虚拟”出来的,STM32片内的这部分地址空间并没有物理存储介质存在。
下面通过一个简单的例子讲述如何实现STM32微控制器平台上的“位带”技术实现一个简单的点亮发光二极管的操作。其中发光二极管使用STM32的PA4引脚的输出高电平点亮,则只要在PA4引脚输出一个高电平,即可点亮该发光二极管。
通过查阅STM32的开发手册可以知道,要在PA4引脚输出高电平,则只需要在初始化完毕GPIOA设备之后对GPIOA的ODR寄存器的第4位写入一个“1”即可。这个目的很简单,重点是如何计算ODR寄存器的第4位在“位带别名区”中所对应的“字”空间地址。获取该地址的过程如下图4所示。
图4
事实上有了前文的描述,相信图4是比较容易理解的。图中自上往下最终推算出了GPIOA的ODR各个位的“位带别名区”的地址,可以看到ODR寄存器的第4位所对应的“字”空间地址为0x42210190。从STM32的开发手册上也可以获取“位带别名区”的字空间所对应的“位”:
bit_word_addr = bit_band_base + (byte_offset×32) + (bit_number×4)
上述公式中,bit_word_addr表示“位带别名区”字空间,bit_band_base表示对应的“位带区别名区”起始地址,byte_offset表示“位”在“位带区”中的字节偏移地址,bit_number则表示“位”在对应“位带区”字节中的位置。
以对GPIOA的ODR寄存器的第4位写入一个“1”为例,首先要找到ODR寄存器的第4位的“位带区”起始地址,字节偏移地址和在字节中的位置。其中“位带区”起始地址已知为0x42000000,而字节偏移地址由在图4找出为0x0001080C(注意是此处偏移地址,不是图中的绝对地址),同时位置为第4位,因此可以套用上述公式计算对应的“字空间”
bit_word_addr = 0x42000000 + (0x0001080C × 32) + (4 × 4) = 0x42210190
可知可图中推算的结果一致。因此,只要向地址为0x42210190的空间写入“1”即可点亮发光二极管。程序清单见PDF。
|