正在预备做毕业设计,装备LED_Config()的时分,又看到了位带操作的宏界说,我又嘀咕了,什么是位带操作,一年前在运用位带操作的时分,就查阅过很多材料,Core-M3也看过,可是关于博主这种“低能儿”来说,你不把它说的白一点,便是感觉了解的不行透彻,所以今日又一次,查阅了各种手册,也算是根本弄懂了,鉴于博主的个人特色,所以自己的介绍也会非常深入浅出,希望能帮到各位!
首要,抛砖引玉,来两个问题:
1)为什么STM32里边会有位带操作?
2)STM32里边的位带操作是什么意思?
我也不想去弄什么官方界说了,来两个比如,信任各位心里即便不能给出一个切当的界说,也不会再去纠结这个问题,
答:
1)51单片机信任各位都用过,假定P1.1的IO口上挂了一个LED,那么你独自对LED的操作便是P1.1 = 0或P1.1 = 1,留意,是你能够独自的对P1端的第一个IO口进行操作,可是STM32是不答应这样做的,那么为了像51单片机相同能够独自的对某个端的某一个IO独自操作,就引入了位带操作这样的概念,简而言之,言而总归,便是为了去独自操作32里边PA端的第1个IO口,所以才有了位带这样的操作机制。
2)打个形象的比如,以某个村,就张村把,该村有3户人家别离为A,B,C,我想给张村的A送礼,可是明文规定,不能给详细的个人送礼,可是能够给村委会送礼,那我该怎样办呢,OK,即日起,A不叫A了,改名叫做村委会1,B和C别离改叫做村委会2和村委会3,哦了,能够给A送礼了,尽管我送礼的对象是村委会1,听起来如同比个人等级高一点,可是终究收到礼物的仍是个人A。同理,STM32不答应对某个端的某一个IO口进行操作,也便是PA.1 = 0或许PA.1 = 1这样的操作是不合法的,好了,那我就给PA.1起个别号,将本来PA.1的地址扩展成一个32位的字地址,对32位的地址进行操作,这个是STM32答应的,必需能够的,STM32对一切的寄存器装备,都是对某个32位地址的操作,因而说白了,便是某个IO端口进行操作,这便是位带操作。
大白话说完,仍是得回归官方介绍,不过这时分你在看,应该会好很多了。咱们一步一步来,首要你应该知道的
位带区,和位带别号区,位带区,便是便是你想独自操作的IO的区域,也便是PA,PB……等这一堆IO口的内存地点区,而位带别号区,便是你给每一位从头起了个姓名的那一片地址区域。能够看下表,M3内核存储器映射表,你能看到1M内存的BitBand区,还有与之对应的32M内存的BitBand别号区,因为你将每一位胀大成为了一个32位的地址,所以相应的别号区的内存也会是位带区的32倍。
OK,现在咱们应该能够知道,你想进行位带操作去操作某个IO口的某一位,那么在STM32的环境下,你应该去找该位对应的别号区的地址,找到了这个地址,对这个地址进行操作,那么实际上也便是对该位进行操作了,接下来,咱们要去找位所对应的地址了。
官方给出了相应的核算公式,咱们以外设部分为例,毕竟用的多的仍是外设部分的端口,详细到PA.1把
AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
AliasAddr是别号区的地址,A是GPIOA->ODR的地址,n是该端口的上的某一位,这儿便是1,经过这个公式你能够找到对应的别号区的地址,接下来便是对这个地址进行操作了,你给他写1,该位输出1,写0,就输出0。
在这儿我想解说以下,为什么这个公式是这个姿态的,因为我也考虑了好久!借助于下面这个图:
0x42000000是位带别号区域的开始地址,A是输出数据寄存器GPIOA->ODR的地址,A的地址先减去位带区基地址,得到的是相关于位带区基地址的偏移地址,那么胀大之后仍是一个偏移地址,是相关于位带别号区基地址的偏移量,加上位带别号区域基地址,就得到了其对应的别号区地址,这是总的原理,
((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*32 + n*4
这部分是胀大公式,乘8是先把单元内的每一位上升到字节的高度上,这样,你想设置第二位,就直接在本来的基地址上+2就能够了,确认完是第几位,再乘4,便是把位再上升到字的高度上,也便是每一位对应一个32位的字,这样终究的地址转化就完结,要害仍是要留意两点,一是,两部分地址的相互转化,主要是每一部分的基地址。二便是位上升的32位地址这样的一个办法概念。
提到这儿,根本现已介绍了80% 了,大都情况下,咱们见到的代码,应该是以下这个姿态,总共分为三步,
1 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 2 #define MEM_ADDR(addr) *((volaTIle unsigned long *)(addr)) 3 #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
第一步,便是咱们上面剖析的,得到位带别号区域的32位地址,至于第二步嘛,其实便是一个转化,给各位举个比如,如下,我想直接拜访0x00000001这个地址,而且给这个地址写1,该怎样做呢,
1 # define ADDR 0x00000001 2 3 *(int *)ADDR = 1;
第二步的操作便是将第一步得到的32位地址,给转化成一个指针变量,而且操作这个地址里的值,仅有的差异,便是因为安全的考虑,多加了一个volaTIle 这样的要害字,可是他不会对咱们发生其他的影响,而第三步,便是将前两部,结合在一起,依据传入的addr和bit核算得到32位的地址,然后强制类型转化,使得咱们能够去操作这个地址里的值,OK,功德圆满,整个的思路根本便是这样,应该不是很难把,至此信任各位现已能够了解什么是位带,以及该怎样去操作位带。
接下来,再写一种常用的位带操作的用法。因为上面的传入的addr是整个区域的基地址,因而,当你想去运用不同GPIO口的时分,选用上面的写法,你将费事需求多写好几个过程,我自己常用的一种写法是下面这个姿态的。
# define BITBAND_REG(Reg,Bit) (*((uint32_t volaTIle*)(0x42000000u + (((uint32_t)&(Reg) – (uint32_t)0x40000000u)<<5) + (((uint32_t)(Bit))<<2))))
# define LED0 BITBAND_REG(GPIOF->ODR,9)
# define LED1 BITBAND_REG(GPIOF->ODR,10)
短短的三行代码,就现已处理了一切问题,输出操控小灯泡,即便再换用其他的端口,改动括号内的内容即可。Reg是操作部分的基地址,Bit便是第几位了。
原理便是,我现已知道,GPIO部分的基地址是0x42000000u ,那么我每次传入详细的GPIOx->ODR寄存器,在界说中,对其取地址,这样能够灵敏拜访各个不必IO输出,相当于把咱们的操作给详细化了,<<5,<<2这两个便是乘32,乘4这样的概念,只不过位操作,会更快一点。