ARM Cortex-M底层技术(2)—启动代码详解

杂谈

  工作了一天,脑袋比较乱。一直想把底层的知识写成一个系列,希望可以坚持下去。为什么要写底层的东西呢?首先,工作用到了这部分内容,最近和内部Flash打交道比较多,自然而然会接触到一些底层的东西;第二,近些年来Cortex-M阵营各厂商(ST、Nordic、ATMEL……)对新产品的迭代速度越来越快,以及微控制器应用普及程度的加深,越来越多的开发者把更多精力投注在应用层开发上,花在对底层技术上的时间越来越少,更深层次的原因是走嵌入式底层没有做互联网上层赚钱。希望自己可以把嵌入式ARM Cortex-M(M0/M0+/M3/M4/M7/M23/M33)底层技术写下去,加油!(长文,慎入,谢谢)

一. STM32的启动代码分析
  当前,STM32因其丰富的学习资料,
已经成为了80%嵌入式工作者入门学习的首选,当然我也不例外,主要是因为在学生时代,没钱买更好的开发板。工作之后,你会发现老板更抠门,产品的核心芯片一代比一代便宜。废话不多说,直接上知识点。

1.1 启动代码的概念

 问题1. 什么是启动代码

   启动代码是系统上电或者复位后运行的第一段代码,是进入C 语言的main 函数之前需要执行的那段汇编代码。或者说用户程序运行之前对系统硬件及软件环境进行必要的初始化并在最后使程序跳转到用户程序。

 问题2. 启动代码主要干了什么

  启动代码直接面对ARM 处理器内核及硬件控制器进行编程,所执行的操作与具体的目标系统紧密相关。C语言程序的运行需要具备一定的条件,如分配好外部数据空间、设置初始堆栈指针、配置时钟、设置中断向量入口、设置初始程序计数器(指向main())等。对于 Cortex-M系列的芯片而言,启动代码大同小异,故我挑选其中一个进行分析。ARM Cortex-M系列MCU的启动代码的主要做3件事:

  • 初始化并正确放置异常/中断向量表;
  •  分散加载;
  • 初始化C语言运行环境(初始化堆栈以及C Library、浮点等)

1.2 启动代码详解

 汇编基础1:
  1. 伪指令EQU
  语法格式:名称  EQU  表达式{,类型}
  EQU 伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于 C 语言的#define,所以这下能理解了吧
  

  2. 伪指令AREA
  语法格式: AREA 段名{, 属性 1}{, 属性 2}……
  AREA 命令指示汇编程序汇编一个新的代码段或数据段。理解:段是独立的、指定的、不可见的代码或数据块,它们由链接程序处理。
  段名: 可以为段选择任何段名。但是,以一个数字开始的名称必须包含在竖杠号内,否则会产生一个缺失段名错误。例如, |1_DataArea|
  有些名称是习惯性的名称。例如: |.text|用于表示由 C 编译程序产生的代码段,或用于以某种方式与 库关联的代码段。
  属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。 常用的属性如下:
——CODE 属性:用于定义代码段,默认为 READONLY
——DATA 属性:用于定义数据段,默认为 READWRITE
——READONLY 属性:指定本段为只读,代码段默认为 READONLY
——READWRITE 属性:指定本段为可读可写,数据段的默认属性为 READWRITE
——ALIGN 属性:使用方式为 ALIGN 表达式。在默认时, ELF(可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为 0~31,相应的对齐方式为 表达式次方。

     如:ALIGN=3表示8字节对齐。

——NOINIT 属性: 表示数据段是未初始化的或初始化为零。


一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。


  3. 伪指令SPACE 用于分配一片连续的存储单元

第一部分 定义栈段,不初始化 

1 Stack_Size      EQU     0x00000400
2 
3                 AREA    STACK, NOINIT, READWRITE, ALIGN=3
4 Stack_Mem       SPACE   Stack_Size
5 __initial_sp

  上面的程序这样理解,定义了一个栈,栈名为STACK (AREA  STACK),大小为Stack_Size(EQU理解为#define),只分配空间不做初始化或者初始化为 0NOINIT,可读可写READWRITE;8 字节对齐: ALIGN3;栈顶地址: __initial_sp ,SPACE表示分配一块连续的区域。

第二部分 定义堆段,不初始化 

1 Heap_Size       EQU     0x00000200
2 
3                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
4 __heap_base
5 Heap_Mem        SPACE   Heap_Size
6 __heap_limit

堆名: HEAP
大小: Heap_Size
只分配空间不做初始化或者初始化为 0NOINIT
可读可写: READWRITE
8 字节对齐: ALIGN3
堆起始地址: __heap_base
堆终止地址: __heap_limit

1 PRESERVE8 ;指示编译器 8 字节对齐(keil 编译器时需要加上)
2 THUMB ;指示编译器为 THUMB 指令

 汇编基础2:

4. 伪指令EXPORT

语法格式: EXPORT 标号{[WEAK]}
EXPORT 伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用
EXPORT 可用 GLOBAL 代替。标号在程序中区分大小写, [WEAK]选项声明其他的同名标号优先于该标号被引用。

5. 伪指令: DCD
语法格式: DCD  表达式
DCD(或 DCDU) 伪指令用于分配一个或多个连续的字(32bit)存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。用 DCD 分配的字存储单元是字对齐的。(一片是指多少?我并没有查到相关资料,但是我看了公司大神们写的启动文件,备注的地址只占了4个字节,所以我理解成分配一个字的存储单元)

 第三部分  定义复位段(中断向量表),并初始化

 1                 AREA    RESET, DATA, READONLY
 2                 EXPORT  __Vectors
 3                 EXPORT  __Vectors_End
 4                 EXPORT  __Vectors_Size
 5 
 6 __Vectors       DCD     __initial_sp               ; Top of Stack
 7                 DCD     Reset_Handler              ; Reset Handler
 8                 DCD     NMI_Handler                ; NMI Handler
 9                 DCD     HardFault_Handler          ; Hard Fault Handler
10                 DCD     MemManage_Handler          ; MPU Fault Handler
11                 DCD     BusFault_Handler           ; Bus Fault Handler
12                 DCD     UsageFault_Handler         ; Usage Fault Handler
13                 DCD     0                          ; Reserved
14                 DCD     0                          ; Reserved
15                 DCD     0                          ; Reserved
16                 DCD     0                          ; Reserved
17                 DCD     SVC_Handler                ; SVCall Handler
18                 DCD     DebugMon_Handler           ; Debug Monitor Handler
19                 DCD     0                          ; Reserved
20                 DCD     PendSV_Handler             ; PendSV Handler
21                 DCD     SysTick_Handler            ; SysTick Handler
22 
23                 ; External Interrupts
24                 DCD     WWDG_IRQHandler            ; Window Watchdog
25                 DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
26                 DCD     TAMPER_IRQHandler          ; Tamper
27                 DCD     RTC_IRQHandler             ; RTC
28                 DCD     FLASH_IRQHandler           ; Flash
29                 DCD     RCC_IRQHandler             ; RCC
30                 DCD     EXTI0_IRQHandler           ; EXTI Line 0
31                 DCD     EXTI1_IRQHandler           ; EXTI Line 1
32                 DCD     EXTI2_IRQHandler           ; EXTI Line 2
33                 DCD     EXTI3_IRQHandler           ; EXTI Line 3
34                 DCD     EXTI4_IRQHandler           ; EXTI Line 4
35                 DCD     DMA1_Channel1_IRQHandler   ; DMA1 Channel 1
36                 DCD     DMA1_Channel2_IRQHandler   ; DMA1 Channel 2
37                 DCD     DMA1_Channel3_IRQHandler   ; DMA1 Channel 3
38                 DCD     DMA1_Channel4_IRQHandler   ; DMA1 Channel 4
39                 DCD     DMA1_Channel5_IRQHandler   ; DMA1 Channel 5
40                 DCD     DMA1_Channel6_IRQHandler   ; DMA1 Channel 6
41                 DCD     DMA1_Channel7_IRQHandler   ; DMA1 Channel 7
42                 DCD     ADC1_2_IRQHandler          ; ADC1 & ADC2
43                 DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TX
44                 DCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0
45                 DCD     CAN1_RX1_IRQHandler        ; CAN1 RX1
46                 DCD     CAN1_SCE_IRQHandler        ; CAN1 SCE
47                 DCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5
48                 DCD     TIM1_BRK_IRQHandler        ; TIM1 Break
49                 DCD     TIM1_UP_IRQHandler         ; TIM1 Update
50                 DCD     TIM1_TRG_COM_IRQHandler    ; TIM1 Trigger and Commutation
51                 DCD     TIM1_CC_IRQHandler         ; TIM1 Capture Compare
52                 DCD     TIM2_IRQHandler            ; TIM2
53                 DCD     TIM3_IRQHandler            ; TIM3
54                 DCD     TIM4_IRQHandler            ; TIM4
55                 DCD     I2C1_EV_IRQHandler         ; I2C1 Event
56                 DCD     I2C1_ER_IRQHandler         ; I2C1 Error
57                 DCD     I2C2_EV_IRQHandler         ; I2C2 Event
58                 DCD     I2C2_ER_IRQHandler         ; I2C2 Error
59                 DCD     SPI1_IRQHandler            ; SPI1
60                 DCD     SPI2_IRQHandler            ; SPI2
61                 DCD     USART1_IRQHandler          ; USART1
62                 DCD     USART2_IRQHandler          ; USART2
63                 DCD     USART3_IRQHandler          ; USART3
64                 DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
65                 DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI Line
66                 DCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend
67                 DCD     TIM8_BRK_IRQHandler        ; TIM8 Break
68                 DCD     TIM8_UP_IRQHandler         ; TIM8 Update
69                 DCD     TIM8_TRG_COM_IRQHandler    ; TIM8 Trigger and Commutation
70                 DCD     TIM8_CC_IRQHandler         ; TIM8 Capture Compare
71                 DCD     ADC3_IRQHandler            ; ADC3
72                 DCD     FSMC_IRQHandler            ; FSMC
73                 DCD     SDIO_IRQHandler            ; SDIO
74                 DCD     TIM5_IRQHandler            ; TIM5
75                 DCD     SPI3_IRQHandler            ; SPI3
76                 DCD     UART4_IRQHandler           ; UART4
77                 DCD     UART5_IRQHandler           ; UART5
78                 DCD     TIM6_IRQHandler            ; TIM6
79                 DCD     TIM7_IRQHandler            ; TIM7
80                 DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
81                 DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
82                 DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
83                 DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
84 __Vectors_End
85 
86 __Vectors_Size  EQU  __Vectors_End - __Vectors

段名: RESET(根据前面的套路,发现AREA的第一个属性表示段名)
大小: __Vectors_Size(大小肯定要表示出来,其实堆栈是直接给出的,RESET段是先分配,后计算得到的)
数据段: DATA
只读: READONLY
按字节对齐: 默认 ALIGN
向量表起始地址: __Vectors(标号)
向量表终止地址: __Vectors_End

注意:SPACE 和 DCD有什么区别?

1. SPACE和DCD的功能类似,SPACE申请一片内存空间,DCD申请一个或多个字(32bit)的内存空间。
2. SPACE和DCD的区别在于,SPACE申请空间但不赋初值,DCD申请一个字的空间,并赋初值。

参考资料:https://blog.csdn.net/inurlcn/article/details/20691233#reply

 汇编基础3:

  6. 过程定义伪指令: PROC、 ENDP
  语法格式: <过程名> PROC [类型]
        ……
        RET
        <过程名> ENDP
  过程就是子程序,即定义一个子程序。一个过程可以被其它程序所调用(用 CALL 指令),过程的最后一条指令一般是返回指令(RET)。

  7. 伪指令: IMPORT
  语法格式: IMPORT 标号 {[WEAK]}
  IMPORT 伪指令用于通知编译器要使用的标号在其他源文件中定义。

   [WEAK]选项表示弱定义,如果编译器发现在别处定义了同名的函数,则在链接时用别处的地址进行链接,如果其它地方没有定义,编译器也不报错,以此处地址进行链接。
  

  8. 伪指令: LDR
  语法格式: LDR{执行条件,如 EQ、 NE 等} register,=expr/label_expr

  大范围的地址读取伪指令 LDR 用于加载 32 位的立即数或一个地址值到指定寄存器,在汇编编译源程序时, LDR 伪指令被编译器替换成一条合适的指令。

  9. Thumb 跳转指令: B、 BL、 BX
  语法格式: B{执行条件,如 EQ、 NE 等} label
  带链接 BL{执行条件,如 EQ、 NE 等} label
  带状态切换 BX{执行条件,如 EQ、 NE 等} label

 1                 AREA    |.text|, CODE, READONLY
 2                 
 3 ; Reset handler
 4 Reset_Handler   PROC
 5                 EXPORT  Reset_Handler             [WEAK]
 6                 IMPORT  __main
 7                 IMPORT  SystemInit
 8                 LDR     R0, =SystemInit
 9                 BLX     R0               
10                 LDR     R0, =__main
11                 BX      R0
12                 ENDP

段名: .text
代码段: CODE
只读: READONLY
按字节对齐: 默认 ALIGN
代码段起始地址: Reset_Handler

更详细的来说一下这段代码,

  这部分可以称作Reset_Handler实体,是芯片上电经过厂商BOOTROM后,用户最开始可控的地方。

  • 第一行,申请一个名为.text的代码段,该代码段的属性是只读的,其他没写,认为认为是默认的;
  • 第三行,注释
  • 第四行,Reset_Handler是标号,定义同一个名为Reset_Handler的子程序(代码段)
  • 第五行,声明一下Reset_Handler程序可以在外部使用,[WEAK]表示没有找到其他地方的定义时,然后连接器使用此处定义的Reset_Handler程序
  • 第六行和第七行,在Reset_Handler函数中导入SystemInit 和__main ,这两个标号在其他文件,在链接的时候需要到其他文件去寻找
  • 第八行,把SystemInit 的地址加载到寄存器R0
  • 第九行,程序跳转到R0 中的地址执行程序,如果在SystemInit中配置了时钟,之后系统的时钟就被设置成我们配置的了。
  • 第十行把_main 的地址加载到寄存器R0。
  • 第十一行程序跳转到R0 中的地址执行程序,执行完毕之后就去到我们熟知的C 世界。

  • 第十二行表示子程序的结束。

   因为默认的标准的启动代码主要工作是在Reset_Handler里面完成的,调用函数一般也会再这里。我们可以发现,在启动代码的汇编语言里调用C语言函数都可以使用以上两步:

  1. 导入函数标号
  2. 调用这个函数

  例如:

1 IMPORT  SystemInit        ;导入函数标号
2 LDR     R0, =SystemInit     ;2行和3行合起来,是调用函数的功能
3 BLX     R0          

当然这里有几点注意事项,这里不是所有函数都可以在汇编语言中调用的,因为此时__main还没有运行,C语言运行环境还没有被完整搭建起来,堆栈也没有初始化完成,所以要注意:

    (1)调用的C函数参数不能超过4个,不用可以,但用的话不能超过4个参数,原因是在Cortex-M体系MCU中,函数的1-4个形参会压进R0-R3这4个通用寄存器(Cortex-M系列MCU,M0也好、M3也好、M4也好都只有16个通用寄存器,内部寄存器结构去参照ARM官方的白皮书)如果有第五个参数,这个参数会被压栈,但因为此时__main还没有运行,堆栈没有被初始化所以此时如果函数有超过4个以上的参数,会导致程序跑飞;

    (2)不要把需要调用的函数写到__main之后,因为没有意义,程序不会跑到那里;

参考网址:https://blog.csdn.net/weixin_39118482/article/details/79632734

 汇编基础4:

  10. 内置变量: {PC} 或“.” 当前指令地址

  11. 汇编语句格式规范:
  ARM 汇编中,所有标号必须在一行的顶格书写,其后面不要添加“:”,但所有指令均不能顶格书写
  ARM 汇编器对标识符大小写敏感,书写标号及指令时字母大小写要一致, 在 ARM 汇编程序中,一个 ARM 指令、伪指令、寄存器名可以全部为大写字母,也可以全部为小写字母,但不要大小写混合使用。

 异常处理函数 1

 1 NMI_Handler     PROC        ;定义一个名为NMI_Handler的子程序
 2                 EXPORT  NMI_Handler                [WEAK]    ;外部声明
 3                 B       .     ;跳转到子程序的地址(这个函数里面通常写的是死循环,所以当出现异常时,就会卡死)
 4                 ENDP      ;结束
 5 HardFault_Handler\
 6                 PROC
 7                 EXPORT  HardFault_Handler          [WEAK]
 8                 B       .
 9                 ENDP
10 MemManage_Handler\
11                 PROC
12                 EXPORT  MemManage_Handler          [WEAK]
13                 B       .
14                 ENDP
15 BusFault_Handler\
16                 PROC
17                 EXPORT  BusFault_Handler           [WEAK]
18                 B       .
19                 ENDP
20 UsageFault_Handler\
21                 PROC
22                 EXPORT  UsageFault_Handler         [WEAK]
23                 B       .
24                 ENDP
25 SVC_Handler     PROC
26                 EXPORT  SVC_Handler                [WEAK]
27                 B       .
28                 ENDP
29 DebugMon_Handler\
30                 PROC
31                 EXPORT  DebugMon_Handler           [WEAK]
32                 B       .
33                 ENDP
34 PendSV_Handler  PROC
35                 EXPORT  PendSV_Handler             [WEAK]
36                 B       .
37                 ENDP
38 SysTick_Handler PROC
39                 EXPORT  SysTick_Handler            [WEAK]
40                 B       .
41                 ENDP

异常处理函数2

这个默认的异常处理函数处理所有外部中断 。

  1 Default_Handler PROC
  2 
  3                 EXPORT  WWDG_IRQHandler            [WEAK]
  4                 EXPORT  PVD_IRQHandler             [WEAK]
  5                 EXPORT  TAMPER_IRQHandler          [WEAK]
  6                 EXPORT  RTC_IRQHandler             [WEAK]
  7                 EXPORT  FLASH_IRQHandler           [WEAK]
  8                 EXPORT  RCC_IRQHandler             [WEAK]
  9                 EXPORT  EXTI0_IRQHandler           [WEAK]
 10                 EXPORT  EXTI1_IRQHandler           [WEAK]
 11                 EXPORT  EXTI2_IRQHandler           [WEAK]
 12                 EXPORT  EXTI3_IRQHandler           [WEAK]
 13                 EXPORT  EXTI4_IRQHandler           [WEAK]
 14                 EXPORT  DMA1_Channel1_IRQHandler   [WEAK]
 15                 EXPORT  DMA1_Channel2_IRQHandler   [WEAK]
 16                 EXPORT  DMA1_Channel3_IRQHandler   [WEAK]
 17                 EXPORT  DMA1_Channel4_IRQHandler   [WEAK]
 18                 EXPORT  DMA1_Channel5_IRQHandler   [WEAK]
 19                 EXPORT  DMA1_Channel6_IRQHandler   [WEAK]
 20                 EXPORT  DMA1_Channel7_IRQHandler   [WEAK]
 21                 EXPORT  ADC1_2_IRQHandler          [WEAK]
 22                 EXPORT  USB_HP_CAN1_TX_IRQHandler  [WEAK]
 23                 EXPORT  USB_LP_CAN1_RX0_IRQHandler [WEAK]
 24                 EXPORT  CAN1_RX1_IRQHandler        [WEAK]
 25                 EXPORT  CAN1_SCE_IRQHandler        [WEAK]
 26                 EXPORT  EXTI9_5_IRQHandler         [WEAK]
 27                 EXPORT  TIM1_BRK_IRQHandler        [WEAK]
 28                 EXPORT  TIM1_UP_IRQHandler         [WEAK]
 29                 EXPORT  TIM1_TRG_COM_IRQHandler    [WEAK]
 30                 EXPORT  TIM1_CC_IRQHandler         [WEAK]
 31                 EXPORT  TIM2_IRQHandler            [WEAK]
 32                 EXPORT  TIM3_IRQHandler            [WEAK]
 33                 EXPORT  TIM4_IRQHandler            [WEAK]
 34                 EXPORT  I2C1_EV_IRQHandler         [WEAK]
 35                 EXPORT  I2C1_ER_IRQHandler         [WEAK]
 36                 EXPORT  I2C2_EV_IRQHandler         [WEAK]
 37                 EXPORT  I2C2_ER_IRQHandler         [WEAK]
 38                 EXPORT  SPI1_IRQHandler            [WEAK]
 39                 EXPORT  SPI2_IRQHandler            [WEAK]
 40                 EXPORT  USART1_IRQHandler          [WEAK]
 41                 EXPORT  USART2_IRQHandler          [WEAK]
 42                 EXPORT  USART3_IRQHandler          [WEAK]
 43                 EXPORT  EXTI15_10_IRQHandler       [WEAK]
 44                 EXPORT  RTCAlarm_IRQHandler        [WEAK]
 45                 EXPORT  USBWakeUp_IRQHandler       [WEAK]
 46                 EXPORT  TIM8_BRK_IRQHandler        [WEAK]
 47                 EXPORT  TIM8_UP_IRQHandler         [WEAK]
 48                 EXPORT  TIM8_TRG_COM_IRQHandler    [WEAK]
 49                 EXPORT  TIM8_CC_IRQHandler         [WEAK]
 50                 EXPORT  ADC3_IRQHandler            [WEAK]
 51                 EXPORT  FSMC_IRQHandler            [WEAK]
 52                 EXPORT  SDIO_IRQHandler            [WEAK]
 53                 EXPORT  TIM5_IRQHandler            [WEAK]
 54                 EXPORT  SPI3_IRQHandler            [WEAK]
 55                 EXPORT  UART4_IRQHandler           [WEAK]
 56                 EXPORT  UART5_IRQHandler           [WEAK]
 57                 EXPORT  TIM6_IRQHandler            [WEAK]
 58                 EXPORT  TIM7_IRQHandler            [WEAK]
 59                 EXPORT  DMA2_Channel1_IRQHandler   [WEAK]
 60                 EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
 61                 EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
 62                 EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]
 63 下面的全部异常处理函数标号都对应同一个地址, 这个地址也是 Default_Handler 的地址 
64 WWDG_IRQHandler 65 PVD_IRQHandler 66 TAMPER_IRQHandler 67 RTC_IRQHandler 68 FLASH_IRQHandler 69 RCC_IRQHandler 70 EXTI0_IRQHandler 71 EXTI1_IRQHandler 72 EXTI2_IRQHandler 73 EXTI3_IRQHandler 74 EXTI4_IRQHandler 75 DMA1_Channel1_IRQHandler 76 DMA1_Channel2_IRQHandler 77 DMA1_Channel3_IRQHandler 78 DMA1_Channel4_IRQHandler 79 DMA1_Channel5_IRQHandler 80 DMA1_Channel6_IRQHandler 81 DMA1_Channel7_IRQHandler 82 ADC1_2_IRQHandler 83 USB_HP_CAN1_TX_IRQHandler 84 USB_LP_CAN1_RX0_IRQHandler 85 CAN1_RX1_IRQHandler 86 CAN1_SCE_IRQHandler 87 EXTI9_5_IRQHandler 88 TIM1_BRK_IRQHandler 89 TIM1_UP_IRQHandler 90 TIM1_TRG_COM_IRQHandler 91 TIM1_CC_IRQHandler 92 TIM2_IRQHandler 93 TIM3_IRQHandler 94 TIM4_IRQHandler 95 I2C1_EV_IRQHandler 96 I2C1_ER_IRQHandler 97 I2C2_EV_IRQHandler 98 I2C2_ER_IRQHandler 99 SPI1_IRQHandler 100 SPI2_IRQHandler 101 USART1_IRQHandler 102 USART2_IRQHandler 103 USART3_IRQHandler 104 EXTI15_10_IRQHandler 105 RTCAlarm_IRQHandler 106 USBWakeUp_IRQHandler 107 TIM8_BRK_IRQHandler 108 TIM8_UP_IRQHandler 109 TIM8_TRG_COM_IRQHandler 110 TIM8_CC_IRQHandler 111 ADC3_IRQHandler 112 FSMC_IRQHandler 113 SDIO_IRQHandler 114 TIM5_IRQHandler 115 SPI3_IRQHandler 116 UART4_IRQHandler 117 UART5_IRQHandler 118 TIM6_IRQHandler 119 TIM7_IRQHandler 120 DMA2_Channel1_IRQHandler 121 DMA2_Channel2_IRQHandler 122 DMA2_Channel3_IRQHandler 123 DMA2_Channel4_5_IRQHandler 124 B . 125 126 ENDP

先定义,当外部中断触发时,B . 表示跳进去执行。

  汇编基础5:

  由于前面只是定义了堆栈段并没有初始化,这里对堆栈段进行初始化。 就像定义了: int a; 初始化 a = 1;也可以像代码段一样定义的同时就初始化: int b = 2;

  在_main中,会调用一下的程序,进行堆栈的初始化,进而为进入到C语言中的main函数做好准备。

  下面代码中有个__MICROLIB,对应后面 MDK 截图的 Use MicroLIB,如果选了勾选了 Use MicroLIB, IF 就为真,否则为假

初始化堆栈段

 1                  IF      :DEF:__MICROLIB
 2                 
 3                  EXPORT  __initial_sp
 4                  EXPORT  __heap_base
 5                  EXPORT  __heap_limit
 6                 
 7                  ELSE
 8                 
 9                  IMPORT  __use_two_region_memory
10                  EXPORT  __user_initial_stackheap
11                  
12 __user_initial_stackheap
13 
14                  LDR     R0, =  Heap_Mem
15                  LDR     R1, =(Stack_Mem + Stack_Size)
16                  LDR     R2, = (Heap_Mem +  Heap_Size)
17                  LDR     R3, = Stack_Mem
18                  BX      LR
19 
20                  ALIGN
21 
22                  ENDIF
23 
24                  END

  microlib是缺省C库的备选库。它旨在与需要装入到极少量内存中的深层嵌入式应用程序配合使用。这些应用程序不在操作系统中运行。microlib进行了高度优化以使代码变得很小。它的功能比缺省C库少,并且根本不具备某些ISOC特性。某些库函数的运行速度也比较慢,例如,memcpy()。不管使用与否,都可以,但是启动时稍微有点区别。

  • 启动流程1(使用标准库,不使用Microlib)如下图:

  • 启动流程2(使用Microlib)如下图:

  假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处(大部分是这个地址),而复位中断服务入口地址存放于0x8000004处(复位地址在栈顶地址4字节后)。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。

  

  解释一下一个小细节,绝大部分ARM-M协议的芯片,复位之后先进入厂商boot,此时所有的用户均无法接入处理器;厂商boot主要负责芯片最初级的初始化,加密及

对MCU进行一些差异性设置等,BOOT完成后,会把主动权交给用户,也就是启动代码;启动代码(执行汇编语言不需要此启动代码),在启动文件中,会设置MSP(主堆栈指针)和PC(程序计数器)的值,MSP的地址默认是0x00000000,PC的地址默认是0x00000004,这两个地址可以通过CORTEX-M中的VTOR寄存器来进行重映射,修改。

  下一节,手把手写一下启动文件,并进行验证一下;写完启动文件之后写分散加载文件。

猜你喜欢

转载自www.cnblogs.com/39950436-myqq/p/11574799.html