ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI时代嵌入式C++工程师的进阶之路:从编码到架构决策

AI时代嵌入式C++工程师的进阶之路:从编码到架构决策

1. AI渗透进嵌入式研发之后,我看到的“改动”和“不动”

1.1 最先被AI改变的,是嵌入式研发链条里最“软”的部分

我在嵌入式这个行当里待了十多年,给各种MCU写过裸机代码,也折腾过嵌入式Linux,做过驱动、中间件,也被应用层折磨过。所以当AI编程工具开始在圈子里流行的时候,我最大的感觉不是“饭碗要没了”,而是“嵌入式研发里头,终于有一批活儿可以甩给AI去干了”。

这批活儿有一个共同特点:它们是软的。所谓“软”,不是指软件的意思,而是指它们不依赖具体的硬件环境,不依赖万用表、示波器、逻辑分析仪,不需要你坐在板子前面反复烧录验证。常见的比如:寄存器描述文件转成结构体定义、根据数据手册写驱动骨架、生成常规的单元测试用例、给代码补注释、把一段逻辑从C风格改写成现代C++风格。这些事情本质上是在做模式识别和文本转换,AI做得又快又稳,比人肉手工敲效率高了一个量级。

举个例子,我之前在做一个传感器数据采集模块的时候,数据手册里给的寄存器定义将近两百个。以前我的做法是新建头文件,对着PDF一个个敲宏定义、结构体、位域,半天时间就耗进去了。现在用AI辅助,把寄存器表格截图丢过去,让它生成C语言的结构体定义和偏移量宏,再人工核对一遍关键字段,一个上午能省下至少三个小时。这种效率提升是实打实的,不是“以后也许能用”,是当下就能用的程度。

1.2 那些AI很难碰的“硬骨头”依然稳如磐石

但真正到了板子上,AI的局限性立刻暴露出来了。嵌入式研发和纯互联网后端研发最大的不同在于:我们面对的不只是代码逻辑,还有物理世界的电压时序、时钟频率、中断优先级、内存布局、总线冲突、电源噪声,这些东西在AI生成的代码里面几乎是不存在的。

我举一个特别典型的例子:中断服务函数。AI能帮你写一个ISR的逻辑,但它不会告诉你这个中断号在某个具体芯片型号上有几个保留位,不会告诉你这个外设的中断标志位要软件清零还是硬件自动清零,更不会告诉你在高优先级中断里调了一个带锁的函数会不会导致死锁。这些全部要靠在手册上逐字核对、在逻辑分析仪上看波形、在调试器里看调用栈才能确认。

还有一类是时序问题。嵌入式系统里经常有严格的时序窗口:比如某个外设要求片选信号拉低至少100ns之后才能开始读数据,而你现在用的这套时钟配置算出来的指令周期就是临界值,这个时候是调整时钟树还是在外设上插入等待周期,需要的是对微控制器内部总线架构的理解,而不是对语法的理解。

所以我的看法一直没变:AI进入嵌入式研发,改变的是一线工程师的“体力劳动密度”,并没有改变这个方向的核心门槛。硬件仍在,时序仍在,晶振仍在,芯片手册的错误和勘误更新也仍在。

1.3 真正被改写的,是嵌入式工程师的工作时间分配

如果只看到“AI帮我把代码写了”这一层,那格局就小了。我认为AI带给嵌入式研发的最大改变,是把工程师的时间重心从“写代码”推向了“做评估”。

以前我们的一天大概是这样的:上午看手册,下午写代码,晚上调试,加班改bug。现在有了AI工具之后,本来一天的编码工作量,压缩到半天不到。而省下来的那一半时间,不会自动消失,它会转向另外一些过去没空做的事情,比如:

  • 评估一种新的通信拓扑是否适合当前产品需求;
  • 分析现有模块之间的耦合度高不高,拆了会不会更合理;
  • 对比RTOS选型的时候,对内存占用、调度延迟、生态成熟度做更细的权衡;
  • 把代码层面的问题拉高到系统层面去推演,某个功能设计改动的成本到底有多大。

换个直白的说法:AI把嵌入式工程师的“体力”解放出来之后,拼“脑力”的部分就被放大了。而架构能力,恰恰是嵌入式和嵌入式系统里最依赖“脑力”的部分。这也引出标题里我最想表达的那件事——AI入局之后,真正值得持续投入的方向不是和AI比谁代码写得快,而是走到架构这条路上,去掌握那些AI暂时缺乏判断力的全局性决策。

2. C++在嵌入式里的根,比很多人想象的要深得多

2.1 嵌入式场景不是“会写语法”就能覆盖的

