知识点①:单片机的启动流程

单片机上电后,是怎么一步步跑到 main 的?(术语详解版)

副标题:把 MSP、PC、向量表、.data/ .bss 一次讲清楚

开篇:main 不是「开机键」

很多人学 C 时被灌输一句:程序从 main 开始。

换到 单片机 (也称 MCU,Microcontroller Unit,把 CPU、存储器、外设集成在一颗芯片上的微型计算机)上,很容易产生错觉:烧录完、上电,芯片就会立刻执行你写的 main()

更准确的说法是:

  • mainC 应用程序的入口(你写业务逻辑的地方)
  • 芯片复位后真正先跑的,是 硬件复位序列 + 启动文件 + C 运行时初始化

这篇把整条链路拆开,并约定:每个专业名词、英文缩写第一次出现时,都用一句话说明它是什么。


先建立一张总地图

Cortex-M (ARM 公司的一类嵌入式 CPU 内核,STM32 多数型号基于它)/ STM32(意法半导体的 Cortex-M 单片机产品系列)典型路径为例:

步骤 动作 为何进入下一步
01 上电/复位释放 → 进入复位序列 架构规定先取向量表,不直接进 main
02 读 0x00000000 → 加载初始 MSP 没有有效栈,后面函数调用不安全
03 读 0x00000004 → 复位向量写入 PC PC 已指向 Reset_Handler,取指必然跳转
04 执行 Reset_Handler 启动例程先做系统级初始化
05 调用 SystemInit 时钟等就绪后,才能准备 C 变量的 RAM 映像
06 复制 .data,清零 .bss 变量语义满足后,才允许进应用入口
07 调用用户 main() ---

阶段划分很重要:

  • 01--03:硬件自动完成
  • 04--06:启动软件 / C 运行时
  • 07:你的应用

一句话压舱:main 是应用入口,不是复位后第一条指令。


01 复位:从「已知状态」重来

复位是什么?

复位(Reset):让 CPU 中断当前一切执行,把关键状态拉回手册规定的初始值,再按固定流程重新启动。

常见触发源:

缩写/名称 是什么
POR(Power-On Reset,上电复位) 刚接上电源时的复位
NRST 芯片外部复位引脚,按复位键常走这条路径
IWDG(Independent Watchdog,独立看门狗) 软件「喂狗」超时,硬件强制复位,防止死机挂死
欠压复位等 供电电压异常时的保护性复位

「寄存器回到复位值」到底在说什么?

寄存器(Register):CPU 内部极快、容量很小的存储单元,用来临时保存正在用的数------比如「下一条指令在哪」「栈顶在哪」。

常见几个:

名称 全称/含义 一句话
PC Program Counter,程序计数器 指向「下一条要执行的指令地址」
MSP Main Stack Pointer,主栈指针 指向当前主栈的栈顶位置
xPSR Program Status Register,程序状态寄存器 保存运算结果标志、执行状态等位

复位时,硬件会把这些关键寄存器恢复为 架构/芯片手册规定的复位值(已知、可控的默认状态)。

注意两件事:

  1. 清的是 CPU 内部临时状态,不是删掉你烧进芯片的程序。
  2. Flash(非易失存储器,掉电不丢,常用来存程序代码)里的程序映像还在。

类比:重启电脑会清掉内存里乱跑的临时状态,但硬盘上的安装文件还在。

关键:为什么复位完不直接进 main?

因为 Cortex-M 的 复位序列 写死了下一步:去固定位置读 向量表。硬件不会替你「猜」main 在哪。


02--03 向量表:先拿栈顶,再拿入口

向量表是什么?

向量表(Vector Table) :一张放在存储器开头附近的「入口地址表」。

发生复位、异常、中断时,CPU 查这张表,知道该跳到哪个处理函数。

对启动来说,最关键的是前两项:

偏移地址 内容 硬件动作
0x00000000 初始栈顶地址 读出后写入 MSP
0x00000004 复位向量(通常是 Reset_Handler 地址) 读出后写入 PC,并开始取指执行

