[其他ST产品] stm32系列启动文件解读(KEIL编译环境)

[复制链接]
 楼主| 在水一方00 发表于 2023-7-30 02:27 | 显示全部楼层 |阅读模式
基于stm32f103c8t6芯片的启动文件进行分析。

启动文件在嵌入式芯片开发是必不可少的,其文件后缀是.s,通常需要加入工程参与编译。它的的用包括一下几点:

开辟栈、堆的空间。
初始化中断向量表。
调用外部SystemInit函数,初始化时钟。
调用C库函数__main初始化用户栈,调用main函数转到C世界。
启动文件使用汇编语言编写,如果熟悉汇编语言,那么很容易理解它;如果不熟悉汇编语言,针对启动文件里用到的汇编指令,下面会一一介绍。

 楼主| 在水一方00 发表于 2023-7-30 02:32 | 显示全部楼层
1.启动文件使用到的汇编指令

8817564c55b4047ca6.png
 楼主| 在水一方00 发表于 2023-7-30 02:55 | 显示全部楼层
2.启动文件代码分析
  1. Stack_Size      EQU     0x00000400
  2.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3
  3. Stack_Mem       SPACE   Stack_Size
  4. __initial_sp
 楼主| 在水一方00 发表于 2023-7-30 02:57 | 显示全部楼层
上面代码定义了栈的大小为0x00000400(1KB),名称为STACK,NOINIT表示不初始化,REAWRITE表示可读可下,ALIGN = 3表示按照8(即2^3)字节对齐。还定义了标号__initial_sp(栈的结束地址)。利用SPACE关键字来申请空间,大小为Stack_Size,记作Stack_Mem。栈是由高向低生长的。

栈的空间用于局部变量、函数调用、函数形参等开销,栈的大小不能超过芯片内部SRAM大小;如果编写的程序局部变量比较多,占用内存比较大,会造成栈溢出,导致进入硬故障,这时候需要考虑增加栈空间大小

  1. Heap_Size       EQU     0x00000200
  2.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
  3. __heap_base

  4. Heap_Mem        SPACE   Heap_Size
  5. __heap_limit
 楼主| 在水一方00 发表于 2023-7-30 02:57 | 显示全部楼层
上面的代码定义了堆大小为0x00000200(512字节),名称为HEAP,NOINIT表示不初始化,READWRITE表示可读可写,ALIGN = 3表示按照8(即2^3)字节对齐。还定义了标号:__heap_base(堆的起始地址)、__heap_limit(堆的结束地址)。利用SPACE关键字来申请空间,大小为Heap_Size,记作Heap_Mem。堆是由低向高生长的,与栈的生长方向相反。

       堆的主要用于动态内存的分配,像malloc()函数申请的内存就在堆中。这个在STM32里面用得比较少。
  1.                 PRESERVE8
  2.                 THUMB
 楼主| 在水一方00 发表于 2023-7-30 03:01 | 显示全部楼层
上面两个在前面的指令表格里已经说明。
  1. ; Vector Table Mapped to Address 0 at Reset

  2.                 AREA    RESET, DATA, READONLY
  3.                 EXPORT  __Vectors
  4.                 EXPORT  __Vectors_End
  5.                 EXPORT  __Vectors_Size
 楼主| 在水一方00 发表于 2023-7-30 03:01 | 显示全部楼层
上面代码定义了一个数据段,名称为RESET,仅可读,还声明了3个外部文件可以使用的标号:__Vectors(向量表起始地址)、__Vectors_End(向量表结束地址)、__Vectors_Size(向量表大小)。

向量表其实就是一个WORD(32bit整数)数组,每一个下标对应一种异常,该下标元素的值则是该ESR的入口地址。向量表在地址空间的位置是可以被设置的,同过NVIC中的一个重定位寄存器来指定向量表的地址。复位之后,该寄存器的值为0。因此,在地址0(即flash地址0)处必须包含一张向量表,用于初始化时的异常分配。
 楼主| 在水一方00 发表于 2023-7-30 03:02 | 显示全部楼层
