正点原子STM32(基于HAL库)1

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了正点原子STM32(基于HAL库)1相关的知识,希望对你有一定的参考价值。

正点原子B站视频地址:https://www.bilibili.com/video/BV1bv4y1R7dp?p=1&vd_source=cc0e43b449de7e8663ca1f89dd5fea7d

目录

单片机简介


Cortex-M介绍

初识STM32

STM32芯片分类
ST中文社区网:https://www.stmcu.org.cn/
ST官网:https://www.st.com/content/st_com/en.html

STM32命名规则

STM32 选型

了解了STM32 的系列和命名以后,我们再进行STM32 选型就会比较容易了,这里我们只要遵循:由高到低,由大到小的原则,就可以很方便的完成设计选型了,具体如下:

由高到低原则:在不能评估项目所需性能的时候,可以考虑先选择高性能的STM32 型号进行开发,比如选择F4/F7/H7 等,在高性能STM32 上面完成关键性能(即最需要性能的代码)开发验证,如果满足使用要求,则可以降档,如从H7→F7→F4→F1,如不满足要求,则可以升档,如从F4→F7→H7,通过此方法找到最佳性价比的STM32 系列。

由大到小原则:在不能评估项目所需FLASH 大小、SRAM 大小、GPIO 数量、定时器等资源需求的时候,可以考虑先选择容量较大的型号进行开发,比如选择512K/甚至1M FLASH 的型号进行开发,等到开发完成大部分功能之后,就对项目所需资源有了定论,从而可以根据实际情况进行降档选择(当然极少数情况可能还需要升档),通过此方法,找到最合适的STM32
型号。

整个选型工作大家可以在正点原子开发板上进行验证,一般我们开发板都是选择容量比较大/资源比较多的型号进行设计的,这样可以免去大家自己设计焊接验证板的麻烦,加快项目开发进度。一些资深工程师,对项目要求认识比较深入的话,甚至都不需要验证了,直接就可以选出最合适的型号,这个效率更高。当然这个需要长期积累和多实践,相信只要大家多学习,多实践,总有一天也能达到这个级别。

STM32 设计

这里我们简单给大家介绍一下STM32 的原理图设计,上一节我们通过选型原则可以确定项目所需的STM32 具体型号,但是在选择型号以后,需要先设计原理图,然后再画PCB、打样、焊接、调试等步骤。这里我们重点介绍如何设计STM32F103 的原理图。
任何MCU 部分的原理图设计,其实都遵循:最小系统+ IO 分配的设计原则。在开始设计原理图之前,我们要通读一遍对STM32F103 原理图设计非常有用的手册:STM32F103 的数据手册,可以说不看这个数据手册,我们就无法设计STM32F103 原理图。

数据手册

在设计STM32F103 原理图的时候,我们需要用到一个非常重要的文档:STM32F103 数据
手册,里面对STM32F103 的IO 定义和说明有非常详细的描述,是我们设计原理图的基础。战舰开发板所使用的STM32F103ZET6 芯片数据手册,存放在:A 盘→ 7,硬件资料→2,芯片资料→ STM32F103ZET6.pdf / STM32F103ZET6(中文版).pdf,接下来我们简单介绍一下如何使用该文档。

STM32F103ZET6.pdf 是最新的英文版(V13)STM32 数据手册

STM32F103ZET6(中文版).pdf 是中文版(V5)STM32 数据手册

大家可以根据自己的喜欢来选择合适的版本进行阅读,内容上基本大同小异,从准确性全面性的角度来说,看V13 英文版是最好的,从简单,易懂来说,看V5 中文版也是可以的。

STM32F103ZET6.pdf 数据手册是针对大容量系列(FLASH 容量在256KB~512KB 之间),主要包括8 个章节,如表2.3.4.1 所示:

章节概要说明
介绍简单说明数据手册作用:介绍大容量增强型F103xC/D/E 产品的订购信息和机械特性
规格说明简单介绍STM32F103 内部所有资源及外设特点
引脚定义介绍不同封装的引脚分布、引脚定义等,含引脚特性、复用功能、脚位等
存储器映像介绍STM32F103 整个4GB 存储空间和外设的地址映射关系
电气特性介绍STM32F103 的详细电气特性,包括工作电压、电流(自己设计电路的时候超过额定值就要额外加电阻限流)、温度、各外设资源的电气性能等
封装特性介绍了STM32F103 不同封装的封装机械数据(脚距、长短等)、热特性等
订货代码和2.3.2 节内容类似,介绍STM32 具体型号所代表的意义,方便选型订货
版本历史介绍数据手册不同版本之间的差异和修订内容

