1. STM32H7芯片简介1.1 STM32H7与STM32F1、F4系列芯片的区别相对于 F1、F4 系列,H7 最大的区别是 MPU 和 Cache 的配置,而外设的学习与应用大同小异。其中,STM32H7 多出的一个 L1 Cache 一级缓存,在为低速存储器带来加速的同时,也为程序设计带来了一些问题,其中最为主要的是数据一致性问题。H7 携带了 DTCM 和 ITCM,ITCM 用于运行指令,即程序代码,DTCM 用于数据存取,特点是跟内核速度一样,而片上的其它 RAM 主频都是 200MHz .STM32H7 系列只有 HAL 库,没有再配套标准库。HAL 库的优点是配置方便,特别是配合图形开发工具 STM32CubeMX 时,缺点是源代码稍显臃肿,封装的有点多。而对于 F1,F4 系列,标准库和 HAL 库都有。STM32H7 的自带外设比较之前的任何 STM32 型号都要强劲,更换了 ADC,DMA,USART 等重要外设。如 ADC 换成了 3.6Msps 16 位分辨率,DMA 支持任意互联,USART 也支持波特率自适应。STM32F1 是 M3 内核,STM32F4 是 M4 内核,而 STM32H7 是 M7 内核,性能要比前两者要好一些。1.2 硬件框图下图的硬件框图可在芯片的官网中查看,若还需要更深入的了解,就去读官网介绍页面上的“Key Features”内容,再详细可翻查其数据手册;1.3 STM32H7各型号对比ST 有一个专门的文件,叫 STM32H7x3 MCUs High-performance line,它可在官网介绍页面中找到,对各个型号有作简单的对比,如引脚数、封装、FLASH 大小、RAM 大小以及是否带 HW CRYPTO硬件加密的区别;1.4 总线框图和时钟在芯片的数据手册中(搜索* Figure 1*)提供有芯片的总线框图,可方便查看每个总线时钟速度和该总线所挂的外设;从上面的框图可知:SYSCLK(Hz) = 400000000 (CPU Clock)HCLK(Hz) = 200000000 (AXI and AHBs Clock)AHB Prescaler = 2D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)因为 APB1 prescaler != 1, 所以 APB1 上的 TIMxCLK = APB1 x 2 = 200MHz;因为 APB2 prescaler != 1, 所以 APB2 上的 TIMxCLK = APB2 x 2 = 200MHz;APB4 上面的 TIMxCLK 没有分频,所以就是 100MHz;APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1APB2 定时器有 TIM1, TIM8 , TIM15, TIM16,TIM17APB4 定时器有 LPTIM2,LPTIM3,LPTIM4,LPTIM51.5 AXI总线该总线主要用于高并发,AXI支持高频率、高性能的系统设计:支持高带宽、低延迟设计;提供高频操作,无需复杂的总线桥;满足各种组件的接口需求;适用于具有高初始访问延迟的内存控制器;为互连架构的实现提供灵活性;与现有的AHB、APB接口向后兼容;除此之外AXI总线还有以下关键特性:独立的地址、控制和数据线;支持非字节对齐方式传输;基于起始地址对其方式传输;分开的读、写数据通道,且提供DMA传输;支持发起多个地址;支持无序传输;允许添加寄存器,以提供时许收敛;AXI总线框架如下该图可方便识别总线所外挂的外设,共分为三个域:D1 Domain,D2 Domain 和 D3 Domain;D1 Domain:D1 域中的各个外设是挂在 64 位 AXI 总线组成 6*7 的矩阵上;6 个从接口端 ASIB1 到 ASIB6:外接的主控是 LTDC,DMA2D,MDMA,SDMMC1,AXIM 和 D2-to-D1 AHB 总线;7 个主接口端 AMIB1 到 AMIB7:外接的从设备是 AHB3 总线,Flash A,Flash B,FMC 总线,QSPI 和 AXI SRAM;另外 AHB3也是由 AXI 总线分支出来的,然后再由 AHB3 分支出 APB3 总线。D2 Domain:D2 域的各个外设是挂在 32 位 AHB 总线组成 10*9 的矩阵上;10 个从接口:外接的主控是 D1-to-D2 AHB 总线,AHBP 总线,DMA1,DMA2,Ethernet MAC,SDMMC2,USB HS1 和 USB HS2;9 个主接口:外接的从设备是 SRAM1,SRMA2,SRAM3,AHB1,AHB2,APB1,APB2,D2-to-D1 AHB总线和 D2-to-D3 AHB 总线;D3 Domain:D3 域的各个外设是挂在 32 位 AHB 总线组成 3*2 的矩阵上;3 个从接口:外接的主控 D1-to-D3 AHB 总线,D2-to-D3 AHB 总线和 BDMA;2 个主接口:外接的从设备是 AHB4,SRAM4 和 Bckp SRAM。另外 AHB4 也是这个总线矩阵分支出来的,然后再由 AHB4 分支出 APB4 总线;这三个域的互联:D1 域到 D2 域的 D1-to-D2 AHB bus:允许 D1 域中的主接口外设访问 D2 域里面的从接口外设。比如 D1 域里面的 DMA2D 访问 D2 域里面的 SRAM1;D2 域到 D1 域的 D2-to-D1 AHB bus:允许 D2 域中的主接口外设访问 D1 域里面的从接口外设。比如 D2 域里面的 DMA2 访问 D1 域里面的 AXI SRAM;D1 域到 D3 域的 D1-to-D3 AHB bus:允许 D1 域中的主接口外设访问 D3 域里面的从接口外设。比如 D1 域里面的 MDMA 访问 D3 域里面的 SRAM4;D2 域到 D3 域的 D2-to-D3 AHB bus:允许 D2 域中的主接口外设访问 D3 域里面的从接口外设。比如 D2 域里面的 DMA2 访问 D3 域里面的 SRAM4;1.5.1 AXI总线简介关于AXI总线,ARM有详细的介绍文档《AXI4_specification》;AXI总线的工作框图如下通过上图可知,AXI 总线有 6 个从接口 ASIBs(AMBA slave interface blocks)和 7个主控接口 AMIBs(AMBA master interface blocks)ASIBs:重点介绍最后一列读/写发起能力,如 IN5 连接的主控 DMA2D,支持的读发起能力是 2,写发起能力是 1,即存在两路读信号同时进行(因为 AXI 接口有一个 FIFO 的功能,可供同时进行),反映到 DMA2D 的实际应用中,就是 DMA2D 同时读取前景色和背景色的缓存区做 Alpha 融合之类的操作。写操作同理,DMA2D 的写发起能力仅支持一路。AMIBs:重点介绍最后一列读/写发起能力/总接收能力,这里多了一个总接收能力(Total acceptance),也就是读发起能力和写发起能力同时执行的情况。1.5.2 AXI总线优先级编程由于存在多个 ASIB 从接口访问 AMIB 主控的问题,这就涉及到谁先谁后等问题。所以 AXI 总线矩阵就做了一个基于优先级的仲裁方案,每个 ASIB 接口支持读通道和写通道分别设置,优先级从 0 到 15。数值越大,优先级越高,默认情况都是优先级 0。如果有两个传输同时到达 AMIB 主控接口,那么优先级高的 ASIB 接口传输优先处理;如果优先级相同的话,根据 LUR 方案选择(least recently-used 最近最少使用情况)。1.6 总线互联STM32H7 的总线矩阵四通八达,但不是任意 Bus Master 总线主控端和 Bus Slave 设备端都可以相互通信的。黑色加粗字体是 64 位总线(ITCM,DTCM,Flash A,Flash,AXI SRAM,FMC 等),普通字体是32 位总线。访问通路(每个小方块里面的字符):任何有数字的表示有访问通路。短横杠“-”表示不可访问。有灰色阴影的表示有实用价值的访问通路。表格中具体数值所代表的含义:D=direct1=via AXI bus matrix2=via AHB bus matrix in D23=via AHB bus matrix in D34=via AHB/APB bridge in D15=via AHB/APB bridge in D26=via AHB/APB bridge in D37=via AHBS bus of Cortex-M7多个数值组合 = 互连路径以数字的顺序经过多个矩阵或/和桥。总线访问类型:普通字体表示 32 位总线。斜体表示 32 位总线主机端/ 64 位总线从机端。粗体表示 64 位总线。当前要对这个图有个了解,后面章节讲解各个外设的时候要用到,比如DTCM和ITCM不支持DMA1,DMA2 和 BDMA,仅支持 MDMA。1.7 FLASH双 BANK,每个 BANK 的带宽都是 64bits,如下图所示:H7 中 Flash 的延迟和主频关系:H7 中已经没有 F1 和 F4 系列中的 ART Chrome 加速,通过 H7 中的 Cache加速即可。具体延迟数值和主频关系如下:从上图可知,当延迟等待设置为 0 的时候,即无等待,单周期访问,速度可以做到70MHz。增加 1 个 Flash 周期后,访问速度可以做到 140MHz。当增加到 3 个或 4 个 Flash 周期后,最高速度可以做到 225MHz。Flash 编程操作(写)最好以 256bits 为单位进行,应用中也可以小于 256bits,但是容易造成 ECC校验出问题,所以不推荐。Flash 读操作支持 64bits,32bits,16bits 和 8bits。Flash 支持 ECC 校验,每 256bits 配 10bit 的 ECC 位,可以检测到 1 个 bit 并纠正或者检测 2 个 bit。随着芯片的制造工艺水平越高,带电粒子能产生的位翻转就越多,此时的 ECC 是必须要有的,一般可以纠正 1-2 个 bit。安全等级高的 Flash 类存储器和 RAM 类都是必须要带 ECC 的。1.8 RAM1.9 电源系统电源管理部分最繁琐的就是 CPU,D1,D2,D3 域的各种运行,待机,停机状态切换。常见电源表示简介:1.9.1 系统上电启动STM32H7上电后的系统时序图如下:主要看 Operating mode 部分,依次是 Power down –> Reset -> Wait Oscillator ->HW system init -> Run -> Wait ACTVOS RDY –> Run,即断电状态 -> 复位状态 -> 等待振荡器 HSI 就绪->硬件初始化 -> 运行 -> 等待 ACTVOS 位就绪 -> 正式运行。其详细些的执行流程如下:当系统上电后,POR(Power on reset 上电复位)会检测 VDD 供电,当 VDD 大于 POR 设置的阀值时,将使能电压稳压器。看 VCORE那条曲线,只要 VOSRDY 未就绪,就会一直处于复位状态。一旦 VCORE 正常输出,系统将走出复位状态,内部高速 RC 振荡器 HSI 将使能。HSI 稳定后,将开始系统初始化,主要是 Flash 和可选字节的加载,这些都是由硬件完成的,CPU 也将以受限的方式运行(主要是指不允许对 RAM 进行写操作)。软件程序初始化系统,包括供电配置。当供电配置完成后,等待 ACTVOSRDY 位置 1,完成置 1 后,CPU 就进入正常的运行的模式,允许读写 RAM 了。1.9.2 低功耗模式为了实现各种低功耗模式,CPU 和 D1,D2,D3 域支持的各种模式如下:CPU 模式:CRun:运行状态,CPU 和 CPU 子系统外设正常运行。CSleep:休眠状态,CPU 时钟停止运行,CPU 子系统外设正常运行。CStop:停止状态,CPU 和 CPU 子系统外设都停止运行。D1 域模式:DRun:运行状态,D1 域的总线矩阵正常运行,CPU 子系统运行在 CRun 或者 CSleep 模式。DStop:停机状态,D1 域的总线矩阵时钟停止运行,CPU 子系统运行在 CStop 模式,PDDS_D1 位选择 DStop 模式。DStandby:待机状态,D1 域的总线矩阵断电,CPU 子系统运行在 CStop 模式,PDDS_D1 位选择DStandby 模式。D2 域模式:DRun:运行状态,D2 域的总线矩阵正常运行,CPU 子系统在 D2 域中有分配的外设,CPU 子系统运行在 CRun 或者 CSleep 模式。DStop:停机状态,D2 域的总线矩阵时钟停止运行,CPU 子系统没有在 D2 域分配外设,PDDS_D1位选择 DStop 模式。或者 CPU 子系统在 D2 域中有分配的外设,CPU 子系统运行在 CStop 模式,PDDS_D1 位选择 DStop 模式。DStandby:待机状态,D2 域的总线矩阵断电,CPU 子系统没有在 D2 域分配外设,PDDS_D1 位选 择 DStandby 模式。或者 CPU 子系统在 D2 域中有分配的外设,CPU 子系统运行在 CStop 模式,PDDS_D1 位选择 DStandby 模式。系统/D3 域模式:Run:运行状态,系统时钟和 D3 域总线矩阵时钟处于运行状态。CPU 子系统处于 CRun 和 CSleep模式,或者一个唤醒信号处于激活状态。Stop:停止状态,系统时钟和 D3 域总线矩阵时钟处于停止状态,CPU 子系统处于 CStop 模式。所有的唤醒信号都处于非激活状态,并且至少某个域的一个 PDDS_Dn 位选择了 Stop 模式。Standby:待机状态,系统处于断电状态,CPU 子系统处于 CStop 模式,所有的唤醒信号都处于非激活状态,并且所有域的所有 PDDS_Dn 位选择 Standby 模式。1.9.3 电源去耦电容的选择如下图,每个电源引脚 (VDD/VSS, VDDA/VSSA …)必须使用下述的滤波陶瓷电容去耦。这些电容必须尽量靠近芯片引脚,以确保器件正常工作。1.10 复位系统1.10.1 硬件复位当系统由可靠的电源供电时,一旦通电,电源迅速地达到额定输出电压,一旦断电,电源迅速地下降到 0V,并且在接通的时候,电压不会降低。这时能够可靠地使用基于一个电容和一个电阻的低成本硬件复位。这种形式的复位电路称为阻容复位。注:如果电源不够可靠,考虑到安全性,这种简单的阻容解决方案就不合适了。上电复位和手动复位系统:STM32 这款 CPU 的复位引脚是低电平有效,即 NRST 为低电平时,CPU 处于复位状态。当系统上电瞬间,C114 电容两端电压可以认为是 0,CPU 处于复位状态。3.3V 电源通过 R173 给 C114 充电,当 C114 的电压升到 CPU 的高电平门槛电压时,CPU 退出复位状态转入运行状态。在设计电路时,需要选择适当的 R 值和 C 值,以保证 NRST 低电平持续时间满足 CPU 复位最小脉宽的要求。当按下 S4 轻触开关时,C114 两端被短路接地,可实现手动复位 CPU。1.10.2 软件复位除了上电和手动复位,程序设计设置中还经常要用到软件复位,即调用一条函数就可以实现复位功能。此函数已经由 CMSIS 软件包中的 core_cm7.h 文件提供,函数如下:- /**
- \brief System Reset
- \details Initiates a system reset request to reset the MCU.
- */
- __STATIC_INLINE void __NVIC_SystemReset(void)
- {
- __DSB(); /* Ensure all outstanding memory accesses included
- buffered write are completed before reset */
- SCB->AIRCR = (uint32_t)((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
- (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
- SCB_AIRCR_SYSRESETREQ_Msk ); /* Keep priority group unchanged */
- __DSB(); /* Ensure completion of memory access */
- for(;;) /* wait until reset */
- {
- __NOP();
- }
- }
复制代码 软件复位反映到实际硬件上,就是给硬件复位部分发一个复位信号:1.11 RCC 时钟系统1.11.1 STM32H7 RCC时钟简介STM32H7 有六种时钟可供使用:HSI (High-speed internal oscillator) :HSI 是内部的高速 RC 振荡器,频率 64MHz,可被用于系统时钟。优势是低成本,无需外部时钟,快速启动(仅需几个微秒),缺点是精度差,即使经过校准。HSE (High-speed external oscillator):HSE 是外部的高速振荡器,通过外接时钟源,有源或者无源晶振驱动,时钟范围 4-48MHz。优势是精度高,缺点是增加成本。LSE (Low-speed external oscillator) :LSE 是外部的低速振荡器,通过外接时钟源,有源或者无源晶振驱动,一般接 32.768KHz,主要用于RTC 实时时钟。LSI (Low-speed internal oscillator) :LSI 是内部的低速 RC 振荡器,频率约是 32KHz,主要用于独立看门狗和自动唤醒,也可以用于 RTC实时时钟。CSI (Low-power internal oscillator) :CSI 是内部的低速振荡器,频率约是 4MHz,相比 64MHz 的 HSI,主要用于低功耗。HSI48 (High-speed 48 MHz internal oscillator) :HSI48 是内部高速振荡器,频率约是 48MHz,用于给特定的外设提供时钟,比如 USB。时钟树:
1.11.2 HSE 和 LSE 硬件设计HSE 时钟:下面硬件电路用的 25MHz 晶振为 HSE 提供时钟:注:晶振和负载电容需要尽可能近地靠近 H7 的晶振引脚,以减小输出失真和启动稳定时间。负载电容值必须根据选定的晶振进行调节。对于图中 C15 和 C17,推荐使用高质量陶瓷电容,这种电容是设计用于需要高频率的场合,并且可以满足晶体或谐振器的需求。C15 和 C17 通常具有相同的值。HSE 旁路时钟和外置晶振区别:当STM32H7采用的外置晶振模式时,高速外部 (HSE) 时钟可以使用一个 4 到 48MHz 的晶振 / 陶瓷谐振振荡器产生:而 bypass 旁路的意思即不使用它、绕过它。具体到 HSE 旁路的话,使用用户直接提供 4-50MHz 的时钟源,可以使用有源晶振或者 FPGA 提供时钟等方式:LSE 时钟:下面电路用 32768Hz 晶振为 LSE 提供时钟,STM32 的 LSE 晶振起振难(又称 RTC 起振)是老毛病了,选取晶振和配套电容比较讲究1.12 GPIO对于不使用的引脚,推荐设置为模拟模式,悬空即可。GPIO 的速度等级高的时候,最好使能 IO 补偿单元。GPIO 还涉及到一个注入电流的问题1.12.1 GPIIO 功能模式分析STM32H7 的 GPIO 特性如下:输出状态:开漏/推挽 + 上拉/下拉电阻。通过输出数据寄存器(GPIOx_ODR)或者外设(GPIO 设置为复用模式时)输出数据。GPIO 速度等级设置。输入状态:浮空,上拉/下拉,模拟。通过输入数据寄存器(GPIOx_IDR)或者外设(GPIO 设置为复用模式)输入数据。通过寄存器 GPIOx_BSRR 实现对寄存器 GPIOx_ODR 的位操作。通过配置寄存器 GPIOx_LCKR 的锁机制,实现冻结 IO 口配置。每两个时钟周期就可以翻转一次 IO。高度灵活的引脚复用功能,允许 IO 引脚既可以做 GPIO 也可以做功能复用。STM32H7 的 GPIO 端口可以配置为如下的 8 种模式:输入浮空输入上拉输入下拉模拟功能具有上拉或下拉功能的开漏输出具有上拉或下拉功能的推挽输出具有上拉或下拉功能的复用功能推挽具有上拉或下拉功能的复用功能开漏另外,由于上拉和下拉是可选配置,对应的 HAL 库配置使用下面 6 种就可以表示:GPIO_MODE_INPUT 输入模式GPIO_MODE_OUTPUT_PP 推挽输出GPIO_MODE_OUTPUT_OD 开漏输出GPIO_MODE_AF_PP 复用推挽GPIO_MODE_AF_OD 复用开漏GPIO_MODE_ANALOG 模拟模式1.12.2 推挽输出推挽电路是两个参数相同的三极管或 MOSFET,以推挽方式存在于电路中。 电路工作时,两只对称的开关管每次只有一个导通,导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级提高电路的负载能力。 相对于开漏输出模式,推挽输出最大优势是输出高电平时,上升时间快,电压驱动能力强。1.12.3 开漏输出开漏端相当于 MOS 管的漏极(三极管的集电极),要得到高电平状态必须外接上拉电阻才行,因此输出高电平的驱动能力完全由外接上拉电阻决定,但是其输出低电平的驱动能力很强。开漏形式的电路有以下几个特点:输出高电平时利用外部电路的驱动能力,减少 IC 内部的驱动。开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平。如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。上拉电阻的阻值决定了逻辑电平转换的速度。阻值越大,速度越低,功耗越小。开漏输出提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。可以将多个开漏输出连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系,即“线与”。可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑 0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为 0,只有都为高电平时,与的结果才为逻辑 1 .1.12.4 复用推挽和开漏复用指的是 GPIO 切换到 CPU 内部设备(比如 SPI,I2C,UART 等电路),也就是 GPIO 不是作为普通IO 使用,是由内部设备直接驱动。推挽和开漏的特征同上。1.12.5 四种输入模式浮空输入:CPU 内部的上拉电阻、下拉电阻均断开的输入模式。下拉输入:CPU 内部的下拉电阻使能、上拉电阻断开的输入模式。上拉输入:CPU 内部的上拉电阻使能、下拉电阻断开的输入模式。模拟输入模式: GPIO 引脚直接连接内部 ADC。任何不使用的引脚都推荐设置为模拟输入模式:主要从功耗和防干扰考虑。所有用作带上拉电阻输入的 I/O 都会在引脚外部保持为低时产生电流消耗。此电流消耗的值可通过使用的静态特性中给出的上拉 / 下拉电阻值简单算出。对于输出引脚,还必须考虑任何外部下拉电阻或外部负载以估计电流消耗。若外部施加了中间电平,则额外的 I/O 电流消耗是因为配置为输入的 I/O。此电流消耗是由用于区分输入值的输入施密特触发器电路导致。除非应用需要此特定配置,否则可通过将这些 I/O 配置为模拟模式以避免此供电电流消耗。 ADC 输入引脚应配置为模拟输入就是这种情况。任何浮空的输入引脚都可能由于外部电磁噪声,成为中间电平或意外切换。为防止浮空引脚相关的电流消耗,它们必须配置为模拟模式,或内部强制为确定的数字值。这可通过使用上拉 / 下拉电阻或将引脚配置为输出模式做到。1.12.6 GPIO 的拉电流负载和灌电流负载能力拉电流负载:一种负载电流从驱动门流向外电路,称为拉电流负载。比如使用 STM32H7 的 GPIO 直接驱动 LED (MCU输出高电平)就是拉电流形式。灌电流负载:负载电流从外电路流入驱动门,称为灌电流负载。如下面这种形式的 LED 驱动电路。STM32H7 的 IO 驱动能力(截图来自 STM32H7 数据手册):通过截图可知STM32H7 总的拉电流和灌电流不可超过 140mA,单个引脚灌电流最大不可超过 20mA .1.12.7 IO 补偿单元(高速IO)IO 补偿单元用于控制 I/O 通信压摆率(tfall / trise)以此来降低 I/O 噪声。当前 STM32H7 的速度等级可以配置为以下四种:- /** @defgroup GPIO_speed_define GPIO speed define
- * @brief GPIO Output Maximum frequency
- * @{
- */
- #define GPIO_SPEED_FREQ_LOW ((uint32_t)0x00000000U) /*!< Low speed */
- #define GPIO_SPEED_FREQ_MEDIUM ((uint32_t)0x00000001U) /*!< Medium speed */
- #define GPIO_SPEED_FREQ_HIGH ((uint32_t)0x00000002U) /*!< Fast speed */
- #define GPIO_SPEED_FREQ_VERY_HIGH ((uint32_t)0x00000003U) /*!< High speed */
复制代码 1.12.8 GPIIO 兼容 CMOS 和 TTL 电平下面是CMOS、TTL电平图:2. STM32H7的HAL库框架简介HAL 库为各种外设基本都配了三套 API:查询,中断和 DMA .2.2 HAL 库的头文件stm32h7xx_hal_conf.h配置HAL 库有一个专门的配置文件叫 stm32h7xx_hal_conf.h用于配置一些核心参数。晶振频率配置:参数HSE_VALUE用于配置MCU使用的晶振频率,板子使用的实际晶振频率一定要与这个数值一致。如 V7 开发板的外置晶振是 25MHz,那么就配置宏定义为:#define HSE_VALUE ((uint32_t)25000000)滴答定时器优先级配置:在调用基于此时间基准的延迟函数HAL_Delay()时会选用时间基准是滴答定时器还是通用定时器,一般都使用滴答定时器,将滴答定时器优先级调到最高即可。#define TICK_INT_PRIORITY ((uint32_t)0x0F)实时系统使用:参数USE_RTOS用于配置MCU是否使用实时系统,用则配置为1,否则为0 .使能UHS-I 模式:参数USE_SD_TRANSCEIVER用于使能UHS-I 模式,不使能时注释掉即可。使能断言功能:参数USE_FULL_ASSERT用于使能断言assert功能,不使用时注释掉即可。当使能断言功能后 ,若断言功能被触发(传入参数不正确)将调用函数void assert_failed(uint8_t* file, uint32_t line),但HAL库仅定义了该函数而将函数实现的内容留给了用户,下面给出一个参考的函数方案。- /********************************************************************************
- * 函 数 名: assert_failed
- * 形 参:file : 源代码文件名称。关键字__FILE__表示源代码文件名。
- * line :代码行号。关键字 __LINE__ 表示源代码行号
- * 返 回 值: 无
- ********************************************************************************/
- void assert_failed(uint8_t* file, uint32_t line)
- {
- /* 用户可以添加自己的代码报告源代码文件名和代码行号,比如将错误文件和行号打印到串口printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
- /* 这是一个死循环,断言失败时程序会在此处死机,以便于用户查错 */
- while (1){}
- }
复制代码 ST 库函数使用了 C 编译器的断言功能,如果定义了 USE_FULL_ASSERT,那么所有的 ST 库函数将检查函数形参是否正确。如果不正确将调用 assert_failed() 函数,这个函数是一个死循环,便于用户检查代码,其中:关键字 __LINE__ 表示源代码行号。关键字__FILE__表示源代码文件名。断言功能使能后将增大代码大小,推荐仅在调试时使能,在正式发布软件应禁止掉。2.3 HAL 库的硬件初始化下面简介硬件初始化函数bsp_Init() .- /********************************************************************************
- * 函 数 名: bsp_Init
- * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
- * 形 参: 无
- * 返 回 值: 无
- ********************************************************************************/
- void bsp_Init(void)
- {
- /* 配置MPU */
- MPU_Config();
-
- /* 使能L1 Cache */
- CPU_CACHE_Enable();
- /*
- STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- - 设置NVIV优先级分组为4。
- */
- HAL_Init();
- /*
- 配置系统时钟到400MHz
- - 切换使用HSE。
- - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
- */
- SystemClock_Config();
- /*
- Event Recorder:
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
- */
- #if Enable_EventRecorder == 1
- /* 初始化EventRecorder并开启 */
- EventRecorderInitialize(EventRecordAll, 1U);
- EventRecorderStart();
- #endif
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
- bsp_InitTimer(); /* 初始化滴答定时器 */
-
- bsp_InitUart(); /* 初始化串口 */
- bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
- bsp_InitLed(); /* 初始化LED */
- bsp_InitExtSDRAM(); /* 初始化SDRAM */
- bsp_InitI2C(); /* 初始化I2C总线 */
-
- bsp_InitBMP085();
- bsp_InitBH1750();
-
- bsp_InitSPIBus(); /* 初始化SPI总线 */
- bsp_InitSFlash(); /* 识别串行flash W25Q64 */
-
- bsp_InitQSPI_W25Q256();
-
- bsp_InitRTC(); /* 配置RTC */
-
- TOUCH_InitHard();
- LCD_InitHard();
- }
复制代码 配置 MPU 和使能 Cache,对于 H7 而已,这两个函数要优先执行,因为 Flash 速度,SRAM 速度跟 CPU 和 TCM 有些差距的,所以要使能 Cache。而不同存储区的 Cache 策略要通过MPU 分别进行配置。注意调用函数 HAL_Init 时,系统依然使用的 64MHz HSI 时钟。此函数会调用函数 HAL_InitTick,初始化滴答时钟中断为 1ms,并设置 NVIV 优先级分组为 4。同时它会开启滴答定时器中断,如果用户也要使用滴答定时器中断,建议对其终端服务函数配置如下:- /********************************************************************************
- * 函 数 名: SysTick_Handler
- * 功能说明: 系统嘀嗒定时器中断服务程序。启动文件中引用了该函数。
- * 形 参: 无 * 返 回 值: 无
- * 备 注:该函数定义在文件bsp_timer.C 中
- ********************************************************************************/
- void SysTick_Handler(void)
- {
- HAL_IncTick(); /* ST HAL 库的滴答定时中断服务程序 */
- if (g_ucEnableSystickISR == 0) /* 做了一个变量标志,调用了函数 bsp_InitTimer 才置位此变量 */
- {
- return;
- }
- SysTick_ISR(); /* 安富莱 bsp 库的滴答定时中断服务程序 */
- }
复制代码 通过函数SystemClock_Config()切换 HSI 到外部高速时钟 HSE,并配置系统时钟到 400MHz。调用这个函数会更新全局变量 SystemCoreClock,并重新配置函数 HAL_InitTick .2.4 HAL 库的 DMA 处理思路为了方便各种外设直接启动 DMA,HAL 库专门为支持 DMA 操作的外设都提供了对应的 DMA 函数,。比如串口的:- HAL_UART_Transmit_DMA()
- HAL_UART_Receive_DMA()
- HAL_UART_DMAPause()
- HAL_UART_DMAResume()
- HAL_UART_DMAStop()
复制代码 特别注意一点,针对外设的 DMA 函数基本都有开启中断,如果用户使能此外设的 NVIC,使用中务必别忘了写 DMA 的中断服务程序,比如使用 DMA1_Stream1的中断服务函数:- void DMA1_Stream1_IRQHandler(void)
- {
- /* USER CODE BEGIN USART1_IRQn 0 */
- 此处可以添加用户代码
- /* USER CODE END USART1_IRQn 0 */
- /* 参数是 DMA 句柄 */
- HAL_DMA_IRQHandler(&hdma_usart1_tx);
- /* USER CODE BEGIN USART1_IRQn 1 */
- 此处可以添加用户代码
- /* USER CODE END USART1_IRQn 1 */
- }
复制代码 如果要在 DMA 传输完成,半传输完成等中断里面执行功能,也是通过 HAL_DMA_IRQHandler 调用的各种回调函数里面实现,这些回调都是弱定义的,方便用户直接在其它文件里面重定义:- HAL_UART_TxHalfCpltCallback()
- HAL_UART_TxCpltCallback()
- HAL_UART_RxHalfCpltCallback()
- HAL_UART_RxCpltCallback()
复制代码 2.5 HAL 库 API下面通过一个简单的初始化流程来了解 STM32H7 的工程模板所必备的库文件和 API:第 1 步:系统上电复位,进入启动文件 startup_stm32h743xx.s,在这个文件里面执行复位中断服务程序。在复位中断服务程序里面执行函数 SystemInit,此函数在文件 system_stm32h7xx.c 里面。之后是调用编译器封装好的函数,比如用于 MDK 的启动文件是调用__main,最终进入到 main函数。第 2 步:进入到 main 函数就可以开始用户应用程序编程了。在这个函数里面要做几个重要的初始化,依次是:MPU 初始化,需要用到库文件 stm32h7xx_hal_cortex.c 和 stm32h7xx_hal_cortex.h .Cache 初始化,需要用到 core_cm7.h 文件。HAL 库初始化函数 HAL_Init,需要用到文件 stm32h7xx_hal.c .系统时钟初始化,需要用到库文件 stm32h7xx_hal_rcc.c .前面的两步完成后,就可以开始做用户需要的按键、串口等方面的初始化和应用代码的实现了。2.5.1 源文件 stm32h7xx_hall.c基准电压大小配置,EXTI 配置,IO 补偿配置等都在这个文件里面设置。下面是一些注意事项:HAL 库中各个外设驱动里面的延迟实现是基于此文件提供的时间基准,而这个时间基准既可以使用滴答定时器实现也可以使用通用的定时器实现,默认情况下是用的滴答定时器。函数 HAL_Init 里面会调用时间基准初始化函数 HAL_InitTick,而调用函数HAL_RCC_ClockConfig也会调用时间基准初始化函数 HAL_InitTick .如果在中断服务程序里面调用延迟函数 HAL_Delay 要特别注意,因为这个函数的时间基准是基于滴答定时器或者其他通用定时器实现,实现方式是滴答定时器或者其他通用定时器里面做了个变量计数。如此一来,结果是显而易见的,如果其他中断服务程序调用了此函数,且中断优先级高于滴答定时器,会导致滴答定时器中断服务程序一直得不到执行,从而卡死在里面。所以滴答定时器的中断优先级一定要比它们高。2.5.1.1 函数 HAL_Init- HAL_StatusTypeDef HAL_Init(void)
- {
- /* 设置中断优先级分组 */
- HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
- /* 使用滴答定时器做为默认时基,配置为 1ms 滴答,另外系统上电后默认使用的 HIS 时钟 */
- if(HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
- {
- return HAL_ERROR;
- }
- /* 初始化底层硬件 */
- HAL_MspInit();
- /* 返回函数状态 */
- return HAL_OK;
- }
复制代码 此函数用于初始化 HAL 库,此函数主要实现如下功能:设置 NVIC 优先级分组是 4。设置滴答定时器的每 1ms 中断一次。HAL 库不像之前的标准库,在系统启动函数 SystemInit 里面做了 RCC 初始化,HAL 库是没有做的,所以进入到 main 函数后,系统还在用内部高速时钟 HSI,对于 H7 来说,HSI 主频是 64MHz .函数 HAL_Init 里面调用的 HAL_MspInit 一般在文件 stm32h7xx_hal_msp.c 里面做具体实现,主要用于底层初始化。当前此函数也在文件 stm32h7xx_hal.c 里面,只是做了弱定义。返回值:返回 HAL_ERROR 表示参数错误,HAL_OK 表示发送成功,HAL_BUSY 表示忙,正在使用中。注意事项:必须在 main 函数里面优先调用此函数。用户务必保证每 1ms 进入一次滴答中断。2.5.1.2 函数 HAL_DeInit- HAL_StatusTypeDef HAL_DeInit(void)
- {
- /* 复位所有外设 */__set_PRIMASK
- __HAL_RCC_AHB3_FORCE_RESET();
- __HAL_RCC_AHB3_RELEASE_RESET();
- /* 省略未写 */
- __HAL_RCC_APB4_FORCE_RESET();
- __HAL_RCC_APB4_RELEASE_RESET();
- /* 复位底层硬件初始化 */
- HAL_MspDeInit();
- /* 返回值 */
- return HAL_OK;
- }
复制代码 此函数用于复位 HAL 库和滴答时钟:复位了 AHB1,2,3,4 的时钟以及 APB1L,APB1H,APB2,3,4 的时钟。函数HAL_DeInit里面调用的HAL_MspDeInit一般在文件stm32h7xx_hal_msp.c里面做具体实现,主要用于底层初始化,跟函数 HAL_Init 里面调用的 HAL_MspInit 是一对。当前此函数也在文件stm32h7xx_hal.c 里面,只是做了弱定义。2.5.1.3 函数 HAL_InitTick- __weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
- {
- /* Configure the SysTick to have interrupt in 1ms time basis*/
- if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
- {
- return HAL_ERROR;
- }
- /* Configure the SysTick IRQ priority */
- if (TickPriority < (1UL << __NVIC_PRIO_BITS))
- {
- HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
- uwTickPrio = TickPriority;
- }
- else
- {
- return HAL_ERROR;
- }
- /* Return function status */
- return HAL_OK;
- }
复制代码 此函数用于初始化滴答时钟,此函数相关问题如下:此函数有个前缀__weak ,表示弱定义,用户可以重定义。此函数用于初始化滴答时钟 1ms 中断一次,并且为滴答中断配置一个用户指定的优先级。此函数由 HAL_Init 调用,或者任何其它地方调用函数 HAL_RCC_ClockConfig 配置 RCC 的时候也会调用 HAL_InitTick .调用基于此函数实现的 HAL_Delay 要特别注意,因为这个函数的时间基准是基于滴答定时器或者其他通用定时器实现,实现方式是滴答定时器或者其他通用定时器里面做了个变量计数。如此一来,结果是显而易见的,如果其他中断服务程序调用了此函数,且中断优先级高于滴答定时器,会导致滴答定时器中断服务程序一直得不到执行,从而卡死在里面。所以滴答定时器的中断优先级一定要比它们高。输入形参: TickPriority 用于设置滴答定时器优先级。返回值:返回 HAL_ERROR 表示参数错误,HAL_OK 表示发送成功,HAL_BUSY 表示忙,正在使用中。此函数由 HAL_Init 调用,无需用户操作,除非需要重定义。2.5.1.4 Systick 的相关函数调用了函数 HAL_Init 后,Systick 相关的函数就可以使用了。这些函数如下:- __weak void HAL_IncTick(void)
- __weak uint32_t HAL_GetTick(void)
- uint32_t HAL_GetTickPrio(void)
- HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq)
- HAL_TickFreqTypeDef HAL_GetTickFreq(void)
- __weak void HAL_Delay(uint32_t Delay)
- __weak void HAL_SuspendTick(void)
- __weak void HAL_ResumeTick(void)
复制代码 函数有个前缀__weak ,表示弱定义,用户可以重定义函数 HAL_IncTick 在滴答定时器中断里面被调用,实现一个简单的计数功能,因为一般滴答定时器中断都是配置的 1ms,所以计数全局变量 uwTick 每毫秒加 1 .函数 HAL_GetTick 用于获取全局变量 uwTick 当前的计数。函数 HAL_GetTickPrio 用于获取滴答时钟优先级。函数 HAL_SetTickFreq 和 HAL_GetTickFreq 是一对,前者用于设置滴答中断频率,后者用于获取滴答中断频率。函数 HAL_Delay 用于阻塞式延迟,默认单位是 ms .函数 HAL_SuspendTick 和 HAL_ResumeTick 是一对,前者用于挂起滴答定时器,后者用于恢复。2.5.1.5 函数 HAL_SYSCFG_VREFBUF_VoltageScalingConfig- void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling)
复制代码 此函数用于配置 STM32H7 内部电压基准大小:当形参 VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE0 时输出基准是 2.048 V,条件是 VDDA >= 2.4V .当形参 VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE1 时输出基准是 2.5 V,条件是 VDDA >= 2.8V .当形参 VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE2 时输出基准是 1.5 V,条件是 VDDA >= 1.8V .当形参 VoltageScaling = SYSCFG_VREFBUF_VOLTAGE_SCALE3 时输出基准是 1.8 V,条件是 VDDA >= 2.1V .2.5.1.6 STM32H7 自带电压基准相关函数- void HAL_SYSCFG_VREFBUF_HighImpedanceConfig(uint32_t Mode)
- void HAL_SYSCFG_VREFBUF_TrimmingConfig(uint32_t TrimmingValue)
- HAL_StatusTypeDef HAL_SYSCFG_EnableVREFBUF(void)
- void HAL_SYSCFG_DisableVREFBUF(void)
复制代码 函数 HAL_SYSCFG_VREFBUF_HighImpedanceConfig此函数用于配置 STM32H7 内部电压基准是否在芯片内部与 VREF+引脚接通。形参为 SYSCFG_VREFBUF_HIGH_IMPEDANCE_DISABLE 时,表示导通。形参为 SYSCFG_VREFBUF_HIGH_IMPEDANCE_ENABLE 时,表示高阻,即不导通。函数 HAL_SYSCFG_VREFBUF_TrimmingConfig此函数用于内部电压基准的校准调节。函数 HAL_SYSCFG_EnableVREFBUF 和 HAL_SYSCFG_DisableVREFBUF 是一对,分别用于内部电压参考基准的禁止和使能。2.5.1.7 函数 HAL_SYSCFG_AnalogSwitchConfig- void HAL_SYSCFG_AnalogSwitchConfig(uint32_t SYSCFG_AnalogSwitch , uint32_t SYSCFG_SwitchState )
复制代码 引脚 PA0,PA1,PC2,PC3 用于 ADC 时,还有一组对应的可选引脚 PA0_C,PA1_C,PC2_C 和PC3_C . 此函数的作用就是切换可选引脚。2.5.1.8 BOOST 的使能和禁止(用于 ADC)- void HAL_SYSCFG_EnableBOOST(void)
- void HAL_SYSCFG_DisableBOOST(void)
复制代码 这两个函数用于使能或者禁止 Booster。如果使能了 booster 的话,在供电电压低于 2.7V 时,可以减少模拟开关总的谐波失真,这样的话,模拟开关的性能跟正常供电电压时的全范围测量一样。2.5.1.9 函数 HAL_SYSCFG_CM7BootAddConfig- void HAL_SYSCFG_CM7BootAddConfig(uint32_t BootRegister, uint32_t BootAddress)
复制代码 用于配置 BOOT = 0 或者 BOOT = 1 时的启动地址。2.5.1.10 IO 补偿相关函数- void HAL_EnableCompensationCell(void)
- void HAL_DisableCompensationCell(void)
- void HAL_SYSCFG_EnableIOSpeedOptimize(void)
- void HAL_SYSCFG_DisableIOSpeedOptimize(void)
- void HAL_SYSCFG_CompensationCodeSelect(uint32_t SYSCFG_CompCode)
- void HAL_SYSCFG_CompensationCodeConfig(uint32_t SYSCFG_PMOSCode, uint32_t SYSCFG_NMOSCode )
复制代码 函数 HAL_EnableCompensationCell 和 HAL_DisableCompensationCell分别用于使能或者禁止 IO 补偿,只有在供电电压范围是 2.4V 到 3.6V 之间时,使用此功能才有意义。函数 HAL_SYSCFG_EnableIOSpeedOptimize 和 HAL_SYSCFG_DisableIOSpeedOptimize分别用于优化 IO 速度或者禁止优化,不过仅在供电电压低于 2.5V 时可用,高于 2.5V 不可以使用,另外使用这个功能的前提是用户使能了 PRODUCT_BELOW_25V(是可选字节配置选项里面的一个 bit)。函数 HAL_SYSCFG_CompensationCodeSelectIO 补偿单元的选择,函数形参可以是SYSCFG_CELL_CODE,即寄存器 SYSCFG_CCVR,也可以是SYSCFG_REGISTER_CODE,即寄存器 SYSCFG_CCCR .函数 HAL_SYSCFG_CompensationCodeConfig用于设置 GPIO 内部构造中 NMOS 和 PMOS 的补偿值,两个形参的范围都是 0-15。根据用户调用函数HAL_SYSCFG_CompensationCodeSelect 选择的寄存器,这里仅有一个形参的设置是有效的。2.5.2 源文件 stm32h7xx_hal_rcc.c这个文件主要是实现内部和外部时钟(HSE、HSI、LSE、CSI、LSI、HSI48、PLL、CSS、MCO)以及总线时钟(SYSCLK、AHB3、 AHB1、AHB2、AHB4、APB3、APB1L、APB1H、APB2、 APB4)的配置。注意事项:系统上电复位后,通过内部高速时钟 HSI 运行(主频 64MHz),Flash 工作在等待周期,所有外设除了 SRAM、Flash、JTAG 和 PWR,时钟都是关闭的。AHB 和 APB 总线无分频,所有挂载这两类总线上的外设都是以 HSI 频率运行。所有的 GPIO 都是模拟模式,除了 JTAG 相关的几个引脚。系统上电复位后,用户需要完成以下工作: 选择用于驱动系统时钟的时钟源。配置系统时钟频率和 Flash 设置。配置分频器。使能外设时钟。配置外设时钟源,部分外设的时钟可以不来自系统时钟,此时通过配置寄存器RCC_D1CCIPR、RCC_D2CCIP1R、RCC_D2CCIP2R 和 RCC_D3CCIPR 实现。使能了外设时钟后,不能立即操作对应的寄存器,要加延迟。不同外设延迟不同:如果是 AHB 的外设,使能了时钟后,需要等待 2 个 AHB 时钟周期才可以操作这个外设的寄存器。如果是 APB 的外设,使能了时钟后,需要等待 2 个 APB 时钟周期才可以操作这个外设的寄存器。2.5.2.1 HCLK、PCLK时钟树由上图可知:HCLK1,2,3,4 对应的是 AHB 总线 AHB1,AHB2,AHB3 和 AHB4 时钟。PCLK1、2、3、4 对应的是 APB 总线 APB1,APB2,APB3 和 APB4 时钟。内部和外部时钟配置:HSI (high-speed internal):高速内部 RC 振荡器,可以直接或者通过 PLL 倍频后做系统时钟源。缺点是精度差些,即使经过校准。CSI (Low-power internal oscillator):低功耗内部 RC 振荡器,作用同 HSI,不过主要用于低功耗。LSI (low-speed internal):低速内部时钟,主要用于独立看门狗和 RTC 的时钟源。HSE (high-speed external):高速外部晶振,可接 4 - 48MHz 的晶振,可以直接或者通过 PLL 倍频后做系统时钟源,也可以做 RTC 的是时钟源。LSE (low-speed external):低速外部晶振,主要用于 RTC。CSS (Clock security system):时钟安全系统,一旦使能后,如果 HSE 启动失败(不管是直接作为系统时钟源还是通过 PLL 输出后做系统时钟源),系统时钟将切换到 HSI。如果使能了中断的话,将进入不可屏蔽中断 NMI。MCO1 (micro controller clock output):可以在 PA8 引脚输出HSI,LSE,HSE,PLL1(PLL1_Q)或 HSI48 时钟。**MCO2 (micro controller clock output):**可以在 PC9 引脚输出HSE,PLL2(PLL2_P),SYSCLK, LSI, CSI 或 PLL1(PLL1_P) 时钟。PLL 锁相环:时钟输入来自 HSI , HSE 或者 CSI,主锁相环 PLL1,用于给 CPU 和部分外设提供时钟。专用锁相环 PLL2 和 PLL3,用于给部分外设提供时钟。3. STM32H7 启动过程详解这里的启动过程是指从 CPU 上电复位执行第 1 条指令开始(汇编文件)到进入 C 程序 main()函数入口之间的部分。相比 F1,F4 的启动方式,H7 的启动方式更灵活些,只需一个 boot 引脚即可。但是一个引脚只能区分出两个状态,为了解决这个问题,H7 专门配套了两个 option bytes 选项字节来解决此问题。如此以来就可以方便设置各种存储器地址了。详见节3.2.2 BOOT 启动模式 。3.1 不同 H7 系列芯片对应的启动文件在工程路径 \Libraries\CMSIS\Device\ST\STM32H7xx\Source\Templates\arm 下,有Keil下的芯片启动文件。不同型号的启动文件区别如下:3.2 启动文件分析启动文件是后缀为.s 的汇编语言文本文件。他主要完成如下工作:设置堆栈指针 SP = __initial_sp。设置 PC 指针 = Reset_Handler。设置中断向量表。配置系统时钟。配置外部 SRAM/SDRAM 用于程序变量等数据存储(这是可选的)。跳转到 C 库中的 __main ,最终会调用用户程序的 main()函数。Cortex-M 内核处理器复位后,处于线程模式,指令权限是特权级别(最高级别),堆栈设置为使用主堆栈 MSP。3.2.1 复位序列硬件复位之后,CPU 内的时序逻辑电路首先完成如下两个工作(程序代码下载到内部 flash 为例,flash首地址 0x0800 0000)将 0x08000000 位置存放的堆栈栈顶地址存放到 SP 中(MSP)。将 0x08000004 位置存放的向量地址装入 PC 程序计数器。CPU 从 PC 寄存器指向的物理地址取出第 1 条指令开始执行程序,也就是开始执行复位中断服务程序 Reset_Handler。复位中断服务程序会调用SystemInit()函数来配置系统时钟、配置FMC总线上的外部SRAM/SDRAM,然后跳转到 C 库中__main 函数。由 C 库中的__main 函数完成用户程序的初始化工作(比如:变量赋初值等),最后由__main 函数调用用户写的 main()函数开始执行 C 程序。3.2.2 BOOT 启动模式BOOT_ADD0 和 BOOT_ADD1 对应 32 位地址的高 16 位。通过这两个选项字节,所有 0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置,包括:所有 Flash 地址空间。所有 RAM 地址空间,ITCM,DTCM 和 SRAM。设置了选项字节后,掉电不会丢失,下次上电或者复位后,会根据 BOOT 引脚状态从 BOOT_ADD0, 或 BOOT_ADD1 所设置的地址进行启动。使用 BOOT 功能,注意以下几个问题:如果用户不慎,设置的地址范围不在有效的存储器地址,那么 BOOT = 0 时,会从 Flash 首地址0x0800 0000 启动,BOOT = 1 时,会从 ITCM 首地址 0x0000 0000 启动。如果用户使能了 Flash Level 2 保护,那么只能从 Flash 地址空间进行启动。作为对比,下面补充 F1,F4 的启动方式由 BOOT0 和 BOOT1 引脚共同决定: |