第 4 章:嵌入式软件开发 —— 从代码到"烧录"¶
场景: 你在电脑上写好了智能家居中控的代码。但电脑是 x86 架构,中控是 ARM 架构——电脑上的程序不能直接在 ARM 芯片上运行。你需要"交叉编译"——在 x86 上编译出 ARM 能执行的程序,然后"烧录"到芯片里。
4.1 交叉编译¶
核心比喻:交叉编译就是"用中文写菜单,翻译成英文给外国厨师"
你(开发者)用中文写菜单(C 代码),但厨师(ARM 芯片)只懂英文(ARM 机器码)。你需要一个翻译(交叉编译器)把中文菜单翻译成英文。
这个翻译过程在你的电脑(x86)上完成,但翻译结果给 ARM 芯片用——这就是"交叉编译"。
交叉编译工具链¶
源代码(.c/.h)
│
▼
┌──────────────┐
│ 编译器(gcc) │ → 汇编文件(.s)
└──────┬───────┘
▼
┌──────────────┐
│ 汇编器(as) │ → 目标文件(.o)
└──────┬───────┘
▼
┌──────────────┐
│ 链接器(ld) │ → 可执行文件(.elf/.bin/.hex)
└──────┬───────┘
▼
┌──────────────┐
│ 烧录/下载 │ → 写入芯片 Flash
└──────────────┘
| 工具 | 作用 | 输入 | 输出 |
|---|---|---|---|
| 编译器(arm-none-eabi-gcc) | C/汇编 → 目标代码 | .c .s |
.o |
| 汇编器(arm-none-eabi-as) | 汇编 → 机器码 | .s |
.o |
| 链接器(arm-none-eabi-ld) | 链接目标文件 + 库 | .o .a |
.elf |
| 格式转换(objcopy) | ELF → HEX/BIN | .elf |
.hex .bin |
| 调试器(GDB + JTAG/SWD) | 下载和调试 | .elf |
芯片 |
4.2 启动代码与 Bootloader¶
核心比喻:启动代码就是中控主机的"开机自检"
你按下中控主机的电源键后,它不会立刻进入工作状态。它先做"开机自检":
- 检查内存是否正常
- 初始化时钟系统
- 把程序从 Flash 搬到 RAM
- 设置堆栈指针
- 跳转到 main() 函数
这个过程由启动代码(Startup Code)完成。
启动流程¶
上电/复位
│
▼
┌──────────────┐
│ 1. 复位向量 │ ← CPU 从固定地址读取第一条指令
└──────┬───────┘
▼
┌──────────────┐
│ 2. 初始化堆栈 │ ← 设置 MSP(主堆栈指针)
└──────┬───────┘
▼
┌──────────────┐
│ 3. 初始化时钟 │ ← 配置系统时钟(PLL、分频)
└──────┬───────┘
▼
┌──────────────┐
│ 4. 复制数据段 │ ← 把 .data 从 Flash 复制到 RAM
└──────┬───────┘
▼
┌──────────────┐
│ 5. 清零 BSS 段 │ ← 初始化未初始化的全局变量为 0
└──────┬───────┘
▼
┌──────────────┐
│ 6. 调用 main()│ ← 进入用户程序
└──────────────┘
Bootloader¶
| 功能 | 说明 |
|---|---|
| 初始化硬件 | 时钟、内存、串口等基本外设 |
| 加载应用程序 | 从 Flash/SD 卡/网络加载主程序 |
| 固件更新 | 支持通过串口/USB/网络升级固件 |
| 恢复模式 | 主程序损坏时进入恢复模式 |
| 引导选择 | 选择启动哪个应用程序 |
4.3 内存映射¶
嵌入式系统的内存布局(链接脚本 .ld 文件定义):
高地址 ┌──────────────┐
│ 栈(Stack) │ ← 向下增长
│ │
├──────────────┤
│ 堆(Heap) │ ← 向上增长(动态内存分配)
├──────────────┤
│ .bss 段 │ ← 未初始化全局变量(在 RAM 中)
├──────────────┤
│ .data 段 │ ← 已初始化全局变量(Flash→RAM)
├──────────────┤
│ .rodata 段 │ ← 只读数据(常量字符串等)
├──────────────┤
│ .text 段 │ ← 程序代码(在 Flash 中)
低地址 └──────────────┘
| 段 | 存储位置 | 内容 |
|---|---|---|
.text |
Flash | 程序代码(机器指令) |
.rodata |
Flash | 只读数据(const 变量、字符串常量) |
.data |
Flash→RAM | 已初始化的全局/静态变量 |
.bss |
RAM | 未初始化的全局/静态变量(启动时清零) |
Heap |
RAM | 动态分配内存(malloc) |
Stack |
RAM | 函数调用栈、局部变量 |
4.4 常见开发工具¶
| 工具类型 | 代表产品 | 用途 |
|---|---|---|
| IDE | Keil MDK、IAR EWARM、STM32CubeIDE | 集成开发环境 |
| 编译器 | ARM GCC、ARMCC、IAR ICCARM | 交叉编译 |
| 调试器 | J-Link、ST-Link、DAP-Link | 下载和调试 |
| 调试接口 | JTAG、SWD | 芯片调试接口 |
| RTOS | FreeRTOS、μC/OS、RT-Thread | 实时操作系统 |
| 库 | HAL 库、LL 库、CMSIS | 硬件抽象层 |
4.5 常见考试题型¶
例题 1: 交叉编译是指( )。
A. 在 ARM 上编译 x86 程序 B. 在一种平台上编译出另一种平台的可执行程序 C. 同时编译多个源文件 D. 使用多种编译器编译同一程序
查看答案
答案:B
交叉编译是在一种平台(如 x86 PC)上使用交叉编译器生成另一种平台(如 ARM)可执行程序的过程。嵌入式开发几乎都使用交叉编译。
例题 2: 嵌入式系统启动时,.bss 段的处理是( )。
A. 从 Flash 复制到 RAM B. 清零 C. 保持不变 D. 从 RAM 复制到 Flash
查看答案
答案:B
.bss 段存储未初始化的全局变量和静态变量。C 标准规定这些变量初始值为 0,所以启动代码会将 .bss 段清零。.data 段(已初始化变量)才需要从 Flash 复制到 RAM。
例题 3: Bootloader 的主要功能不包括( )。
A. 初始化硬件 B. 加载应用程序 C. 管理文件系统 D. 固件更新
查看答案
答案:C
Bootloader 主要负责硬件初始化、加载和启动应用程序、支持固件更新。文件系统管理是操作系统的功能,不属于 Bootloader 的核心职责。
要点总结¶
- 交叉编译:在 PC(x86)上编译出嵌入式芯片(ARM)可执行的程序
- 启动流程:复位→初始化堆栈→初始化时钟→复制 .data→清零 .bss→调用 main()
- 内存布局:.text(Flash)、.data(Flash→RAM)、.bss(RAM)、堆、栈
- Bootloader:初始化硬件、加载程序、固件更新
- 常见工具:Keil/IAR IDE、J-Link/ST-Link 调试器、JTAG/SWD 接口
课后练习¶
-
流程描述 :描述嵌入式系统从上电复位到执行 main() 函数的完整启动流程。
-
段分析 :以下变量分别存储在哪个段中?
-
真题演练 :嵌入式系统开发中,将 ELF 格式文件转换为 HEX 文件使用的工具是( )。
下一章预告: 程序写好了,但芯片怎么和外部世界交互?GPIO 控制 LED、UART 串口通信、SPI/I2C 连接传感器——第 5 章见。