整个STM32F103 数据手册,对我们开发学习STM32 来说都比较重要,因此建议大家可以简单的通读一遍这个文档,以加深印象。对于原理图设计,最重要的莫过于引脚定义这一章节了,只有知道了STM32 的引脚定义,才能开始设计原理图。

STM32F103ZET6 引脚分布如图2.3.4.1 所示:

STM32F103ZET6 引脚定义如表2.3.4.2 所示:

引脚定义表的具体说明如表2.3.4.3 所示:

序号名称说明
脚位对应芯片的引脚,LQFP封装使用纯数字表示,BGA 使用字母+数字表示 这里列出了6 种封装的脚位描述,根据实际型号选择合适的封装查阅
管脚名称即对应引脚的名字,PD0~5 表示GPIO 引脚,VSS_10/VDD_10 表示第10 组电源引脚,其他类似
类型I/O :表示输入/输出引脚 I :表示输入引脚 S :表示电源引脚
IO 电平FT :表示5V 兼容的引脚(可以接5V/3.3V) 空:表示5V 不兼容引脚(仅可以接3.3V)
主功能(复位后)复位后,该引脚的默认功能
可选的复用功能默认复用功能:是指开启复用功能后,该引脚默认的复用功能;重定义功能:是指可以通过重映射的复用功能,需设置重映射寄存器

了解引脚分布和引脚定义以后,我们就可以开始设计STM32F103 的原理图了。

最小系统

最小系统就是保证MCU 正常运行的最低要求,一般是指MCU 的供电、复位、晶振、BOOT等部分。STM32F103 的最小系统需求如表2.3.4.4 所示:





完成以上引脚的设计以后,STM32F103 的最小系统就完成了,关于这些引脚的实际原理图,大家可以参考我们战舰开发板的原理图。接下来就可以开始进行IO 分配了。

IO 分配

IO 分配就是在完成最小系统设计以后,根据项目需要对MCU 的IO 口进行分配,连接不同的器件,从而实现整体功能。比如:GPIO、IIC、SPI、SDIO、FSMC、USB、中断等。遵循:先分配特定外设IO,再分配通用IO,最后微调的原则,见表2.3.4.5 所示:

分配外设说明
特定外设IICIIC 一般用到2 根线:IIC_SCL 和IIC_SDA(ST 叫I2C)数据手册有I2C_SCL、I2C_SDA 复用功能的GPIO 都可选用
特定外设SPISPI 用到4 根线:SPI_CS/MOSI/MISO/SCK 一般SPI_CS 我们使用通用GPIO 即可,方便挂多个SPI 器件;数据手册有SPI_MOSI/MISO/SCK 复用功能的GPIO 都可选用
特定外设TIM根据需要可选:TIM_CH1/2/3/4/ETR/1N/2N/3N/BKIN 等;数据手册有TIM_CH1/2/3/4/ETR/1N/2N/3N/BKIN 复用功能的GPIO 都可选用
特定外设USART UARTUSART 有USART_TX/RX/CTS/RTS/CK 信号 UART 仅有UART_TX/RX 两个信号 一般用到2 根线:U(S)ART_TX 和U(S)ART_RX ;数据手册有U(S)ART_TX/RX 复用功能的GPIO 都可选用
特定外设USBUSB 用到2 根线:USB_DP 和USB_DM;数据手册有USB_DP、USB_DM 复用功能的GPIO 都可选用
特定外设CANCAN 用到2 根线:CAN_RX 和CAN_TX;数据手册有USB_DP、USB_DM 复用功能的GPIO 都可选用
特定外设ADCADC 根据需要可选:ADC_IN0 ~ ADC_IN15;数据手册有ADC_IN0 ~ ADC_IN15 复用功能的GPIO 都可选用
特定外设DACDAC 根据需要可选:DAC_OUT1 / DAC_OUT2;DAC 固定为:DAC_OUT1 使用PA4、DAC_OUT2 使用PA5
特定外设SDIOSDIO 一般用到6 根线:SDIO_D0/1/2/3/SCK/CMD;数据手册有SDIO_D0/1/2/3/SCK/CMD 复用功能的GPIO 都可选用
特定外设FSMC根据需要可选:FSMC_D0 ~ 15 /A0 ~ 25/ NBL0 ~ 1/NE1~ 4/NCE2~ 3/ NOE/NWE/NWAIT/CLK 等;数据手册有FSMC_D0~ 15/A0~ 25/ NBL0~ 1/NE1~ 4/NCE2~3/NOE/;NWE/NWAIT/CLK 复用功能的GPIO 都可选用
通用GPIO在完成特定外设的IO 分配以后,就可以进行GPIO 分配了比如将按键、LED、蜂鸣器等仅需要高低电平读取/输出的外设连接到空闲的普通GPIO 即可
微调IO微调主要包括两部分:1,当IO不够用的时候,通用GPIO 和特定外设可能要共用IO 口;2,为了方便布线,可能要调整某些IO 口的位置。这两点,根据实际情况进行调整设置,做到:尽可能多的可以同时使用所有功能,尽可能方便布线。

