赞
踩
当前单片机的开发方式多种多样,寄存器,库函数,图形化编程等等,可以说是越发的方便,开发速度也是越来越快,库函数开发固好,但是了解一些寄存器操作也绝不是一件多余的事情,因为有很多库函数其底层其实就是寄存器操作编写而成的,能够看懂、理解寄存器操作,对于库函数也会有更深入的理解,有时候也能更快更好地解决一些你以前认为很无厘头的bug。
在学习单片机过程中我们不可避免的要接触了解一点寄存器的内容,初学者刚刚听到这个名字往往有些不知所云,脑子里对寄存器没有一个具象的概念,所以我们就先从寄存器介绍开始吧。
什么是寄存器?
给有特定功能的内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。给一个我们不能直观理解的数字(这里指地址)一个名字,就会让我们在进行操作时更快更省力的完成任务。
偏移地址以及如何计算:
偏移地址是微控制器里的内存分段后,在段内某一地址相对于段首地址(段地址)的偏移量。偏移地址也成为偏移量。最后硬件电路山所应用的物理地址,是由相应段地址加上偏移地址组成的,在单片机中即为:物理地址=寄存器首位地址+偏移量。
计算方法:物理地址 = 基地址+偏移量。
关于单片机寄存器地址的计算实际上是非常简单的,举个例子就可以明白:关于MSP432E401Y单片机来说GPIO_PORTA的寄存器的值=0x40058000H,我们认为这就是基地址。RCGCGPIO Register的偏移地址是是0x608H。
GPIO Port A (AHB): 0x40058000 (ending address of 0x40058FFF)
RCGCGPIO Register (Offset = 0x608) [reset = 0x00]
我们如果想操作GPIO_PORTA的RCGCGPIO这个寄存器的话,我们就可以算出:
GPIO_PORTA的RCGCGPIO的物理地址=0x40058000H+0x608H=0x4005 8608H
位带操作这个概念很早就存在了,要说其起始那还要说到51 单片机的位寻址操作。为了控制篇幅,在此就不再过多介绍其中渊源,我们当前只需了解CortexM3/4 就是将位寻址操作能力进化,就得到了威力大幅加强的位带操作。
概念明析:
位寻址操作: 许多嵌入式使用者的第一块单片机都是51单片机,这是大家非常熟悉的。51单片机中,假设P1.5的IO口上挂了一个LED,那么你单独对LED的操作就是P1.5 = 0或P1.5 = 1,在51单片机中你可以单独的对P1端的第5个IO口进行操作,这种操作就叫位寻址操作。然而在像STM32、MSP432等等资源量更大的单片机中是不允许这样做的,那么为了像51单片机一样能够单独的对某个端的某一个IO单独操作,就引入了位带操作这样的概念,简而言之,就是为了去单独操作更大资源量单片机里面某个GPIO_PORT口中的某一个IO口(当然也支持同时操作几个),所以就有了位带这样的操作机制。
位带操作: 就是指可以使用普通的加载/存储指令来对单一的比特(bit)来进行读写。打个比喻,就相当于是为位带区的每一位都起了一个别名,或者说是为为带区的每一位新建了一个快捷方式,通过对指定别名的访问来代替对指定位的访问。说明:指定位与别名之间的映射过程是由内核完成的,无需人工干预。
位带区: 支持位带操作的地址范围,在CortexM4中有两个位置实现了位带操作,一个是SRAM的最低1MB范围,另一个是片内外设区的最低1MB范围。这两个区中的地址除了可以像普通的RAM 一样使用外,它们还都在“位带别名区”有自己的位带别名,位带别名区把每个比特膨胀成一个32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。为了能使用普通指令来加载和存储那么这个别名肯定得膨胀成32位(一个字),不过这个32位只有低位有效。所以这样就可以通过对别名的访问来代替对位带区指定位的访问了。
位带别名区: 对别名地址的访问最终会作用到位带区对应位,注意这个过程中有一个地址映射的过程。
其实TI的手册是及其详尽的,缺点就是它对英文不太好的朋友不是很友好。对于位带的这部分内容上面手册中讲解的这部分内容就很直观清楚了,我谨在下进行少量的内容总结与补充。
在CotexM4中支持位带操作的两个内存区的范围是:
SRAM区最低的1M:从 0x2000.0000 到 0x2006.FFFF
片上外设区的最低1M:从 0x4000.0000 到 0x400F.FFFF
对于SRAM位带区的某个比特,记它所在字节地址为A,位序号为n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr = 0x2200.0000+((A-0x2000.0000)*8+n)*4=0x2200.0000+(A-0x2000.0000)*32+n*4
对于片上外设位带区的某个比特,记它所在字节地址为A,位序号为n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr = 0x4200.0000+((A-0x4000.0000)*8+n)*4=0x4200.0000+(A-0x4000.0000)*32+n*4
上式中,“*4”表示为一个字4个字节,“*8”表示为一个字节中有8个比特。
本部分内容到此结束,如果实在看不懂也请不要放弃,看起来复杂,你只需要会用就可以,接下来本文将进入实操内容,会对如何进行实际操作进行尽可能详细的介绍。
首先我们手里要确保有一本你正在使用的单片机型号相相对应的手册,TI各系列单片机的手册均可在官网下载得到,在此不赘述。
(以下以寻找MSP432E401Y单片机GPIO_PORTA的GPIODIR寄存器为例)
1.从目录找到对应外设的寄存器内容描述部分,获得基地址GPIO Port A (AHB): 0x40058000 (ending address of 0x40058FFF)。
2.根据手册中的寄存器表,找到GPIODIR寄存器,详细查看。
3.了解该寄存器的偏移量及其功能。
我们就可以知道GPIO_PORTA的GPIODIR的首地址为: 0x40058000 + 0x400 = 0x40058400 。
以下内容适用于目前大多数TI单片机。本文均以MSP432E401Y单片机为例。
//***************************************************************************** // // types.h - Macros used when accessing the comparator hardware. // Copyright (c) 2017 Texas Instruments Incorporated. All rights reserved. // Software License Agreement // //***************************************************************************** #ifndef __TYPES_H__ #define __TYPES_H__ //***************************************************************************** // // Macros for hardware access, both direct and via the bit-band region. // //***************************************************************************** #define HWREG(x)//HWREG 就是用来取外设寄存器地址 \ (*((volatile uint32_t *)(x))) #define HWREGH(x) \ (*((volatile uint16_t *)(x))) #define HWREGB(x) \ (*((volatile uint8_t *)(x))) #define HWREGBITW(x, b) \ HWREG(((uint32_t)(x) & 0xF0000000) | 0x02000000 | \ (((uint32_t)(x) & 0x000FFFFF) << 5) | ((b) << 2)) #define HWREGBITH(x, b) \ HWREGH(((uint32_t)(x) & 0xF0000000) | 0x02000000 | \ (((uint32_t)(x) & 0x000FFFFF) << 5) | ((b) << 2)) #define HWREGBITB(x, b) \ HWREGB(((uint32_t)(x) & 0xF0000000) | 0x02000000 | \ (((uint32_t)(x) & 0x000FFFFF) << 5) | ((b) << 2)) #endif // __TYPES_H__
在MSP432E401Y(TIVA系列同)的开发过程当中,使用的SDK开发都是库函数操作,即只需要调用相关的函数,确定相应的入口参数就可以实现特定功能。但是库函数归根结底的功能是操作寄存器中的相关位,通过一层一层的调用关系,而归结到最底层,凡是对硬件的操作最终都会以上几条语句。
前面3句是直接的取寄存器操作,后面三句是通过“位带操作”取寄存器。
位带操作最主要是理解位带区和未带别名区的对应关系。接下来是对HWREGBITW()操作进行解析:
HWREG(((uint32_t)(x) & 0xF0000000) | 0x02000000 | (((uint32_t)(x) & 0x000FFFFF) << 5) | ((b) << 2)) :
bit_word_offset = (byte_offset x 32) + (bit_number x 4)
bit_word_addr = bit_band_base + bit_word_offset
其中:
bit_word_offset-在位带区中的目标位的位置。
(((uint32_t)(x) & 0x000FFFFF) << 5) | ((b) << 2)
bit_word_addr-在别名区中与目标位映射的字的地址。
(((uint32_t)(x) & 0xF0000000) | 0x02000000 | (((uint32_t)(x) & 0x000FFFFF) << 5) | ((b) << 2)
bit_band_base-别名区的起始地址
((uint32_t)(x) & 0xF0000000) | 0x02000000
byte_offset-字节在包含目标位的位带区中的编号
((uint32_t)(x) & 0x000FFFFF)
bit_number-目标位的位置,0-7。
接下来是直接操作寄存器位的C语言语句: (*((volatile uint8_t *)
1、(volatile uint32_t )(x)表示将X强制转化为uint32-t型指针;
2、((volatile uint32_t )(x)),设(volatile uint32_t )(x)=P,则表达式为P,我们可以对P进行取值和赋值。
3、volatile是告诉编译器这段代码不要优化,也就是每次取值,都是从内存里面取,而不是从寄存器里面直接取。
最后我们现在再来看一下GPIODirModeSet()这个函数的原型,相信各位已经可以看懂了。
//Sets the direction and mode of the specified pin(s). //设置具体的引脚的方向与模式 void GPIODirModeSet(uint32_t ui32Port, uint8_t ui8Pins, uint32_t ui32PinIO) { // // Check the arguments. // ASSERT(_GPIOBaseValid(ui32Port)); ASSERT((ui32PinIO == GPIO_DIR_MODE_IN) || (ui32PinIO == GPIO_DIR_MODE_OUT) || (ui32PinIO == GPIO_DIR_MODE_HW)); // // Set the pin direction and mode. // HWREG(ui32Port + GPIO_O_DIR) = ((ui32PinIO & 1) ? (HWREG(ui32Port + GPIO_O_DIR) | ui8Pins) : (HWREG(ui32Port + GPIO_O_DIR) & ~(ui8Pins))); HWREG(ui32Port + GPIO_O_AFSEL) = ((ui32PinIO & 2) ? (HWREG(ui32Port + GPIO_O_AFSEL) | ui8Pins) : (HWREG(ui32Port + GPIO_O_AFSEL) & ~(ui8Pins))); }
[1]: 《MSP432E4 SimpleLink™ 微控制器技术参考手册》
[2]: “ti\simplelink_msp432e4_sdk_4_20_00_12\source\ti\devices\msp432e4\driverlib\types.h”
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。