前两篇我们把这个 AI 协同开发项目从零搭了起来,工程能编译、点灯能亮、串口能打印,AI 生成的初始化代码也确实省了不少事。但走到今天这个阶段,我心里清楚,真正的考验才刚刚开始。因为嵌入式软件最硬核的部分从来不是建工程、配时钟,而是当多个外设、多路传感器、中断和时序搅在一起时,系统还能不能稳定地跑起来。这一篇,我就把第三个阶段的真实过程原样复盘出来,包括我给了 AI 哪些指令、AI 交回了什么代码、真机上又踩了哪几个坑,以及最后这套“人机协作”的工作流沉淀成了什么样子。
1. 阶段三的切入点:从“能编译”到“能干活”
1.1 项目背景回顾:这个AI协同开发项目到底在做什么
简单交代一下项目的完整轮廓。我们要做的是一块农田灌溉控制节点,主控选了 STM32F407VET6,外扩 RS485 总线接口,下面挂 4 路土壤温湿度传感器,走的标准 Modbus RTU 协议。节点负责轮询这 4 路传感器,拿回数据进行边界判断,再把结果通过一个 4G 模块上报到云平台,本地 OLED 屏同步展示当前数值。
第一阶段让 AI 生成的是 CubeMX 图形化配置之外的补充代码,比如自定义 GPIO 控制逻辑、串口重定向、基础定时器等。第二阶段把调试链路打通,AI 协助写了一套简洁的日志输出工具,配合 ITM/SWO 和串口两种方式,把运行状态实时拉出来看。
到了第三阶段,也就是今天这篇的主题,目标很具体:把 Modbus 主站轮询、串口 DMA 接收、传感器掉线重连、数据超时处理全部落进同一个工程。也就是说,项目从“点灯级”正式进入“工业级”状态。前两篇那种“AI 写完我看一眼就合入”的节奏,到这一阶段彻底失效了。
1.2 跨越“能编译”到“能干活”之间的那条隐形鸿沟
很多刚接触 AI 编程的嵌入式工程师会觉得,AI 能写出语法正确的代码,编译能过,功能好像也对,那就够了。但做过现场的人都知道,从“能编译”到“能干活”,中间隔着一条巨大的鸿沟,这条鸿沟的名字叫硬件时序。
举个例子。AI 生成一段 Modbus 主站代码,逻辑上完全正确:打包请求帧、计算 CRC、等待应答、解析数据。但到了实际 RS485 总线上,半双工链路的收发切换方向是需要延时等待的;传感器的应答时间有长有短,掉线传感器的总线表现为完全沉默;多个传感器里只要有一个响应慢了,整个轮询周期就可能被拖垮。这些问题,编译器一个字节都看不出来,语法检查也完全无感。只有把程序烧到板子上,用示波器或者逻辑分析仪盯着总线波形,才能真正暴露。
这一阶段我给自己定的原则是:AI 生成的每一段代码,都必须先过“真机验证”这一关,再谈合入主干。事实证明这个原则救了我很多次。
1.3 为什么选 Modbus 轮询作为 AI 协同学的“试验田”
选择 Modbus 轮询这个功能来做 AI 协同开发的压力测试,是因为它足够典型,也足够刁钻。Modbus RTU 是一个非常成熟的二进制协议:帧格式固定、CRC 校验明确、主从关系清晰,AI 训练资料里关于它的代码示例简直多到爆炸,这部分 AI 确实擅长。
但嵌入式场景下的 Modbus 主站实现,真正难的不是协议本身,而是三个附加条件:多路从机轮询、不定时掉线、单总线半双工。三个条件叠加,任何一个时序处理不合理,轻则数据错乱,重则整机卡死。这实际上是在考察 AI 是否理解“嵌入式实时系统的失败模式”,而不是单纯考察它背了多少代码模板。
事实证明,AI 对正常流的理解接近满分,对异常流的理解就惨不忍睹了。
2. 给 AI 的第一批任务:把 Modbus 主站框架“按我的规矩”写出来
2.1 这次我没有让 AI 自由发挥
第一次尝试时,我的提示词特别随意,基本就是“帮我写一个 STM32 Modbus 主站轮询代码”。AI 也确实交出了一份看起来非常体面的代码:帧打包函数、CRC 函数、超时判断、解析函数,五脏俱全。但这个版本我根本没法用。
原因很简单:它对硬件的假设太多了。代码里用了HAL_Delay()来模拟轮询间隔,接收数据用的是阻塞式的中断等待,还假设 UART 一定工作在非 DMA 模式。这些都是典型的“桌面软件思维”——函数调用可以阻塞等待返回值,但在嵌入式实时系统里,CPU 是共享资源,一个阻塞延时就会导致其他外设失去响应。
所以这次我给 AI 下了一个更完整的约束条件包,让它在一个明确的状态机框架内工作。提示词大概是这样的:
你在为STM32F407编写Modbus RTU主站轮询模块。 硬件约束: - UART2 + DMA接收,串口波特率9600,8N1 - RS485收发切换由PB12控制,发送前置高,发送完成后置低 - 系统Tick为1ms,禁止使用HAL_Delay,所有超时用Tick差值判断 - 挂载4个从站,地址分别为0x01-0x04,寄存器地址0x0000,长度2 - 每个从站请求超时300ms,轮询周期2s - 当某个从站连续3次无响应,标记掉线,跳过3轮后再重试 请设计状态机,包含IDLE、发送、等待应答、解析、错误处理五个状态, 给出头文件和实现文件。不要写main函数,只写模块。2.2 AI 第一版代码里我认为合理的部分
这份代码整体框架我是认可的,主要亮点集中在帧处理层面。帧打包函数把地址、功能码、寄存器地址、数据长度、CRC 填充组织得干净利落;CRC 计算模块用了查表法,效率对于 9600 波特率来说绰绰有余;状态机的骨架也已经搭起来了,五个状态之间用 enum 切换,而不是嵌套 if。
这些基础功能,AI 确实做到了“拿来即用”。我几乎没改就通过了代码审查,直接进编译。对于一个成熟的二进制协议,AI 在“协议格式层”的编码能力已经完全可以信任。
2.3 但代码里也藏着几处必须由人挡下来的雷
编译通过之后,细读代码时我发现了三个隐患,这几个问题也是嵌入式场景下 AI 代码的通病。
第一,超时机制的实现仍是阻塞等待。虽然提示词里强调了“禁止 HAL_Delay”,但 AI 在等待应答状态里还是写了一个while循环配合HAL_GetTick()去卡超时。如果放在主循环里单独跑一个传感器还行,但一旦跑完整轮询 4 路从机,任何一个响应超时都会把整个系统按计算周期拖住。正确的做法应该是事件驱动:发送完请求帧直接返回主循环,通过 Tick 差值判断何时进入超时分支。
第二,AI 对 RS485 收发切换的时机理解得不够精确。代码里发送完成后立刻把 PB12 拉低,看起来天经地义,实际上 UART 的数据发送是移位寄存器逐位移出的,调用HAL_UART_Transmit返回只意味着数据已经交给了外设 FIFO,不代表总线上的电平已经发完。PB12 拉低早了,帧尾就会直接被砍断。这个坑,我必须手动修正,改成了在发送完成中断回调里置低方向脚。
第三,掉线重连逻辑整体偏乐观。AI 默认传感器“要么在线要么离线”,但实际上 RS485 总线上传感器故障分好几种:完全沉默、回应 CRC 错误、回应长度错误、以及间歇性丢字节。我的提示词里只写了“连续3次无响应标记掉线”,没有覆盖 CRC 错误分支。这意味着 CRC 错误会被 AI 当作正常应答去解析,得到错误数据还浑然不知。这里我补了一个错误状态累加计数器,把 CRC 错误和超时统一处理。
3. 真机实测:AI 生成的轮询逻辑在板子上的三个大坑
3.1 第一个坑:掉线传感器把整个轮询节奏拖入泥潭
我最早是在一块连了 2 路传感器的小测试板上跑的,一路在线、一路故意断开。AI 版本的状态机跑起来以后,在线的那路数据能正常刷新,但一旦轮到掉线路,整个系统的刷新节奏立刻变得一顿一顿的,OLED 刷新频率肉眼可见地降了下来。
打开调试日志才发现问题:掉线路的从机让主站陷入了超时等待,每次超时 300ms,而我在提示词里没有明确要求“超时期间不阻塞主流程”,AI 的实现就真的把 CPU 按在了 while 循环里。也就是说,当 4 路从机里有 3 路掉线时,系统每轮要白白浪费接近 1 秒的 CPU 时间,这段时间里其他事情全停摆。
真实的嵌入式系统里,主控通常还要同时处理按键扫描、通信上报、看门狗刷新,这种阻塞式等待是绝对不能接受的。这个问题的根源不是 AI 不会写非阻塞逻辑,而是它在没有任何关于“实时性约束”的明确提示下,会默认选择最直观的代码路径。用户不说,它就按桌面程序的思维方式写了。
3.2 第二个坑:接收缓冲区被 Modbus 异常帧撑爆
第二个坑就更隐蔽了。Modbus RTU 的标准数据帧长度是固定的,我这个项目里正常响应帧也就 7 个字节左右。AI 分配了一个 64 字节的接收缓冲区,我认为绰绰有余,也没细看。
但实际运行中,现场环境里有一路传感器比较老旧,它的 RS485 芯片在总线空闲时会输出随机噪声,主站发请求帧过去,它那边回了一串没有任何协议结构的垃圾字节,长度接近 70 字节。DMA 接收是连续写入环形缓冲区的,缓冲区溢出后,后续所有从机的正常应答也跟着被截断、被污染。
最要命的是,DMA 的接收中断只有在帧完成或缓冲区满时才会触发,垃圾帧如果正好卡在某个非对齐位置,整个接收状态机就彻底错乱了。这里 AI 的复盘能力暴露出了明显短板:它知道 Modbus 帧最大长度是 256 字节这个理论值,所以给缓冲区留了 64 字节已经“很安全”,但它不理解实际现场中,一个坏从机可能把总线上的一切都搅浑。
3.3 第三个坑:AI 的“帮助函数”把独立看门狗喂死了
第三个坑出现在 IWDG(独立看门狗)上。我在提示词里没有提到看门狗,但工程里早就在主循环末尾加了一个HAL_IWDG_Refresh()。AI 生成的代码里,那个阻塞式超时 while 循环耗时接近 1 秒,而我的看门狗超时配置是 640ms。
结果非常戏剧化:系统跑起来后每隔一段时间就整体复位一次,日志循环往复地重新打印。用串口抓了复位原因,才发现是 IWDG 复位。当时我还以为是我自己的主循环逻辑出问题了,排查了整整半天,最后在代码评审时才发现 AI 写的那段阻塞等待函数,直接把主循环卡在原地,看门狗饿死了。
这里也暴露了一个思考方式的问题:AI 在生成函数时,只看到自己眼皮底下的模块,看不到它嵌入到一个更大的系统里。你给了它一个任务函数,它不知道这个函数会被放在一个 1ms Tick、640ms 看门狗、多任务共享同一份 CPU 时间的环境里。这个全局视角恰恰是嵌入式工程师真正的看家本事。
3.4 第二次对话:修正 AI 行为需要对“边界”进行穷举式声明
第一轮的教训把我推到了一个核心认知上:想让 AI 生成真正可用的嵌入式代码,单纯给它功能需求是不够的,必须同时给它“系统约束清单”和“边界行为声明”。这就像给新同事交接工作,光说“把报表做出来”远远不够,还得告诉他“必须在 5 点前发邮件”“服务器每晚会重启”“中途掉线不要慌,先把数据存本地”。
带着这个认知,我重新组织了一轮提示词,把坑全部点名:
重构Modbus轮询状态机,要求: 1. 发送完成后立即返回主循环,通过状态机的Tick差值判断应答超时 2. 接收采用DMA + 空闲中断方式,每收到完整一帧后置标志位 3. 帧缓冲区长度增加边界保护,正常帧长度超过10字节时,按异常帧清空重来 4. 任何从站的超时、CRC错误、帧长错误都累计到错误计数,不阻塞主流程 5. 3次错误后标记掉线,之后每5轮尝试一次重连 6. 补充看门狗喂狗接口,主循环调用这一轮 AI 产出的代码框架就明显“懂事”多了。所有超时分支都被改写成了事件查询方式,主循环只需要检查状态机的 Tick 差值是否达到阈值;接收缓冲区增加了溢出错标志处理;RS485 方向切换也照着我的要求改成了发送完成中断里执行。也就是说,当我把隐性约束全部显式化之后,AI 是能给出符合嵌入式规范的代码的。
4. 一次典型排查:DMA 偶发丢字节,AI 给出的方向与盲区
4.1 现象描述:抓包发现每次都丢第一个字节
进入整机联调之后,我又遇到了一个比轮询更隐蔽的问题。4 路传感器全部在线,数据表面上都正常,但用逻辑分析仪对比总线波形和主控实际收到的数据时,发现偶尔会出现第一个字节丢失的情况。
这个现象很恶心。它不固定频率,可能跑几个小时才出现一次,而且一旦丢了第一个字节,传感器回的这一帧因为帧头错位,CRC 校验必挂,整个系统会多一组无效的掉线计数。虽然重试机制会把它纠正过来,但统计报表里会出现一些虚假的离线记录,对我来说这就是不能忍受的缺陷。
4.2 我让 AI 基于代码来研判:它给出了三条方向
出了问题,我习惯先让 AI 从代码上下文里给排查建议。我把 DMA 接收初始化代码、中断处理函数和状态机接收解析函数一起丢给 AI,问它哪些环节最可能导致偶发性丢首字节。
AI 的分析能力在线,几秒钟后给出了三个方向:
- DMA 配置里数据宽度和外设寄存器宽度不一致,可能导致接收错位;
- UART 接收中断优先级和 DMA 传输完成中断优先级配置不当,存在竞争;
- RS485 方向切换后立刻开启 DMA 接收,可能在总线上有一个残留电平导致首个字节无意义。
这三条方向,前两条都是正确的排查起点,第三条也符合现场判断。事实证明,让 AI 以“资深工程师”的视角做代码走查,它的知识储备确实能覆盖大部分常见问题。但 AI 的分析也就止步于此了,它无法感知一件事:总线上的噪声会不会触发 UART 的溢出错误。
4.3 根因定位:反汇编窗口里看到的真相
我按照 AI 的三条建议逐项排查,DMA 配置没问题,中断优先级也没问题。然后我把目光转向了总线物理层,用逻辑分析仪长时间抓取异常发生瞬间的波形,终于发现丢字节往往出现在主站拉低 PB12、总线上出现一个沿跳变的瞬间。这个沿跳变会通过 RS485 收发器耦合到 RX 线上,产生一两个无效电平。如果此时 UART 正好处于接收状态,这个干扰就会被当成一个“起始位+无用字节”接收到,占用接收帧头位置,导致真正的第一个字节被挤走。
到这里,问题已经不在 AI 给出的三条建议范围内了。真正帮我坐实判断的,是我打开了反汇编窗口,跟踪了 UART 接收中断异常处理入口的底层行为,并对照参考手册确认了 ORE(溢出错误)标志的触发条件。在 Cortex-M4 平台下,如果一个字节在 RXNE 标志位清除前到达,硬件会置 ORE 标志并保留原有数据,但后续数据不再写入数据寄存器,DMA 拿到的就是一个错位的数据流。
这个根因,AI 不会主动想到。这不是它的知识盲区,而是它缺少“看波形、翻手册、盯寄存器”的物理世界感知能力。但这个案例恰好说明了嵌入式 AI 编程的一个分工原则:AI 负责在代码空间里快速收敛方向,人负责在物理空间里落地验证。
4.4 修复方案:让 AI 帮忙补全预防机制
定位到根因之后,修复逻辑反而简单。代码层要做的就是在 UART 异常处理分支里增加 ORE 错误清除逻辑,当串口接收期间检测到溢出标志时,直接读 SR、读 DR 清掉错误标志,让 DMA 接收重新同步。我把这个意图描述给 AI,让它生成对应的中断处理补丁。AI 这次很利落地给出了修复代码:在HAL_UART_ErrorCallback里加入__HAL_UART_CLEAR_OREFLAG处理,并在 DMA 空闲中断之后重新初始化接收缓冲区。
同时我让 AI 帮我在发请求帧之前加了一个 10ms 的总线稳定延时——不是通用延时,而是只针对 RS485 方向切换逻辑做的短延时。实际测试两周,这个丢首字节的现象再也没出现过。
5. 这一个月 AI 协同开发下来,我重新划分了人机界面
5.1 用数据说话:AI 产出的代码到底有多少可以合入主干
这一步我特意做了一个统计表:这个 Modbus 轮询模块里,哪些代码来自 AI 原样合入、哪些经过人工修改、哪些完全推翻重写。统计下来大概是下面这张表。
| 代码模块 | AI 初版可用度 | 人工介入程度 | 介入原因 |
|---|---|---|---|
| CRC 计算与查表 | 可用 | 无 | 逻辑清晰、无状态依赖 |
| Modbus 帧打包/解析 | 可用 | 少量修改 | 增加帧长边界保护 |
| 轮询状态机骨架 | 部分可用 | 重构超时分支 | 阻塞式等待不可接受 |
| RS485 方向切换 | 不可用 | 全部重写 | 发送完成时机理解错误 |
| DMA 接收与错误处理 | 部分可用 | 补充 ORE 清理 | 缺少物理层噪声认知 |
| 掉线重连策略 | 部分可用 | 增加错误分类 | 对故障模式理解过浅 |
这份统计告诉我一件很现实的事:AI 并不是不能写嵌入式代码,而是它的可用度高度依赖任务类型。凡是边界清晰、规则固定、不依赖物理世界的模块,比如协议解析、CRC、数据校验、报表打包,AI 已经能输出非常适合合入的代码;凡是涉及硬件时序、总线竞争、异常恢复、中断优先级的模块,AI 初版大概率会踩坑,人工必须深度介入。
5.2 AI 时代的嵌入式软件到底应该怎么学
之前很多人焦虑,说 AI 编程来了,嵌入式工程师是不是要失业了。这一个月做下来,我的答案反而是:AI 会让真正懂嵌入式的人更值钱。
为什么?因为嵌入式开发正在从“写代码的能力竞赛”变成“提需求与验证结果的能力竞赛”。我曾经面试过一个候选人,算法题刷得很溜,但问到他知不知道 UART 的 RTS/CTS 流控和 RS485 方向切换有什么关系,他完全没概念。这种人如果靠 AI 写驱动,是没法判断 AI 生成的代码会不会在波形的尖峰上翻车的。
反过来说,一个懂中断、懂 DMA、懂时序的工程师,有了 AI 加持,产出效率简直荒谬。以前写一个传感器驱动模块至少需要两三天,现在半天就能出活,且因为 AI 能快速给出多种实现路径,最后由人来挑选和验证,代码质量比单打独斗时期还高。
所以我的建议很直接:想在这个行业里继续混,寄存器和中断不能丢,反汇编阅读能力不能丢,但写模板类代码的时间可以大幅压缩。把省下来的精力花在系统架构、异常处理、失败模式分析上,这才是 AI 时代嵌入式工程师的主战场。
5.3 给刚入坑嵌入式 AI 编程的同行几条实在建议
这一路踩坑踩下来,如果让我给刚开始尝试用 AI 做嵌入式开发的同行提建议,我最想说的是下面这几点。
第一,先让 AI 写小模块,不要上来就让它生成整个工程。我最早让 AI 直接生成 full project,结果它给我一套带私有 RTOS 封装的代码,连芯片型号都判断错了。从 CRC、帧打包这样的纯函数开始,逐步扩大范围,人对 AI 产物的信任度和把控力是递增的。
第二,提示词里必须写“禁止条款”,而不是只写“需求条款”。只告诉 AI 要实现什么,它会选择自认为最简单的路径;把禁止用什么延时、禁止在哪来阻塞、禁止在中断里调用哪些函数这些写清楚,产物的可用度会直接上一个台阶。
第三,任何 AI 生成的与外设时序相关代码,都必须做真机或者总线级联调后再合入。看着正确的代码配上实际硬件,行为可能完全不同。我一直保留逻辑分析仪和示波器在桌面上的位置,就是因为这类验证永远无法被编译器和 AI 替代。
第四,学会让 AI 做代码走查。写完一版功能代码后,把代码丢回去问它“这里有没有潜在的中断冲突、缓冲区溢出、不可重入函数”,它的静态分析能力非常值得用,往往能提前发现人工容易忽略的边界问题。
5.4 下一个阶段准备做什么
到这里,第一个 AI 协同开发项目从工程搭建、驱动开发到轮询协议栈的完整闭环已经跑通了。下一阶段我打算把这块 Modbus 轮询逻辑从裸机状态机迁移到一个轻量级 RTOS 上,让 AI 帮我完成任务的优先级划分和信号量设计,同时保持现有功能不回归。这个迁移过程大概率又会暴露一批新问题,到时候再来记录一轮实战过程。