经过以上几个步骤,我们就可以完成STM32F103 的原理图设计了。

STM32 基础知识入门

STM32F103 系统架构

STM32F103 是ST 公司基于ARM 授权Cortex M3 内核而设计的一款芯片,而Cortex M 内核使用的是ARM v7-M 架构,是为了替代老旧的单片机而量身定做的一个内核,具有低成本、低功耗、实时性好、中断响应快、处理效率高等特点。

Cortex M3 内核& 芯片

ARM公司提供内核(如Cortex M3,简称CM3,下同)授权,完整的MCU还需要很多其他组件。芯片公司(ST、NXP、TI、GD、华大等)在得到CM3内核授权后,就可以把CM3内核用在自己的硅片设计中,添加:存储器,外设,I/O以及其它功能块。不同厂家设计出的单片机会有不同的配置,包括存储器容量、类型、外设等都各具特色,因此才会有市面上各种不同应用的ARM芯片。Cortex M3内核和芯片的关系如图5.3.1.1所示:

可以看到,ARM公司提供CM3内核和调试系统,其他的东西(外设(IIC、SPI、UART、TIM等)、存储器(SRAM、FLASH等)、I/O等)由芯片制造商设计开发。这里ST公司就是STM32F103芯片的制造商。

STM32 系统架构

STM32F103ZET6 内部系统结构如图5.3.2.1:

STM32F103的系统主要由:四个驱动单元(可以主动发起通信,图中①区域)和四个被动单元(只能被驱动工作,图中②区域)组成,如表5.3.2.1所示:

这里的驱动/被动单元都是指连接了总线矩阵的部分,未连接总线矩阵的部分,则不算作驱动/被驱动单元。接下来我们介绍一下这些单元。

1. I Code 总线(I - Bus)
这是Cortex M3内核的指令总线,连接闪存指令接口(如:FLASH),用于获取指令。由于该总线功能单一,并没有直接连接到总线矩阵(优点:稳定,避免总线冲突导致的等待),因此被排除在驱动单元之外。
2. D Code 总线(D - Bus)
这是Cortex M3内核的数据总线,连接闪存存储器数据接口(如:SRAM、FLASH等),用于各种数据访问,如常量、变量等。
3. 系统总线(S - Bus)
这是Cortex M3内核的系统总线,连接所有外设(如:GPIO、SPI、IIC、TIM等),用于控制各种外设工作,如配置各种外设相关寄存器等。

4. DMA 总线
DMA是直接存储访问控制器,可以实现数据的自动搬运,整个过程不需要CPU处理。如可以实现DMA传输内存数据到DAC,输出任意波形,传输过程不需要CPU参与,可以大大节省CPU支,从而更高效的处理事务。STM32F103ZET6内部有2个DMA控制器,可以实现内存到外设、外设到内存、内存到内存的数据传输。
5. 内部FLASH
内部FLASH即单片机的硬盘,用于代码/数据存储,CPU通过ICode总线经FLASH接口访问内部FLASH,FLASH最高访问速度是24Mhz,因此以72M速度访问时,需要插入2个时钟周期延迟。
6. 内部SRAM
内部SRAM即单片机的内存,用于数据存储,直接挂载在总线矩阵上面,CPU通过DCode总线实现0等待延时访问SRAM,最快总线频率可达72Mhz,从而保证高效高速的访问内存。
7. FSMC
FSMC即灵活的静态存储控制器,实际上就是一个外部总线接口,可以用来访问外部SRAM、NAND/NOR FLASH、LCD等。它也是直接挂在总线矩阵上面的,以方便CPU快速访问外挂器件。
8. AHB/APB 桥
AHB总线连接总线矩阵,同时通过2个APB桥连接APB1和APB2,AHB总线速度最大为72Mhz,APB2总线速度最大也是72Mhz,但是APB1总线速度最大只能是36Mhz。这三个总线上面挂载了STM32内部绝大部分外设。
9. 总线矩阵
总线矩阵协调内核系统总线和DMA主控总线之间的访问仲裁,仲裁利用轮换算法,保证各个总线之间的有序访问,从而确保工作正常。

存储器映射

STM32是一个32位单片机,他可以很方便的访问4GB以内的存储空间(2^32 = 4GB字节),因此Cortex M3内核将图5.3.2.1中的所有结构,包括:FLASH、SRAM、外设及相关寄存器等全部组织在同一个4GB的线性地址空间内,我们可以通过C语言来访问这些地址空间,从而操作相关外设(读/写)。数据字节以小端格式(小端模式)存放在存储器中,数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

