STM32:启动过程

STM32:启动过程
前⾔
  上电之后,CPU⾸先根据boot引脚选择存储器重映射区域,将该区域的地址重映射为地址偏移量为0;
  CPU从地址偏移量为0的地址处开始执⾏;该地址烧录的代码必须是xx.s启动⽂件,使⽤汇编语⾔编写;
  上电之后,具体步骤截图如下;执⾏完以下步骤之后单⽚机就可以开始使⽤外设,运⾏逻辑代码了;
  另外,MDK并没有将启动⽂件的所有配置开源,⽐如⼀部分的配置由__main闭源执⾏;
  我们只能配置开源的⼀部分启动⽂件的参数;本⽂我们了解⼀下完整的启动流程及其原理;
1 boot的启动⽅式
  1.1 对于STM32的F0和F4开发板⽽⾔,⼀共有3种启动⽅式;以下的存储空间⼤⼩和地址是以⼤容量F1芯⽚作为参考的;不同芯⽚有⼀些差别;
    注意下⾯存储空间⼤⼩的单位都是byte,这是因为内存的数据线是8bit的;内存的数据线和单⽚机的
数据线是不同的总线;
boot[0:1]启动地址存储空间启动⽅式事项
[0:x]0x0800 0000-
0x0807 FFFF
512K
bytes
Flash启动
使⽤JTAG或SWD下载
较为常⽤;
[1:0]0x1FFF F000-
0x1FFF F7FF
2K
bytes
系统存储器启动
使⽤串⼝下载,代码是通过bootloader搬运到flash中;
该区域内存储了⼚家烧录的bootloader程序(即ISP程序),需要配合st提供的下载软件;
[1:1]0x2000 0000-
0x2001 0000
64K
bytes
内嵌SRAM启动
使⽤JTAG或SWD下载,仅⽀持调试模式;较为少⽤;
没有掉电存储程序的功能,需要添加宏定义VECT_TAB_SRAM,配合脚本⽂件使⽤;
  1.2 对于 STM32H7x3 ⽽⾔,只有⼀个boot引脚,配合BOOT_ADD0/BOOT_ADD1 的组合配置,可以让系统从两个不同的区域启动;
boot引脚启动地址默认值默认值启动⽅式事项
0BOOT_ADD0[15:0] 0x0800Flash启动启动地址⾼16位由BOOT_ADD0寄存器决定,低16位为0x0000;
BOOT_ADD0寄存器可以修改,修改后掉电不丢失;
0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置;
1BOOT_ADD1[15:0] 0x1FF0系统存储器启动
2 startup_stm32h743xx.s的堆栈
  2.1 堆栈的汇编程序
1;********************** ⽬的是分配数据段⽤来做"栈"**********************
2; 表⽰即将声明数据段STACK⽤来做"栈",不⽤填⼊初始数据,可读写,⾸地址按照2^3对齐(8字节对齐);
3 Stack_Size      EQU    0x00001000
4                AREA    STACK, NOINIT, READWRITE, ALIGN=3
5 Stack_Mem      SPACE  Stack_Size
6 __initial_sp
7
8; **********************⽬的是分配数据段⽤来做"堆"**********************
9 Heap_Size      EQU    0x0000800
10                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
11 __heap_base
12 Heap_Mem        SPACE  Heap_Size
13 __heap_limit
  2.2 那么什么是堆栈呢?总结了⼀下,⼤概概括成了下⾯的表格形式;
主要⽤来存储局部变量,变量的内存块;栈空间是从⾼地址开始向低地址⽣长的;
STACK 栈
对于多级调⽤的函数⼊⼝地址,需要使⽤栈空间配合连接寄存器来存储主调函数的地址;
Stack_Mem表⽰栈的⾸地址,应该是给microLIB库使⽤的;
__initial_sp栈标号,表⽰栈顶地址;也就是栈的内存最⼤地址;
动态分配时使⽤的内存块;堆空间是从低地址向⾼地址⽣长的;
HEAP 堆
如果程序中没有使⽤到动态内存分配的话,编译器是不会编译出"堆"空间的;
__heap_base 堆标号,表⽰"堆"的起始地址
Heap_Mem  表⽰堆的⾸地址,应该是给microLIB库使⽤的;
__heap_limit  堆标号,表⽰"堆"的结束地址
  2.3 ⾄于堆栈标号Stack_Mem和Stack_Size⼤概是标识堆栈地址给microLIB库使⽤的把,应该没什么⽤;如下所⽰
