购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.3 STM32寄存器简介

STM32系统的开发方法通常有两种,即寄存器开发和库函数开发,其中寄存器开发是基础,库函数开发是在寄存器开发的基础上发展而来的一种易于学习和编程的开发方法。虽然库函数开发方法容易学习,编程简单迅速,但是它毕竟是在寄存器开发的基础上发展而来的,因此想要掌握库函数开发,必须先对STM32寄存器的配置有一个基本的认识和了解。下面将主要以STM32F103ZET6芯片为例,介绍STM32寄存器。

1.3.1 STM32芯片的结构

常见的STM32芯片是已经封装好的成品,能够看到的只有芯片的外观和芯片四周伸出的引脚,图1.3展示了STM32F103ZET6芯片的外观与引脚分布。该芯片为144引脚(LQFP144)封装,芯片上的小圆点位置表示引脚1位置,从引脚l位置开始,所有引脚按逆时针顺序排列。STM32F103ZET6芯片包括7个16位的通用输入输出(GPIO)端口,依次称为PA、PB、PC、PD、PE、PF和PG。在芯片的设计过程中,为不同的引脚定义了不同的功能,并且几乎每个GPIO端口都复用了其他功能(PG8和PG15例外),STM32F103ZET6芯片的引脚功能定义见附录A。

图1.3 STM32F103ZET6芯片的外观与引脚分布

仅从芯片外部是无法捕捉到芯片的内部结构的,那么STM32芯片内部究竟包含着什么呢?其实它与常见的计算机主机很相似,计算机主机主要由CPU和主板、显卡、内存、硬盘等外设组成,类似地,STM32芯片也是由内核和片上外设组成的。例如,STM32F103系列芯片的CPU即Cortex-M3内核,除了内核,还设有GPIO、USART(串口)、ADC、I 2 C、SPI等模块,这些即片上外设。内核与片上外设之间通过各种总线连接,形成一个相互协调的统一整体。STM32F103系列芯片内部系统结构框图如图1.4所示。

图1.4 STM32F103系列芯片内部系统结构框图

STM32芯片主体系统由驱动单元和被动单元构成,驱动单元主要包括内核D-Code总线、System总线、通用DMA(Direct Memory Access,直接内存存取)总线等;被动单元主要包括AHB到APB的连接桥(用于连接所有的APB设备)、内部FLASH存储器、内部SRAM和FSMC等。其中,各种总线是指令和数据的传输通道,也是连接内核与各种片上外设的纽带,主要包括I-Code总线、D-Code总线、System总线、DMA总线、AHB总线和APB总线。下面具体讲解一下图1.4中的这几条总线。

1. I-Code总线

I-Code总线连接内核与内部FLASH存储器的指令接口,可实现指令的预取功能,是基于AHB-Lite总线协议的32位总线,读取指令以字的长度执行。开发时,编写好的程序经过编译之后会变成单片机能够识别的一条条指令,而这些指令一般会被存放在内部FLASH存储器中,内核想要读取这些指令从而执行程序就必须通过I-Code总线来完成。

2. D-Code总线

D-Code总线连接内核与内部FLASH存储器的数据接口,可实现数据访问功能,也是基于AHBLite总线协议的32位总线。在编程开发时,用到的数据有常量和变量两种,在存储过程中,常量会被放到内部FLASH存储器中,而全局变量和局部变量会被存放到内部SRAM中。当在执行指令的过程中,内核需要访问内部FLASH存储器中存放的数据时,必须通过D-Code总线来读取。

3. System总线

System总线即系统总线,连接内核和总线矩阵。System总线通过总线矩阵可以访问外设寄存器,通常说的寄存器开发,即编程读/写寄存器,就是通过System总线来完成的。

4. DMA总线

DMA总线实现DMA的AHB主控接口到总线接口的连接,主要用来访问和传输数据,这些数据可以是某个外设的数据寄存器中的,可以是SRAM中的,也可以是内部FLASH存储器中的。因为内部FLASH存储器中的数据既可以通过D-Code总线访问,也可以通过DMA总线访问,为了避免访问冲突,在读取数据时需要通过总线矩阵来进行仲裁,最终决定通过哪条总线来读取数据。总线矩阵的作用正是仲裁协调内核和DMA之间的访问,此仲裁利用轮换算法。