存储器本身是没有地址信息的,我们对存储器分配地址的过程就叫存储器映射(比如把一个内存条插到主机上,这个行为就是存储器映射)。这个分配一般由芯片厂商做好了,ST将所有的存储器及外设资源都映射在一个4GB的地址空间上(8个块),从而可以通过访问对应的地址,访问具体的外设。其映射关系如图5.3.3.1所示:


存储块功能介绍
ST将4GB空间分成8个块,每个块512MB,如上图所示,从图中我们可以看出有很多保留区域(Reserved),这是因为一般的芯片制造厂家是不可能把4GB空间用完的,同时,为了方便后续型号升级,会将一些空间预留(Reserved)。8个存储块的功能如表5.3.3.1所示:

这里我们重点挑前面3个存储块给大家介绍。第一个块是Block 0,用于存储代码,即FLASH空间,其功能划分如表5.3.3.2所示:

可以看到,我们用户FLASH大小是512KB,这是对于我们使用的STM32F103ZET6来说,如果是其他型号,可能FLASH会更小,当然,如果ST喜欢,也是可以随时推出更大容量的STM32F103单片机的,因为这里保留了一大块地址空间。还有,STM32的出厂固化BootLoader非常精简,整个BootLoder只占了2KB FLASH空间。

第二个块是Block 1,用于存储数据,即SRAM空间,其功能划分如表5.3.3.3所示:

这个块仅使用了64KB 大小(仅大容量STM32F103 型号才有这么多SRAM ,比如STM32F103ZET6等),用于SRAM访问,同时也有大量保留地址用于扩展(类似电脑再插个内存条)。

第三个块是Block 2,用于外设访问,STM32内部大部分的外设都是放在这个块里面的,该存储块里面包括了AHB、APB1和APB2三个总线相关的外设,其中AHB和APB2是高速总线(72Mhz max),APB1是低速总线(36M max)。其功能划分如表5.3.3.4所示:

同样可以看到,各个总线之间,都有预留地址空间,方便后续扩展。关于STM32各个外设具体挂在哪个总线上面,大家可以参考前面的STM32F103系统结构图和STM32F103存储器映射图进行查找对应。

寄存器基础知识

寄存器(Register)是单片机内部一种特殊的内存,它可以实现对单片机各个功能的控制,简单的来说可以把寄存器当成一些控制开关,控制包括内核及外设的各种状态。所以无论是51单片机还是STM32,都需要用寄存器来实现各种控制,以完成不同的功能。

由于寄存器资源非常宝贵,一般都是一个位或者几个位控制一个功能,对于STM32 来说,其寄存器是32 位的,一个32 位的寄存器,可能会有32 个控制功能,相当于32 个开关,由于STM32 的复杂性,它内部有几百个寄存器,所以整体来说STM32 的寄存器还是比较复杂的。
不过,我们不要被其吓到了,实际上STM32 是由于内部有很多外设,所以导致寄存器很多,实际上我们把它分好类,每个外设也就那么几个或者几十个寄存器,学起来就不难了。

从大方向来区分,STM32 寄存器分为两类,如表5.2.1 所示:

  • 内核寄存器,我们一般只需要关心中断控制寄存器和SysTick 寄存器即可,其他三大类,我们一般很少直接接触。
  • 片上外设寄存器(学习单片机的重点),则是学到哪个外设,就了解哪个外设相关寄存器即可,所以整体来说,我们需要关心的寄存器并不是很多,而且很多都是有共性的,比如STM32F103ZET6 有8 个定时器,我们只需要学习了其中一个的相关寄存器,其他7 个基本都是一样。

说了这么多,给大家举个简单的例子,我们知道寄存器的本质是一个特殊的内存,对于STM32 来说,以GPIOB 的ODR 寄存器为例,其寄存器地址为:0X40010C0C,所以我们对其赋值可以写成:

(*(unsigned int *))(0X40010C0C) = 0XFFFF;

这样我们就完成了对GPIOB->ODR 寄存器的赋值,全部0XFFFF,表示GPIOB 所有IO 口(16 个IO 口)都输出高电平。对于我们来说,0X40010C0C 就是一个寄存器的特殊地址,至于它是怎么来的,我们后续再给大家介绍。

虽然上面的代码实现了我们需要的功能,但是从实用的角度来说,这么写肯定是不好的,可读性极差,可维护性也很差,所以一般我们使用结构体来访问,比如改写成这样:

GPIOB->ODR = 0XFFFF;

这样可读性就比之前的代码好多了,可维护性也相对好一点。至于GPIOB 结构体怎么来的,我们也会在后续给大家介绍。

寄存器映射

