1. 为什么嵌入式项目里,最值钱的东西往往没被记下来
干了十几年嵌入式开发,我有个越来越强烈的感受:一个项目最核心的资产不是代码,不是电路图,而是当初那些"为什么要这么做"的决策过程。代码没了可以重写,电路图丢了可以反推,但决策上下文丢了,后面接手的人只能靠猜,猜错了就得交学费。
我见过太多嵌入式项目的真实状态是这样:芯片选型的时候,硬件工程师和软件负责人吵了三天,最后定了一颗 Cortex-M4 内核的 MCU,理由是主频够、外设丰富、供货稳定。这个结论可能就写在某封邮件里,或者干脆只存在几个人的脑子里。半年后新人入职,看到代码里大量使用 DMA 和中断嵌套,忍不住问"为什么不用 RTOS",没人能完整回答,只能含糊地说"当初评估过,好像不太合适"。至于怎么评估的、基于什么条件评估的、否掉 RTOS 的关键指标是什么,全部丢失。
这玩意儿在软件开发领域有个名字,叫 ADR,全称 Architecture Decision Record,架构决策记录。说白了就是一张卡片、一个文档或者一个 commit,把"我们决定做什么、为什么做、当时有哪些选项、最后怎么选出来的"固定下来。这个理念最早是从敏捷和软件架构圈子里流行起来的,Michael Nygard 那篇经典的《Documenting Architecture Decisions》算是启蒙读物。但说实话,这个工具在互联网后端团队里已经挺常见了,在嵌入式领域却依然稀罕。
为什么?因为嵌入式项目有它的特殊性。嵌入式开发通常是软硬件协同,决策链条长,而且很多决定一旦落到 PCB 上、焊到板子里,就"物理不可逆"了——你软件写错了还能改,Flash 烧错了还能重烧,但引脚分配错了、电源方案选小了、Flash 容量买小了,这些是改不动的。越是不可逆的决策,越需要记录决策依据,可恰恰是这种项目,大家越不爱写文档。这背后的原因很现实:嵌入式项目周期紧、调试占时间、硬件迭代慢,开发者习惯性把精力花在"让板子跑起来"上,觉得写文档是浪费时间。
这个观念得改。不光是"文档有价值"这种空话,而是嵌入式项目的技术债里,最大的一笔往往是"决策记忆缺失债"。今天这篇,我把自己在几个嵌入式项目里推行 ADR 的完整思路、模板、踩过的坑全部摊开讲,希望能帮在固件开发、板卡设计、软硬件协同项目里挣扎的朋友们,找到一条让记忆不丢失的路。
2. 嵌入式项目到底"缺"什么记忆
2.1 代码注释和 Commit Message 远远不够
很多人会说,我们项目有代码注释啊,有 git 提交记录啊,怎么会缺记忆?但嵌入式项目里真正关键的决策,往往散落在代码之外的层面。
举个例子,一个典型的电机控制项目,主控芯片选型时对比过 A、B、C 三颗芯片,最后选了 A。代码里你只看到#include "stm32f4xx.h",看不到为什么不用 B。B 的主频更高,但封装是 BGA 不好手工焊接;C 的功耗更低,但 ADC 采样率和 DMA 支持的通道数不够。这种决策写不进代码,commit message 里顶多写一句"更换主控芯片为 STM32F4",背后的三颗芯片对比矩阵、焊接工艺约束、外设需求清单全丢了。
更隐蔽的是软硬件接口层的决策。比如串口 DMA 收发缓冲区的设计,为什么用环形缓冲区,缓冲区为什么定 256 字节而不是 128 或 512。这背后可能有中断延迟的实测数据、有无 DMA 半传输中断的硬件差异、内存占用和通信吞吐量的折中。这种决策直接决定代码结构,如果后来者不知道当初的约束条件,很可能"优化"一下把缓冲区改成 64 字节,结果在高波特率下丢包,查了三天才发现是自己把缓冲区改小了。
2.2 嵌入式项目的"决策生命周期"更长
web 后端项目一个架构决策可能影响的是服务集群怎么扩展,改起来相对灵活。但嵌入式项目不一样,一个决策的生命周期可以从需求分析一直延续到产品退市。硬件方案定了,后面十年的维护都受这个约束。固件架构定了,即使换芯片,核心的设计约束还会延续。
我做车载电子那几年,有个项目里用了外置 EEPROM 存储标定参数,因为当初选 MCU 时内部 Flash 容量刚好够用但余量不大,就设计了一套将参数区放到外部 EEPROM 的方案。这个决策当时没有记录。三年后产品迭代,新 MCU 内部 Flash 大了四倍,完全可以取消外部 EEPROM 降低成本,但没人知道当初为什么要用外部存储——是因为容量不够,还是因为擦写寿命,还是因为抗震动可靠性?结果项目组不敢动这个设计,每台设备继续多花几毛钱的 BOM 成本。几毛钱看着不多,一年出货百万台就是几十万。这不是技术问题,这是记忆缺失造成的真金白银损失。
2.3 团队协作中的"口头知识"陷阱
嵌入式项目往往是软硬件协同,硬件工程师、固件工程师、测试工程师各管一段。大量的决策发生在会议室、实验室、生产线的沟通中。这些场景里说出来的结论,如果没有同步记录下来,就成了团队里的"隐性知识"。隐性知识的问题是它在的时候大家觉得理所当然,一旦相关人员离职、转岗或者记忆模糊,整个团队就面临失忆。
我带过的一个团队,有位资深硬件工程师特别擅长电源设计,他决定板子上所有 3.3V 和 1.8V 电源轨都用 LDO 而不是 DC-DC,理由是纹波控制对 ADC 采样精度影响大。这个决策在团队里人尽皆知,但他离职之后,新来的硬件工程师觉得 LDO 效率低、发热大,改成了 DC-DC,结果 ADC 采样的噪声底噪从正负 2mV 变成正负 15mV,整个传感器的精度指标直接不合格。后来翻遍文档,才在旧版原理图评审记录里找到一句"为保障 ADC 精度,电源方案选用低纹波 LDO"。如果当时有一条结构化的 ADR,记录了纹波实测曲线和精度测试数据,新硬件工程师第一时间就能知道这个约束,根本不会踩这个坑。
3. ADR 的写法与模板,不整虚的直接给方案
3.1 一个可落地的 ADR 模板
标准的 ADR 模板有很多变体,但核心要素万变不离其宗:背景、决策、理由、后果。我在嵌入式项目里用的模板,是在 Nygard 基础上定制过的,增加了"验证方式"和"风险与缓解"两栏,因为嵌入式决策必须绑定验证手段,否则后任者无法判断这个决策现在还有效没有。
我的模板长这样:
# [编号] 标题:一句话描述决策内容 ## 状态 - 提议中 / 已接受 / 已替代 / 已废弃 ## 背景 - 要解决什么问题 - 当前的技术约束、业务约束、时间约束 - 相关的需求和依赖 ## 决策 - 最终选定的方案,一句话说清楚 - 关键参数和关键设计点 ## 备选方案 - 方案A:描述 + 优点 + 否决原因 - 方案B:描述 + 优点 + 否决原因 ## 理由 - 为什么选这个方案,列出决定性的 2-5 个理由 - 有数据就贴数据,有测试结果就贴测试结果 ## 后果 - 正面:带来什么好处 - 负面:接受什么代价,埋下什么隐患 ## 验证方式 - 怎么证明这个决策是有效的(测试项、指标、实验) - 什么条件下需要重新审视这个决策 ## 关联 - 关联的其他 ADR、需求、代码模块、硬件版本这个模板看着比网上很多版本复杂,但在嵌入式项目里很值得。因为嵌入式决策多数是环环相扣的,没有"关联"栏,你很难追溯这套决策网络的上下文。硬件选了某个 MCU,会影响软件用不用 RTOS,会影响驱动怎么分层,会影响测试方案怎么设计。这些关联不写下来,光靠人脑根本记不住。
3.2 选题原则:不是所有决定都配写 ADR
写 ADR 最大的误区是事无巨细都记,最后变成了流水账,没人看。真正值得写 ADR 的决策,至少要满足一条:影响面大、代价高、难逆转。
我整理过一套嵌入式项目的 ADR 选题过滤清单:
- 芯片/模组选型:替代成本高,直接影响 BOM、开发周期、后续维护,必须写
- 操作系统方案:裸机还是 RTOS,选哪个 RTOS,做不做二次开发,必须写
- 通信协议设计:私有协议还是标准协议,定长还是变长,重传机制怎么定,必须写
- 存储布局与分区:Flash 分区、升级方案、掉电保护策略,必须写
- 电源与低功耗架构:电源域划分、休眠机制、唤醒源选择,必须写
- 关键算法方案:传感器融合算法选型、控制算法结构,建议写
- 工具链与构建系统:IDE、编译器、构建框架的选择,建议写
- 测试策略:自动化测试框架、HIL 测试方案、产测方案的架构性选择,建议写
至于某个函数的实现用循环还是递归,某个 GPIO 是开漏还是推挽,某处代码要不要加断言,这些细节没必要写 ADR。写多了反而稀释注意力。判断标准很简单:如果明天这个决策被推翻,需要改动的代码/硬件面积有多大,连带风险有多高。大,就写;小,就别折腾。
3.3 时机与流程:嵌入式项目里什么时候写最舒服
有人习惯决策做完了再补写 ADR,我不太建议。最佳时机是决策刚定下来、还没动手实施的时候写。那时候讨论的细节、对比过的数据、否决的选项都在脑子里,写起来最快最完整。等代码写完、板子调完再补,基本等于考古,能回忆出七成就算不错了。
我在项目里的实际操作方式也很简单:每次架构评审会结束时,当场认领一个 ADR 编写人,要求两天内提交初稿。如果评审结论触发了 ADR 的状态变化,比如原来的方案被否了、换新方案了,那就在下次评审里安排更新对应 ADR。这个流程不复杂,但一定要有人负责跟踪,否则很容易不了了之。
ADR 的存放位置也很关键。嵌入式项目和 Web 项目不太一样,Web 项目可以直接放在仓库的docs/adr/目录,因为整个团队都在一个代码仓库里协作。但嵌入式项目的软硬件团队往往不是一套仓库,硬件工程师不一定看 Git。我的选择是:软件团队的 ADR 放在固件仓库的docs/adr/,硬件团队的 ADR 放在硬件版本管理的文档目录里,同时在项目共享文档区建一个总索引。这个总索引是给所有人看的,谁要查某个决策,先从索引里找到对应 ADR 编号,再去对应仓库看细节。
4. 嵌入式 ADR 实战:三个典型决策拆解
4.1 芯片选型类 ADR:从"拍脑袋"到"有据可查"
芯片选型是嵌入式项目里最典型的"高代价不可逆"决策,也是最应该有 ADR 的场景。我分享一个实际做过的例子,项目是一款便携式数据采集设备,需要 4 路模拟量采集、1 路 CAN 通信、低功耗、批量成本控制在某个范围内。
候选芯片有三颗:A 是 Cortex-M0+ 内核,主频 48MHz,价格低,但片上 ADC 只有 12 位,需要外挂 ADC 芯片;B 是 Cortex-M4 内核,主频 80MHz,内置 16 位 ADC,价格是 A 的两倍;C 是 Cortex-M7 内核,主频 200MHz,性能过剩,价格最高。
这个选型过程如果只写一个结论"选用 B",后面的人永远不知道为什么。ADR 里我记录了完整的对比矩阵:
| 维度 | 方案A(M0+外挂ADC) | 方案B(M4内置16位ADC) | 方案C(M7内置16位ADC) |
|---|---|---|---|
| BOM成本 | 低,但需要外挂ADC和配套电路 | 中 | 高 |
| PCB面积 | 大,外挂ADC占面积 | 小 | 小 |
| 开发周期 | 长,需要调试SPI/ADC驱动 | 短,内置ADC | 短 |
| 功耗 | 低 | 低 | 偏高 |
| ADC精度 | 12位,需校准 | 16位,满足需求 | 16位 |
| 供货风险 | 中 | 低(多家渠道) | 高(单一来源) |
最后选 B 的决策理由是:BOM 成本虽比 A 高约 15%,但节省了外挂 ADC 的 PCB 面积和调试周期,且 16 位 ADC 满足精度需求,不需要额外的校准和补偿算法。C 性能过剩,功耗和成本都是负担。
这个 ADR 的价值在三个月后立刻体现出来了。B 芯片出现了供货紧张,采购部门提出换用 C 芯片的备选方案。但因为 ADR 里记录了当初选 B 的决策依据,团队很快就能判断:C 芯片功耗偏高,如果换用,需要重新评估电池续航;同时 C 供货单一来源的风险更大,不比 B 好。于是果断放弃换型,改为采购备货。没有 ADR 的话,可能又要开三轮会才能达成共识。
4.2 RTOS 与裸机路线之争:必须白纸黑字定下来
嵌入式项目里每次选 RTOS 都能引发一番争论,而且争论的核心往往不是技术本身,而是"当初到底为什么不用 RTOS"这种失忆梗。
我参与过的一个电力仪表项目,就经历过裸机转 RTOS 的过程。当时团队里有一部分人主张裸机到底,理由是简单可控、内存占用少、没有调度开销;另一部分人坚持上 RTOS,理由是功能模块越来越多,裸机的超级循环已经难以维护。
最后拍板用 RTOS,这个决策对应了一条 ADR。我记录了关键的决策理由:功能模块从 8 个膨胀到 20 多个,裸机的超级循环里while(1)已经膨胀到三千多行,新增功能极易引入优先级和时序问题;同时目标 MCU 的资源足够跑 RTOS,RAM 占用约多 4KB,完全能接受。
更加关键的是,ADR 里把这个项目的"任务划分基本原则"也固定了。比如:高实时性任务(电流采样、保护逻辑)用高优先级任务,界面刷新用低优先级任务,不允许在中断服务函数里做耗时操作。这些原则写下来之后,新人开发时就有了行为准则,不会出现"在定时器中断里刷 LCD 导致采样抖动"这种低级错误。
4.3 Flash 分区与升级策略:最容易变成烂账的决策
嵌入式产品基本都逃不过 OTA 升级或者本地升级。Flash 怎么分区、Bootloader 和 App 怎么划分、升级失败怎么回滚,这个设计一旦定下来,整个生命周期都要跟着走,而且和芯片型号强绑定——换了 Flash 容量、换了芯片,整个方案可能都要推翻。
我见过一个活生生的烂账案例。某项目在量产一年后,产品需要增加一个新功能,固件体积预计要扩大 40%。但当初 Flash 分区时,App 区的大小是拍脑袋定的,没留余量。结果新功能加不进去,被迫做一次"重新分区 + 全量升级"的特殊版本,还要考虑升级过程中断电导致变砖的风险。整个过程折腾了半个多月,其实当初只需要多留一点空间或者做一个可扩展的动态分区设计。
这个教训催生了我现在坚持的一条 ADR:Flash 分区方案必须写。我在 ADR 里记录的内容包括:分区表的起始地址、大小、各分区用途、升级流程、回滚方案、预留空间比例、以及"为什么预留这么多"——通常是根据历史固件增长速度估算的。这样后续者想调整分区时,先读 ADR,明白当前分区的边界条件和历史原因,再评估能不能动。
还有一个容易被忽略的点,掉电保护策略。如果升级过程中突然断电,系统怎么恢复?是双备份还是单备份加引导恢复?这个决策如果不说清楚,后面的人写升级逻辑时很容易默认"升级失败就出厂恢复",把用户数据全清了。ADR 里把这个决策和理由写清楚,就能避免这种灾难。
5. 推行 ADR 路上的拦路虎与破解办法
5.1 "没时间写"综合征
推行 ADR 遇到最多的阻力就是一个字:忙。嵌入式项目节奏紧,大家觉得写文档不是干活。我承认,写 ADR 确实要花时间,一条完整 ADR 写下来,半小时到一小时是正常的。但我不想用"磨刀不误砍柴工"这种大道理来压人,只想算一笔账:
一次没有 ADR 支撑的架构决策讨论,通常要开几次会,每次一小时,好几个人参加。如果这个决策三个月后被重新质疑,又要开会。已经写过 ADR 的决策,新成员可以直接读文档,省掉至少一次会议的时间。一条 ADR 抵消一场会,这个投资回报率是正的。
更实际的做法是降低写入门槛。不要求一开始就写得特别全,先建个 ADR 文件,把决策结论写上,再慢慢补背景和理由。只要文件存在,后面的人就有线索可循。最怕的是直接不建,让所有信息只停留在讨论里。
我个人的习惯是,每次架构讨论结束后,顺手在共享文档里开一条 ADR,标题先写上,结论先写上,理由用关键词记一下,后续有时间再完善。这个"种子"机制很管用,很多 ADR 就是在种子的基础上慢慢长大的。
5.2 ADR 没人看怎么办
写下没人读,是所有文档的通病。ADR 要避免这个问题,唯一的办法是把它嵌入到工作流里,而不是作为独立文档存在。
我在团队里做过几个动作:
- 每次新功能设计评审前,要求评审材料里必须列出受影响的 ADR 编号。如果没有相关 ADR,就说明这是一个新决策,需要在新功能开发前补一条 ADR。
- 代码评审时,如果代码逻辑和设计目标有关联,要求 PR 描述里引用 ADR 编号。
- 新人入职培训时,安排一次"项目架构决策阅读"环节,把项目里已有的 ADR 从头读一遍,同时讲解每条 ADR 对应的硬件设计和代码模块。
这些动作的核心目的是让 ADR 成为工作流程的一部分,而不是摆在文档库里的摆设。
5.3 ADR 过期了怎么办
嵌入式项目生命周期长,一个 ADR 写完之后,可能在一年后因为器件停产、需求变更、技术迭代而失效。如果失效的 ADR 不处理,最后就会变成一堆"僵尸文档",反而误导后人。
我在流程里设置了 ADR 的"定期体检"机制。项目里程碑节点,包括方案评审、样机评审、量产评审,都检查一遍已有的 ADR,看有没有状态需要更新。如果一个 ADR 对应的决策已经被实际执行所推翻,比如换了主控芯片,那么旧 ADR 状态标为"已替代",新 ADR 记录新决策。这样一条条串起来,整个决策的历史脉络就清晰了。
对比那些"文档只写一次,之后再也不管"的项目,有状态管理的 ADR 库才是真正有价值的决策记忆库。
5.4 团队协作中的边界问题
嵌入式项目的软硬件团队,写 ADR 的边界容易模糊。比如引脚分配是硬件牵头定的,但直接影响固件开发。谁负责写这条 ADR?我的经验是"决策主责人"写,相关方审阅。引脚分配这件事,硬件工程师主责,但必须经过固件工程师的确认,ADR 里要有固件工程师的签名或者评审记录。
同样一个决策,如果软硬件理解不一致,ADR 就充当了"共识固化"的工具。比如某个 GPIO 的默认电平,硬件工程师认为是上拉、固件工程师认为是下拉,这种不一致如果不通过 ADR 定下来,等板子贴出来才发现,就是一轮飞线或者返工。
我见过最夸张的一次,硬件原理图里把 boot 引脚默认设计为高电平,固件工程师却一直按默认低电平启动来调试,整整两周找不到原因。最后翻原理图才发现,硬件设计认为默认高电平更合理,但根本没告诉软件。如果当初在硬件设计评审时记录一条 ADR,哪怕只有一句话,也不会浪费两周时间。
6. 从一条 ADR 到一套决策记忆库
6.1 ADR 之外的配套档案
ADR 是决策记忆的核心,但光有 ADR 还不够。我逐渐建立了一套"决策记忆库"的配套档案体系:
- 需求追溯表:把需求、ADR、代码模块、测试用例关联起来
- 硬件版本变更记录:每次硬件改版,记录对应的 ADR 变化
- 实验、测试数据存档:ADR 里引用的验证数据,原始数据文件要归档
- 故障复盘记录:线上问题、生产问题复盘时,如果发现是决策层面的问题,关联到对应 ADR
这套档案不要求做得特别复杂,只要关系和数据是连贯的,就能在关键时刻提供完整上下文。
6.2 小团队怎么低成本起步
很多读者可能觉得,这套做法适合大团队,自己就三五个人的小项目,值得搞吗?我的回答是:越小的团队越值得,因为小团队的记忆往往完全依赖核心一两个人。核心成员一旦变动,整个项目的决策上下文就断层了。
小团队的起步方式可以非常轻:
- 在 Git 仓库里建一个
docs/adr/目录,用 Markdown 文件存 ADR,每条一个文件,编号 0001 开始 - 遇到关键决策时,花 20 分钟写一条 ADR,不用追求完美
- 每次代码提交时,如果和某条 ADR 相关,在 commit message 里加
ADR-0001的引用 - 项目例会时,如果有 ADR 状态变化,花 5 分钟同步一下
这套流程不需要工具链,不需要额外平台,坚持下来,项目历史就会被一点点记录下来。等团队规模扩大、人员更替的时候,会发现这些 ADR 是整个项目最宝贵的入职教材和决策底稿。
6.3 工具选择与自动化
如果团队想更进一步,可以引入一些轻量工具:
- Git + Markdown:最零成本的方案,ADR 跟着代码仓库走,天然有版本管理
- 文档平台(如 Confluence、语雀、飞书):适合跨团队共享查阅,但要注意和代码仓库的同步
- 带模板的 ADR 生成工具:命令行工具可以快速生成 ADR 模板文件,省去重复输入格式的时间
我个人最喜欢的是"ADR 文件跟随代码仓库"这种模式。它的好处是,ADR 和代码的变更历史天然关联,你可以通过 git log 看到某条 ADR 是哪次提交引入的,当时的代码是什么状态。这种时间线上的关联性,是独立文档平台很难模拟的。
7. 写在最后的实操体会
项目做个五六个之后,我越来越清楚一件事:架构决策记录这件事,难的不是方法和格式,而是把它当成项目基础设施的一部分来对待。就像你画原理图不会省略电源滤波电容,写代码不会省略错误处理,做项目管理也不应该省略决策记录。
我在实际项目里最深的体会是,ADR 的力量不是立刻显现的。前几个星期你可能感觉不到什么变化,甚至觉得是在浪费时间。但半年后、一年后,当新人快速上手、当架构争论能快速收敛、当人员变动没有造成知识断层,你就会意识到,这些看起来不起眼的文档,已经把团队从"靠人脑记忆"升级成了"靠制度记忆"。
最后分享一个我在每个项目启动时都会做的小动作:在项目仓库初始化的时候,就把docs/adr/目录建好,放一个 README 简要说明 ADR 的格式和流程,再放一个0000-template.md模板文件。这个动作只需要十分钟,但它像一个提醒器,让团队从一开始就知道,这个项目不是"先做起来再说",而是从第一天就重视决策的记忆。
等到项目结束、产品落地、团队解散或重组那天,你会特别庆幸当初做了这个决定。因为那些 ADR 还在那里,记录着这个项目从无到有的每一个关键选择,等待着下一个接手的人来翻阅。