;MDK针对嵌⼊式推出了microLIB⼩型库,在功能上是⽤来替代C标准库的;
;下⾯代码的功能主要是决定要不要启⽤microLIB库;以下代码⽐较不重要;
IF      :DEF:__MICROLIB
EXPORT  __initial_sp
EXPORT  __heap_base
EXPORT  __heap_limit
ELSE
IMPORT  __use_two_region_memory
EXPORT  __user_initial_stackheap
__user_initial_stackheap
LDR    R0, =  Heap_Mem
LDR    R1, =(Stack_Mem + Stack_Size)
LDR    R2, = (Heap_Mem +  Heap_Size)
LDR    R3, = Stack_Mem
BX      LR
ALIGN
ENDIF
3 startup_stm32h743xx.s的中断向量表
;**************中断向量表*****************************************************************
AREA    RESET, DATA, READONLY  ; 即将声明⼀个内存区域RESET,是数据段,只读;
EXPORT  __Vectors              ; 声明"标识__Vectors"可以被外部⽂件调⽤
EXPORT  __Vectors_End          ; 声明"标识__Vectors_End"可以被外部⽂件调⽤
EXPORT  __Vectors_Size          ; 声明"标识__Vectors_Size"可以被外部⽂件调⽤
__Vectors      DCD    __initial_sp            ; 分配4字节内存,初始化为栈顶地址,该内存开始的地址标识为"__Vectors"
DCD    Reset_Handler          ; 分配4字节内存,放⼊复位中断服务函数的"地址标识Reset Handler"
DCD    NMI_Handler            ;
;.....
DCD    0;
DCD    WAKEUP_PIN_IRQHandler  ;
__Vectors_End                                  ; 内存结束的地址标识为"__Vectors_End"
__Vectors_Size  EQU  __Vectors_End - __Vectors  ; 声明标识"__Vectors_Size"⽤来表⽰中断向量表的⼤⼩
;**************汇编代码段:上电复位中断服务程序举例***************************************************************
AREA    |.text|, CODE, READONLY ; 即将声明⼀个内存区域.text,是代码段,只读;
Reset_Handler    PROC                          ;伪指令PROC和ENDP⽤来声明⼀个程序,当前程序段标识为"Reset_Handler"
EXPORT  Reset_Handler  [WEAK] ;声明当前程序可被外部函数调⽤,为弱函数
IMPORT  SystemInit                      ;引⼊⽂件外部声明的函数SystemInit()
IMPORT  __main                          ;引⼊⽂件外部声明的函数__main()
LDR    R0, =SystemInit        ;将32bit函数⼊⼝地址放⼊R0寄存器中?
BLX    R0                    ;跳转到R0寄存器内的地址去执⾏?
LDR    R0, =__main            ;__main为编译器⾃动⽣成的函数,主要是解析代码段table的数据,然后跳转到main函数执⾏,
BX      R0                    ;给需要初始化的变量和不需要初始化的变量分配堆栈运⾏空间;
ENDP                          ;
;**************汇编代码段:NMI中断服务程序举例***************************************************************
NMI_Handler    PROC                            ;
EXPORT  NMI_Handler    [WEAK]  ;这⾥导⼊了中断服务程序,如果外部⽂件写了,那此处的弱函数就不使⽤了
B      .                      ;B. 表⽰在这⾥跳转当前程序? 就是跳转到前⾯导⼊的程序
ENDP                            ;
  3.1 伪指令
    相当于预处理器指令,编译器在编译时会进⾏替换,并不占⽤内存空间;
EQU声明常数,汇编的常数单位不是bit,⽽是byte
AREA表⽰即将分配⼀个数据段或代码段;需要跟分配内存的SPACE指令⼀起使⽤;
EXPORT声明为可被外部⽂件引⽤,主要提供给链接器⽤于连接库⽂件;
IMPORT引⼊外部⽂件声明
WEAK弱声明,如果外部⽂件有相同的声明,则编译外部⽂件的声明,不编译弱声明
ALIGN声明编译器需要对指令或数据的存放地址进⾏对齐;默认缺省值为32bit对齐
PRESERVE8当前⽂件的堆栈需按照8字节对齐,也就是64bit数据线对齐;
THUMB表⽰接下来的指令都是thumb指令集,cm3,cm7采⽤的是thumb-2指令集,
  3.2 汇编指令
