ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

嵌入式AI编程实战:STM32从站Modbus与RS485联调全记录

2026/10/4 19:27:54 拓冰建站 浏览量
嵌入式AI编程实战:STM32从站Modbus与RS485联调全记录 自己动手做了两三个AI辅助的嵌入式小项目后我明显感觉到一条界线AI能帮你写代码但帮不了你做工程决策。这篇是“嵌入式软件AI编程”系列的第18篇也是“第一个AI协同开发项目”的收尾篇。上一篇我把项目的需求和骨架定了下来这篇要干的活很具体把核心驱动、通信协议全部实现出来并且在真实硬件上把电跑起来处理各种联调问题。项目本身不复杂甚至有点“教科书味”STM32F103C8T6作为从站挂一个SHT30温湿度传感器通过RS485接口跑Modbus RTU协议把温湿度数据以保持寄存器的形式供上位机读取。真正有意思的是整个开发过程我一直在有意识地用AI编程工具参与并且把“提示词怎么组织”、“AI写的代码哪些能直接用、哪些必须改”这件事完整地记了下来。如果你正准备把AI编程引入嵌入式开发但不知道从哪下手或者你已经试过让AI帮你写代码结果发现“编译能过、一跑就废”那这篇应该能给你一些拿得出手的参考。1. 项目整体设计与AI边界划分1.1 为什么把第一个AI协同项目拆成两篇做我身边不少人第一次接触AI编程时容易产生一个错觉给AI丢一句“帮我写个Modbus从站程序”然后复制粘贴就能跑。我第一个项目就吃了这个亏。AI确实能在半分钟内把一个看起来完整的程序甩到我脸上但那个程序用的是串口调试助手思路根本没有考虑RS485方向切换、没有定时器超时保护甚至把STM32F103的Flash当成无限大来用。所以我坚持把这个项目拆成两篇来写。第一篇做的是“人与AI对齐需求”明确设备地址、波特率、数据帧格式、寄存器映射表、传感器采样周期然后基于CubeMX把工程框架和引脚初始化先搭好。这些事不能省也不适合让AI做因为AI对你的硬件拓扑一无所知。到了这一篇也就是项目-2才开始进入真正的“协同”环节。AI负责快速产出模块级代码我负责给它提供足够的上下文然后逐行审查、编译、烧录、调试。你可以把这种工作方式理解成AI是一个熟悉各种外设驱动和协议栈的高年级工程师但它不了解你的板子、你的时钟配置、你的供电情况。你要做的就是把“它不知道的信息”尽量喂给它然后让它出力。1.2 模块划分哪些环节交给AI哪些必须自己拍板我这个项目的代码最终被拆成了四块SHT30驱动、Modbus RTU协议栈CRC、帧解析、状态机、主循环调度、以及串口/定时器等HAL层回调。它们在AI协同开发中的“参与度”是完全不同的。我先把结论放在这里后面会展开说协议栈和数据处理类的代码AI生成的可靠度最高基本可以进行小规模修正后直接用传感器驱动类的代码AI生成的是“半成品”时序和错误处理必须人工核对涉及硬件资源分配和系统架构的决策AI没有发言权必须自己拍板。这个结论不是我拍脑袋想的是这次项目中反复“碰壁-修正-再碰壁”之后形成的经验。比如Modbus的CRC16计算函数AI一次就给对了因为它是一个纯逻辑算法不依赖任何外设状态而SHT30的I2C读取时序AI第一次给的是模拟I2C实现——不是说不能用而是项目中我用的已经是硬件I2C两者在代码风格、错误处理、阻塞时间上差别很大需要我明确指出来它才会修正。侧面说明一个道理AI在嵌入式项目里的价值不在于“替代你”而在于把你从重复性的、范式化的编码工作中解放出来。你需要把自己的精力放在AI不擅长的那些交叉点上——比如某个引脚同时被JTAG和GPIO复用了比如DMA缓冲区大小和Modbus最大帧长之间的关系。2. AI辅助核心模块实现从提示词到可用代码2.1 嵌入式提示词的写法把需求说成芯片听得懂的话先说最容易上手、也是回报最高的一块提示词。很多人抱怨“AI写的代码没用”大多数时候不是AI不行而是你的提示词给的信息量不够。嵌入式开发的上下文密度比Web开发和桌面应用高得多你让AI写一个“读取温度”的代码它只能靠猜而猜出来的东西大概率跟你手头的外设型号、HAL库版本、时钟树对不上。我这次项目里写提示词前先给自己一个约束如果这段代码需要查阅芯片参考手册才能写出来那么在提示词里就必须把手册里的关键参数贴出来。我举一个实际的例子让AI帮我生成SHT30的读取命令时第一版提示词是这样的“写一个读取SHT30温湿度的函数MCU是STM32F103用HAL库的I2C。”AI给的代码用的是软件模拟I2C还带了一堆delay根本没法用。后来我把提示词改成了这样“STM32F103C8T6使用STM32CubeMX生成的HAL库I2C1外设句柄为hi2c1时钟频率400kHzSHT30的I2C地址为0x447位。需要实现向SHT30发送0x2C 0x06命令触发单次测量高重复性随后读取6字节数据。读取超时设为10ms。返回值为HAL状态码温湿度通过指针参数回传内部做CRC8校验。注意不要使用阻塞式delay不要用模拟I2C不要包含printf。”这样改完之后AI给出的代码基本能用了几乎不用改。区别在哪区别在于第一版提示词让AI自己“选型”第二版提示词让它“按图施工”。嵌入式开发中芯片型号、外设编号、句柄名称、Lrequent 库API风格、超时时长、错误处理方式这些信息就是施工图AI拿到图纸才算真正开始干活。我再把自己经常用的一个提示词模板分享出来后面所有模块生成我都是套这个思路写的硬件上下文芯片型号、外设句柄、引脚编号、时钟主频功能描述要做什么事、输入是什么、输出是什么约束条件不能用什么API、不能阻塞、内存限制、超时要求交互方式通过return状态码还是全局变量、是否需要回调已有代码贴出相关配置代码或头文件让AI对齐变量名2.2 三个典型模块的生成与人工修正这次项目里最能体现“AI协同”价值的是三个模块Modbus CRC16、Modbus从站状态机、SHT30单次测量驱动。我分别讲一下生成过程和需要人工修正的点。第一个是CRC16也是我建议每个想入门AI嵌入式协同开发的人拿来练手的模块。因为这个模块输入输出极其明确AI写错的概率几乎为零。我直接给了提示词“用C语言实现Modbus RTU的CRC16函数输入是uint8_t指针和长度输出是uint16_t多项式0xA001初值0xFFFF。”AI在几秒内生成的结果和标准实现完全一致我这边直接编译进工程上位机验证CRC也全部通过。这个模块是整个项目里第一个“AI写完即用”的代码省了大概二十分钟的手写时间。第二个是Modbus从站状态机。这个稍微复杂一点。Modbus从站的本质是“收到一帧报文校验通过后按功能码组织应答”而RS485是半双工你必须在发送数据之前把收发芯片的方向引脚切换到发送模式发完再切回来。AI很擅长把这种状态流转写成switch-case但它不会主动关心DE引脚的电平切换时机。我就在提示词里把这个点写了进去“DE引脚控制RS485方向高电平发送低电平接收请在发送前拉高发送完成后拉低。”这次AI就能生成靠谱的代码了。不过还有个坑它还是没避掉发送完成之后立刻把DE拉低间隙太短可能会把最后一个字节的尾巴切断。实测中最稳的做法是等到发送完成中断里或者TXE为空且TC置位之后再拉低DE。这个细节是我手动加的。这就是典型的人机协同AI把主干逻辑写好了而硬件时序上的“手感”还是得靠人来补。第三个是SHT30驱动。这个模块AI生成的代码在逻辑上完全正确但有个问题它默认你每次读取前都会先发送测量命令而且忽略了SHT30在收到命令后需要一段时间才能完成测量的时序。最要命的是它生成的i2c传输函数用HAL_I2C_Master_Transmit阻塞方式在400kHz下倒是能跑但我整个工程的定时器中断优先级比它高导致它在传输过程中频繁被打断直接触发I2C错误回调。这个问题是我在调试时用逻辑分析仪抓波形才最终确认的。修正方案是改用HAL_I2C_Master_Transmit_IT中断方式并把错误回调里加上了重新初始化I2C的逻辑才彻底稳定下来。2.3 代码审查清单AI写代码容易在哪里翻车这些天下来我整理了一份“AI嵌入式代码审查清单”现在基本每次让AI生成代码后都会对着清单过一遍。这个清单的核心是五个问题第一个问题是“有没有引入阻塞式延时”。AI特别喜欢用HAL_Delay因为它简单。但嵌入式工程里一旦进入主循环前的初始化阶段我不希望任何地方出现无条件的阻塞延时尤其不能出现在中断上下文里。我会让工具帮我全局搜索“HAL_Delay”查到就必须改掉用状态机或定时器替代。第二个问题是“外设句柄名称是否与CubeMX配置一致”。AI生成的代码里默认的句柄名和实际工程里CubeMX生成的变量名经常对不上。我提醒自己提示词里明确写出句柄名比如hi2c1、huart2生成后再检查一遍防止它自作主张改名。第三个问题是“缓冲区大小与DMA配置是否匹配”。尤其是串口接收AI默认用数组接收但没考虑接收中断里可能出现的溢出。我在项目中把Modbus接收缓冲设成128字节DMA半满和全满中断同时开启AI生成的裸数组接收直接被我推倒重写了。第四个问题是“全局变量还是局部变量”。AI默认喜欢把中间变量定义在全局因为这样调试时方便监控。但嵌入式系统里全局变量太多容易造成内存碎片化其实是静态RAM占用上升而且多线程/中断环境下容易出现不可重入问题。我会在提示词里明确“不要用全局变量除非是static const”。第五个问题是“是否检查了HAL函数的返回值”。HAL函数几乎都会返回HAL_StatusTypeDefAI经常忽略。我要求所有HAL_I2C、HAL_UART调用必须检查返回值并且在错误时记录错误标志。这样后续出了问题才能快速定位。这套清单帮我节省了大量时间。AI生成的代码本来就已经比手写速度高出一截再经过这个清单的过滤代码质量基本能到直接上板跑的水平。我也建议你把清单沉淀成自己的版本用多了自然形成肌肉记忆。3. 联调排错实战AI不是调试器但会读日志3.1 第一次上电就HardFault让AI帮你做栈回溯第一次给这个板子通电时我把程序烧录进去跑了几秒钟就死机了。现象很典型LCD屏数据刷了几屏后停止更新看门狗没有复位当时还没开串口没有任何输出但我从调试器里看到程序停在HardFault_Handler。这种问题对新手来说非常致命因为你看不到是哪一行代码触发的错误。这里我第一次认真尝试让AI参与调试。方法是将调试器停在HardFault_Handler时读取几个关键寄存器PC、LR、MSP、PSR然后把调试器里对应的汇编源码窗口内容复制出来连同寄存器值一起贴给AI问它“这个栈内容像是哪类故障可能从哪里进来的”AI虽然不能直接告诉我答案但它给了一个我当时没意识到的方向从LR的值0xFFFFFFF9来看代码应该是跑在Thread模式用的MSP主栈指针而PC落在一个SHT30数据处理函数里。它提醒我检查是不是有未对齐访问或者栈溢出。顺着这个方向我在SHT30的回调里找到了一个导致局部结构体数组越界写入的循环。手动计算后发现那个数组定义的索引上限是7而循环里用到了索引7即第8个元素属于典型的off-by-one溢出把相邻的变量踩坏了。这个排查过程大概花了一个多小时。其中有30分钟是我自己对着参考手册和调试器瞎猜真正让AI发挥价值的地方在于它帮我把“排查范围”从整个工程缩小到了几个函数。它其实不会调试但它能读“寄存器汇编反编译”组合出来的信息并且给出让你重新审视代码的提示。前提是你得懂得把现场信息完整地搜集给它而不是只丢一句“我的板卡死了”。3.2 Modbus超时的排查帧时序与CRC谁在撒谎HardFault解决后第二个比较折磨人的问题出现了上位机读温湿度第一帧能通第二帧开始全部超时。这个现象和Modbus从站程序关系非常大也特别适合拿出来讲讲因为它涉及RS485半双工的一个经典陷阱。我一开始怀疑是CRC校验出错。因为如果CRC不对从站就会丢弃帧上位机自然收不到应答。我在上位机侧用调试模式去观察发现发送的帧内容没有任何问题CRC计算也是对的那就不是帧内容的问题。接着我开始抓总线波形逻辑分析仪挂在A/B差分线两端。抓到一半我发现一个让人哭笑不得的现象上位机发出请求帧后波形上确实出现了从站应答帧的开头但只发了两个字节然后总线就被拉回高电平相当于应答被腰斩了。问题出在DE方向引脚的控制时机上。从我把程序烧进去的那一刻起DE引脚默认是低电平也就是接收状态。收到上位机请求帧后程序解析完准备进入发送模式执行到“拉高DE”这句的时机正好是在UART发送完成中断之后。逻辑上没错但实际执行时从拉高DE到真正把第一个字节写入发送寄存器之间还有几次CPU时间片RS485收发器需要一小段时间完成方向切换。结果就是UART已经把第一字节送出去了但收发器方向还没来得及完全切到发送状态导致信号被截断。解决办法很简单就是在拉高DE之后、发送第一字节之前插入一个极短延时大约10微秒即可解决。AI生成的代码里没有替我考虑这个物理切换时间因为这种“硬件物理特性”一般不会写在数据手册的编程指南里完全靠现场调试积累。顺带说一嘴这类问题如果让我重新做一遍我会在写代码的时候就把RS485的DE切换抽象成两个函数——rs485_tx_mode()和rs485_rx_mode()在函数内部统一处理延时比在业务逻辑里到处散落HAL_GPIO_WritePin要优雅得多排查起来也快得多。3.3 反编译产物对照排查把“黑匣子”变透明这个项目里还有一件事值得一提我第一次认真地用反编译工具辅助调试了固件。不是拿来破解别人的程序而是把自己编译出来的Release固件拿来做“编译产物对照”排查一个很隐蔽的问题我发现我定义的一个结构体在内存中的实际大小和预期不符。当时的情况是我定义了一个Modbus寄存器映射结构体里面有uint16_t变量和uint8_t变量交错排列。按C语言的默认对齐规则这个结构体在内存里会有padding实际大小远大于我手动计算出来的“所有成员字节数之和”。而我在主循环里用memcpy把这个结构体直接拷贝到发送缓冲区发送长度用的却是手动计算的字节数。结果就是每次Modbus应答帧里最后一个寄存器值总是错位有时候干脆是垃圾数据。这种问题靠看代码能发现但很费眼。我当时图省事直接把编译生成的.elf文件丢到工具里用arm-none-eabi-objdump生成反汇编和映射文件然后把结构体变量对应的地址段裁给我看。过程其实不复杂先找map文件里结构体变量的地址再去反汇编里看它的空间分配和访问方式AI辅助我快速解读这段汇编表达的含义确认编译器实际给结构体分配了padding。这一下就确定了问题根源。后面我把结构体成员手动按2字节对齐调整顺序小成员合并、补齐到2的倍数同时改用#pragma pack(push,1)显式控制对齐再次编译后map文件里的结构体大小终于和预想一致了。这里要把规矩说清楚反编译自己写的固件用于调试、对比、学习编译器的行为是完全合法合规的。但如果拿去分析别人的固件、或者试图绕过授权机制那就越界了。我这次之所以用反编译核心目的很单纯搞清楚编译器到底把C代码编译成了什么这样你才能真正理解“为什么有些C代码看起来对跑起来却不对”。4. AI协同开发后的效率复盘真实涨幅与没被说破的真相4.1 效率提升的真实分布项目全部调通后我做了一个比较冷静的复盘也给时间里每一项工作拉了清单。这个项目的总开发时长大概是三个周末的零散时间其中真正花在“写代码”上的时间大约占五分之一剩下的时间全都在“改代码”和“查为什么跑不对”上面。AI编程带来的效率提升是真实存在的但我必须诚实地说它的提升很不均匀。我先把各个模块的提升幅度列出来工作环节传统模式所需时间AI协同后实际时间提升幅度备注CRC16、帧解析、大小端转换约1小时约15分钟明显AI一次生成到位几乎无返工Modbus状态机主体约2小时约1小时中等主干能直接用但边界条件需要补SHT30驱动约1小时约1小时不明显AI给了框架但时序细节返工占大头RS485 DE方向切换逻辑约30分钟约1小时反而变慢需要人工理解物理现象AI帮不了HardFault排查约2小时约1.5小时中等AI提供方向性建议但需要调试器信息配合结构体padding问题约1小时约30分钟明显反编译AI解读少走很多弯路你会发现一个规律越是“逻辑封闭、规则明确”的任务AI提效越大越是“依赖硬件时序、物理特性”的任务AI反而会因为“过于理想化”给你制造额外工作量。嵌入式软件的特点恰好就是后者占比不低所以AI协同开发的真实体感不是“从三天变半天”而是“从三天变两天半”或者“同样是三天但你交付的代码质量上了一个档次”。另一个隐性收益是和AI结对写代码的过程中我发现自己查阅参考手册的效率和写代码的规范性都有提升。因为AI会给出它认为规范的写法你为了验证它给你的东西是否适合你的板子会不自觉地更频繁地查看芯片参考手册。这个习惯一旦养成长期回报非常大。4.2 AI时代嵌入式软件该怎么学结合本次项目的体会顺着项目复盘我特别想说说“AI下的嵌入式软件怎么学”这个话题。现在总有一种声音在讲“嵌入式已死”其实完全不是那么回事。嵌入式软件的门槛不在于你会不会写几句C语言程序而在于你懂不懂芯片是怎么工作的、外部设备是怎么和主控沟通的、出了问题你能不能查出来。AI能帮你把C语言代码写得更快但它改变不了硬件本身的规律。我这次的体会是如果你正好处在学习嵌入式的阶段千万被把AI当成“作业代写工具”而要把它当成“可以对话的参考手册”和“代码审查员”。具体我用的方法是拿到一个新的外设比如这次是SHT30我先把它数据手册里的关键时序图读一遍理解大致通信流程然后再让AI写一版代码。写完我不急着用而是拿着数据手册去逐行对照AI代码里每一句的对应关系。这个过程非常痛苦但极其有效。因为你等于在“用AI代码教你如果写驱动”而你亲手对照过一遍之后对这个外设的理解深度远超自己闷头写十遍。我还会定期让AI对我手写的代码做反向审查我写完一个模块让AI指出可能的错误和需要改进的地方。这个方法用在复合场景下特别好比如DMA加中断加环形缓冲AI能快速指出多个容易出bug的交叉点而我只需要验证它说的对不对。这比纯粹的“让AI替我写”有价值得多因为审查意见里有AI认为的“合理工程做法”而这些正是嵌入式工程师的“内力”。这次项目做完之后我还沉淀了一套自己的“协同开发行为准则”第一AI生成的代码必须经过自己编译验证才能并入工程第二AI改变任何硬件相关配置或者外设初始化必须重新读一遍芯片参考手册确认第三调试时千万不要让AI帮你做“决策”而是让它帮你做“推理”决策权永远在自己手上。写在最后先交代一个操作性很强的小技巧这也是我这次项目“踩完坑”之后总结出来的每次让AI生成代码之前先给它一个“环境上下文块”这个块里固定包含芯片型号、HAL库版本、工程IDE类型、主要外设句柄名、外设时钟频率以及“已经确认可用的模板代码”的片段开头。我为你把这个块整理成一个可以直接用的起始模板后面我给AI发消息都会把它附加在最前面【嵌入式开发上下文】 - 芯片型号: STM32F103C8T6 - 开发工具: STM32CubeMX STM32CubeIDE - HAL库版本: V1.8.0 - 系统时钟: FCLK 72MHz, APB1 36MHz, APB2 72MHz - 已配置外设: I2C1(hi2c1,400kHz), USART2(huart2,115200,8N1) - RS485方向引脚: PB1 (高电平发送低电平接收) - 工程模板: STM32CubeMX生成使用HAL库 - 约定: 禁止HAL_Delay禁止全局变量返回HAL_StatusTypeDef超时10ms一次把这段上下文端到端地贴过去AI返回的代码和让你反复追问“我的芯片是什么型号”得到的代码完全不是一个质量层级。你多试两次就知道前期花在上下文整理上的时间后期会在纠错和返工上数倍还回来。最后再说我个人的一点真实体会AI协同开发不会把一个新手变成高手但它一定会把“高手”的效率放大很多倍。它的价值不在于替代你思考而在于把那些不需要思考的重复劳动从你的工作台里挪走让你有精力去琢磨真正复杂的、芯片层面的、时序层面的问题。换句话说AI能帮你把路铺平但得自己朝前走。这是我第一个完整跑完的AI协同开发项目后面我准备用同样的流程做一个稍微智能一点的系统带状态机和逻辑控制的到时候再拿实测数据来聊。