zoukankan      html  css  js  c++  java
  • 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引脚 启动地址 默认值 默认值启动方式 事项
    0 BOOT_ADD0[15:0]  0x0800 Flash启动

    启动地址高16位由BOOT_ADD0寄存器决定,低16位为0x0000;

    BOOT_ADD0寄存器可以修改,修改后掉电不丢失;

    0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置;

    1 BOOT_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函数执行;其他的函数都是弱声明,不断递归调用自己,使用的话需要重新写函数;

  • 相关阅读:
    WPF中实现Button.Content变化的简易动画
    WPF中ListBox的项ListBoxItem被选中的时候Background变化
    GD库处理图像
    PHP上传文件示例
    会话控制:Cookie和session
    PHP表单与验证
    PHP日期与时间
    PHP正则表达式
    PHP字符串处理
    目录与文件操作
  • 原文地址:https://www.cnblogs.com/caesura-k/p/13639279.html
Copyright © 2011-2022 走看看