给存储器分配地址的过程叫存储器映射,寄存器是一类特殊的存储器,它的每个位都有特定的功能,可以实现对外设/功能的控制,给寄存器的地址命名的过程就叫寄存器映射。

举个简单的例子,大家家里面的纸张就好比通用存储器,用来记录数据是没问题的,但是不会有具体的动作,只能做记录,而你家里面的电灯开关,就好比寄存器了,假设你家有8个灯,就有8个开关(相当于一个8位寄存器),这些开关也可以记录状态,同时还能让电灯点亮/关闭,是会产生具体动作的。为了方便区分和使用,我们会给每个开关命名,比如厨房开关、大厅开关、卧室开关等,给开关命名的过程,就是寄存器映射。

当然STM32内部的寄存器有非常多,远远不止8个开关这么简单,但是原理是差不多的,每个寄存器的每一个位,一般都有特定的作用,涉及到寄存器描述,大家可以参考《STM32F10XXX参考手册(中文版)_V10.pdf》相关章节的寄存器描述部分,有详细的描述。

1. 寄存器描述解读
我们以GPIO的ODR寄存器为例,其参考手册的描述如图5.3.4.1所示:

  • ①寄存器名字
    每个寄存器都有一个对应的名字,以简单表达其作用,并方便记忆,这里GPIOx_ODR表示寄存器英文名,x可以从A~E,说明有5个这样的寄存器(每个端口有一个,事实上最新的STM32F103型号,可能还有F,G等端口,IO数量更多)。
  • ②寄存器偏移量及复位值
    地址偏移量表示相对该外设基地址的偏移,比如GPIOB,我们由图5.3.3.1可知其外设基地址是:0x4001 0C00。那么GPIOB_ODR寄存器的地址就是:0x4001 0C0C。知道了外设基地址和地址偏移量,我们就可以知道任何一个寄存器的实际地址。
    复位值表示该寄存器在系统复位后的默认值,可以用于分析外设的默认状态。这里全部是0。
  • ③寄存器位表
    描述寄存器每一个位的作用(共32bit),这里表示ODR寄存器的第15位(bit),位名字为ODR15,rw表示该寄存器可读写(r,可读取;w,可写入)。
  • ④位功能描述
    描述寄存器每个位的功能,这里表示位0~ 15,对应ODR0~ODR15,每个位控制一个IO口的输出状态。

其他寄存器描述,参照以上方法解读接口。

2. 寄存器映射举例
从前面的学习我们知道GPIOB_ODR 寄存器的地址为:0x4001 0C0C,假设我们要控制GPIOB的16个IO口都输出1,则可以写成:

*(unsigned int *)(0x40010C0C) = 0XFFFF;

这里我们先要将0x4001 0C0C 强制转换成unsigned int 类型指针,然后用*对这个指针的值进行设置,从而完成对GPIOB_ODR 寄存器的写入。
这样写代码功能是没问题,但是可读性和可维护性都很差,使用起来极其不便,因此我们将代码改为:

#define GPIOB_ODR *(unsigned int *)(0x40010C0C)
GPIOB_ODR = 0XFFFF;

这样,我们就定义了一个GPIOB_ODR 的宏,来替代数值操作,很明显,GPIOB_ODR 的可读性和可维护性,比直接使用数值操作来的直观和方便。这个宏定义过程就可以称之为寄存器的映射

当然,为了简单,我们只举了一个简单实例,实际上大量寄存器的映射,使用结构体是最方便的方式,stm32f103xe.h 里面使用结构体方式对STM32F103 的寄存器做了详细映射,等下我们再介绍。

3. 寄存器地址计算

STM32F103大部分外设寄存器地址都是在存储块2上面的,见图5.3.3.1。具体某个寄存器地址,由三个参数决定:1、总线基地址(BUS_BASE_ADDR);2,外设基于总线基地址的偏移量(PERIPH_OFFSET);3,寄存器相对外设基地址的偏移量(REG_OFFSET)。可以表示为:

寄存器地址= BUS_BASE_ADDR + PERIPH_OFFSET + REG_OFFSET
总线基地址(BUS_BASE_ADDR),STM32F103内部有三个总线(APB1、APB2和AHB),对应的总线基地址如表5.3.4.1所示:


上表中APB1的基地址,也叫外设基地址,表中的偏移量就是相对于外设基地址的偏移量。

注意:AHB的总线基地址是0X4001 8000,从该基地址到0X4002 0000,只挂了SDIO一个外设,后续的AHB外设基地址都大于等于0X4002 0000。为了方便计算,我们可以将AHB的总线基地址改成:0X4002 0000,而SDIO则单独定义一个基地址给他即可。