5. AHB总线和APB总线

AHB(Advanced High Performance Bus)和APB(Advanced Peripheral Bus)是ARM公司推出的AMBA片上总线规范的主要总线结构。

AHB是Advanced High Performance Bus的缩写,可译为高级高性能总线,它通过总线矩阵与System总线相连,允许DMA访问,主要用于高性能模块(如CPU、DMA和DSP等)之间的连接。AHB系统由主模块、从模块和基础结构三部分组成,整个AHB总线上的传输由主模块发出,由从模块负责回应,基础结构则由仲裁器(Arbiter)、主模块到从模块的多路器、从模块到主模块的多路器、译码器(Decoder)、虚拟从模块(Dummy Slave)、虚拟主模块(Dummy Master)组成。

APB是Advanced Peripheral Bus的缩写,是一种外围总线,它主要用于低带宽的周边外设之间的连接,如UART、1284等。它的总线架构不像AHB支持多个主模块,在APB中唯一的主模块就是APB桥,再往下,由于不同的外设需要的时钟(高速时钟和低速时钟)不同,APB总线分为低速外设总线APB1和高速外设总线APB2,其上分别挂载着不同的外设。APB1操作速率限于36MHz,APB2操作于全速(最高为72MHz);APB1负责DA、USB、SPI、I 2 C、CAN、USART2~5和普通TIMx,APB2负责AD、I/O、USART1和高级TIMx。

这些总线通过相互协调,将芯片的内核与内部FLASH存储器、SRAM、FSMC和各种片上外设连接起来,形成一个相互配合、统一调配的整体,从而构成了强大的STM32芯片。

1.3.2 从存储区映射到寄存器

1. 存储区映射与寄存器映射

尽管拥有多条总线,STM32芯片内部的存储区仍然是一个大小为4GB的线性地址空间。FLASH、SRAM、FSMC和各种片上外设等部件通过存储区的地址分配,共同排列在这个4GB的地址空间中。在编程开发时,通过在存储区中的位置地址找到它们,进而通过指令操作它们。给存储区分配地址的过程称为存储区映射,如果给存储区再分配一个地址就叫存储区重映射。

对于这个4GB的地址空间,ARM公司已经将它平均划分成了8块,每块512MB,分别规定了不同的用途。STM32存储区映射方案如图1.5所示。

图1.5 STM32存储区映射方案

在上述8块中,Block0被划分为代码区,主要用于装载和执行指令代码,其起始地址为0x00000000。在代码区中,0x00000000~0x0007FFFF的地址范围为FLASH、系统存储器和SRAM的别名区;对于STM32F103ZET6单片机而言,0x08000000~0x0807FFFF的地址范围划分为内部FLASH存储器,共为512KB,属于大容量片上闪存,通过I-Code总线与内核连接,主要用于装载和存储指令代码;0x1FFFF000~0x1FFFF7FF的地址范围划分为系统存储器,存储着芯片出厂时即烧写好的isp自举程序(Bootloader),用户无法改动,这部分程序会在串口下载时用到;0x1FFFF800~0x1FFFF80F的地址范围用于配置读/写保护、BOR级别、软硬件看门狗和部件处于待机或停止模式下的复位,当芯片不小心锁住之后,可以从RAM里启动,以修改这部分相应的位;其余地址范围为预留空间。

Block1被划分为SRAM区,其起始地址为0x20000000,所有的内部SRAM都位于底部的位带区。SRAM主要用于存放各类局部变量和全局变量,也可以用来装载和执行指令代码,但是这样做会使得内核不得不通过System总线来读取指令,从而会产生额外的CPU等待周期,因此在SRAM中装载和执行指令代码要比在FLASH中缓慢。