聊到架构之前,必须先C++在嵌入式里的地位,因为这是标题里绕不开的“C++工程师”四个字。嵌入式领域围绕C++一直有一个奇怪的争议:有人觉得嵌入式就应该用C,C++太“重”,用不好容易出事;也有人觉得C++早就是嵌入式主流了,还在纠结这个是不是没跟上时代。两边各说各话,但实际项目里C++的身影已经非常普遍了,尤其是在嵌入式Linux、复杂中间件、自动驾驶控制单元、工业控制器这些体量稍大的系统里,C++的使用率非常高。

但有一点需要看清楚:嵌入式的C++和桌面端的C++虽然同源,使用场景却大不相同。桌面端可以随便用异常、RTTI、大块动态内存分配,嵌入式没那么奢侈。资源受限是第一约束:Flash空间要精打细算,RAM要按块分配,实时性要求高的模块里几乎不能出现锁竞争或动态分配导致的延迟抖动。

我自己在一款基于Cortex-M系列的处理器上维护过一个2万多行的C++模块,为了控制代码体积,整个工程是关掉异常、关掉RTTI的,动态内存只在初始化阶段统一分配到位,运行期new是禁止的。在这个约束条件下写C++,你会被迫把一半的精力放在“如何让抽象不产生额外成本”上面。这个能力,AI生成的代码很难替你兜住,因为它没有对你当前硬件平台的Flash占用、SRAM预算、中断延迟指标负责。

2.2 实时性、确定性、内存边界,是C++在嵌入式的立足之本

为什么嵌入式会越来越多地用C++,而不是继续留在纯C里面?我觉得最核心的驱动不是“喜欢现代语法”,而是C++提供的抽象能力确实能在不牺牲性能的前提下,让复杂系统的结构更清晰。

举一个常见的场景:设备里要维护一个多状态机的调度网络,每个状态机有自己的状态转移表、超时计时、事件队列。用C语言写,会有一大堆结构体和函数指针,时间长了以后,执行流变得极度隐晦,跨模块状态耦合很难理清。换C++之后,状态机本身抽象成一个类模板,不同类型的设备只需要传入各自的转移表和事件处理器,公共逻辑全部收拢到基类里,上层维护者只需要关心“这个设备特有的规则”,而不是重复阅读一坨机械的switch-case。代码量不一定会减少,但认知负担绝对是下降的。

这个例子说明一个很实际的事情:嵌入式系统越来越复杂,控制逻辑和通信协议的层次越来越深,纯C这种偏平铺直叙的方式,在系统的可维护性上会渐渐跟不上节奏。C++的价值在“恰到好处的抽象”上——它允许你在必要的层级建立边界,而这些抽象在编译期就能被压平,运行时几乎零开销。这就是现代嵌入式项目里C++地位越来越重要的根本原因:不是审美选择,是系统工程复杂度倒逼出来的结果。

2.3 AI生成C++代码的真实水平:能补函数,难接硬件

那么AI在这个环节能干什么?以我实测大半年插件、对话式生成工具的体感来说,它在“给定接口描述去补全一个模块内部实现”这件事上已经做得不错了。你给它一个类定义,告诉它大致逻辑,它能生成一个看起来挺像样的成员函数实现,模板、标准库容器也用得溜溜的。

但一旦涉及到硬件的具体行为,AI就开始“一本正经地胡说八道”。我有一次让它生成一块外部Flash的驱动代码,接口、擦写流程、状态检查的逻辑框架都在,但到了读状态寄存器的时序部分,它写了三个在手册上根本不存在的延时参数,还自洽地加了注释说“等待内部操作完成需要足够的延时”。这种错误不是语法层面的,是芯片物理行为层面的,必须靠人来纠正。

更关键的是结构性的东西。AI在看到“这段代码用了双缓冲机制”的时候,能照着现有模式的样式写第二份。但它很难回答“为什么这个模块需要双缓冲”“能不能用环形队列替代”“如果主频降低20%这套机制还扛不扛得住”这类问题。而这些问题,恰好是嵌入式C++研发中要做“架构决策”的起点。换句话说,AI在“写代码”这个较低的层次可以帮忙,但在“为什么这么写”这个更高的层次,它目前只能复述已有模式,不能自行推导出适合本项目的结构。

3. 当代码实现成本被AI拉低,架构决策才是真正的稀缺品

3.1 代码不再稀缺,稀缺的是“结构正确”

我观察到一个很有趣的变化。几年前我们招人,面试重点之一就是编码能力:白板题、算法题、手写链表、手写状态机,考察的是“一个人能不能把逻辑用代码表达清楚”。AI成熟之后,这种筛选方式的区分度会越来越低——因为单纯的编码表达,AI已经能干80%的活。但反过来,另一个能力正在变得越来越重要:面对一个模糊的需求、一堆互相冲突的约束、一套需要支持五年的产品规划,你能否给出一个结构正确、且能被团队长期维护的软件骨架?

