首页 理论教育 位带操作:单片机原理及接口技术

位带操作:单片机原理及接口技术

时间:2023-11-23 理论教育 版权反馈
【摘要】:图3.18位带区与位带别名区的膨胀关系图图3.19位带区与位带别名区的膨胀对应关系图例如:欲设置地址0x2000_0000中的比特2,则使用位带操作的设置过程如图3.20所示。图3.22从位带别名区中读取比特数据图3.23读取比特数据时传统方法与位带方法的比较位带操作的概念其实30多年前就有了,那还是8051单片机开创的先河。图3.25通过位带操作实现互锁访问同样的道理,多任务环境中的紊乱现象也可以通过互锁访问来避免。

位带操作:单片机原理及接口技术

支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。在Cortex-M3中,有两个区中实现了位带。其中第一个是SRAM区的最低1 MB范围,第二个则是片内外设区的最低1 MB范围。这两个区中的地址除了可以像普通的RAM一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个32位的字。当通过位带别名区访问这些字时,就可以达到访问原始比特的目的。位带区与位带别名区的膨胀及对应关系如图3.18、图3.19所示。

图3.18 位带区与位带别名区的膨胀关系图

图3.19 位带区与位带别名区的膨胀对应关系图

例如:欲设置地址0x2000_0000中的比特2,则使用位带操作的设置过程如图3.20所示。

图3.20 写数据到位带别名区

对应的汇编代码如图3.21所示。

图3.21 位带操作与普通操作的对比(在汇编程序的角度)

位带读操作相对简单些,从位带别名区中读取比特数据如图3.22所示;读取比特数据时传统方法与位带方法的比较如图3.23所示。

图3.22 从位带别名区中读取比特数据

图3.23 读取比特数据时传统方法与位带方法的比较

位带操作的概念其实30多年前就有了,那还是8051单片机开创的先河。如今,Cortex-M3将此能力进化,这里的位带操作是8051位寻址区的增强版。

Cortex-M3使用如下术语来表示位带存储的相关地址:

①位带区:支持位带操作的地址区;

②位带别名:对别名地址的访问最终作用到位带区的访问上(注意这中间有一个地址映射过程)。

在位带区中,每个比特都映射到别名地址区的一个字——这是只有LSB有效的字。当一个别名地址被访问时,会先把该地址变换成位带地址。对于读操作,读取位带地址中的一个字,再把需要的位右移到LSB,并把LSB返回。对于写操作,把需要写的位左移至对应的位序号处,然后执行一个原子的“读—改—写”过程。

支持位带操作的两个内存区的范围是:

对于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个比特。

对于SRAM内存区,位带别名的重映射见表3.6。

表3.6 SRAM区中的位带地址映射

对于片上外设,映射关系见表3.7。

表3.7 片上外设区中的位带地址映射

这里再举一个位带操作的例子:

①在地址0x20000000处写入0x3355AACC。

②读取地址0x22000008。本次读访问将读取0x20000000,并提取bit2,值为1。

③往地址0x22000008处写0。本次操作将被映射成对地址0x20000000的“读—改—写”操作(原子的),把bit2清零。

④现在再读取0x20000000,将返回0x3355AAC8(bit2已清零)。(www.xing528.com)

位带别名区的字只有LSB有意义。另外,在访问位带别名区时,不管使用哪一种长度的数据传送指令(字/半字/字节),都把地址对齐到字的边界上,否则会产生不可预料的结果。

(1)位带操作的优越性

位带操作有什么优越性呢?最容易想到的就是通过GPIO的管脚来单独控制每盏LED的点亮与熄灭。此外,也对操作串行接口器件提供了很大的方便(典型如74HC165、CD4094)。总之位带操作对硬件I/O密集型的底层程序最有用处。

位带操作还能用来简化跳转的判断。当跳转依据是某个位时,以前必须这样做:

①读取整个寄存器;

②屏蔽不需要的位;

③比较并跳转。

现在只需:

①从位带别名区读取状态位;

②比较并跳转。

使代码更简洁,这只是位带操作优越性的初等体现,位带操作还有一个重要的好处是在多任务中,用于实现共享资源在任务间的“互锁”访问。多任务的共享资源必须满足一次只有一个任务访问它——所谓的“原子操作”。以前的“读—改—写”需要3条指令,导致中间留有两个能被中断的空当,于是可能会出现如图3.24所示的紊乱现象。

图3.24 共享资源在紊乱现象下丢失数据演示

同样的紊乱现象可以出现在多任务的执行环境中。其实,图3.24所演示的情况可以看成多任务的一个特例:主程序是一个任务,ISR是另一个任务,这两个任务并发执行。

通过使用Cortex-M3的位带操作,就可以消灭上例中的紊乱现象。Cortex-M3把这个“读—改—写”做成一个硬件级别支持的原子操作,不能被中断,如图3.25所示。

图3.25 通过位带操作实现互锁访问

同样的道理,多任务环境中的紊乱现象也可以通过互锁访问来避免。

(2)其他数据长度上的位带操作

位带操作并不只限于以字为单位的传送,也可以按半字和字节为单位传送。例如,可以使用LDRB/STRB来以字节为长度单位去访问位带别名区,同理可用于LDRH/STRH。但是不管用哪一种数据长度的操作指令,都必须保证目标地址对齐到字的边界上。

(3)在C语言中使用位带操作

不幸的是,在C编译器中并没有直接支持位带操作。比如,C编译器并不知道同一块内存能够使用不同的地址来访问,也不知道对位带别名区的访问只对LSB有效。欲在C中使用位带操作,最简单的做法就是#define一个位带别名区的地址。例如:

为简化位带操作,也可以定义一些宏。比如,我们可建立一个把“位带地址+位序号”转换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏:

//把“位带地址+位序号”转换成别名地址的宏

#define BITBAND(addr,bitnum)((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))

//把该地址转换成一个指针

#define MEM_ADDR(addr)∗((volatile unsigned long∗)(addr))

在此基础上,我们就可以改写如下代码:

需要注意的是:当使用位带功能时,要访问的变量必须用volatile来定义。因为C编译器并不知道同一个比特可以有两个地址。所以就要通过volatile使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的考虑,在中途使用寄存器来操作数据的复本,直到最后才把复本写回(这和Cache的原理是一样的)。

在GCC和RealView MDK(即Keil)开发工具中,允许定义变量时手工指定其地址。如:

volatile unsigned long bbVarAry[7]_attribute_((at(0x20003014)));//位带区

volatile unsigned long∗const pbbaVar=(void∗)(0x22000000+0x3014∗8∗4);//位带别名区

这样,就在0x20003014处分配了7个字,共得到了32×7=224个比特。在long∗后面的“const”通知编译器,该指针不能再被修改而指向其他地址。at()中的地址必须对齐到4字节边界。再使用这些比特时,可以通过如下的形式:

不过这种方式有个局限,编译器无法检查是否下标越界。那为什么不定义成“pbbaVar[224]”的数组呢?这也是一个编译器的局限,它不知道这个数组其实就是bbVarAry[7],从而在计算程序对内存的占用量上,会平白无故地多计入224×4个字节。对于指针形式的定义,可以使用宏定义,为每个需要使用的比特取一个字面值的名字,在下标中只使用字面值名字,不再写真实的数字,就可以极大程度地避免数组越界。在定义这“两个”变量时,前面加上了“volatile”,如果不再使用bbVarAry来访问这些比特,而只使用位带别名的形式访问时,这两个volatile就均不再需要。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