Block2被划分为片上外设区,其起始地址为0x40000000,所有片上外设的存储映射地址必须位于外设位带区。其中,APB1总线外设的地址范围为0x40000000~0x400077FF,APB2总线外设的地址范围为0x40010000~0x40013FFF,AHB总线外设的地址范围为0x40018000~0x5003FFFF。在这些区域中,每4个字节共32位作为一个单元,每个单元对应不同的功能,控制这些单元就能够驱动外设进行相应的工作。先找到每个单元的起始地址(具体如何找,后面会进行详细的讲解),当找到所需单元的起始地址之后,就可以通过C语言的指针操作方法来访问这些单元,从而驱动外设进行相应的工作。但是如果每次都通过这种方式访问,不仅费时费力,还容易出错,为了解决这个问题,可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是寄存器,这个给已经分配好地址、有特定功能的内存单元取别名的过程就叫作寄存器映射。

Block3~Block6共2GB的存储区空间是用来拓展外部SRAM和外设的;Block7是Cortex-M3内核的内部外设区,其中内部私有设备的地址范围为0xE0000000~0xE003FFFF;外部私有设备的地址范围为0xE0040000~0xE00FFFFF;其余地址范围为自由定制区,自由定制区的一部分是为生产商将来对Cortex-M3内核增加特殊功能而预留的。所有使用Cortex-M3内核的微控制器的内核寄存器都处于同一地址位置,这就使得所编写的代码在以Cortex-M3为内核的不同型号微控制器之间的移植变得更加容易。

2. STM32寄存器寻址与解读

Block2作为片上外设区,其地址范围主要依据APB1、APB2和AHB三条总线进行划分,根据外设速度不同,不同总线挂载着不同的外设,APB1总线挂载低速外设,APB2总线和AHB总线挂载高速外设。相应总线的最低地址称为该总线的基地址,在这三条总线中,APB1总线的地址最低,片上外设地址从这里开始,因此该总线基地址也称为片上外设基地址。三条总线的基地址如表1.2所示,其中相对片上外设基地址的偏移是指该总线基地址与片上外设基地址0x40000000之间的差值。

表1.2 三条总线的基地址

三条总线上分别挂载着不同的外设,每个外设都有自己的地址范围,特定外设的首个地址称为该外设的基地址,也称为该外设的起始地址。在该特定外设的地址范围中,分布着该外设的寄存器,每个寄存器为32位,占4个字节,从该外设的基地址开始,按顺序排列,寄存器的地址是以寄存器相对于其外设基地址的偏移地址来描述的。通过编程访问这些寄存器,就可以驱动外设完成相应的工作。这里以GPIO端口为例,讲解如何解读外设的基地址及其寄存器,其他外设的基地址和寄存器的具体说明可以查阅《STM32F10x中文参考手册》。GPIO端口是通用输入输出端口的简称,其基本功能是控制STM32引脚输出高电平或低电平,属于高速外设,挂载在APB2总线上。GPIO端口基地址如表1.3所示。

表1.3 GPIO端口基地址

STM32F103ZET6单片机的GPIO端口分为7组,即GPIOA~GPIOG,每组端口都分别有一套相同功能寄存器,即两个32位配置寄存器(GPIOx_CRL和GPIOx_CRH)、两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR)、一个32位置位/复位寄存器(GPIOx_BSRR)、一个16位复位寄存器(GPIOx_BRR)和一个32位配置锁定寄存器(GPIOx_LCKR)。这里以GPIOA端口为例,展示一下GPIO端口中的寄存器。GPIOA端口的寄存器地址列表如表1.4所示。

表1.4 GPIOA端口的寄存器地址列表

关于STM32外设寄存器的详细说明可以查阅《STM32F10x中文参考手册》,这里仅以端口置位/复位寄存器(GPIOx_BSRR)为例,介绍一下应该如何解读寄存器的说明。端口置位/复位寄存器的详细说明如图1.6所示。

图1.6 端口置位/复位寄存器的详细说明

