杨 宇
(昆明冶金高等专科学校电气与机械学院,云南 昆明 650033)
随着社会生产力的发展,在嵌入式控制领域,32位高性能单片机的应用越来越广泛。很多单片机开发者对于单片机的学习都是从8位低性能单片机开始的,8位低性能单片机和32位高性能单片机在很多方面存在差异,这使得许多想继续学习32位高性能单片机的单片机开发者遇到了障碍。
8位低性能单片机的寄存器映射原理相对简单,很多单片机开发者都能理解,但是他们对32位高性能单片机的寄存器映射原理往往理解不足,该文以这2种单片机的2种典型型号为例,即8位低性能单片机中的8051单片机,32位高性能单片机中的STM32F103单片机,将这2种单片机的寄存器映射原理进行对比分析,笔者认为这种比较式的阐述能帮助单片机开发者更好地理解32位高性能单片机的寄存器映射原理。正确理解寄存器映射的原理对于开发者进行STM32单片机后续内容的学习有极大的帮助。
单片机寄存器是具有特定功能的单片机内部存储器单元,所谓寄存器映射是指将每个寄存器单元的名称和绝对地址对应起来,或者说给每个寄存器单元取1个名字,以便在编程时直接使用寄存器名称来访问寄存器单元,这样可以让编程更加方便,程序更加易读。我们首先了解2种单片机寄存器映射完成后的结构图(主要以编程时最常用的通用数据输入输出端口寄存器为例),再分析寄存器映射实现的原理。
8051单片机内部存储器结构相对简单,每个字节对应1存储单元,每个存储单元有唯一的地址,总共有256个字节,对应地址00H到FFH,寄存器位于高128字节,每个寄存器通常占1个字节,并对应不同的地址和名称,见图1,比如常用的4个通用数据输入输出端口P0到P3,每个端口对应1个寄存器:名称P0对应地址为80H的寄存器,名称P1对应地址为90H的寄存器,名称P2对应地址为A0H的寄存器,名称P3对应地址为B0H的寄存器,编程时只需操作名称P0、P1、P2、P3就可以访问相关寄存器单元。STM32F103单片机内部存储器结构比较复杂,存储单元个数也比较多,总共包含4 294 967 296个字节,每个字节对应1个存储单元,每个存储单元有唯一的地址,对应地址00000000H到FFFFFFFFH,见图2,常用寄存器位于block2,地址范围是40000000H到5FFFFFFFH,每个寄存器通常占4个字节(8 051单片机每个寄存器通常只占1个字节),block2中包含7个常用的通用数据输入输出口:PortA、PortB、PortC、PortD、PortE、PortF、PortG这7个名称并不是每个端口的寄存器名称,因为STM32F103单片机的每个通用数据输入输出口都包含7个寄存器(8 051单片机每个通用数据输入输出口只包含1个寄存器):端口配置低寄存器CRL、端口配置高寄存器CRH、数据输入寄存器IDR、数据输出寄存器ODR、位设置/清除寄存器BSRR、端口位清除寄存器BRR、端口配置锁定寄存器LCKR,并且每个寄存器占4个字节的存储空间,所以以B口(PortB)为例,它所包含的寄存器名称为:GPIOB->CRL、GPIOB->CRH、GPIOB->IDR、GPIOB->ODR、GPIOB->BSRR、GPIOB->BRR、GPIOB->LCKR,以上寄存器名称分别对应的寄存器地址为:0x40010C00、0x40010C04、0x40010C08、0x40010C0C、0x40010C10、0x40010C14、0x40010C18,编程时通过操作寄存器名称访问相关寄存器单元。
图1 8 051单片机寄存器映射Fig.1 Register mapping of MCU8 051
图2 STM32F103单片机寄存器映射Fig.2 Register mapping of STM32F103
8 051单片机和STM32F103单片机都是通过头文件中的程序来实现寄存器映射的,8 051单片机使用的头文件是reg51.h,STM32F103单片机使用的头文件是STM32f10x.h,reg51.h文件是单片机开发软件自带的,而STM32f10x.h文件需要用户创建。正确理解头文件中的相关程序就能正确理解单片机实现寄存器映射的原理,上述2种单片机的寄存器映射程序差别很大,下面将主要分析和比较2种单片机寄存器映射的实现代码。
8 051单片机寄存器映射实现原理相对简单,主要是通过C51编程语言中的关键字“SFR”和运算符“=”将寄存器的名称和寄存器的绝对地址联系起来,如图3所示,编程时给寄存器名称赋值就可以操作寄存器。以操作通用数据输入输出端口P1为例:如图3中的语句sfr P1=0x90,P1是8051单片机通用数据输入输出端口1的名称,0x90是该寄存器在内存中的绝对地址,可在图1中找到。这样在编写应用程序时就可以直接使用P1来操作该端口,比如:想让8 051单片机P1端口的8个引脚都输出高电平,使用语句P1=0xFF就可以实现。操作其他的寄存器也是同样的道理。
图3 8 051单片机寄存器映射实现原理Fig.3 Register mapping principle of MCU8 051
相较于8 051单片机,STM32F103单片机由于存储器结构更复杂,存储单元数量更多,并且程序中不能使用关键字SFR,所以STM32F103单片机寄存器映射实现原理比较复杂。还是以操作通用数据输入输出端口(GPIO)为例来阐述,由于STM32F103单片机的片内外设结构层级较多并且1个通用数据输入输出端口包含多个寄存器,所以映射实现代码的思路是首先确定各个GPIO的基地址,然后将各个基地址转换成一种合适的数据类型的指针。
(1)确定各个通用数据输入输出端口的基地址
STM32F103单片机的CPU是通过3种总线(APB1、APB2、AHB)来连接各种外围设备的,通用数据输入输出端口连接在APB2总线上,1个通用数据输入输出端口包含多个寄存器。根据这种层级关系,某个通用数据输入输出端口的基地址是根据CPU的外设基地址(图4中的PERIPH_BASE)、总线基地址(图4中的APB2PERIPH_BASE)和偏移量(图4中“+”号后的常量)来确定的。如图4所示,用C语言关键字#define进行宏定义,将宏GPIOA_BASE、GPIOB_BASE、GPIOC_BASE、GPIOD_BASE、GPIOE_BASE、GPIOF_BASE、GPIOG_BASE定义成7个通用数据输入输出端口的绝对基地址,这7个绝对基地址分别对应图2中PortA、PortB、PortC、PortD、PortE、PortF、PortG的首地址。
图4 STM32F103单片机寄存器映射实现原理1Fig.4 Register mapping principle 1 of STM32F103
(2)把各个通用数据输入输出端口的基地址转换成结构体型指针
STM32F103单片机包含7个通用数据输入输出端口,每个通用数据输入输出端口包含多个寄存器,如果确定7个通用数据输入输出端口的基地址之后,再根据每个通用数据输入输出端口中的各个寄存器(端口配置低寄存器CRL、端口配置高寄存器CRH、数据输入寄存器IDR、数据输出寄存器ODR、位设置/清除寄存器BSRR、端口位清除寄存器BRR、端口配置锁定寄存器LCKR)的偏移地址得到各个寄存器的绝对地址,再将这些绝对地址使用#define进行宏定义,这种方法也可以实现各个寄存器的映射,但是重复性代码量
过多,技术含量低,不是一种好方法。STM32F103单片1个通用数据输入输出端口包含多个寄存器这一特征可以使用C语言中的1种构造数据类型—结构体来表示,用结构体的名称代表某个通用数据输入输出端口,用结构体的各个成员代表该通用数据输入输出端口的各个寄存器。图5中的GPIO_TypeDef就是创建的1个结构体,该结构体的成员CRL代表端口配置低寄存器,成员CRH代表端口配置高寄存器,成员IDR代表数据输入寄存器,成员ODR代表数据输出寄存器,成员BSRR代表位设置/清除寄存器,成员BRR代表端口位清除寄存器,成员LCKR代表端口配置锁定寄存器。将之前定义的各个通用数据输入输出端口基地址的宏强制转换成GPIO_TypeDef型指针,最后用#define将宏GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF、GPIOG分别定义成7个通用数据输入输出端口的指向每个端口基地址的GPIO_TypeDef型指针,这样就可以通过使用这些宏名称加结构体成员名称来操作各个通用数据输入输出端口的寄存器,比如使用语句GPIOB->ODR=0xFFFF就可以让通用数据输入输出端口B对应的引脚输出高电平。相比第一种方法,这种引入结构体的方法更加高效合理。操作其他的寄存器也是同样的道理,这里就不赘述。
图5 STM32F103单片机寄存器映射实现原理2Fig.5 Register mapping principle 2 of STM32F103
8 051单片机和STM32F103单片机的寄存器映射原理既有相同点又有差异性,对于单片机开发者来说,将两者结合起来对比学习比单纯学习STM32F103单片机的寄存器映射原理更容易理解,采用基地址结合结构体的方法能够很好的实现STM32F103单片机的寄存器映射,一旦掌握了STM32单片机的寄存器映射原理,对于STM32单片机的寄存器编程和固件库编程会有极大的帮助,能够让单片机开发者在编程时知其然并知其所以然。