ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI协同开发Modbus轮询:硬件时序是嵌入式代码的隐形门槛

AI协同开发Modbus轮询:硬件时序是嵌入式代码的隐形门槛 前两篇我们把这个 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接收串口波特率96008N1 - 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 帮我完成任务的优先级划分和信号量设计同时保持现有功能不回归。这个迁移过程大概率又会暴露一批新问题到时候再来记录一轮实战过程。
返回列表