从STM32的位带操作重谈嵌入式中寻址与对齐的理解
,实际上之间有4*1Byte的空间,即4*8bit=32bit的空间,这一空间刚好可以容下一个32bit的寄存器值存放。实际上,你可以看到几乎所有访问寄存器时的地址值的末尾均为0,4,8,C,即寄存器们一个挨着一个,32bit为一组,塞满了他们所在的一片物理地址区域。因此对于32位CPU来说,出于效率一般均按字访问,即访问地址末尾为0,4,8,C的物理地址,一次访问到4个字节,不会单独访问其他地址,比如地址末尾为1的物理地址。当然,还有所谓的以半字(Half-Word)方式访问,例如Thumb指令集,一次访问2个字节,访问地址末尾为2的倍数的物理地址。
好了,那怎么保证访问到这个地址时能读取到32bit的数据,且他们并不错位、顺序相反呢?这就涉及到字节的对齐问题。
我们先分析一下前面的一条预定义
@->#defineIOPIN0 (*((volatile unsigned long *) 0xE0028000))
这是一个指针的写法。首先当访问一个已知地址值的内容时我们可以先定义一个指针,比如↓
@-> (uint32*)0xE0028000//当然也可以是unsigned int来代替uint32,都可以。
即将地址位于0xE0028000的数据用指针来表达。对于这一指针,uint32是一个32位的数据结构,限制了这一指针指向的内容是以0xE0028000开始往地址增长方向,共计4个Byte,32bit的这么一块区域,其数据结构是uint32。之后我们需要得到这个指针的值,那么很简单,用*运算取值即可↓
@-> ( *( (uint32*)0xE0028000 ) )//我故意多留了空格,目的是为了看得清楚。
这样一整块就得到了0xE0028000这一地址上的值,剩下想要读取或写入都可以了。原本的宏定义中用到的数据类型是unsigned long,也是32位无符号型整数,加上volatile修饰,表示编译器对这个数不做优化处理。大小确定了之后,现在我们看着这4个字节,假如其中的内容如下(还记得每个地址上存放的是一个字节么),以十六进制表示↓
@-> 0xE0028000 :0xDD
@-> 0xE0028001 :0xCC
@-> 0xE0028002 :0xBB
@-> 0xE0028003 :0xAA
当读取时,你认为我们最终得到的值是什么样的?是0xDDCCBBAA(高位数存在地址低位),还是反过来的0xAABBCCDD(高位数存在地址高位)?想一想。
关于这一点,就是CPU在设计时最有争议的地方,许多芯片厂商在设计时也并没有很好的统一。习惯上将,规定第一种存储方式,即高位数存放在地址低位,称为大端(Big-endian),而第二种存储方式,即高位数存放在地址高位,称为小端(Small-endian)。对于我们来说,觉得小端对齐方式更符合常规思维,高位对应高地址,地位对应低地址。可以从这个wiki网址参考有哪些硬件使用大端,哪些使用小端。注意ARM架构是可以Bi-endian的,即可设置为大小端的一种,只不过我们常用的ARM芯片被制造商设置为小端,大小端设置的寄存器位往往设为只读,只能通过REV指令零时调换存储大小端而已。
回过头看看我们访问寄存器时,已知了地址值0xE0028000,并且我们需要读取4Byte,即32bit因此需要设立变量为unsinged long,我们也知道了读取后的字节顺序为小端,因此对(*((volatile unsigned long *) 0xE0028000)) 这样一句话的操作就恰好对应为我们需要的4个Byte的顺序正确的寄存器值,我们在对嵌入式的寄存器进行操作时也都是这么做的而且运行的很好。
之前提到的两个区域,SRAM区和Peripheral区都有位带操作区,这样一来↓
IN A NUTSHELL:
@->位带区(Bit band region)中的每一个bit均扩充到别名区(Bit band alias)上的一个字(Word),即4个字节(Byte),32个bit,因此总共1MB的位带区被扩充为32MB的别名区。
@->为什么每一个bit位要扩充为一个字(Word)而不是字节(Byte)?因为CPU进行常规操作都是以字(Word)为单位访问地址的。所以位带区的相邻一bit映射到别名区的地址增量是4,正好是4个字节(Byte),一个字(Word)。
之前提到的,编程手册中给出的别名区和位带区之间的计算公式,我想只要你有高中知识,用数学归纳法就可以推导出来了。选择几个实际地址试试看,你就明白了。↓
在实际操作中,根据Cortex-M3权威指南,可以根据如下宏定义进行位带操作。以GPIOA口的控制输出引脚寄存器ODR为例,有如下定义
#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 PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //OutPut
使用时只需要写
@-> PAout(4)=1
就可以将GPIOA口的第四个bit位置为1了。
STM32位带操作寻址与对 相关文章:
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)
- openwebmailV1.60安装教学(11-12)
- Linux嵌入式系统开发平台选型探讨(11-09)
- Windows CE 进程、线程和内存管理(二)(11-09)