说实话,接触AI编程工具一年多,真正用进嵌入式项目之后,我最大的感受是:这行当的开发范式该变了。不是说要丢掉C语言、丢掉调试器,而是从“一个人趴在代码里抠每一个字节”慢慢转向“人定义规则和边界,AI去填那些重复的模式化代码”。这篇是这个系列的第二篇,我想认真聊聊面向AI协同的嵌入式软件开发范式——不吹AI多神,也不无脑贬低,就说清楚这套协作流程里,什么该交给AI,什么必须攥在自己手里,以及具体到代码层面怎么落地、怎么验证、怎么避免翻车。
我最早接触这个范式,是因为一个UART DMA驱动的需求。当时把需求丢给AI,生成的第一版代码逻辑看着完全没问题,结果烧到板子上,接收的数据隔三差五就乱一个字节。排查了整整两天,最后发现是DMA描述符链表在Cache一致性上没有做处理。这个事给了我一个特别重要的教训:AI能帮你写出80%的代码,但剩下那20%的硬件边界知识,如果自己心里没数,AI生成的代码就是一颗定时炸弹。所以从这个案例开始,我一直在琢磨到底怎么跟AI协作才靠谱——后来慢慢总结出了一套方法论,就是今天要聊的“面向AI协同的嵌入式软件开发范式”。
1. 开发范式转变:从人肉编码到“意图工程师”
1.1 传统嵌入式开发模式的困局
干嵌入式这些年,最常见的开发流程是:看芯片手册、翻参考代码、写驱动、烧录、调bug。一个稍有经验的工程师,大量时间其实花在“搬运”上——把数据手册里的寄存器操作翻译成C代码,把厂商库函数拼接到自己的工程里,把以前项目里类似模块的代码拷过来改改。真正需要动脑子设计的部分,可能只占两到三成。
这种模式本身没什么问题,但它有一个致命弱点:效率天花板太明显。芯片手册上千页,寄存器描述密密麻麻,如果你接手的是一颗不太熟悉的新型号,光是把基础外设的点灯、串口、I2C调通,可能就得耗掉一两个星期。而这其中真正有技术含量的,是“为什么这么配置”以及“配置后硬件会怎么表现”,而不是“哪一行代码该写到哪个寄存器”。
更麻烦的是,嵌入式软件的调试往往是串行的。代码量越大,编译烧录的反馈周期越长,人肉排查的问题链路越深,效率越低。尤其是在带着硬件时序、中断嵌套、低功耗切换这类问题的时候,你要是还要分心去写那些“模板化”的初始化代码,精力根本不够用。
1.2 AI协同范式的核心:人管意图,AI管实现
那AI进来之后能改变什么?说白了,就是把上面那些“搬运”工作接走,让人把时间留给真正需要判断力和硬件认知的部分。我现在的日常工作方式是这样的:
- 我负责梳理功能需求、硬件约束、边界条件和验收标准;
- AI负责根据我给的约束生成具体的代码实现,包括驱动初始化、状态转移、数据处理、协议解析这类模式化内容;
- 我做review和验证,确认AI产出的东西在硬件上真实可用,而不是只对逻辑抽象负责。
这个分工的本质,是把工程师的角色从“人肉编码器”升级成了“意图工程师”。你不必再亲自记住UART外设每一比特位的配置含义,但你得知道“波特率是否支持分频整数”、“FIFO中断阈值怎么选会影响实时性”、“DMA描述符的Cache一致性谁来维护”这类问题。
有人可能会说:“这不就是把活推给AI,自己还得懂底层吗?”对,恰恰是这样。AI协同不是让你变懒,而是让你把认知从代码层级往上提一层。你不需要背代码,但必须知道代码敲下去之后硬件的真实行为。
1.3 人机分工边界:一线嵌入式场景经验总结
说得再具体一点,我把当前跟AI协作时比较舒服的分工边界整理成了一张表,这也是我在多个项目里反复试出来的结果:
| 交给AI做的 | 必须自己做或严格审核的 |
|---|---|
| 外设驱动的初始化模板代码 | 硬件时序与信号完整性判断 |
| 协议栈的状态机实现(有明确状态表和事件表) | 中断优先级与临界区设计 |
| 寄存器读写封装、HAL层样板函数 | 低功耗模式切换的完整路径 |
| 单元测试桩代码、Mock外设生成 | 内存布局、链接脚本、启动文件 |
| 数据解析、校验算法、环形缓冲区实现 | 实时性预算和任务调度策略 |
| 配套脚本(烧录、批量改配置、日志分析) | 安全完整性等级(SIL/ASIL)相关逻辑 |
| 数据手册内容的摘要与要点提取 | OTA升级的加签、验签、回滚机制 |
这张表的逻辑很直接:凡是在“给定输入就能推出正确输出”的范畴内,AI可以做得又快又好;凡是依赖现场感知、硬件状态、异常流程和隐性知识的部分,短期内AI都不太靠谱,需要人来兜底。这不是能力歧视,而是基于工具本质的判断——AI没有亲手摸过你的板子,它不知道你那个电源纹波有多大,也不知道你那颗芯片的勘误手册里写了哪些坑。
2. 状态机与分层抽象:AI协同时代最值钱的“旧武器”
2.1 为什么AI代码最容易在“隐式状态”上翻车
AI写代码有个特点:它擅长生成“看起来很有逻辑”的代码,但逻辑正确不等于状态正确。嵌入式系统里大量bug都出在隐式状态上——你看着代码的if-else写得很清楚,但运行时的执行路径却不是你想象的那样。比如一个多事件的按键扫描模块,直接用flag变量的置位和清除来表达状态变化,长按、短按、双击混在一起,状态多了之后,AI很容易生成那种“局部正确、整体混乱”的代码。
原因也不难理解。大语言模型本身就是基于概率推断的,它看到你的上下文里有一堆中断标志位和回调函数,就会按最常见的方式去拼接。如果显式地告诉它“这里必须用状态机来组织”,它反而能生成结构清晰、边界分明的代码。
所以在我现在的项目里,凡是逻辑复杂度可能膨胀的模块,我都会先做状态建模,再让AI在这个模型框架内填充实现。这么做不仅让人和AI之间有了一本“共同语言说明书”,也把模块的复杂度收敛在一个可验证的范围内。
2.2 从状态建模开始收敛并发复杂度
可能有读者不是特别熟悉状态机在嵌入式里的用法,我简单展开一下。所谓状态建模,就是把一个模块的所有运行模式列出来,定义清楚每个模式下能处理哪些事件、处理完会跳到哪个状态。通常包括三个要素:状态、事件、转移条件。
举一个多方传输协议的典型例子,假设我们要实现一个MODBUS从站协议解析器。状态就可以定义为:空闲、等待地址、接收数据、校验、响应发送、出错恢复。事件就是:收到字节、地址匹配、校验失败、发送完成。转移条件则决定了状态之间怎么跳。
传统开发里,很多人会直接开写,用一堆if-else把协议逻辑揉在一起。但一旦出问题,排查起来非常痛苦,因为运行时你根本不知道当前是哪个阶段,只能靠打断点看变量。而先做状态建模再编码,最大的好处是:逻辑变成了一张可查的二维表,哪一步该做什么一目了然。这个好处在AI协同时代被放大了,因为你可以直接把状态表喂给AI,让它生成一个表驱动的执行引擎,正确率比让它凭空去写高一大截。
下面这是我常用的状态转移表描述方式,也是喂给AI的输入形式之一:
| 当前状态 | 事件 | 动作 | 下一状态 |
|---|---|---|---|
| IDLE | FRAME_START | 清空接收缓冲,启动帧超时定时器 | RECEIVING |
| RECEIVING | BYTE_RECEIVED | 写入缓冲,更新校验和 | RECEIVING |
| RECEIVING | FRAME_TIMEOUT | 丢弃缓冲,记录错误计数 | IDLE |
| RECEIVING | LENGTH_MATCH | 计算并比对校验值 | CHECK_SUM |
| CHECK_SUM | CHECK_OK | 触发应用层回调 | RESPOND |
| CHECK_SUM | CHECK_ERR | 丢弃缓冲,记录错误计数 | IDLE |
| RESPOND | SEND_DONE | 重置发送状态 | IDLE |
我给AI的指令往往只有一句话:“基于下面这张状态转移表,用C语言生成一个表驱动的状态机执行器,状态枚举、事件枚举、转移表要在代码中显式定义。”生成出来的代码,只要表格本身是对的,逻辑基本不会跑偏。
2.3 状态机模型如何反哺AI代码审查
状态建模还有一个隐藏好处:它天然提供了一套验证标准。AI生成完代码,你不需要一行行读c文件去抠逻辑,你只需要对照状态表测几个关键路径:正常流程、异常分支、边界事件。这个做法大大降低了review的成本。
我自己的习惯是,拿到AI生成的代码之后,先看三个地方:
- 状态枚举是否和表里完全一一对应,有没有多余的“发明创造”;
- 事件驱动的入口是否统一,是不是所有外部输入都收敛到了同一个事件分发机制上;
- 每个case分支是否都有明确的下一状态,不允许出现fall-through和隐式状态残留。
这三点看完,基本能判断AI是不是真的理解了你给的状态表,还是只是生成了一个貌似正确的空壳。如果这三点都过关,再往下看具体动作代码的硬件相关部分。
2.4 硬件抽象层(HAL)边界的意义
除了状态机,第二个对AI协同特别有意义的“旧武器”是分层抽象。如果你拿到一个没有分层的裸工程,所有代码直接操作寄存器,文件之间互相#include,那AI改起来就是一场灾难。它无法知道修改这个文件会不会影响另一个文件里的宏定义,也无从判断该把新代码放在哪个层级。
所以在面向AI协同的开发范式里,我会强制把工程按“应用层—协议层—HAL—寄存器映射”四层拆开。AI生成代码时,明确限定它的作用域在某一层里。比如让它生成一个I2C EEPROM的驱动,我会先告诉它“HAL层接口是i2c_master_transmit(uint16_t dev_addr, uint8_t *buf, uint16_t len)”,并限定它只能调用这个函数接口,不允许直接操作寄存器。这样一来,AI只能在接口约束的范围内发挥,生成代码的硬件耦合风险会小很多。
这个做法的本质是:用人的架构能力把AI的“发挥空间”框在安全边界内。你在边界上花的心思越多,AI在边界内犯错的概率就越低。
3. 提示词工程在嵌入式领域不是玄学
3.1 很多人的提示词方式,根本没法让AI干活
现在AI编程工具不少,很多嵌入式工程师也试过。但大部分人遇到的问题是:“AI生成的代码看着行,一跑就废。”于是得出一个结论:AI写不了嵌入式代码。
我先说个可能有点扎心的判断:很多时候不是AI不行,是提示词给得太糊弄了。你要是只丢一句“帮我写一个CAN驱动”,AI只能根据它训练数据里的“平均印象”去猜你的芯片型号、你的时钟树、你的帧格式标准、你的中断使用习惯。它猜对了是运气,猜错了才是常态。
我见过最夸张的案例,同事让AI“生成一个ESP32的蓝牙配网代码”,AI给了他一套乐鑫官方示例的简化版。看着一点错没有,结果编译出来的固件体积超了Flash限额。原因在于他没告诉AI这个项目用的还是老款ESP32芯片、Flash只有4MB,而且分区表里已经塞了大量OTA资源。AI不知道这些硬件和系统约束,自然会按“最大兼容性”的默认方案来。
3.2 嵌入式AI提示词的六个必要信息
经过大半年反复调整,我现在给AI提需求时,固定会包含六个维度的信息。你可以把这个当成一个模板来用:
- 角色与目标:告诉AI你希望它扮演什么,要做什么事。比如“你是一个有十年经验的嵌入式软件工程师,擅长STM32H7系列的外设驱动开发。”
- 硬件环境:芯片型号、开发板、外设挂接方式、时钟频率、引脚分配。越详细越好,不确定的宁可多写也不要漏。
- 接口契约:给出现有的函数接口、头文件、数据结构定义,明确“只能调用哪些接口”“不能访问哪些资源”。
- 功能需求:描述输入是什么、要做什么处理、输出是什么。这里用“如果收到X,就做Y”这类判断句式,比“处理数据”这种模糊描述强无数倍。
- 约束清单:内存限制、实时性要求、不可用的库函数、编码规范(比如MISRA C子集)、可移植性要求。
- 验收标准:给出什么样的测试用例、什么样的输出结果算通过。把这个写清楚,AI会主动往可验证的方向靠。
举个实际例子,我要让AI写一个软件定时器模块,提示词大概长这样:
角色:你是一名嵌入式软件工程师,负责基于C语言的RTOS应用层开发。 硬件环境:Cortex-M4核心,主频168MHz,有硬件SysTick,系统时基1ms。 接口契约:只能使用<stdint.h>、<stdbool.h>,禁止使用动态内存malloc;已有的接口是void systick_isr_cb(void),由中断每1ms调用一次。 功能需求:实现一个软件定时器模块,支持创建定时器、启动、停止、查询剩余时间,回调函数在systick中断上下文中执行,回调不允许阻塞。 约束清单:定时器实例上限为32个,内存用静态数组分配,不支持删除定时器(可复用),回调执行时间不超过10us。 验收标准:提供完整的timer.c和timer.h,以及一组host端测试用例,验证定时器在正常、重复启动、边界时间三种场景下行为正确。
你看,这样给出来的需求,AI生成的东西和“帮我写一个定时器”完全不是一个水平线。
3.3 上下文窗口不够用怎么办:用“接口契约文件”压缩信息
有人会说:“芯片手册那么厚,我哪能每次提示词里都写那么细?”这就要提到另一个经验:建立工程级的“接口契约文件”。
我现在的做法是,在每个嵌入式工程里放一个docs/contract.md文件,里面写清楚了:
- 芯片型号、编译工具链版本、链接脚本位置;
- 所有封装的HAL层函数原型及简要说明;
- 引脚复用表(哪根PIN连到哪个外设);
- 全局状态枚举和关键数据结构定义;
- 内存区域划分(RAM、Flash、NoInit段等);
- 团队编码规范的核心条目。
写提示词的时候,我只需要在开头注明“请先阅读项目根目录下的docs/contract.md,所有代码必须遵循其中定义的接口和约束”,AI就能自动去读取并组织上下文。相当于你把整个项目的核心信息压缩进了一个文件,AI拿到这个压缩包后,生成的代码会明显更贴合你的工程。
这个方法最大的价值是:你不需要每次复制粘贴一堆重复信息,也不需要依赖AI那有限的上下文窗口去记忆全文,而是把契约变成一个可以反复引用的锚点。我实测下来,加入契约文件后,AI生成代码的一次性通过率至少翻了一倍。
3.4 生成之后的“反Prompt”:让AI自己Review自己
还有一个小技巧,很多人不知道:AI生成的代码完成之后,不要急着拿板子烧,先让它做一轮自审。你可以追加一句:
请以硬件工程师的视角审视这份代码,着重检查:1) 中断安全;2) 临界区保护;3) volatile使用场景;4) 内存对齐;5) 异常分支处理。如果有问题,列出问题清单和修改建议,不要直接给完整修改后的代码。
这一步看起来简单,但效果出奇地好。因为AI在“发现问题”模式下,会调用它训练数据里的那些常见bug模式库,找出它自己刚才生成代码里的遗漏。我遇到过好几次,AI自己指出“该处临界区未关中断”或“该变量缺少volatile修饰”,被我确认后确实需要修改。这等于用AI的自我校验,把一部分隐性bug在编译前就消灭掉了。
4. 从“编译通过”到“硬件跑通”:三级验证闭环不能省
4.1 为什么编译通过是最低标准而不是验收标准
很多人用AI写代码,最开心的一瞬间是“编译零错误”。但对嵌入式开发来说,编译通过连及格线都算不上。嵌入式代码的验证是分层的:编译通过只能证明语法正确、符号引用正确,它证明不了运行时行为正确,更证明不了硬件配合正确。
举一个我真实遇到过的场景:AI给一个低功耗管理模块生成了代码,编译零错误零警告,结果板子烧进去一进sleep就醒不过来。最后仔细排查发现,AI在进入低功耗模式前没有正确配置RTC闹钟中断唤醒,而是把一个外部GPIO中断配置成了唤醒源,但那个引脚上根本没有接信号。这种问题编译期绝对不会报错,甚至静态分析也不一定抓得出来,因为语法、逻辑都是通的,它只是和硬件实际连接不匹配。
所以我现在要求自己团队里的开发流程,必须要过三级验证闭环。
4.2 第一级:编译与静态分析
第一级是编译加静态分析。编译没什么好说的,把警告当成错误来对待,宁可多花时间消除警告,也不要留着隐患进下一级。对于AI生成的代码,这一步尤其重要,因为AI经常生成一些类型宽度不匹配、隐式转换、未使用的变量之类的代码,这些在编译期就有迹可循。
静态分析工具我常用的有cppcheck和PC-Lint。在嵌入式场景里,我会额外打开这些规则:未初始化变量、数组越界、空指针解引用、switch缺少default、变量声明遮蔽、隐式类型转换。每一条都是在硬件上排查起来非常痛苦的bug类型。实测下来,AI生成的代码在第一次过静态分析时,平均能查出四到五个中等级别以上的问题,其中最常见的是未初始化变量和隐式转换。
4.3 第二级:Host端单元测试
第二级是Host端单元测试。这一步很多非嵌入式的开发者可能不熟悉,它的核心思路是:把不依赖具体硬件的逻辑代码,从嵌入式工程中抽出来,放到PC上用测试框架跑。相当于在硬件还没准备好之前,先用软件的方式验证逻辑正确性。
我会把需要验证的模块设计成不直接操作寄存器而是调用HAL层接口的模式,然后写一份Mock的HAL实现,在PC上模拟外设行为。框架我推荐Ceedling加Unity,这个组合在嵌入式圈子里用得比较多,支持自动生成测试桩,也支持直接编译运行。
举个实际例子,我之前让AI写了一个环形缓冲区模块,以及一个基于该缓冲区的串口命令解析器。我把这两个文件抽出来后,在Host端写了测试用例,模拟了“连续塞入一帧完整数据”“数据中间插入错误字节”“缓冲区溢出时写入”三种场景,AI生成的代码在第二种场景下就暴露了一个问题:错误字节之后它没有复位解析状态,导致后续所有命令解析全部错位。这个bug如果直接烧到板子上,你得用串口调试助手反复发数据、看打印才能定位到,在Host端测试用例里,它跑一遍就能蹦出来。
4.4 第三级:硬件在环验证
三级验证的最后一环,是回到真实的芯片上跑。这一环AI帮不了太多,它没有自己的示波器、逻辑分析仪和JTAG调试器。但AI可以为这一环做不少配合工作,比如生成测试脚本、生成自动化烧录的命令行脚本、甚至帮你把日志输出格式整理成方便解析的JSON结构。
硬件验证阶段,我自己会重点盯几个板上容易翻车的点:
- 外设初始化序列是否正确,特别是时钟树和GPIO复用;
- 中断服务程序的执行时间和嵌套行为;
- DMA和Cache一致性(如果芯片带Cache);
- 低功耗模式切换后的时钟恢复;
- Flash写操作的等待周期和电源电压波动。
每跑一条测试用例,我会把结果喂回给AI,让它根据日志分析可能的原因。这等于让AI参与硬件调试的“推理环节”,但最终的判断和修改决定,我会自己做一遍确认。
这里分享一个排查DMA乱数据的完整链路,也算是对开头那个案例的复盘。当时的现象是UART DMA接收的数据偶发错位。我先把问题现象和HAL层初始化代码喂给AI,AI列了五个可能原因:GPIO复用配置错误、DMA通道配置错误、缓存一致性问题、FIFO溢出、中断优先级冲突导致丢数据。然后我去检查硬件:GPIO复用配置和手册一致,DMA通道请求映射对得上,排除前两个。剩下三个里,缓存一致性是最可疑的——芯片带Cache,DMA是私有的,CPU写的描述符数据还留在Cache里没回写内存,DMA就直接去内存里读了,读到的是旧数据。我回过去让AI在DMA描述符初始化后加一条Cache Clean操作,同时把接收缓冲区的Cache Invalidate配置好,问题就消失了。这个过程,AI负责的是知识检索和假设生成,我负责的是硬件确认和最终决策,两边配合着来,效率明显比一个人闷头查高。
5. AI在嵌入式里的翻车现场:能力边界与识别方法
5.1 寄存器地址和芯片手册:AI幻觉的重灾区
聊完验证闭环,我想花点篇幅讲一讲AI在嵌入式领域的常见翻车点。如果说有一个最需要警觉的领域,就是“寄存器地址、芯片手册细节、勘误表信息”这类高度精确的内容。大语言模型本质上是一个概率图谱,它知道“这个芯片的I2C外设通常有配置寄存器、数据寄存器、状态寄存器”,但它不保证它给出的地址偏移是这颗具体芯片对应手册里的真实值。
我碰过一次特别离谱的情况:让AI生成一个新系列MCU的ADC驱动,AI直接使用了它训练数据中“最接近”的另一个系列芯片的寄存器布局。两个系列的外设模块名字一样,但寄存器地址偏移差了整整8个字节。结果就是:寄存器读写表面上没有触发HardFault,但采样值永远不对。这类问题的隐蔽性在于,它不会崩溃,它只是静默地产生错误数据。
所以我总结了一条铁律:凡是涉及具体寄存器地址、位域定义、时钟树参数的内容,都必须回到官方头文件和数据手册去核对。AI生成的这些信息只能当“检索线索”用,不能当“事实依据”。
5.2 排查实例:AI帮忙改低功耗,改完设备睡死
再分享一个排查链路比较有代表性的案例。当时我们要给一个传感器节点做低功耗优化,我让AI基于现有的外设初始化代码生成一套“进入睡眠-定时唤醒-恢复工作”的流程。AI很快就给了一套代码:进入sleep前关掉外设时钟,把GPIO全部设为低功耗状态,然后配置RTC闹钟唤醒。
代码烧进去之后,设备确实进去了低功耗,但怎么都醒不过来。用调试器连上去看,发现CPU停在WFI指令里,RTC中断确实触发了,但中断处理程序从头到尾没有执行到用户回调。
当时我一步步排查的链路是这样的:
- 先怀疑RTC中断没有使能,查NVIC配置,没问题;
- 再怀疑RTC闹钟比较值不对,对照手册算了一下,配置正确;
- 然后怀疑中断标志位没有清除,但手测时中断标志位是置位的;
- 最后看启动文件里的中断向量表,发现AI生成的代码在关闭外设时钟时,把RTC所在的备份域时钟也关了,RTC中断标志位虽然置位,但内核无法响应该中断源的pending请求。
问题根因就是:RTC依赖备份域供电和时钟,进入低功耗前不应该关闭它的时钟源,反而要确保LSI或者LSE仍在运行。AI是按“关闭不用的外设时钟”这个通用优化原则去做的,但它没有真正理解这颗芯片备份域的特殊性。这个案例再次验证了我前面说的:AI擅长综合“常规做法”,但不擅长理解“这颗芯片的特殊情况”,后者只能靠人来把关。
5.3 怎么识别“优雅地错误”的AI代码
AI生成代码最让人头疼的一点是,它的错误往往是“优雅的”——不是明显缺了分号、少了头文件那种低级错误,而是逻辑自洽、风格统一、但概念理解错误。举个例子,AI生成一个I2C读写函数,函数名、参数、返回值设计得都很专业,但它在读操作时长尾的NACK处理上可能漏了一个总线释放步骤,导致连续读取时通讯卡死。这种错误,静态检查查不出来,单元测试不一定覆盖到,只有硬件在环测试才会暴露。
我自己识别这种“优雅错误”的方法是:看AI生成代码里的“隐含假设”。比如它默认外设时钟已经开启、默认中断已经配置、默认引脚功能已经复用好。在传统人写代码里,这些假设会暴露在“这个函数只负责A,不负责B”的注释和设计里;但AI生成的代码往往会把这些预设写得很含蓄,甚至隐藏在一个参数默认值里。所以review的时候,我会专门找这类“假设点”,逐个确认在真实工程里是否成立。
5.4 应对策略:让AI拿出证据链
针对这种问题,我的另一个习惯是:要求AI给出“证据链”。比如让AI写一段涉及芯片功能的代码时,我会在提示词里加一句:
请在代码注释中标注每个关键配置的依据,具体到数据手册章节号或头文件中的宏定义名称。
加了这句话之后,AI的行为会有明显变化。它不再凭空给出一个神秘的寄存器值,而是会明确标注“这里取0x23是因为参考手册第xx节推荐的启动时序”,或者“这个枚举值来自device.h中的xxx宏”。看到这种依据,我就能快速去对照手册和官方头文件,核实真伪。这比AI直接给出一段看起来完美无缺的代码要安全得多。
6. 从个人习惯到团队流程:AI协同开发范式落地建议
6.1 工程组织上的建议:AI产物要有清晰边界
如果只是在个人项目里偶尔用AI,那前面的内容已经足够。但如果你想在团队里推行这套开发范式,工程组织的设计就要跟上。我的第一个建议是:在代码仓库里为AI生成的代码打上明确的标记,既要在文件头注释里写清楚“本文件由AI辅助生成,review人xxx”,也要在目录结构上有所区分,比如用ai_generated/或者module/gen/之类的路径单独管理。
这么做不是为了“甩锅”或者“贴标签”,而是为了让review机制真正有效。AI生成的代码和人类写的代码,在审查时的关注点是不同的。人类写的代码你可能担心算法逻辑有没有想清楚,AI生成的代码你更担心它有没有误解硬件约束、有没有引入隐式假设。如果review的人不知道这段代码是AI生成的,他很可能按人类代码的审查习惯草草扫过,反而漏掉AI特有的问题。
6.2 提交规范与AI痕迹管理
第二个建议是:在Git提交信息里明确标注AI参与的程度。我们团队约定了几种标签:
- [AI生成]:完全由AI生成的模块,人工review后合入;
- [AI辅助]:人类主要编写,AI负责补全、优化部分代码;
- [AI审查]:代码由人类编写,AI参与了逻辑审查或找错。
标签的意义在于建立数据积累。跑几个迭代之后,你可以回看这些数据,统计一下AI生成代码的缺陷率、返工率,从而判断哪些任务适合继续交给AI,哪些任务必须收回人来写。没有标签,这些复盘数据就是一笔糊涂账。
6.3 个人工作流参考:一个完整的AI协同开发周期
如果你刚开始尝试这种模式,我建议先从一个低风险的小模块练手,比如一个命令行解析器、一个软件定时器、一个CRC校验单元。我自己的完整流程大概是这样的:
第一步,先花十分钟梳理需求,把状态、接口、约束写清楚,形成一份简洁的设计说明。这个环节不碰代码,但决定了后面AI生成代码的质量上限。
第二步,把设计说明整理成提示词,结合工程的contract.md一起发给AI,要求它生成代码和对应的Host端单元测试。
第三步,拿到代码后先过静态分析,再在Host端跑测试用例。这一步能解决大部分逻辑层面的问题。
第四步,把验证过的代码合入工程,烧录到目标板,用真实的硬件外设跑通关键用例,特别注意那些Host端测不到的中断时序、Cache一致性、DMA交互问题。
第五步,把硬件测试的结果和遇到的问题反馈给AI,让它给出修复建议或追加测试用例,形成一个“人-机-硬件”三方的闭环迭代。
6.4 团队推行AI协同容易遇到的三个阻力
最后说说团队落地时最常遇到的阻力。第一个是抵触心理——“AI写出来的代码我不放心,还不如自己写”。这种想法可以理解,但建议团队里先在小范围做实验,选一个非关键模块跑一遍上述流程,拿数据说话。看到“AI生成基础驱动框架+人做硬件验证”的组合能把开发周期压缩一半之后,抵触情绪会自然缓解。
第二个阻力是“AI生成的代码风格不统一”。这个问题可以通过一个强有力的contract.md来缓解,里面明确写出命名规则、注释规范、错误处理方式、模块接口风格。我实测下来,只要约束给得足够具体,AI生成的代码风格可以做到和团队人类代码基本一致。
第三个阻力是“review压力变大”。AI生成的代码量通常比人写得多,动不动就给你铺一整个文件的代码,看着就头疼。我的解决方案是要求AI按函数粒度交付,先交付接口声明和空实现,逐个函数填充。这样review可以聚焦在核心逻辑和硬件交互点上,而不是被一片代码淹没。
说在最后:AI协同范式的下一步
我自己用下来的感受是,AI协同带给嵌入式开发的最大价值,不是“帮你把活干完”,而是“帮你把活干得更有谱”。它会逼着你把需求想清楚、把接口定义明白、把验证标准写具体——这些原本就是优秀嵌入式工程师应该具备的能力,只是以前被大量重复编码掩盖了。
如果你正在观望要不要在嵌入式项目里引入AI,我的建议是别一上来就往产品代码里塞。先挑一个周末,找一个功能边界清晰的模块,按照前言里那套状态建模、契约文件、提示词模板、三级验证的流程走一遍。等你习惯了“先定义清楚,再让AI填充”的工作方式,你会发现自己对项目的理解反而比以前更深了,因为你终于有精力去关注那些真正决定系统成败的硬件细节了。
我下一篇文章打算聊一聊具体工具链的搭配,包括编辑器里的AI插件、命令行辅助工具、以及怎么把它们组织成一条顺手的工作流。如果你也在嵌入式项目里折腾AI,欢迎在评论区聊聊你踩过的坑。