接下来的代码就是分配向量表的内存
  1. __Vectors       DCD     __initial_sp               ; Top of Stack(栈顶地址)
  2.                 DCD     Reset_Handler              ; Reset Handler(复位程序地址)
  3.                 DCD     NMI_Handler                ; NMI Handler
  4.                 DCD     HardFault_Handler          ; Hard Fault Handler
  5.                 DCD     MemManage_Handler          ; MPU Fault Handler
  6.                 DCD     BusFault_Handler           ; Bus Fault Handler
  7.                 DCD     UsageFault_Handler         ; Usage Fault Handler
  8.                 DCD     0                          ; Reserved(保留)
  9.                 DCD     0                          ; Reserved
  10.                 DCD     0                          ; Reserved
  11.                 DCD     0                          ; Reserved
  12.                 DCD     SVC_Handler                ; SVCall Handler
  13.                 DCD     DebugMon_Handler           ; Debug Monitor Handler
  14.                 DCD     0                          ; Reserved
  15.                 DCD     PendSV_Handler             ; PendSV Handler
  16.                 DCD     SysTick_Handler            ; SysTick Handler

  17.                 ; External Interrupts

  18.                 DCD     WWDG_IRQHandler            ; Window Watchdog
  19.                 DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
  20.                 DCD     TAMPER_IRQHandler          ; Tamper
  21.                 ;(中间代码省略)

  22.                 DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
  23.                 DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI Line
  24.                 DCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend
  25. __Vectors_End

  26. __Vectors_Size  EQU  __Vectors_End - __Vectors
 楼主| 在水一方00 发表于 2023-7-30 03:02 | 显示全部楼层
上面代码利用DCD关键字以4字节对齐,分配了一堆内存,类似之前SPACE关键字的作用。利用__Vectors_End(堆结束地址)减去__Vectors(堆的起始地址)得到堆的大小__Vectors_Size。

       向量表从flash的0地址开始放置,以4字节为一个单位,地址0存放的时栈顶地址,接着0x04存放的时复位程序的地址,以此类推。向量表中存放的都是中断函数的函数名,可我们知道C语言中函数名就是一个地址。
  1.         AREA    |.text|, CODE, READONLY
 楼主| 在水一方00 发表于 2023-7-30 03:03 | 显示全部楼层
上面代码表示是定义一个名称为.text的代码段,仅可读。
  1.        ; Reset handler
  2. Reset_Handler    PROC
  3.                  EXPORT  Reset_Handler             [WEAK]
  4.           IMPORT  __main
  5.           IMPORT  SystemInit

  6.                  LDR     R0, =SystemInit
  7.                  BLX     R0
  8.                  LDR     R0, =__main
  9.                  BX      R0
  10.                  ENDP
 楼主| 在水一方00 发表于 2023-7-30 03:04 | 显示全部楼层
上面代码利用DCD关键字以4字节对齐,分配了一堆内存,类似之前SPACE关键字的作用。利用__Vectors_End(堆结束地址)减去__Vectors(堆的起始地址)得到堆的大小__Vectors_Size。

       向量表从flash的0地址开始放置,以4字节为一个单位,地址0存放的时栈顶地址,接着0x04存放的时复位程序的地址,以此类推。向量表中存放的都是中断函数的函数名,可我们知道C语言中函数名就是一个地址。
  1.         AREA    |.text|, CODE, READONLY
 楼主| 在水一方00 发表于 2023-7-30 03:04 | 显示全部楼层
   上面代码表示是定义一个名称为.text的代码段,仅可读。

  1.        ; Reset handler
  2. Reset_Handler    PROC
  3.                  EXPORT  Reset_Handler             [WEAK]
  4.           IMPORT  __main
  5.           IMPORT  SystemInit

  6.                  LDR     R0, =SystemInit
  7.                  BLX     R0
  8.                  LDR     R0, =__main
  9.                  BX      R0
  10.                  ENDP
 楼主| 在水一方00 发表于 2023-7-30 03:07 | 显示全部楼层
   上面代码表示是定义一个名称为.text的代码段,仅可读。

  1.        ; Reset handler
  2. Reset_Handler    PROC
  3.                  EXPORT  Reset_Handler             [WEAK]
  4.           IMPORT  __main
  5.           IMPORT  SystemInit

  6.                  LDR     R0, =SystemInit
  7.                  BLX     R0
  8.                  LDR     R0, =__main
  9.                  BX      R0
  10.                  ENDP
 楼主| 在水一方00 发表于 2023-7-30 03:09 | 显示全部楼层
   复位子程序是系统上电第一个执行的程序,调用SystemInit函数(sysyem_stm32f4xx.c文件里定义的)初始化系统时钟等,然后调用C库函数_main(编译器自带的),在_main函数里最终会调用main函数转到C的世界。

       LDR、BLX、BX是CM4内核的指令:LDR:从储存器中加载一个字到寄存器中。

BL:跳转到由寄存器/标号给出的地址,并把跳转前的下一条指令保存到LR中。

BLX:跳转到由寄存器/标号给出的地址,并根据寄存器的LSE确定处理器的状态,并把跳转前的下一条指令保存到LR中。

BX:跳转到有寄存器/标号给出的地址,不用返回。
 楼主| 在水一方00 发表于 2023-7-30 03:09 | 显示全部楼层