外设基于总线基地址的偏移量(PERIPH_OFFSET),这个不同外设偏移量不一样,我们可以在STM32F103存储器映射图(图5.3.3.1)里面找到具体的偏移量,以GPIO为例,其偏移量如表5.3.4.2所示:


上表的偏移量,就是外设基于APB2总线基地址的偏移量(PERIPH_OFFSET)。

知道了外设基地址,再在参考手册里面找到具体某个寄存器相对外设基地址的偏移量就可以知道该寄存器的实际地址了,以GPIOB的相关寄存器为例,如表5.3.4.3所示:


上表的偏移量,就是寄存器基于外设基地址的偏移量(REG_OFFSET)。

因此,我们根据前面的公式,很容易可以计算出GPIOB_ODR的地址:

GPIOB_ODR地址= APB2总线基地址+ GPIOB外设偏移量+ 寄存器偏移量

所以得到:GPIOB_ODR 地址= 0X4001 0000 + 0XC00 + 0X0C = 0X4001 0C0C。

4. stm32f103xe.h 寄存器映射说明

STM32F103所有寄存器映射都在stm32f103xe.h里面完成,包括各种基地址定义、结构体定义、外设寄存器映射、寄存器位定义(占了绝大部分)等,整个文件有1W多行,非常庞大。我们没有必要对该文件进行全面分析,因为很多内容都是相似的,我们只需要知道寄存器是如何被映射的,就可以了,至于寄存器位定义这些内容,知道是怎么回事就可以了。

我们还是以GPIO为例进行说明,看看stm32f103xe.h是如何对GPIO的寄存器进行映射的,通过对GPIO寄存器映射,了解stm32f103xe.h的映射规则。

stm32f103xe.h文件主要包含五个部分内容,如表5.3.4.4所示:

寄存器映射主要涉及到表5.3.4.4中加粗的两个组成部分:外设寄存器结构体类型定义和寄存器映射,总结起来,包括3个步骤:

  • 1,外设寄存器结构体类型定义
  • 2,外设基地址定义
  • 3,寄存器映射(通过将外设基地址强制转换为外设结构体类型指针即可)

以GPIO为例,其寄存器结构体类型定义如下:

typedef struct

	__IO uint32_t CRL; /* GPIO_CRL 寄存器,相对外设基地址偏移量:0X00 */
	__IO uint32_t CRH; /* GPIO_CRH 寄存器,相对外设基地址偏移量:0X04 */
	__IO uint32_t IDR; /* GPIO_IDR 寄存器,相对外设基地址偏移量:0X08 */
	__IO uint32_t ODR; /* GPIO_ODR 寄存器,相对外设基地址偏移量:0X0C */
	__IO uint32_t BSRR; /* GPIO_BSRR寄存器,相对外设基地址偏移量:0X10 */
	__IO uint32_t BRR; /* GPIO_BRR 寄存器,相对外设基地址偏移量:0X14 */
	__IO uint32_t LCKR; /* GPIO_LCKR寄存器,相对外设基地址偏移量:0X18 */
 GPIO_TypeDef;

GPIO外设基地址定义如下:

#define PERIPH_BASE 0x40000000UL /* 外设基地址*/
#define APB1PERIPH_BASE PERIPH_BASE /* APB1总线基地址*/
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL) /* APB2总线基地址*/
#define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000UL) /* AHB总线基地址*/
#define GPIOA_BASE (APB2PERIPH_BASE + 0x00000800UL) /* GPIOA基地址*/
#define GPIOB_BASE (APB2PERIPH_BASE + 0x00000C00UL) /* GPIOB基地址*/
#define GPIOC_BASE (APB2PERIPH_BASE + 0x00001000UL) /* GPIOC基地址*/
#define GPIOD_BASE (APB2PERIPH_BASE + 0x00001400UL) /* GPIOD基地址*/
#define GPIOE_BASE (APB2PERIPH_BASE + 0x00001800UL) /* GPIOE基地址*/
#define GPIOF_BASE (APB2PERIPH_BASE + 0x00001C00UL) /* GPIOF基地址*/
#define GPIOG_BASE (APB2PERIPH_BASE + 0x00002000UL) /* GPIOG基地址*/

GPIO外设寄存器映射定义如下:

#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE) /* GPIOA寄存器地址映射*/
#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE) /* GPIOB寄存器地址映射*/
#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE) /* GPIOC寄存器地址映射*/
#define GPIOD ((GPIO_TypeDef *)GPIOD_BASE) /* GPIOD寄存器地址映射*/
#define GPIOE ((GPIO_TypeDef *)GPIOE_BASE) /* GPIOE寄存器地址映射*/
#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE) /* GPIOF寄存器地址映射*/
#define GPIOG ((GPIO_TypeDef *)GPIOG_BASE) /* GPIOG寄存器地址映射*/