这种能力,我把它归结为“结构判断力”。具体表现是:你知道哪些模块应该独立成服务,哪些逻辑应该下沉到驱动层,哪些状态应该由上层统一管理,哪些接口不能轻易暴露给任何调用方。它和编码能力有关系,但不完全等于编码能力。一个代码写得很漂亮的人,不一定能设计出一个二十年不被推翻的模块边界;一个架构能力强的人,未必能手写各种精妙的模板元编程技巧。但在AI时代,前者的稀缺性在下降,后者的稀缺性在快速上升。

举一个我亲身经历过的反例。当时接手一个远程固件升级模块,之前的开发者把升级包的下载、校验、写入Flash、回滚标记、日志上报全部堆在同一个类里,两千多行。功能上没有问题,每个函数写得也不算烂。但到了要支持第二款硬件平台的时候,问题来了:Flash布局不同、下载通道不同、升级策略不同,所有代码都要跟着微调,改完还要担心影响原有平台的稳定性。这就是典型的“功能正确但结构错误”——它不是个bug,但它是一个比bug更昂贵的时间炸弹。

这种结构问题AI不是完全发现不了,它能在静态分析中提示你“这个类太大”“这段代码有重复”,但给不出“把升级策略做成可插拔、把Flash操作抽象成平台接口、把日志通道单独剥出去”的完整重构方向。因为这种方向需要对产品未来演进方向有预判,需要对两个硬件平台的差异有实质认知,而这些都超出了“代码补全”的能力范围。

3.2 嵌入式架构到底是什么:从分层到权衡

很多人一听到“架构”就往分布式、微服务、Cloud Native那边联想,仿佛只有大厂后台才配谈架构。放到嵌入式的语境里,架构这个词完全没那么玄,但也没有更简单。它本质上做的是三件事:分层、抽象、权衡。

分层解决的是“变化如何隔离”。传感器类型会变、通信协议会换、控制策略会调整、存储介质可能从Nor Flash换到eMMC,每一类变化都应该被控制在一个相对的边界内部。驱动层不关心业务语义,应用层不直接操作寄存器,中间件层提供稳定的接口把两边衔接起来。一句“分层”,落到工程里就是上百个接口签名设计、头文件依赖关系管理、编译模块划分。

抽象解决的是“复杂性如何降低”。比如同一个产品系列里,有3种通信接口:CAN、RS485、以太网。如果不做抽象,上层代码就会到处出现#ifdef分支或者switch判断,新增一种接口就要改全链路。抽象之后,上层只对接一个“发送报文”的接口,具体走哪种物理链路由配置决定,新增接口只需要实现统一契约。这就是典型的用抽象换可扩展性。

权衡是最难的。嵌入式架构里没有银弹,每个选择都是取舍:内存够不够,多抽象一层可能就多消耗几十字节RAM;实时性允不允许,引入一个通用消息队列可能就增加几微秒延迟;团队维护成本接受度如何,完全自研轻量中间件理论上最优,但是不是值得投入两三个月人力?架构师干的就是这种处处对不上账的活,不同方案没有绝对对错,只有“在给定约束下哪一个更合理”。

我在做架构评审的时候,有时候会让提问的工程师先把“接口层要稳定”这个执念放下,先算一笔账:这个模块以后真的会扩展吗?会换方案吗?如果大概率三五年不换,那保持简单硬编码完全合理。换句话说,好的架构不是最花哨的架构,而是在正确的时间用正确的成本做出的正确取舍。

3.3 我为什么说“不是替代C++工程师,而是走向架构路”

把前面几节的内容串起来,就能回答标题里的那个设问:AI既然进入了嵌入式研发,它到底会不会替代C++工程师?

我的答案很明确:替代的,是那些只把C++当成“编码工具”、不深入理解系统和产品形态的岗位。而一个持续走在架构路上的C++工程师,反而会因为AI的出现变得更值钱。原因也很简单:AI解决的是“怎么做”,架构解决的是“做什么和为什么做”。前者是执行成本,后者是决策成本。AI降低了执行成本,决策成本就相对浮出水面,成为更核心的竞争点。

换个角度说,我在这个行业里持续写的C++代码,本质上是我理解系统的一种方式。通过写一个中断驱动的DMA收发模块、一个可重入的日志缓冲、一个编译期就能计算出大小的配置结构体,我真正在做的不是复制语法,而是验证自己对硬件行为、对系统结构、对边界条件的理解是否正确。代码是实现路径,架构才是目的地。所谓“路上”,就是指代码背后,一整套关于系统设计、模块划分、成本权衡的思考体系——这套体系才是长期职业价值所在,它不太可能被AI快速覆盖,反而会在AI时代被放大。

4. 在AI辅助时代,实操层面的成长路径调整

4.1 让AI干“确定”的事,自己干“不确定”的事

