您的位置 首页 编程

从STM32的位带操作重谈嵌入式中寻址与对齐的了解

觉得这篇文章很不错,普及知识:初接触STM32的人一定花了不少时间用于理解其位带操作(bitbanding)的原理与步骤。位带操作允许编程人员…

觉得这篇文章很不错,遍及常识:

初触摸STM32的人必定花了不少时刻用于了解其位带操作(bit banding)的原理与过程。位带操作答应编程人员以字的单位读/写单一bit位。回想咱们平常关于一个bit位的操作比方:↓

@-> PIN0 |= (1<<3);

@-> PIN0 &= ~(1<<5);

尽管这仅仅一行代码,可是实践上这一行做了好几步的作业。比方榜首行,首要读出当时PIN0的值放到缓存区,将1左移三位放入缓存区,将二者进行“或”操作,行将当时PIN0的第三方位位1,将成果存入到实践PIN0地点的地址,即更新了PIN0的值。当然实践写成汇编后或许过程不见得必定相同,可是这几步作业是必定得做的。

而关于位带操作,STM32中将上述PIN0(假定它处于答应从头映射的区域,即位带区->Bit Band Region)的每一个bit位从头映射到了一个独自的地址,只需对这一个新的地址进行写操作,则原PIN0值的对应位主动置位或清零。假定方才咱们PIN0的第3bit位从头映射的地址咱们用变量PIN0BIT3一共,则方才的操作能够写作如下↓

@-> PIN0BIT3 = 1; //等同于PIN0 |= (1<<3), 这是由地址重映射确保的。

这一行的操作是,将1写入到PIN0BIT3地点的地址,即更新了PIN0BIT3的值,完毕。咱们地址重映射,将确保PIN0的第三bit位被置一了。能够看出,操作过程比之前简略,因而相同的操作处理的速度更快了。

好,以上便是位带操作的原理,悉数介绍完了,是不是很简略。接下来咱们天然就想问了,这个PIN0第三bit位从头映射的地址在哪?这样地址重映射不是把内存扩展了么,答应重映射的地址会不会有约束?原地址跟重映射的地址之间有没有个换算公式将他们对应上?

咱们天然而然会去寻觅STM32的官方手册的阐明。在STM32F1系列的的编程参阅以及官方手册里均有说到位带操作的感念,那份编程参阅里更是说到了核算二者联络的公式。

在编程参阅P25页能够找到,答应bit位从头映射的位带区只需两处,一处是SRAM区,一处是片内的外设区Peripheral,均有1M巨细。了解的人一眼就看出来了,SRAM区里寄存的是仓库(heap, stack)、全局变量等,外设区Peripheral区便是咱们操作这块CPU常常打交道的GPIO, TIMER, PWM, A/D等各个功用的寄存器的地点地址。从头映射的区域叫位带别号区(Bit band alias),均有32MB巨细。也便是说,咱们终究操作的地址都仅仅是1MB,那扩大出来的32MB空间无外乎是为了操作便利快速而设定的,终究仍是得影响到那1MB空间才干起作用。编程参阅的P30页以SRAM区介绍了这一对应联系↓

以0x20000000(1MB的最初)这SRAM最低地址为例,其榜首bit位从头映射到了0x22000000(32MB的最初)地址上,第7bit位映射到了0x2200001C地址上,以此类推,到SRAM最高地址0x200FFFF(1MB的结束)F的第7bit位映射到了0x23FFFFFC(32MB的结束)。留意到上面跟下面的区域之间每个方格的地址增加差异,下面(bit-band region)每块方格地址增加1,而上面(alias region)地址增加4,因而有了编程参阅的第P30页的联系转化核算公式↓

好了,关于根底厚实了解的人来说到这儿现已能够了,可是关于我,或许现在隐约觉得有点疑问的人来说,或许关于这个换算的成果(1MB对应32MB)有点想进一步搞清楚这是为什么。为什么一会是字偏移(word_offset),一会是字节偏移(byte_offset),等等,字,bit,字节,是怎样对应的?等等,不是说寄存器都是32位的,怎样上面的对应图都是8bit(一字节)一对应的?晕了。所以这儿有必要稳固一下这方面的根底常识。

首要回忆最基本概念。

在二进制中,从单纯数学上讲咱们知道有

@-> 2^10=1024=1K

@-> 2^20=1024*1024=1M

@-> 2^30=1024*1024*1024=1G

最小二进制单位为比特(bit),即单纯的0,1,0,1,等等。关于音乐、图画等模仿信号咱们进行紧缩时一般选用的单位为比特率(bps),比方MP3最大比特率320Kbps,即每秒有320K个bit位,也便是每秒采样后的数字0,1的个数有320K个。一般CD的采样率为1411.2Kbps,因而音质就好许多了。一般VCD为1.25Mbps,DVD视频为5Mbps,规范蓝光为40Mbps,所以选用蓝光光盘的PS3游戏机的内部通讯带宽比一般PC大许多也便是这个道理,由于每秒需求吞吐很大的数据量才干确保画面的明晰。

一个字节(Byte)等于8个bit,依照惯例我手写的B大写了。字节是一般的核算机存储的基本单位。咱们一般所说的500GB硬盘、2GB内存便是指500个G的字节(Byte)和2个G的字节(Byte)。一般咱们所说的32位处理器(比方ARM)的内存寻址规模为4GB就很好了解了。从单纯数学上讲↓