以上三部分代码,就完成了STM32F103内部GPIOA~GPIOG的寄存器映射,其原理其实是比较简单的,包括两个核心知识点:1,结构体地址自增;2,地址强制转换;

结构体地址自增,我们第一步就定义了GPIO_TypeDef结构体类型,其成员包括:CRL、CRH、IDR、ODR、BSRR、BRR和LCKR,每个成员是uint32_t类型,也就是4个字节,这样假设:CRL地址是0的话,CRH就是0X04,IDR就是0X08,ODR就是0X0C,以此类推。

地址强制转换,以GPIOB为例,GPIOB外设的基地址为:GPIOB_BASE(0X4001 0C00),我们使用GPIO_TypeDef将该地址强制转换为GPIO结构体类型指针:GPIOB,这样GPIOB->CRL的地址就是:GPIOB_BASE(0X4001 0C00),GPIOB->CRH的地址就是:GPIOB_BASE + 0X04(0X4001 0C04),GPIOB->IDR的地址就是:GPIOB_BASE + 0X08(0X4001 0C08),以此类推。

这样我们就使用结构体方式完成了对GPIO寄存器的映射,其他外设的寄存器映射也都是这个方法。

STM32启动过程分析

本章给大家分析STM32F1 的启动过程,这里的启动过程是指从STM32 芯片上电复位执行的第一条指令开始,到执行用户编写的main 函数这之间的过程。我们编写程序,基本都是用C 语言编写,并且以main 函数作为程序的入口。但是事实上,main 函数并非最先执行的,在此之前需要做一些准备工作,准备工作通过启动文件的程序来完成。理解STM32 启动过程,对今后的学习和分析STM32 程序有很大的帮助。

注意:学习本章内容之前,请大家最好先阅读由正点原子团队编写的《STM32 启动文件浅析》和《STM32 MAP 文件浅析》这两份文档(路径:A 盘→ 1,入门资料)。

启动模式(sct文件)

我们知道的复位方式有三种:上电复位,硬件复位和软件复位。当产生复位,并且离开复
位状态后,CM3 内核做的第一件事就是读取下列两个32 位整数的值:

  • (1)从地址0x0000 0000 处取出堆栈指针MSP 的初始值,该值就是栈顶地址。
  • (2)从地址0x0000 0004 处取出程序计数器指针PC 的初始值,该值指向复位后执行的第一条指令(复位向量)。

为何地址加4?32位单片机每次取4个字节(32位)

下面用示意图表示,如图9.1.1 所示。

上述过程中,内核是从0x0000 0000 和0x0000 0004 两个的地址获取堆栈指针SP 和程序计
数器指针PC。事实上,0x0000 0000 和0x0000 0004 两个的地址可以被重映射到其他的地址空间。例如:我们将0x0800 0000 映射到0x0000 0000,即从内部FLASH 启动,那么内核会从地址0x0800 0000 处取出堆栈指针MSP 的初始值,从地址0x0800 0004 处取出程序计数器指针PC 的初始值。
CPU 会从PC 寄存器指向的地址空间取出的第1 条指令开始执行程序,就是开始执行复位中断服务程序Reset_Handler。
将0x0000 0000 和0x0000 0004 两个地址重映射到其他的地址空间,就是启动模式选择。

对于STM32F1 的启动模式(也称自举模式),我们看表9.1.1 进行分析。

注:启动引脚的电平:0:低电平;1:高电平;x:任意电平,即高低电平均可

由表9.1.1 可以看到,STM32F1 根据BOOT 引脚的电平选择启动模式,这两个BOOT 引脚
根据外部施加的电平来决定芯片的启动地址。(0 和1 的准确电平范围可以查看F103 系列数据手册I/O 特性表,但我们最好是设置成Gnd 和VDD 的电平值)
(1)内部FLASH 启动方式(主闪存存储器,用的最多)
当芯片上电后采样到BOOT0 引脚为低电平时(系统复位后,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存),0x00000000 和0x00000004 地址被映射到
内部FLASH 的首地址0x08000000 和0x08000004。因此,内核离开复位状态后,读取内部FLASH的0x08000000 地址空间存储的内容,赋值给栈指针MSP,作为栈顶地址,再读取内部FL ASH的0x08000004 地址空间存储的内容,赋值给程序指针PC,作为将要执行的第一条指令所在的地址。完成这两个操作后,内核就可以开始从PC 指向的地址中读取指令执行了。