由图1.6可以看出,寄存器说明首先给出了该寄存器的名称,即“端口置位/复位寄存器(GPIOx_BSRR,x=A,B,···,G)”,其中,“x=A,B,···,G”表示该寄存器名称中的“x”可以为A~G,说明这个寄存器适用于GPIOA~GPIOG 7组端口中的任意一组,即每组GPIO端口都有一个这样的端口置位/复位寄存器。

紧跟着寄存器名称的是该寄存器的地址偏移,是指该寄存器相对于其外设基地址的偏移,这个偏移量可以用来计算该寄存器的地址。前面也提到过,寄存器的地址是以寄存器相对于其外设基地址的偏移地址来描述的。本寄存器的偏移地址为0x10,从《STM32F10x中文参考手册》中可以查到GPIOA端口外设的基地址为0x40010800,因此可以算出GPIOA端口置位/复位寄存器GPIOA_BSRR的地址为0x40010800+0x10=0x40010810。同理,根据GPIOB端口的外设基地址为0x40010C00,也可算出GPIOB_BSRR的地址为0x40010C10,其他GPIO端口寄存器地址的计算方法以此类推。

寄存器在复位之后每一位的值将转变为初始态,这个初始态的数值由复位值决定,这里的复位值为0x00000000,表示该寄存器在复位之后,所有位都清零。复位值下面紧跟着的是该寄存器的位表,列出它的0~31位的名称和读写权限:表上方的数字表示位编号,表中是对应位的名称,表下方是对应位的读写权限,其中w表示只写,r表示只读,rw表示可读可写。该寄存器中的所有位权限都是w,所以只有写的权限,如果要读本寄存器,则无法保证能够读取到它的真正内容。当然也有一些寄存器的位权限为r,这些寄存器一般用于表示STM32外设的某种工作状态,由STM32硬件自动更改,这样就可以通过读取这些寄存器位来判断外设的工作状态。

寄存器位表下面是对位功能的介绍,这是寄存器说明中最为重要的部分,它详细说明了寄存器每一位的功能,按照功能介绍对相应的位进行操作,即可实现相应的功能。图1.6中的位功能介绍表明该寄存器有两种寄存器位,分别为BRy和BSy,其中y的值可以是0~15,这里的0~15表示端口的引脚号,如BR0、BS0用于控制GPIOx的第0个引脚,而BR1、BS1则用于控制GPIOx的第1个引脚。

根据位功能介绍中的描述,对BRy引脚的介绍是“0:对对应的ODRy位不产生影响。1:清除对应ODRy位为0”,对BSy引脚的介绍是“0:对对应的ODRy位不产生影响。1:设置对应ODRy位为1”。其中ODRy是指端口输出数据寄存器(GPIOx_ODR)中相应的寄存器位,通过查阅《STM32F10x中文参考手册》可以知道,当ODRy位为1时,对应的引脚y输出高电平;当ODRy位为0时,对应的引脚y输出低电平。这里对GPIOx_ODR寄存器不再详述,读者可以自行查阅《STM32F10x中文参考手册》。通过位功能介绍可以知道,当对BR0写入0时,不会影响对应的ODR0位,所以对应GPIOx的第0个引脚电平不会发生改变;当对BR0写入1时,对应的第0个引脚会输出低电平。同样,当对BS0写入0时,不会影响对应的ODR0位,对应的第0个引脚电平不会发生改变;当对BS0写入1时,对应的第0个引脚会输出高电平。由此可以看出,BRy和BSy这两种寄存器位的功能效果是相反的。

前面的介绍已经说明了如何根据端口置位/复位寄存器说明来计算该寄存器的地址和如何解读该寄存器的功能。实际上,虽然不同寄存器的地址和功能不相同,但是计算寄存器的地址和解读寄存器功能的方法大致相同,读者可以根据《STM32F10x中文参考手册》自行解读,这里不再详述。

1.3.3 寄存器的封装与读/写操作

1. 寄存器的封装过程