「固定地址」指什么?从哪来?

  • 指什么 :复位后,CPU 硬规定 从逻辑地址 0x00000000 开始读向量表。
  • 规则从哪来 :写在 CPU 内核架构 (如 Cortex-M)说明书里,芯片出厂就遵守------不是你在 C 代码里随便定的
  • 表里的具体数字从哪来 :由 启动文件 (startup,常是 .s 汇编)和 链接脚本(告诉链接器:代码/数据各放哪段地址)生成,再烧录进 Flash 映像开头。

STM32 为啥常听到 0x08000000

STM32 片上 Flash 的物理起始地址常常是 0x08000000

芯片内部会做 地址映射/别名 :让 CPU「读逻辑地址 0」时,实际读到主 Flash 开头(具体是否如此,还受 BOOT 引脚------选择从 Flash / 系统存储器 / SRAM 启动的配置引脚------影响)。

所以:

  • 读哪里(逻辑 0)→ 架构规定
  • 0 映射到哪块物理存储器→ 芯片与 BOOT 配置
  • 表里写什么地址→ 你的工程启动文件与链接结果

因果链

  1. 先装 MSP:后面一旦调用函数,就要往栈里压返回地址;栈无效,后面全不稳。
  2. 再装 PC :PC 已是 Reset_Handler 地址,CPU 取指就会跳进去。

栈(Stack) 是什么?一块在 RAM(随机访问存储器,可读可写但掉电丢失,用来放运行中的变量和栈)里预留的区域,函数调用、局部变量、中断现场都靠它「暂存」。


04 Reset_Handler:软件启动真正开始

Reset_Handler 是什么?

Reset_Handler :启动文件里的 复位处理函数,是复位后软件真正开始跑的入口(相对硬件自动取向量而言)。

