单片机的启动过程(从上电到main)

本文介绍了嵌入式系统的启动过程,包括C语言编译、链接器的作用、加载视图的形成以及启动代码如何根据Scatter文件进行内存布局调整。重点讲解了复位中断处理流程及其后续步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

理解这篇文章之前,我们需要配合了解两个基础知识。

一个是加载视图和运行试图,一个是单片机的存储空间。

这篇文章会涉及两个新内容,而加载视图又涉及启动代码和Scatter文件。

所以通过集成环境讲解启动代码总显得力不从心。

这里我们从编译器和链接器开始讲起。

1、C语言的编译都是按照文件进行编译的,编译完成后,每一个.c文件会生成相应的.o文件,这个过程是编译器(ARMCC)做的。

2、因为各个.o的信息是分散的,函数变量都是们没有统一编址的,所以lds链接器需要将各个.o文件的信息汇总到一起,就是code+RO+RW的位置,这就形成了加载视图。

3、启动代码starts.s 做一些基础工作外,根据Scatter文件的信息 ,对RW进行搬移,搬移到RAM中,方便后期的访问。

4、搬移完成后,我们就可以访问全局变量了(这个时候全局变量都是在RAM里的,我们通过map文件看到的)

__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler
                DCD     SysTick_Handler           ; SysTick Handler

Cortex-M0 以及M3 内核的 0地址保留,0x00000004地址用于存放复位向量。上电后,程序跳转到0x00000004地址运行,此地址存储的就是复位中断。

 

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

复位中断,先运行 系统初始化(SystemInit),然后跳转到__main过程。

<think>嗯,用户问的是单片机启动流程,特别是__main函数执行的操作。首先,我需要确定用户对单片机启动流程的了解程度。可能他们正在学习嵌入式系统或者刚接触单片机编程,想要深入了解启动过程。 用户之前询问过RTOS的优先级翻转问题,看来他们对嵌入式系统有一定基础。现在转向启动流程,可能是想更底层地理解单片机如何工作。我需要解释清楚从通main函数执行的整个过程,尤其是__main函数的作用,因为这在不同的编译器和架构中可能有所不同,比如ARM的启动文件。 首先,单片机后的启动流程通常包括硬件初始化、设置堆栈指针、初始化静态数据、调用main函数。而__main函数一般是编译器生成的,负责C运行时环境的初始化,比如初始化全局变量、静态变量,设置堆栈等。需要分步骤解释这些过程,确保用户理解每个阶段的作用。 同时,用户可能混淆了__mainmain函数,需要指出两者的区别。__main是编译器插入的,做一些初始化工作,然后跳转到用户的main函数。此外,不同编译器处理方式不同,比如GCC可能使用_start而不是__main,这一点也需要说明,避免用户混淆。 可能用户在使用某个特定的开发环境时遇到了问题,比如启动代码配置错误,导致程序无法正常运行。因此,解释清楚启动流程中的每个步骤,以及如何根据需求定制启动文件,会有助于他们调试和优化程序。 还需要注意技术细节的准确性,比如在ARM Cortex-M中,启动文件处理中断向量表、初始化.data和.bss段等。举一些实际的例子,比如Keil和GCC的不同实现,帮助用户理解不同工具链的差异。 最后,总结关键点,强调初始化过程的重要性,以及如何通过修改启动文件来适应特定需求,比如禁用看门狗或启用时钟。这样用户不仅能理解流程,还能应用到实际开发中。</think>好的,我来逐步解释单片机后的启动流程,以及`__main`函数执行的关键操作: --- ### **1. 单片机启动的核心流程** 单片机启动流程通常分为**硬件初始化**和**软件初始化**两个阶段,以下是典型步骤: #### **(1) 硬件初始化(由芯片自动完成)** - **复位向量定位**:CPU从固定地址(如`0x00000000`)读取**复位向量**,跳转到启动代码入口。 - **时钟初始化**:启用内部/外部振荡器,配置系统时钟(如HSE→PLL→SYSCLK)。 - **内存初始化**:检测并初始化Flash和RAM的访问时序。 - **中断向量表定位**:设置中断向量表基地址(如Cortex-M的`VTOR`寄存器)。 #### **(2) 软件初始化(由启动文件完成)** - **设置堆栈指针**:初始化`SP`(Stack Pointer),分配栈空间。 - **初始化.data段**:将初始化的全局变量从Flash复制到RAM。 - **清零.bss段**:将未初始化的全局变量(如`int a;`)清零。 - **调用`__main`函数**:进入C运行时环境初始化。 --- ### **2. `__main`函数的核心操作** `__main`是**编译器自动生成的函数**(如ARM Compiler中的入口),主要完成以下任务: | 步骤 | 操作 | 说明 | |------|------|------| | 1 | **C库初始化** | 初始化标准库(如`stdio`、`malloc`支持)| | 2 | **复制.data段** | 将Flash中的初始值载入RAM的.data区域 | | 3 | **清零.bss段** | 将RAM中.bss段清零,避免未初始化变量随机化 | | 4 | **设置堆/栈** | 配置堆(Heap)空间(如需动态内存分配) | | 5 | **跳转至用户main()** | 执行用户编写的`main()`函数 | --- ### **3. 具体实现示例(以ARM Cortex-M为例)** #### **(1) 启动文件(startup_xxx.s)片段** ```assembly Reset_Handler: ; 1. 初始化栈顶指针 LDR SP, =_estack ; 2. 复制.data段到RAM LDR R0, =_sdata LDR R1, =_edata LDR R2, =_sidata BL CopyDataInit ; 3. 清零.bss段 LDR R0, =_sbss LDR R1, =_ebss BL ZeroBss ; 4. 调用__main BL __main ``` #### **(2) 内存布局(Linker Script定义)** ```ld MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } SECTIONS { .isr_vector : { *(.isr_vector) } > FLASH .text : { *(.text) } > FLASH .data : { *(.data) } > RAM AT> FLASH ; 初始值存储在Flash .bss : { *(.bss) } > RAM } ``` --- ### **4. 不同编译器的差异** | 编译器 | 入口函数 | 特点 | |---------------|-------------|-------------------------------| | ARM Compiler | `__main` | 自动处理.data/.bss初始化 | | GCC | `_start` | 需手动实现数据初始化逻辑 | | IAR | `__iar_program_start` | 内置完整的启动流程 | --- ### **5. 用户`main()`函数的注意事项** - **避免阻塞初始化**:在`main()`的初期阶段,尽快完成关键硬件初始化(如时钟、看门狗)。 - **慎用全局对象**:C++全局对象的构造函数可能在`__main`阶段执行,需确保内存已就绪。 - **堆栈溢出防护**:检查链接脚本中栈(Stack)和堆(Heap)的大小是否合理。 --- ### **6. 调试技巧** - **查看.map文件**:确认.data/.bss段的地址和大小是否符合预期。 - **断点跟踪**:在`Reset_Handler`和`__main`处设置断点,观察初始化过程。 - **手动绕过`__main`**:在特殊场景(如无操作系统)可跳过`__main`,直接调用用户`main()`。 --- ### **总结** `__main`函数是**C/C++程序运行前的最后一道关卡**,负责构建可执行环境。理解其原理可帮助开发者: 1. 优化启动速度(如减少.data段数据量) 2. 定位初始化阶段的内存错误 3. 自定义启动流程(如省略库初始化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值