@-> 2^32= 4 * 2^30=4*1G=4G

终究,4GB的后边加了个B,即字节(Byte),一共是4G个字节数,因而32位处理器寻址规模为4G个字节。

若觉得4GB内存关于一些运算觉得不行用,选用64位处理器就能够这一问题,咱们看看64位的寻址规模↓

@-> 2^64=2^34 * 2^30=16G*G

看到了吧,寻址规模能有16G*G个字节,远远大于32位处理器,连跳好几个数量级,分量分量许多应用了。一般G*G就称为E了,即64位处理器寻址规模为16EB。不过这么大的数我是现已没什么概念了。

最早的红白机,任天堂的FC,是一台8位机(MOS 6502),小时候玩的红白机觉得画面简略音乐粗糙,与其CPU功能不无联系。FC的接班人超任SFC选用了摩托罗拉的65836,3.58MHz的16位CPU,游戏画面和音质显着上了一个层次。掌机GameBoy(GB)和GameBoyColor(GBC)同为8位机。之后的GBA和NDS均选用了ARM系列芯片则直接是32位机了。这个网址能够很便利地检查GBA和NDS的硬件参数。32位主机年代PlayStation是王者能够说毫无疑问,而PS2你猜猜有多少位?64?不,人家直接跳到128位了。天文数字不是么,尽管PS2的CPU(Emotion Engion 简称EE)主频只需295Mhz。所以说现在许多PC端的PS2模仿器并不能很好的模仿便是这个道理。而到了PS3年代又回到了64位。不过要了解,单纯寻求CPU的带宽并不必定能带来画面和功能的提高,其间架构的合理,缓存、外设时钟等等都会影响功能。

之后,为什么一切这些数字,4GB,16EB后边都要加个B(字节),为什么存储的单位是字节?这个问题咱们先放一放,先来看看字(Word)的概念。

如果说比特(bit),字节(Byte)的概念比较好了解,那么字(Word)的概念就简单把人搞晕了,由于,字的长度并不共同,在不同CPU,不同年代,字的长度并不共同。早年的8位机上,比方前面说到的红白机的MOS 6502,字长为8bit,即一个字节。在一些16位CPU上,比方闻名的8086,字长是16位的,2个字节。而现在的32位CPU比方ARM和咱们手中的PC,字长是32位,即4个字节。

能够参阅这张wiki表对照历史上CPU们对字长的规则。

如果说,字节(Byte)对应于存储的单位巨细,那么字(Word)则对应了CPU一次处理数据/指令的巨细,因而才为了便利起了个字(Word)这个姓名。关于ARM来说,字长是32位的,也便是4个字节。回想起ARM里一切的寄存器,是不是每个寄存器都是32位的?所以,以这个32位为单位进行操作,因而这个32位即为一个字(Word)。那么为什么之前说字节(Byte)是存储的基本单位呢?

关于ARM晒干,数据的地址值跟数据自己自身都是32位的,这样做的优点是操作起来便利,共同。当然,关于ARMv4架构里的指令来说,有着32位的ARM指令集和16位的Thumb指令集,乃至关于Cortex M3来说都是32位或16位的Thumb指令集。这儿先不评论这种指令集之前的差异,仅仅以答应的最大指令为32位来评论。别的,关于Cortex这一重回哈弗架构的CPU来说,指令和数据是分隔的,完全能够不必相同的带宽拜访(当然实践上STM32二者带宽仍是相同的,便利操作,仅仅分隔了罢了)。有爱好的能够参阅这篇文章对照指令集与架构的差异。

现代干流CPU的存储单元为字节(Byte),即物理地址的编码是以字节为单位编码的,一个地址对应于一个字节(Byte)或8个bit的空间,这一地址加上1,则对应于下一个字节或下一组8bit。这种物理地址的编码方法是由CPU的架构所确保的,并且为现在干流CPU所选用,因而说32位CPU的寻址规模是4GB便是指可找到物理地址上一共4G规模的区域,每一个区域上都有1个字节(Byte)的空间用于寄存数据或指令。

那么很显着,关于ARM的寄存器来说,一块这样的1个字节区域肯定是不行的,每个32位的寄存器需求4个这样的区域来寄存才干够。咱们常常能够看到在界说寄存器时运用了下面的查办↓

/* General Purpose Input/Output (GPIO) */#define IOPIN0         (*((volatile unsigned long *) 0xE0028000))#define IOSET0         (*((volatile unsigned long *) 0xE0028004))#define IODIR0         (*((volatile unsigned long *) 0xE0028008))#define IOCLR0         (*((volatile unsigned long *) 0xE002800C))#define IOPIN1         (*((volatile unsigned long *) 0xE0028010))#define IOSET1         (*((volatile unsigned long *) 0xE0028014))#define IODIR1         (*((volatile unsigned long *) 0xE0028018))#define IOCLR1         (*((volatile unsigned long *) 0xE002801C))

以上寄存器在内存里是彼此接连的,咱们能够很清楚的看到,他们之间的地址值的增量为4。这就很清楚了,相邻寄存器地址值差4,实践上之间有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了。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/fangan/biancheng/264841.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部