典型路径(Keil/ARM Compiler 常见写法;GCC 工程细节略有不同,但职责类似):

  1. (可选)做一些 SRAM 相关准备
  2. 调用 SystemInit
  3. 跳转到 C 库入口(常见符号名 __main ,注意:这不是你写的那个 main

__main 是什么?编译器/C 库提供的运行时入口,负责把 C 语言需要的内存环境准备好,最后再调用你的 main()

关联

硬件已经把 PC 指到这里 → 启动代码约定:先做系统级初始化,再进入 C 运行时

若 MSP 无效,这里一开始调用 C 函数就可能触发 HardFault(Cortex-M 的一种严重故障异常,常表示访问了非法地址、栈坏了等)。


05 SystemInit:把硬件跑到「能干活」

SystemInit 是什么?

SystemInit :芯片厂商提供的系统初始化函数(常见于 system_stm32xxx.c),由 Reset_Handler 调用。

它通常做这些事:

工作 白话
配置 时钟树 决定 CPU 和外设用多快的时钟跑
可选设置 VTOR Vector Table Offset Register,向量表偏移寄存器:告诉 CPU 向量表现在在哪个基地址(Bootloader 场景常用)
其他底层参数 如 Flash 等待周期、部分总线/缓存相关设置等(因型号而异)

几个时钟相关缩写:

缩写 含义
HSI High Speed Internal,片内高速内部振荡器
HSE High Speed External,外部高速晶振时钟
PLL Phase-Locked Loop,锁相环,用来倍频得到更高主频

关联

为什么 SystemInit 之后才搬 .data

因为要先让系统时钟等基础硬件处于合理状态,再去做存储器初始化;顺序反了,轻则时序不稳,重则后续访问异常。


06 .data / .bss:让 C 变量「合法」

C 语言有两条默认约定:

  • 写了初值的全局/静态变量,启动后应等于那个初值
  • 没写初值的,应等于 0

但芯片世界更「物理」:

  • Flash:适合长期保存代码和初值,运行时不宜当普通可写变量区
  • RAM:可读写,适合变量,但上电内容不确定(可能是残留垃圾值)

所以 C 运行时要做两件标准动作:

.data

.data :存放「有非零初值」的已初始化全局/静态变量。

动作 :把 Flash 里的初值 复制 到 RAM 的执行地址。

例:int count = 10; ------ 10 跟着程序存在 Flash;运行期要改 count,就必须在 RAM 里有一份可写副本。

.bss

.bss (历史上称 Block Started by Symbol):存放「未初始化或初值为 0」的全局/静态变量。

动作 :把对应 RAM 区域 整段清零

关联

只有这两步完成,C 的变量语义才站得住,启动流程才「允许」把控制权交给 main()

(实现路径因工具链而异:ARMCC 常经 __main / __rt_entry;GCC 常在启动汇编里直接循环拷贝/清零。殊途同归。)


07 进入 main:应用逻辑开场

当栈可用、系统初始化完成、.data/.bss 就绪后,启动路径会完成最后一点 C/C++ 运行时准备(例如全局对象构造,常见于调用 __libc_init_array 一类函数),然后:

调用你写的 main()

从这一刻起,执行权才从「启动与运行时」正式交给「应用程序」。

你熟悉的初始化外设、while(1) 循环,才真正开始。


术语速查表(可收藏)

术语 一句话
MCU / 单片机 集成了 CPU、存储器、外设的微型控制芯片
Cortex-M ARM 嵌入式内核家族,STM32 常用
复位 Reset 让 CPU 回到手册规定的初始状态并重跑启动流程
寄存器 CPU 内部高速小存储,如 PC、MSP
PC 程序计数器:下一条指令地址
MSP 主栈指针:主栈栈顶
向量表 异常/中断/复位入口地址表
Reset_Handler 启动文件中的复位处理函数
SystemInit 厂商系统初始化(时钟等)
VTOR 向量表基址偏移寄存器
Flash 掉电不丢的程序存储器
RAM 可读写、掉电丢失的运行内存
.data 有初值的全局/静态变量段(需从 Flash 拷到 RAM)
.bss 未初始化全局/静态变量段(需清零)
HardFault 严重故障异常,常与非法访问/坏栈相关

你可以怎么做

  1. 打开工程里的 startup_xxx.s,找到向量表、Reset_Handler__initial_sp(初始栈顶符号)。
  2. 打开 system_stm32xxx.c,看 SystemInit 配了哪些时钟。
  3. 下次「一上电就 HardFault」,优先怀疑:向量表、MSP、链接地址、时钟,而不是先怪 main 里第三行业务代码。

写在最后

整条链可以背成一句:

复位 → 取向量表(MSP,再 PC)→ Reset_HandlerSystemInit → 复制 .data / 清零 .bssmain

相关推荐
龙腾AI白云4 小时前
孪生不止在工厂:能源、医疗与农业
人工智能·机器学习·scikit-learn·知识图谱
架构谨制@涛哥13 小时前
知识工程-4.超越RAG:OKF会如何取代矢量数据库吗?
人工智能·软件工程·软件构建·知识图谱
DISOFOO光谱仪1 天前
2026采购指南:台式荧光光谱仪质量可靠供应商推荐
知识图谱
学代码的CJY1 天前
CJY 知识工作台:博客写作使用指南
数据仓库·知识图谱
昇腾知识体系1 天前
vLLM-Ascend 支持矩阵:supported_models 模型列表、BF16/ACLGraph 支持项核对方法
人工智能·华为·知识图谱·vllm
X54先生(人文科技)2 天前
Yuri演唱会碳硅协同思维推演备忘录
人工智能·深度学习·知识图谱·零知识证明
昇腾知识体系3 天前
昇腾环境安装 flash_attn:FlashAttnPrefillBackend 报错与替代算子
人工智能·pytorch·华为·知识图谱
昇腾知识体系3 天前
CANN 安装升级避坑:version.cfg 查版本、银河麒麟找不到驱动、nnrt --version 无输出排查
人工智能·华为·知识图谱
昇腾知识体系4 天前
昇腾 950 RegBase 性能优化:从 msprof 采数到优化手法
人工智能·华为·性能优化·知识图谱