tags: 嵌入式,固件,ARM,单片机,新手入门,代码阅读 category: 嵌入式 type: original series: learning-method
嵌入式新人怎么读懂一个完整固件项目?
一句话: 拿到一个上万行的固件项目不要从目录结构开始读。跟着 CPU 上电的执行顺序走——从
main()的第一个函数调用开始,顺着调用链一层层往下,直到硬件寄存器。本文给一套可复用的阅读方法论。
系列文章:《嵌入式学习方法》 — 怎么读固件项目、英语术语速查——新人上手指南
✅ 别按目录读,跟CPU跑 📄 查表即用
@[TOC]
适合谁读:适合嵌入式开发者、单片机初学者及遇到类似问题的工程师
新手最常犯的错:从目录开始读
打开项目,看到这样的目录结构:
User/ Module/ Drive/ Libraries/ Sys/正常的反应是:先打开Libraries/看有什么库,然后翻Drive/看驱动……翻了一圈,每个文件都看了几行,全忘了,一脸懵。
问题在于:你在读目录,不是读流程。
嵌入式固件和书不一样。书的页码是顺序的,从第 1 页读到第 100 页就行。固件的执行顺序是 CPU 决定的——你该跟着 CPU 走,不是跟着目录走。
正确方法:跟着 CPU 上电跑一遍
芯片上电后的执行路径是固定的:
上电 → 启动文件(向量表) → SystemInit() → main()所以第一个打开的文件应该是main.c。
第 1 步:找到初始化函数,画出执行顺序
打开main.c,找到初始化函数(通常叫App_Init()或System_Init()),把里面的调用顺序列出来:
App_Init() ├── 1. 配置复位行为 ├── 2. 初始化系统时钟 (PLL) ├── 3. 初始化 GPIO 引脚 ├── 4. 初始化 Flash 存储 ├── 5. 初始化 I2C 从机 ├── 6. 初始化 SPI 主机 ├── 7. 初始化 ADC ├── 8. 初始化外设芯片 (比如激光驱动器) ├── 9. 初始化定时器 └── 10. 使能看门狗这个顺序本身就告诉你很多东西:
- GPIO 在第 3 步,I2C 在第 5 步——因为 I2C 需要 GPIO 先配好引脚
- Flash 在第 4 步——后面外设初始化可能要从 Flash 读参数
- 看门狗在最后——前面初始化可能耗时很长,先开狗会误复位
不用看代码细节,先看懂"谁依赖谁"。
第 2 步:看懂主循环在干什么
int main(void) { App_Init(); while (1) { WdtRefresh(); // 1. 喂狗 App_ProcessI2C(); // 2. 处理主机命令 App_State_Run(); // 3. 状态机流转 App_SafetyCheck(); // 4. 安全检查 if (g_bTick100ms) { // 5. 每 100ms g_bTick100ms = 0; App_Monitor_Run(); // 读传感器 + 判告警 } } }主循环就是一个死循环,分成两类任务:
| 类型 | 做什么 | 频率 |
|---|---|---|
| 每次循环必跑 | 喂狗、收命令、状态机、安全检查 | 尽可能快 |
| 定时跑 | 读传感器、更新监控数据 | 100ms 一次(用定时器标志位触发) |
这时候你就知道:哪些事紧急(每次跑),哪些事可以慢点做(定时跑)。
第 3 步:选一个信号,从头追到尾
选一个你关心的物理量,顺着调用链追:
物理世界 → 硬件电路 → ADC 引脚 → ADC 驱动 → 换算函数 → 全局变量 → 显示/上报比如模块温度:
NTC 热敏电阻 → VREF → 分压电阻 → AIN7 引脚 → ADC_Read(ADC_CH_NTC) // ADC 驱动 → ADC_to_Temperature(adc_code) // 查表 + 插值 → g_i16Temperature // 全局变量 → CMIS 寄存器 0x0E-0x0F // 报告给主机追完一条完整的链路,你就能理解好几个文件之间的关系。而且这种理解是串联的——下次看其他信号(电压、电流)是同样的模式。
第 4 步:遇到不懂的,只看接口,不看实现
比如你在main.c里看到:
Drv_PI12030_Init();不要立刻跳进去看实现。先记住:它初始化了一个叫 PI12030 的外设芯片。
等到你追某个信号追到 PI12030 时,再进去看drv_pi12030.c。这时候你带着问题进去("我想知道它怎么设电流"),比无脑通读效率高十倍。
原则:先知道"这个函数干什么",再看"它怎么干的"。
第 5 步:画出文件之间的依赖关系
读完一轮之后,你应该能画出这样的图:
main.c ├── app_state.c 状态机 ├── app_monitor.c 监控采集 │ ├── drv_adc.c ADC 读数 │ └── drv_pi12030.c SPI 读传感器 ├── app_cmis.c I2C 寄存器表 │ └── drv_i2c_slave.c I2C 从机中断 └── drv_flash.c 参数存储每个箭头代表一个调用关系。有了这张图,你就有了整个项目的"地图"。
总结:五步法
| 步骤 | 做什么 | 目的 |
|---|---|---|
| 1 | 打开main(),列出初始化顺序 | 看懂谁依赖谁 |
| 2 | 看懂主循环的任务分配 | 知道哪些事紧急、哪些定时做 |
| 3 | 追一个信号从硬件到软件 | 建立文件间的串联理解 |
| 4 | 遇到不懂的先记接口,不深究实现 | 保持节奏,不陷入细节 |
| 5 | 画出文件依赖关系图 | 建立项目的完整地图 |
核心就一句话:别按目录读,跟着 CPU 跑一遍。CPU 不会骗你——它从哪开始执行,你就从哪开始读。
实测对比:按目录读: 2周还懵 | 按CPU执行顺序读: 1天串通15个文件
有用的话点个收藏,下次调试直接用。有问题欢迎评论区交流,看到了都会回。
下一篇:《查表即用》——怎么读固件项目、英语术语速查——新人上手指南