嵌入式之详解:startup.S文件
文章目录
- 前言
- 一、MCU上电
- 1、上电复位
- 2、复位向量表
- 3、跳转到复位处理程序
- 4、执行startup.s文件
- 二、startup.s代码解析
- 1、汇编指令和架构声明
- 2、复位处理函数定义
- 3、中断屏蔽
- 4、寄存器初始化
- 5、第一阶段内存初始化(RamInit0)
- 6、堆栈指针初始化
- 7、第二阶段内存初始化(RamInit1)
- 8、向量表复制(可选)
- 9、系统初始化(可选)
- 10、第三阶段内存初始化(RamInit2)
- 11、中断使能
- 12、调用主函数
- 13、无限循环
- 总结
前言
我们在实际嵌入式软件开发过程中,大多数都是直接使用,关于其内部底层以及汇编了解甚少。刚好把笔记记录在博客上,方便复习以及分享给需要帮助的人。
本章将从两部分:1 MCU上电->执行startup.s; 2 startup.s代码解析。
不必担心,走你~
一、MCU上电
1、上电复位
- 当 32 位 MCU 上电时,电源电压会从 0V 开始上升,直到达到稳定的工作电压。在这个过程中,复位电路会监测电源电压的变化。一旦电源电压达到复位电路的触发阈值,复位电路会产生一个复位信号。
- 复位信号会将 MCU 的内部状态进行重置,包括将所有的寄存器恢复到初始状态,程序计数器(PC)也会被设置为一个特定的地址,这个地址通常是 MCU 的复位向量地址,对于很多 32 位 ARM Cortex - M 系列的 MCU,这个地址是0x00000000。
2、复位向量表
- 在 MCU 的 Flash 或 ROM 的起始地址(即复位向量地址)处,存放着一个向量表。向量表是一个包含多个异常向量的表格,每个异常向量都是一个 32 位的地址,指向相应异常处理程序的入口地址。
- 向量表的第一个条目是初始栈指针(MSP)的值,第二个条目是复位处理程序的入口地址。当 MCU 复位后,程序计数器(PC)会被设置为复位向量地址,然后 MCU 会从这个地址开始读取数据。首先,MCU 会将向量表中第一个地址处存储的值加载到主堆栈指针(MSP)寄存器中,完成堆栈指针的初始化,确保后续的函数调用和中断处理有正确的堆栈空间可用。接着,MCU 会从向量表的第二个地址处获取复位处理程序的入口地址,并将这个地址加载到程序计数器(PC)中,从而跳转到复位处理程序的入口。
3、跳转到复位处理程序
- 复位处理程序的入口地址通常指向startup.s文件中的Reset_Handler标签处。因此,MCU 会跳转到startup.s文件中的Reset_Handler开始执行代码。
4、执行startup.s文件
- 中断屏蔽:在Reset_Handler中,首先会执行cpsid i指令,用于屏蔽中断。这是为了防止在初始化过程中,外部中断干扰 MCU 的正常初始化操作。
- 寄存器初始化:将一些通用寄存器(如r1 - r12)初始化为 0,为后续的操作提供一个已知的初始状态。
- 内存初始化:
- RamInit0 阶段:调用RamInit0函数,该函数主要进行一些与内存相关的初始化操作,例如 ECC(错误纠正码)的初始化。
- 堆栈指针初始化:将STACK_end所代表的地址加载到堆栈指针(SP,即r13)中,完成堆栈的初始化。
- RamInit1 阶段:调用RamInit1函数,该函数通常用于将数据段从 Flash 复制到 SRAM,并将 BSS 段清零。数据段包含已初始化的全局变量,BSS 段包含未初始化的全局变量。
- 向量表复制(可选):如果没有定义__NO_VECTOR_TABLE_COPY宏,会调用VectorTableCopy函数,将中断向量表从 Flash 复制到 RAM 中,以提高中断响应速度。
- 系统初始化(可选):如果没有定义__NO_SYSTEM_INIT宏,会调用SystemInit函数,用户可以在该函数中初始化 PLL(锁相环)等,以调整系统时钟,提高 MCU 的运行速度。
- RamInit2 阶段:调用RamInit2函数,进行其他内存相关的初始化操作。
- 中断使能:执行cpsie i指令,重新使能中断,允许 MCU 响应外部中断。
- 调用主函数:使用bl main指令调用main函数,进入用户编写的主程序,开始执行具体的应用任务。
通过以上步骤,32 位 MCU 从上电开始,经过复位、初始化等一系列操作,最终执行到startup.s文件中的代码,并完成系统的初始化,进入用户程序的执行阶段。
二、startup.s代码解析
这段代码是来自于云途YTM32B1LE0x系列的,其它也类似。
/*
* Copyright 2020-2024 Yuntu Microelectronics co.,ltd
* All rights reserved.
*
* YUNTU Confidential. This software is owned or controlled by YUNTU and may only be
* used strictly in accordance with the applicable license terms. By expressly
* accepting such terms or by downloading, installing, activating and/or otherwise
* using the software, you are agreeing that you have read, and that you agree to
* comply with and are bound by, such license terms. If you do not agree to be
* bound by the applicable license terms, then you may not retain, install,
* activate or otherwise use the software. The production use license in
* Section 2.3 is expressly granted for this software.
*
* @file startup.S
* @brief
*
*/
.syntax unified
.arch armv6-m
.thumb
/* Reset Handler */
.thumb_func
.align 2
.globl Reset_Handler
.type Reset_Handler, %function
_start:
Reset_Handler:
cpsid i /* Mask interrupts */
/* Init the rest of the registers */
ldr r1,=0
ldr r2,=0
ldr r3,=0
ldr r4,=0
ldr r5,=0
ldr r6,=0
ldr r7,=0
mov r8,r7
mov r9,r7
mov r10,r7
mov r11,r7
mov r12,r7
/* RamInit 0 Stage, focus on ecc init, asm code*/
bl RamInit0
/* Initialize the stack pointer */
ldr r0,=STACK_end
mov r13,r0
/* RamInit 1 Stage, focus on copy data,clear bss, c code*/
ldr r0,=RamInit1
blx r0
/* Copy Vector Table for interrupt, c code */
#ifndef __NO_VECTOR_TABLE_COPY
/* Call the to copy vector table from flash to ram */
ldr r0,=VectorTableCopy
blx r0
#endif
/* SystemInit, user can init PLL to speed up left startup code, c code*/
#ifndef __NO_SYSTEM_INIT
/* Call the system init routine */
ldr r0,=SystemInit
blx r0
#endif
/* RamInit 2 Stage, focus on others ram init, c code */
ldr r0,=RamInit2
blx r0
/* Unmask interrupts */
cpsie i
/* Call the main routine */
bl main
JumpToSelf:
b JumpToSelf
1、汇编指令和架构声明
```asm
.syntax unified
.arch armv6-m
.thumb
.syntax unified
:指定使用统一的汇编语法,这样可以在ARM和Thumb指令集之间更方便地切换。.arch armv6-m
:指定目标架构为ARMv6-M,这是Cortex - M系列微控制器常用的架构。.thumb
:表示后续的代码使用Thumb指令集,Thumb指令集具有更高的代码密度。
2、复位处理函数定义
```asm
.thumb_func
.align 2
.globl Reset_Handler
.type Reset_Handler, %function
_start:
Reset_Handler:
.thumb_func
:声明这是一个Thumb指令集的函数。.align 2
:将函数的起始地址对齐到2的倍数,即按字对齐。.globl Reset_Handler
:声明Reset_Handler
为全局符号,这样其他文件可以引用它。.type Reset_Handler, %function
:指定Reset_Handler
是一个函数。_start
和Reset_Handler
是同一个入口点,当单片机复位时,程序会从这里开始执行。
3、中断屏蔽
```asm
cpsid i /* Mask interrupts */
cpsid i
:这是一条指令,用于屏蔽中断(i
表示IRQ中断)。在初始化过程中,通常会先屏蔽中断,以防止中断干扰初始化操作。
4、寄存器初始化
```asm
/* Init the rest of the registers */
ldr r1,=0
ldr r2,=0
ldr r3,=0
ldr r4,=0
ldr r5,=0
ldr r6,=0
ldr r7,=0
mov r8,r7
mov r9,r7
mov r10,r7
mov r11,r7
mov r12,r7
- 这部分代码将寄存器
r1
到r12
初始化为0。通过ldr
指令将立即数0加载到r1
到r7
中,然后使用mov
指令将r7
的值复制到r8
到r12
中。
5、第一阶段内存初始化(RamInit0)
```asm
/* RamInit 0 Stage, focus on ecc init, asm code*/
bl RamInit0
bl RamInit0
:这是一条带链接的跳转指令,调用RamInit0
函数。该函数可能是用于内存的第一阶段初始化,重点是ECC(错误纠正码)的初始化,并且是用汇编语言编写的。
6、堆栈指针初始化
```asm
/* Initialize the stack pointer */
ldr r0,=STACK_end
mov r13,r0
ldr r0,=STACK_end
:将符号STACK_end
所代表的地址加载到寄存器r0
中。STACK_end
通常是栈的结束地址。mov r13,r0
:将r0
的值(即栈的结束地址)赋值给r13
,r13
是堆栈指针(SP),这样就完成了堆栈指针的初始化。
7、第二阶段内存初始化(RamInit1)
```asm
/* RamInit 1 Stage, focus on copy data,clear bss, c code*/
ldr r0,=RamInit1
blx r0
ldr r0,=RamInit1
:将RamInit1
函数的地址加载到寄存器r0
中。blx r0
:带链接的跳转并根据目标地址的最低位决定使用ARM还是Thumb指令集,调用RamInit1
函数。该函数主要用于数据段的复制和BSS段的清零,是用C语言编写的。
8、向量表复制(可选)
```asm
/* Copy Vector Table for interrupt, c code */
#ifndef __NO_VECTOR_TABLE_COPY
/* Call the to copy vector table from flash to ram */
ldr r0,=VectorTableCopy
blx r0
#endif
#ifndef __NO_VECTOR_TABLE_COPY
:如果没有定义__NO_VECTOR_TABLE_COPY
宏,则执行下面的代码。ldr r0,=VectorTableCopy
:将VectorTableCopy
函数的地址加载到寄存器r0
中。blx r0
:调用VectorTableCopy
函数,该函数用于将中断向量表从Flash复制到RAM中,是用C语言编写的。
9、系统初始化(可选)
```asm
/* SystemInit, user can init PLL to speed up left startup code, c code*/
#ifndef __NO_SYSTEM_INIT
/* Call the system init routine */
ldr r0,=SystemInit
blx r0
#endif
#ifndef __NO_SYSTEM_INIT
:如果没有定义__NO_SYSTEM_INIT
宏,则执行下面的代码。ldr r0,=SystemInit
:将SystemInit
函数的地址加载到寄存器r0
中。blx r0
:调用SystemInit
函数,用户可以在该函数中初始化PLL(锁相环)等,以加速后续的启动代码,该函数是用C语言编写的。
10、第三阶段内存初始化(RamInit2)
```asm
/* RamInit 2 Stage, focus on others ram init, c code */
ldr r0,=RamInit2
blx r0
ldr r0,=RamInit2
:将RamInit2
函数的地址加载到寄存器r0
中。blx r0
:调用RamInit2
函数,该函数用于内存的第三阶段初始化,重点是其他内存相关的初始化,是用C语言编写的。
11、中断使能
```asm
/* Unmask interrupts */
cpsie i
cpsie i
:这是一条指令,用于使能中断(i
表示IRQ中断)。在完成初始化后,重新使能中断,允许系统响应外部中断。
12、调用主函数
```asm
/* Call the main routine */
bl main
bl main
:带链接的跳转指令,调用main
函数,进入用户编写的主程序。
13、无限循环
```asm
JumpToSelf:
b JumpToSelf
- 如果
main
函数执行完毕返回,程序会跳转到JumpToSelf
标签处,然后不断地跳转到自身,形成一个无限循环,防止程序跑飞。
总结
综上所述,这段启动代码的主要功能是在单片机复位后,依次完成中断屏蔽、寄存器初始化、内存初始化、系统初始化等操作,最后调用 main
函数进入用户程序,并在必要时使能中断。