(2)内部SRAM 启动方式
类似于内部Flash,当芯片上电后采样到BOOT0 和BOOT1 引脚均为高电平时,地址
0x00000000 和0x00000004 被映射到内部SRAM 的首地址0x20000000 和0x20000004,内核从SRAM 空间获取内容进行自举。在实际应用中,由启动文件starttup_stm32f103xe.s 决定了0x00000000 和0x00000004 地址存储什么内容,链接时,由分散加载文件(sct)决定这些内容的绝对地址,即分配到内部FLASH 还是内部SRAM。
(3)系统存储器启动方式
当芯片上电后采样到BOOT0 =1,BOOT1=0 的组合时,内核将从系统存储器的0x1FFFF000及0x1FFFF004 获取MSP 及PC 值进行自举。系统存储器是一段特殊的空间,用户不能访问,ST 公司在芯片出厂前就在系统存储器中固化了一段代码(bootloader)。因而使用系统存储器启动方式时,内核会执行该代码,该代码运行时,会为ISP(In System Program)提供支持x,在STM32F1 上最常见的是检测USART1 传输过来的信息,并根据这些信息更新自己内部FLASH 的内容,达到升级产品应用程序的目的,因此这种启动方式也称为ISP 启动方式。

启动文件分析(startup_stm32xxx.s)

STM32 启动文件由ST 官方提供,在官方的STM32Cube 固件包里,对于STM32F103 系列芯片的启动文件,我们选用的是startup_STM32F103xe.s 这个文件。启动文件用汇编编写,是系统上电复位后第一个执行的程序。

启动文件主要做了以下工作:
1、初始化堆栈指针MSP = _initial_sp,从0X0800 0000获取
2、初始化程序计数器指针PC = Reset_Handler,从0X0800 0004获取
3、设置堆和栈的大小
4、初始化中断向量表,_Vectors定义
5、配置外部SRAM 作为数据存储器(可选)
6、配置系统时钟,通过调用SystemInit 函数(可选)
7、调用C库中的_main 函数初始化用户堆栈,最终调用main 函数

启动文件中的一些指令

指令名称作用
EQU给数字常量取一个符号名,相当于C 语言中的define
AREA汇编一个新的代码段或者数据段
ALIGN编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4 字节对齐。要注意的是,这个不是ARM 的指令,是编译器的,这里放到一起为了方便。
SPACE分配内存空间
PRESERVE8当前文件堆栈需要按照8 字节对齐
THUMB表示后面指令兼容THUMB 指令。在ARM 以前的指令集中有16 位的THUMBM 指令,现在Cortex-M 系列使用的都是THUMB-2 指令集,THUMB-2 是32 位的,兼容16 位和32 位的指令,是THUMB 的超级版。
EXPORT声明一个标号具有全局属性,可被外部的文件使用
DCD以字节为单位分配内存,要求4 字节对齐,并要求初始化这些内存
PROC定义子程序,与ENDP 成对使用,表示子程序结束
WEAK弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不会出错。要注意的是,这个不是ARM 的指令,是编译器的,这里放到一起为了方便。
IMPORT声明标号来自外部文件,跟C 语言中的extern 关键字类似
LDR从存储器中加载字到一个存储器中
BLX跳转到由寄存器给出的地址,并根据寄存器的LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到LR
BX跳转到由寄存器/标号给出的地址,不用返回
B跳转到一个标号
IF,ELSE,EN-DIF汇编条件分支语句,跟C 语言的类似
END到达文件的末尾,文件结束

上表,列举了STM32 启动文件的一些汇编和编译器指令,关于其他更多的ARM 汇编指令,我们可以通过MDK 的索引搜索工具中搜索找到。打开索引搜索工具的方法:
MDK->Help->uVision Help,如图9.2.1.1 所示。

打开之后,我们以EQU 为例,演示一下怎么使用,如图9.2.1.2 所示。

图9.2.1.2 搜索EQU 汇编指令

搜索到的结果有很多,我们只需要看位置为Assembler User Guide 这部分即可。

启动文件代码讲解

(1)栈空间的开辟
栈空间的开辟,源码如图9.2.2.1 所示:


源码含义:开辟一段大小为0x0000 0400(1KB)的栈空间,段名为STACK,NOINIT 表示不初始化;READWRITE 表示可读可写;ALIGN=3,表示按照2^3 对齐,即8 字节对齐。
AREA 汇编一个新的代码段或者数据段。
SPACE 分配内存指令,分配大小为Stack_Size字节连续的存储单元给栈空间。

__initial_sp 紧挨着SPACE 放置,表示栈的结束地址,栈是

以上是关于正点原子STM32(基于HAL库)1的主要内容,如果未能解决你的问题,请参考以下文章

正点原子STM32(基于HAL库)0

正点原子STM32(基于HAL库)3

正点原子STM32(基于HAL库)2

正点原子STM32(基于HAL库)2

正点原子STM32(基于HAL库)4

正点原子STM32(基于HAL库)3