前面讲了这么多理念,最后聊一些实操层面可以落地的做法。我现在的日常工作习惯是,把任务分成两类:一类是“确定性的翻译和转换”,另一类是“不确定性的设计与取舍”。

确定性的任务大胆交给AI:寄存器定义转结构体、从文本描述生成函数框架、把旧风格C代码迁移到C++封装、写单元测试的边界用例、根据代码注释生成文档。这类任务的特点是输入输出边界清晰,评价标准客观,AI产出之后人工核对一遍成本很低。

不确定性的任务自己扛住:系统模块应该怎么划分边界、中间层接口要不要做成动态注册、某块共享内存的访问策略是不是需要加保护、当任务延迟抖动超标时是改调度器配置还是重新设计任务优先级、面对下一代硬件预留多少抽象层才不算过度设计。这些问题没有一个标准答案,需要结合具体产品、团队、成本、时间窗口来综合判断,才是架构工作的真正核心。

这里有一个小技巧:当你准备让AI写一个跨模块的函数时,先在注释里把“你希望它遵守的边界”写清楚,例如“此函数只访问驱动层提供的接口,不直接操作寄存器”。AI会大概率遵循这个约束。这其实是在用AI训练自己的边界感——把模块契约想得越清楚,代码落地越顺手,整个系统的结构也越可控。

4.2 一套可以落地的嵌入式架构复盘方法

成长最快的方式之一,就是对已有的代码做架构复盘。我的复盘方法不是看代码本身,而是“把代码翻回设计图”。具体分几步:

第一步,从一个完整的项目里选一个核心子系统,比如通信协议栈、存储管理、电源管理,先不读具体实现,而是读头文件、接口List、模块之间的依赖关系,试图还原出设计者原来的分层逻辑。

第二步,对照源码验证自己的理解。看看哪些地方依赖关系和设计意图一致,哪些地方出现了“破环”——比如上层模块直接包含了下层驱动的头文件、某个本该隔离的硬件平台信息被硬编码到了应用层。

第三步,也是最值钱的一步,问自己:如果让我重新设计这个子系统,我会保留哪些边界,会重构哪些边界?改动后的收益是什么?代价又是什么?把这个答案写成一页纸的文档,存进自己积累的设计决策库。

在这个过程里AI能帮什么?它很适合当一个“依赖关系分析助手”。你可以让它扫描某个目录下的#include关系,生成模块之间的依赖矩阵;也可以让它找出所有跨层调用的函数清单。但最终的架构判断还是要自己做,因为AI能告诉你“A和B有依赖”,但它不会告诉你“这个依赖是合理的,因为A本质上是B的业务视图”。

4.3 几个值得长期沉淀的架构视角

走到架构这条路上,有一些视角值得长期积累,它们往往不是从教科书里来的,而是在一次次项目复盘、一版版升级维护里沉淀出来的。

硬件演进视角。每一代硬件升级,主频更高、内存更大、外设更丰富,但能耗预算可能在收紧。架构上做的抽象,能不能在未来换一颗更便宜的MCU时保住大部分应用代码不动?如果答案是不能,那这个抽象可能需要重新考虑。学会“基于接口而不是基于实现”去设计,是跟随硬件演进而不被拖垮的关键。

实时性预算视角。嵌入式系统的时间就是资源,架构上的每一层封装都会引入开销。一个经过了层层抽象的函数调用,在实时任务里可能成为压垮调度时序的最后一根稻草。架构设计时需要给“开销敏感路径”留一条直通通道,让高频函数绕过不必要的中间层。

团队沟通视角。架构不是画完图就结束的,它是团队协作的契约。模块边界不清晰,后果就是多人修改同一份文件,代码冲突不断。架构师真正的产出,是让每位开发者在写代码之前就清楚自己的代码应该放在哪一层、哪些接口属于自己、哪些绝对不能碰。具备这个视角之后,再看“应用层开发是不是嵌入式”这种争论,你就会发现它根本没有意义——对一个完整的嵌入式系统而言,分层之间永远是相互依存的关系,架构的价值恰恰体现在一层和另一层的交界处。

我在实际工作中还有一个体会:真正想走在架构这条路上,靠的其实不是更高级的AI工具,也不是记住更多设计模式,而是持续地问“为什么”。为什么这个模块放在这一层?为什么这个接口返回错误码而不是抛异常?为什么这个优化要以牺牲可读性为代价?这些问题都值得自己长期琢磨。最后再分享一个小习惯:每天下班前,我会挑一个今天改过的模块,在脑子里推演一遍“如果半年后需求翻倍,这个结构还撑得住吗”。撑得住,说明今天的设计及格了;撑不住,回去趁改动成本还小,马上调整。架构路上的功夫,就是这样一点一点磨出来的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进