STM32F407启动文件

简介

启动文件(汇编):startup_stm32f407xx.s

主要工作:

  1. 初始化堆栈指针SP=_initial_sp
  2. 初始化程序计数器PC=Reset_Handle
  3. 初始化中断向量表
  4. 配置系统时钟
  5. 调用C库函数_main初始化用户堆栈,最终调用main跳转到用户程序

汇编指令

启动文件所用汇编指令:

指令名 描述
EQU 给数字常量取一个符号名,相当于C define
AREA 汇编一个新的代码段或者数据段
SPACE 分配内存空间
PRESERVE8 当前文件堆栈需按照8字节对齐
EXPORT 声明一个标号具有全局属性,可被外部的文件使用
DCD 以字为单位分配内存,要求4字节对齐,并要求初始化这些内存
PROC 定义子程序,与ENDP成对使用,表示子程序结束
WEAK 编译器指令
弱定义,如果外部文件声明标号,则优先使用外部文件定义的;如果外部文件没有定义,也不报错
IMPORT 声明标号来自外部文件,跟C 语言中的EXTERN 关键字类似
B 跳转到一个标号
ALIGN 编译器指令
编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4字节对齐
END 到达文件的末尾,文件结束
IF,ELSE 条件分支语句,同C if-else
  • AREA

定义一个区域

格式:

标号         sectionname{,attr}{,attr}...

sectionname:区域名称
attr: 表示AREA的属性,多个属性用逗号分隔. attr可取值:
1)ALIGN=expression,默认条件为4byte对齐;
2)CODE包含机器指令,默认值READONLY;
3)DATA包含数据,但不包含指令,默认值READWRITE;

READONLY:表示此字节只读. 代码区域默认值
READWRITE:表示此字节可读可写. 数据区域默认值

启动代码

按启动文件内的代码顺序分析.

Stack栈

; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp

开辟一个栈,大小Stack_Size为0x00000400(1KB),名字为STACK. NOINIT表示不初始化,READWRITE表示可读可写,ALIGN=3对应 \(2^3=8\) byte对齐

栈主要用于C函数栈,局部变量的申请,就是在这个栈上.

EQU:宏定义,相当于C #define
AREA:定义代码段或数据段.
SPACE:分配一定大小的内存(byte),相当于C malloc

__initial_sp(符号,symbol)紧接在SPACE之后,表示栈的结束地址,即栈顶地址. 通常由linker自动计算,表示栈的初始栈顶指针(Stack Pointer,SP). 栈由高➡低地址生长.

Stack_Mem(symbol)表示栈的起始地址.

Heap堆

; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB

开辟一个堆,大小0x00000200(512byte),名字HEAP. NOINIT表示不初始化,READWRITE表示可读可写,ALIGN=3对应\(2^3=8\)byte对齐.

__heap_base 表示堆起始地址,_heap_limit表示堆结束地址. 堆由低➡高生长,与栈生长方向相反.

堆主要用于动态内存分配,如malloc()申请的内存就在堆上.(STM32等MCU极少使用)

PRESERVE8:指定当前问题的堆栈按8byte对齐;
THUMB:表示后面指令兼容THUMB指令. THUMB是ARM旧的指令集,16bit;现在Cortex-M系列使用THUMB-2指令集,32bit,兼容THUMB.

向量表

定义向量表的属性:

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

定义一个数据段,名字RESET,只读(READONLY),并声明3个有全局属性的标号(符号)__Vectors__Vectors_End__Vectors_Size

EXPORT:声明一个标号可被外部文件使用,使得标号有全局属性. 如果是IAR编译器,则应使用GLOBAL指令.

当发生异常时,内核响应该异常后,对应异常服务例程(ESR)就会被执行. 内核通过“向量表查表机制”,查找ESR入口地址. 向量表是一个WORD(32bit整数)数组,每个元素对应一种异常,元素值就是一个ESR的入口地址.

向量表在RAM或FLASH中的位置,是可以设置的,通常可通过NVIC的一个重定位寄存器(SCB->VTOR)来设置(默认是0,对应FLASH地址0),因此,地址0必须包含一张向量表,用于初始时异常分配.

注意:向量表中,0号类型不是入口地址,而是复位后的MSP(Main Stack Pointer)初值

img

向量表内容:

__Vectors       DCD     __initial_sp               ; Top of Stack 栈顶指针
                DCD     Reset_Handler              ; Reset Handler 复位向量
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved 0表示保留
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; External Interrupts 外部中断开始
                DCD     WWDG_IRQHandler                   ; Window WatchDog                                        
                DCD     PVD_IRQHandler                    ; PVD through EXTI Line detection                        
                DCD     TAMP_STAMP_IRQHandler             ; Tamper and TimeStamps through the EXTI line            
                DCD     RTC_WKUP_IRQHandler               ; RTC Wakeup through the EXTI line                       
                DCD     FLASH_IRQHandler                  ; FLASH                                           
                
                ; 中间省略
                ...
                DCD     0                                 ; Reserved				                              
                DCD     HASH_RNG_IRQHandler               ; Hash and Rng
                DCD     FPU_IRQHandler                    ; FPU
                
                                         