前面已经讲过,当找到所需要的配置寄存器的起始地址后,就可以通过C语言的指针操作方法来访问相应的位置,进而根据寄存器的功能来驱动外设进行相应的工作。当然,面对STM32强大的功能,在进行复杂操作的过程中,反复寻找各种寄存器的地址和不断进行各种指针操作仍然会浪费宝贵的开发时间。幸运的是,在STM32标准函数库中,已经通过C语言对这部分烦琐的工作进行了封装,具体封装过程如下。

(1)对总线和外设基地址进行封装。

上述程序代码首先定义了片上外设基地址PERIPH_BASE,并在此基础上加入各个总线的地址偏移,从而得到APB1和APB2总线的基地址APB1PERIPH_BASE、APB2PERIPH_BASE;其次在APB2总线基地址上加入外设的地址偏移,得到GPIOA~CPIOG的外设基地址;最后在外设基地址上加入各寄存器的地址偏移,就得到了特定的寄存器基地址,通过指针读/写即可修改相应的寄存器位。

(2)对寄存器列表进行封装。通过上面的方法定义地址还是略显烦琐,如GPIOA~GPIOG的每组端口都有一套功能相同的寄存器,它们只是地址不一样,却要为每个寄存器定义地址。为了更方便地访问寄存器,在STM32标准函数库中引入结构体的方法对寄存器进行封装。

上述程序代码用typedef关键字声明了名为GPIO_TypeDef的结构体类型,结构体有7个成员变量,变量名正好对应寄存器的名字,其中32位变量占用4个字节,16位变量占用2个字节。由于结构体内变量的存储空间是连续的,如果这个结构体的首地址为0x40010C00(也是第1个成员变量CRL的地址),那么结构体中第2个成员变量CRH的地址为0x40010C00+0x04,加上的这个0x04代表CRL所占用的4个字节地址的偏移量,其他成员变量的地址以此类推。这样,成员变量之间的偏移就与GPIO外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,之后就能以结构体指针的形式访问寄存器了。

为了使编程更加方便,直接使用宏将GPIO_TypeDef类型的指针定义好,每个指针指向各个GPIO端口的首地址,由此就可以直接用该宏访问相应的寄存器了。

这里仅以GPIO这个外设为例,讲解C语言对寄存器的封装,其他外设寄存器的封装过程与此类似,这部分工作已经由STM32标准函数库完成,这里只需要了解即可。

2. 修改寄存器位与位带操作

在了解了STM32寄存器的封装过程之后,就能够利用C语言对相应的寄存器赋值,并执行具体的操作。

(1)修改寄存器位的方法。在实际应用过程中,常常要求只修改该寄存器某几位的值,而其他寄存器位不变,这个时候就需要用到C语言的位操作方法。

① 把变量的某位清零。此处以变量a代表寄存器,并假设寄存器中已有数值,此时需要把变量a的某一位清零,而其他位不变,具体操作方法如下。

在上述程序代码中,括号中的1左移两位可以得二进制数00000100b,按位取反后即可得到11111011b,所得的二进制数与a进行“与”运算,最终得到的a的值为10011011b,这样就实现了a的bit2被清零,而其他位不变。

② 把变量的某几个连续位清零。由于在寄存器中有时会有连续几个寄存器位用于控制某个功能,现假设需要把寄存器的某几个连续位清零,而其他位不变,具体操作方法如下。

在上述程序代码中,括号中的3左移两位可以得二进制数00001100b,按位取反后即可得到11110011b,所得的二进制数与a进行“与”运算,最终得到的a的值为10010011b,这样,就实现了a的bit2和bit3被清零,而其他位不变;同理,括号中的7左移四位可以得二进制数01110000b,按位取反后即可得到10001111b,所得的二进制数与b进行“与”运算,最终得到的b的值为10000111b,这样就实现了b的bit4、bit5和bit6被清零,而其他位不变。

③ 对变量的某几位进行赋值。在实际应用中,有时还需要对寄存器中的某几位进行赋值,而其他位不变,从而实现所需要的功能,具体操作方法如下。

在上述程序代码中,括号中的5左移三位可以得二进制数00010100b,与a进行“或”运算,最终得到的a的值为10010111b,这样就实现了a的bit2和bit4被赋值,而其他位不变。