接下来的代码就是中断服务程序的实现,一般来说,我们会在外部的c文件实现中断函数,这里定义了只是备用,以防我们把某个中断使能了,但忘了实现它的中断函数或者函数名写错,那么系统就会执行下面的程序,并且下面的中断函数会进入无限循环,程序也就死在这里。
 楼主| 在水一方00 发表于 2023-7-30 03:10 | 显示全部楼层
复位子程序是系统上电第一个执行的程序,调用SystemInit函数(sysyem_stm32f4xx.c文件里定义的)初始化系统时钟等,然后调用C库函数_main(编译器自带的),在_main函数里最终会调用main函数转到C的世界。

       LDR、BLX、BX是CM4内核的指令:

LDR:从储存器中加载一个字到寄存器中。

BL:跳转到由寄存器/标号给出的地址,并把跳转前的下一条指令保存到LR中。

BLX:跳转到由寄存器/标号给出的地址,并根据寄存器的LSE确定处理器的状态,并把跳转前的下一条指令保存到LR中。

BX:跳转到有寄存器/标号给出的地址,不用返回。
 楼主| 在水一方00 发表于 2023-7-30 03:10 | 显示全部楼层
接下来的代码就是中断服务程序的实现,一般来说,我们会在外部的c文件实现中断函数,这里定义了只是备用,以防我们把某个中断使能了,但忘了实现它的中断函数或者函数名写错,那么系统就会执行下面的程序,并且下面的中断函数会进入无限循环,程序也就死在这里。
 楼主| 在水一方00 发表于 2023-7-30 03:11 | 显示全部楼层
  1. NMI_Handler     PROC    ;不可屏蔽的系统异常中断
  2.                 EXPORT  NMI_Handler                [WEAK]
  3.                 B       .
  4.                 ENDP

  5. HardFault_Handler\             ;所有类型错误的中断
  6.                 PROC
  7.                 EXPORT  HardFault_Handler          [WEAK]
  8.                 B       .
  9.                 ENDP

  10. (省略一部分代码)

  11. SysTick_Handler PROC        ;系统滴答定时器中断
  12.                 EXPORT  SysTick_Handler            [WEAK]
  13.                 B       .
  14.                 ENDP

  15. Default_Handler PROC        ;外设中断

  16.                 EXPORT  WWDG_IRQHandler            [WEAK]
  17.                 EXPORT  PVD_IRQHandler             [WEAK]
  18.                 EXPORT  TAMPER_IRQHandler          [WEAK]
  19. (省略一部分代码)

  20. RTCAlarm_IRQHandler

  21. USBWakeUp_IRQHandler
  22.                 B       .
  23.                 ENDP
  24.                 ALIGN
 楼主| 在水一方00 发表于 2023-7-30 03:11 | 显示全部楼层
B:跳转到一个标号,这里跳转到一个‘.’,表示无限循环。
  1. ;*******************************************************************************

  2. ; User Stack and Heap initialization

  3. ;*******************************************************************************

  4.                  IF      :DEF:__MICROLIB  ;这个宏在KEIL里面开启                     
  5.                  EXPORT  __initial_sp
  6.                  EXPORT  __heap_base
  7.                  EXPORT  __heap_limit            
  8.                  ELSE               
  9.                  IMPORT  __use_two_region_memory     ;这个函数由用户自己实现
  10.                  EXPORT  __user_initial_stackheap         
  11. __user_initial_stackheap

  12.                  LDR     R0, =  Heap_Mem
  13.                  LDR     R1, =(Stack_Mem + Stack_Size)
  14.                  LDR     R2, = (Heap_Mem +  Heap_Size)
  15.                  LDR     R3, = Stack_Mem
  16.                  BX      LR

  17.                  ALIGN
  18.                  ENDIF
  19.                  END
 楼主| 在水一方00 发表于 2023-7-30 03:11 | 显示全部楼层
如果勾选【Options for Target】->【Target】->【Use MicroLIB】,那么就会定义宏__MICROLOB。

如果定义了__MICROLOB,那么把__initial_sp(栈起始地址)、__heap_base(堆开始地址)、__heap_limit(堆结束地址)标号赋予全局属性,那么在外部文件也可以使用,然后堆和栈的初始化就由C库函数__main来完成。

如果没有定义__MICROLOB,采用双段存储器模式,且声明标号__user_initial_stackheap具有全局性,让用户自己来初始化堆和栈。KEIL C库函数会调用__user_initial_stackheap,通过R0~R3将堆栈以参数形式传递给KEIL C库。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

51

主题

581

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部

51

主题

581

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部