SPACE分配⼀段内存空间,并初始化这些内存空间为0;需要跟声明内存属性的AREA伪指令⼀起使⽤;
DCD Define Constant Double-words;分配4字节的内存空间⽤来存储⼀个32bit的数据;并初始化这些内存空间;
LDR Load word;加载指令,将32bit数据存⼊⽬的寄存器中;
B  Branch,跳转到⽬标地址执⾏,执⾏指令集为ARM指令集
BX Branch with Exchange;跳转到⽬的地址执⾏,并且指令集从ARM指令集切换到thumb指令集;
BLX Branch with Link and Exchange;thumb-2兼容许多thumb指令,但是cortex-m3不⽀持当前指令;
  3.3 注释
零散1[地址]:地址加了[],⽤来表⽰地址内的数据;
零散2汇编指令中的常数都是地址,⾃然数的格式为#常数;
4 main函数的初始化
  在配置外设之前,单⽚机⾸先还需要配置⼀下⼯作环境;主要涉及到以下⼏个c⽂件的函数;具体的
话也不要求精确分析,轮廓了解⼀下;
  4.1 stm32h7xx_hal.c
    主要是IO电压的配置函数,boot引脚的启动配置,以及systick的hal库函数;
/* stm32h7xx_hal.h: line528: Peripheral Control functions  systick使⽤和配置,电压配置,boot配置等******************/
void HAL_IncTick(void);                        //SysTick_Handler()中断服务函数调⽤的函数,装载systick为1ms(HAL_TICK_FREQ_1KHZ),
void HAL_Delay(__IO uint32_t Delay);          //systick时钟延时,单位ms;
uint32_t HAL_GetTick(void);
uint32_t HAL_GetTickPrio(void);
HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq);
HAL_TickFreqTypeDef HAL_GetTickFreq(void);
void HAL_SuspendTick(void);                    //挂起systick
void HAL_ResumeTick(void);                    //恢复systick
void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling);    //line575:配置IO⼝输出的电压范围
  4.2 stm32h7xx_hal_rcc.c
    主要是rcc相关的晶振使能,以及总线时钟的配置;以及⼀些查看配置参数的函数;
/*stm32h7xx_hal_rc.h    line2814*/
void HAL_RCC_DeInit(void);//复位RCC默认配置;使⽤HSI;关闭HSE,PLL;根据时钟树可知外设及总线为HSI-64Mhz
HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);//配置晶振是否使能,以及PLL1不为sysclk时PLL1的参数配置;
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);//配置CPU(sysclk),AHB,APB..分频系数;
  4.3 stm32h7xx_hal_cortex.c
    主要是cortex内核的外设NVIC和MPU的配置函数;以及⼀些systick的使⽤函数;之后再分析内核外设的时候⼀起讲把;
    不知道为什么在stm32h7xx_hal.c已经有systick的函数了,这⾥⼜有,没有将他们放⼀个⽂件⾥;先留着疑问;
5 ⼩结
  1)对于启动配置⽽⾔⼀直以来只使⽤过flash启动;感觉其他的具体也没试过,等需要的时候看看安富莱的视频把;
  之前由于代码配置错误导致芯⽚不能flash下载的时候,试过把boot引脚切换到ISP下载模式,然后使⽤SWD下载;
  当然下载之后是跑不起来的,然后再重新把boot引脚改为flash启动,此时芯⽚可以重新flash下载;
  2)对于堆栈,注意⼀下,我们平常使⽤的局部数组什么的不要超出栈的内存⼤⼩,超出的话程序卡死,然后就跑不动了;
  平常说的push和pop应该只针对栈⽽⾔;如果堆也能push和pop,针对⼀整块动态内存,怎么搞呢?从低地址到⾼地址搞;
  3)⾄于中断向量表,主要是从偏移量0地址处开始依次分配内存,⽤来存储各种中断服务程序⼊⼝地址;
  ⼀些内核级别的中断程序的弱声明,这些程序除了Reset_Handler中断的弱函数执⾏了操作外,其他的都是循坏⾃⼰,使⽤时需要在⽤户程序中重新编写;
   后⾯还有外设中断服务程序的弱声明,这些外设中断服务程序执⾏都是不断递归循坏⾃⼰;使⽤时需要在⽤户程序中重新编写;
  4)启动⽂件本质来说,除了分配中断向量表空间之外,只主动执⾏了⼀个Reset_Handler⼀个函数,
   Reset_Handler函数跳转到system_init函数执⾏,然后跳转到MDK的__main函数执⾏;其他的函数都是弱声明,不断递归调⽤⾃⼰,使⽤的话需要重新写函数;

本文发布于:2024-09-23 19:25:43,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/4/95048.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:地址   配置   启动
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议