④ 对变量的某位取反。在某些情况下,需要对寄存器的某位进行取反操作,而其他位不变,具体操作方法如下。

在上述程序代码中,括号中的1左移6位可以得二进制数01000000b,与a进行“异或”运算,最终得到的a的值为11000011b,这样就实现了a的bit6被取反,而其他位不变。

(2)位带操作简介。从修改寄存器位的方法可以看出,STM32不能像传统51单片机那样可以简单地对端口中的某一位进行置位或复位操作,但是能够通过“与”“或”等逻辑指令来实现寄存器或存储区的位操作。通过逻辑运算进行的位操作实际上是一个“读—修改—写”的过程,在实现单个位操作的过程中会耗费数个时钟周期,并且会增加代码量。

为了克服这一限制,Cortex-M3内核引入了一种称为位带的操作技术,在不引入特殊指令的前提下,实现了SRAM区和片上外设区两个区域的位操作。Cortex-M3内核的可位寻址区域由位带区(SRAM起始的1MB空间和片上外设区起始的1MB空间)和两个大小为32MB的位带别名区组成,前面在介绍STM32存储区映射时也曾涉及。位带存储映射如图1.7所示。

图1.7 位带存储映射

位带技术将位带区的每一位映射到对应的位带别名区,只要对位带别名区进行字操作就可以实现对真实内存的位操作,这将允许在不加入任何特殊指令的前提下实现位操作,同时避免了复杂的布尔运算,保持了Cortex-M3内核尺寸的小巧性。在实际应用中,当对一个SRAM或外设寄存器进行位操作时,需要计算与该位对应的位带别名区中的地址,可使用以下的映射公式进行计算(其中位序号的取值范围为0~7)。

① 位带别名区地址=位带别名区基地址+位带别名区偏移地址。

② 位带别名区偏移地址=(位带区偏移地址×8+位序号)×4。

位带区与位带别名区的映射对应关系如图1.8所示。STM32的系统总线是32位的,按照4字节进行访问时的效率最高,因此位带区上的每一位在位带别名区上都会有4字节,即32位与之对应,这也是为什么每个位带区1MB的空间映射到位带别名区需要有32MB空间的原因。如图1.8所示,0x22000000是SRAM位带别名区的起始地址,假如需要求SRAM位带区中0x20000000字节上bit7在位带别名区中对应的地址,则根据映射公式需要先求得相应的位带别名区偏移地址,0x20000000字节相对于位带区基地址的偏移量为0x00,bit7的位序号为7,位带别名区偏移地址为(0×32+7)×4=28,即0x1C,所以可以求出对应的位带别名区地址为0x22000000+0x1C=0x2200001C。同样,对于外设寄存器的位带别名区地址的计算也是如此。

图1.8 位带区与位带别名区的映射对应关系

计算出位在位带别名区中所对应的地址后,就能够通过对位带别名区的字操作实现对位带区的位操作。片上外设的位带区覆盖了全部的片上外设的寄存器,可以通过宏为寄存器的位定义一个位带别名区地址,从而实现寄存器的位操作。这里仅对GPIOA中的端口输入数据寄存器IDR和端口输出数据寄存器ODR的位带操作步骤进行演示,其他寄存器的位带操作与之类似。

从《STM32F10x中文参考手册》中可以查到IDR和ODR两个寄存器对应GPIO基地址的偏移量分别为0x08和0x0C。

① 地址映射操作。

② 输入输出位操作。

这样就能直接通过对PAin(n)或PAout(n)进行赋值实现GPIOA中某一I/O端口的输入/输出位操作,如在主函数中输入“PAout(2)=0;”表示PA2端口输出低电平;在主函数中输入“PAout(5)=1;”表示PA5端口输出高电平。 SGiOmrcSdI9/I0il46RQsE3cYgo1G83BvOTZljjDJ9XpH1U6enbY19zHQGt6sRCs

点击中间区域
呼出菜单
上一章
目录
下一章
×