__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

                AREA    |.text|, CODE, READONLY

__Vectors 向量表起始地址,__Vectors_End向量表结束地址,__Vectors_End - __Vectors向量表大小(byte).

向量表从FLASH地址0开始,4byte为一个单位,地址0存放栈顶地址(__initial_sp),0x04存放复位程序地址(Reset_Handler)... 以此类推,可知中断向量表存放的是中断服务程序的函数名,对应一个函数地址.

DCD:分配一个或多个以字为单位的内存,4byte对齐,并要求初始化这些内存.

复位程序 Reset_Handler

                AREA    |.text|, CODE, READONLY

定义一个名为.text的代码段,可读.

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

复位子程序,也称复位向量,系统上电后第一个执行的程序,调用SystemInit函数初始化系统时钟,然后调用C库函数__main,最终(由__main)调用main函数到C环境.

WEAK:表示弱定义,如果外部文件定义了该标号,则优先使用外部定义的;否则,调用内部定义的. 好处是即使外部不定义,也不报错.

IMPORT:表示该标号来自外部文件,类似于C EXTERN. 这里表示SystemInit__main均来自外部文件.

SystemInit()标准库函数,定义在system_stm32f4xx.c(取决于具体IDE环境、MCU型号). 主要作用:配置系统时钟,这里将系统时钟配置为72M.

__main标准C库函数,主要用于初始化用户堆栈,最终调用main函数跳转到C运行环境.

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

指令名 描述
LDR 从存储器中加载字到一个寄存器中
BL 跳转到由寄存器/标号给出的地址,并把跳转前的下条指令地址保存到LR
BLX 跳转到由寄存器给出的地址,并根据寄存器的LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到LR
BX 跳转到由寄存器/标号给出的地址,不用返回

中断服务程序 ISR

启动文件已经帮我们写好所有的中断服务程序(ISR),不过默认都是空的,而且是WEAK属性. 也就是说,如果我们在外部定义一个同名ISR,中断时就会调用我们定义的,而不是默认的;相反,如果我们没有定义某个中断的ISR,那么中断时,会调用默认ISR

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler     PROC ; 不可屏蔽中断
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP
MemManage_Handler\
                PROC
                EXPORT  MemManage_Handler          [WEAK]
                B       .
                ENDP
BusFault_Handler\
                PROC
                EXPORT  BusFault_Handler           [WEAK]
                B       .
                ENDP
UsageFault_Handler\
                PROC
                EXPORT  UsageFault_Handler         [WEAK]
                B       .
                ENDP
SVC_Handler     PROC
                EXPORT  SVC_Handler                [WEAK]
                B       .
                ENDP
DebugMon_Handler\
                PROC
                EXPORT  DebugMon_Handler           [WEAK]
                B       .
                ENDP
PendSV_Handler  PROC
                EXPORT  PendSV_Handler             [WEAK]
                B       .
                ENDP
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP
Default_Handler PROC ; 外设中断向量

                EXPORT  WWDG_IRQHandler                   [WEAK]                                        
                EXPORT  PVD_IRQHandler                    [WEAK]                      
                EXPORT  TAMP_STAMP_IRQHandler             [WEAK]         
                EXPORT  RTC_WKUP_IRQHandler               [WEAK]                     
                EXPORT  FLASH_IRQHandler                  [WEAK]
                ; 中间省略
                ...
LTDC_IRQHandler                   
LTDC_ER_IRQHandler                 
DMA2D_IRQHandler                  
                B       .

                ENDP

                ALIGN

B:跳转到一个标号. 这里跳转到".",表示无限循环.
ENDP:前面指令小节讲过,与PROC成对使用,定义子程序.
ALIGN:缺省数字,表示默认4byte对齐.

常见异常:

  • NMI(不可屏蔽中断)

不可屏蔽:指无法通过全局开关(e.g. __disable_irq())关闭,优先级高于所有普通中断;
触发源:

1)硬件错误:如时钟安全系统CSS检测到HSE失效
2)外部NMI引脚
3)看门狗定时器:如看门狗IWDG超时

  • HardFault(硬件错误)

优先级最高异常,常见触发原因:

1)访问非法内存:如访问未初始化的指针,或外设寄存器
2)总线错误:如对齐访问错误,非4byte对齐的uint32_t访问
3)指令执行错误:如跳转到非法代码地址
4)堆栈溢出:MSP/PSP指向无效内存
5)ISR未实现:缺少中断处理函数
6)从异常返回错误:如错误修改LR寄存器

获取错误原因,查看4个关键寄存器:

1)SCB->HFSR:查看HardFault状态,FORCED位可确定错误是否由其他错误升级而来
2)SCB->CFSR:可配置错误状态,MMARVALID位可确定内存访问错误地址有效
3)SCB->MMFAR:内存错误地址
4)SCB->BFAR:总线错误地址

  • Memory Management Fault(内存管理错误)

当处理器检测到与内存访问相关的非法操作时,会触发此异常. 常见触发原因:

1)访问非法内存区域:如未映射地址,或写数据到只读区域
2)非对齐访问:如uint32_t访问非4byte对齐地址
3)MPU违规:如果启用了MPU并配置了受保护区域
4)从非执行区域取指令:如执行Flash/Ram之外区域代码
5)栈指针SP指向无效内存:如栈溢出,或被破坏

  • Bus Fault(总线错误)

当处理器访问内存或外设时,发生总线传输错误时触发. 常见触发原因:

1)访问不存在的内存或外设地址:如未初始化指针,指针指向无效区域
2)设备未就绪时的访问:如访问尚未初始化的外设寄存器(包括已经关闭的外设,或者正在复位的外设)
3)非法访问权限:如用户模式尝试访问特权级外设
4)数据总线错误:如非对齐访问在某些条件下触发
5)指令预取失败:尝试执行无效地址的代码,如将数据区当做代码执行

  • Usage Fault(用法错误)

当处理器检测到指令执行或系统配置相关的非法操作时触发. 常见触发原因:

1)执行未定义的指令:如执行 Cortex-M 不支持的指令
2)非法状态转换:如从 ARM 模式切换到 Thumb 模式失败
3)除零操作:默认不触发,需要手动启用 SCB->CCR.DIV_0_TRP
4)未对齐访问:如果启用了 SCB->CCR.UNALIGN_TRP
5)无效的异常返回:如错误的 EXC_RETURN 值
6)协处理器访问错误:

诊断错误用法原因,查看3个关键寄存器:

1)SCB->CFSR:记录错误类型. 其中,UNDEFINSTR(未定义指令)、DIVBYZERO(除零)、UNALIGNED(非对齐访问)、INVSTATE(非法状态)
2)SCB->HFSR:检查是否升级为 HardFault. 其中,FORCED=1 表示 UsageFault 被升级
3)SCB->CCR:控制是否启用特定错误检测. DIV_0_TRP(除零触发)、UNALIGN_TRP(非对齐触发)

  • Debug Monitor Exception(调试监视器异常)

主要用于调试环境下提供一种非侵入式的调试机制,允许开发者在不停止处理器执行的情况下监视系统状态.

非侵入式调试:指当调试器无法连接(如某些低功耗模式)时,仍能通过DebugMon_Handler 捕获调试事件

与普通调试区别:不暂停 CPU 执行,依赖调试硬件(如CoreSight 调试组件).

触发原因:

1)硬件断点命中
2)监视点触发:如数据访问断点
3)调试事件请求:通过DMCR寄存器配置

  • Pendable Service Call(可挂起的系统调用异常,PendSV)

主要用于 实时操作系统(RTOS)的任务切换,是Cortex-M内核为RTOS设计的一种低延迟、可延迟处理的异常. 是实现RTOS的 保存现场、恢复现场的关键机制.

核心作用:

1)任务切换的“幕后工作者”:RTOS利用PendSV在所有中断完成后执行任务切换,避免在中断上下文中直接切换任务导致的复杂性问题

如FreeRTOS 的 portYIELD() 或 taskYIELD() 最终触发 PendSV.

2)可挂起特性(Pendable):通过设置SCB->ICSR[PENDSVSET]触发,实际处理会延迟到没有更高优先级中断运行时

  • SysTick(系统节拍定时器)

由 Cortex-M 内核提供,用于实现精确的时间基准. 是RTOS任务调度的核心,也是裸机程序常用定时器.

常见作用:

1)系统心跳:提供固定的时间间隔中断(如 1ms),用于时间管理、任务调度等
2)RTOS 任务切换:FreeRTOS、RT-Thread 等 RTOS 依赖 SysTick 实现时间片轮转
3)裸机延时:替代低效for循环延时,实现精确的计时

要配置SysTick,涉及3个关键寄存器:

1)SysTick->CTRL:控制寄存器,启用/禁用中断
2)SysTick->LOAD:重装载值,决定中断频率
3)SysTick->VAL:计时器当前值,写入任意值清零

可参考:《CM3 权威指南CnR2》

用户堆栈初始化

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 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

                 END

首先判断是否定义了宏__MICROLIB.

如果定义了,那么赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用.

__MICROLIB可由Keil配置:

img

如果没有定义,则插入标号__use_two_region_memory,该函数需要用户自己实现(可查阅Keil帮助文档实现).

img

然后声明标号__user_initial_stackheap具有全局属性,可供外部文件调用,并实现该标号内容.

IF-ELSE-ENDIF:汇编条件分支语句,类似于C if-else

LDR:从内存加载数据到寄存器,格式LDR{条件} 目标寄存器, [源地址]

e.g.

LDR R0, =0x20000000      ; 将地址 0x20000000 的值加载到 R0

END:文件结束

参考

[野火EmbedFire]《STM32 HAL库开发实战指南——基于野火F4系列开发板》

微机原理伪指令大全及在C语言中的结合应用举例

posted @ 2025-08-13 18:08  明明1109  阅读(115)  评论(0)    收藏  举报