ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI协同嵌入式开发实战:STM32G0环境监测节点全流程避坑记

AI协同嵌入式开发实战:STM32G0环境监测节点全流程避坑记 1. 为什么我决定带AI一起干嵌入式项目而不是让它替我干先说个真实的感受刚接触AI编程那会儿我的心态是终于可以偷懒了给AI丢一句帮我写个STM32的串口驱动它噼里啪啦输出几百行代码我复制粘贴进工程编译一跑——全是乱码。那一刻我明白了嵌入式开发里AI从来不是替身它是一个带过很多项目的老同事你要给它讲清楚需求、给它检查代码、给它擦屁股最终拍板的还是你自己。这篇就是我第一个AI协同开发项目系列的第二篇。上一篇我讲的是怎么选模型、怎么搭开发环境、怎么把代码仓库的上下文喂给AI。这一篇进入正题拿着一个真实的嵌入式小项目从头到尾走一遍AI协同开发的全流程包括需求拆解、提示词设计、代码生成、编译调试、以及让我印象最深的几个坑。如果你是做嵌入式软件开发的不管是老手还是刚入门这篇都值得看。老手可以看我是怎么把AI嵌进现有工作流的新手可以看一个完整的AI协同项目长什么样。我尽量把每一步的操作细节和背后逻辑都讲透不搞那种AI帮你写代码的玄学叙事全部是实际干活的经验。先说清楚这个项目是什么我要做一个基于STM32G0系列的环境监测节点用I2C接口接一个温湿度传感器通过串口把数据发出来同时支持一个简单的按键控制——按一下切一次采样模式。功能听起来很简单但真正的嵌入式开发难就难在硬件外设初始化、中断处理、低功耗逻辑、以及各种边界条件这些恰恰是AI最容易写错的地方。在动手之前我先明确一件事AI在这个项目里承担什么角色、哪些环节必须我来把关。这个分工决定了后面所有的工作节奏。2. 项目起步给AI写入职说明书而不是一句话命令很多人用AI编程有个坏习惯上来就是帮我写个XX然后抱怨AI写得不行。这就像你刚入职一个公司领导不给你看资料、不讲需求和规范直接让你写代码写出来的东西能看才怪。AI也是这样它需要一份入职说明书。2.1 AI协同的第一课把项目背景讲清楚在让AI写任何代码之前我先组织了一份项目背景文档内容包括芯片型号STM32G030F6P6Cortex-M0内核64KB Flash8KB RAM开发环境STM32CubeIDE HAL库C语言硬件连接I2C1接SHT30温湿度传感器USART1接串口调试PA0接按键外部中断关键约束系统需要在待机模式下电流低于10uA唤醒后5ms内完成一次采样并继续休眠其他要求代码风格遵循MISRA-C基本规则关键函数需要注释这份文档我大概写了四十分钟但这四十分钟花得值。后面AI每次生成代码的时候我都把这份文档的前几段贴进上下文它生成的代码明显更贴合硬件实际不会出现随意找个引脚复用这种外行操作。提示给AI的硬件上下文越具体越好。芯片型号、板卡版本、外设连接、时钟频率、Flash/RAM容量这些信息直接决定了AI生成的HAL初始化代码靠不靠谱。缺失任何一个它都可能给你编一个不存在的引脚定义。2.2 提示词不是越详细越好而是越结构化越好我见过不少同行写提示词恨不得把整个需求文档复制进去结果AI输出一大堆冗余内容真正要用的代码淹没在废话里。我的做法是把提示词拆成四层结构角色设定告诉AI它是什么角色比如你是一名有10年经验的嵌入式固件工程师熟悉STM32G0系列和HAL库任务目标一句话说清楚要它做什么比如编写SHT30传感器的驱动模块提供初始化函数和单次读取函数边界条件硬件连接、通信速率、错误处理策略、不允许使用的API等输出格式要求它输出的内容形式比如提供sht30.c和sht30.h两个文件的内容关键函数添加注释禁止使用阻塞式延时举个例子同样是让AI写I2C读取SHT30的功能对比一下糟糕的提示词帮我写个读取SHT30的代码。我实际用的提示词你是一名有10年经验的嵌入式固件工程师熟悉STM32G0系列和STM32 HAL库。 任务编写SHT30温湿度传感器驱动模块支持I2C1接口通信。 硬件信息 - MCUSTM32G030F6P6Cortex-M0主频64MHz - 通信接口I2C1速率400kHzPB6SCLPB7SDA - 传感器地址0x448位地址形式为0x88 - 工作电压3.3V 功能要求 1. SHT30_Init()初始化I2C1外设和传感器 2. SHT30_ReadTemperature()读取温度返回float类型摄氏温度值 3. 错误处理通信失败时返回错误码不能阻塞系统 4. 时钟延展处理SHT30在进行AD转换时可能需要时钟延展 5. 测量模式使用周期测量的高重复性模式0x2C06命令 输出要求提供sht30.c和sht30.h的完整代码关键寄存器配置需要注释说明原因。这样写提示词AI生成的代码基本在首次就能编译通过。这个结果是可复现的关键就在于你给它的信息密度足够高、结构足够清楚。2.3 提示词生成的第一版驱动代码长什么样我让AI生成SHT30驱动后它给出了一个约180行的实现。整个结构还算规整Init函数负责MX_I2C1_Init和发送传感器唤醒命令Read函数发送测量命令、读取6字节数据、计算温湿度。给我印象最深的是它自动处理了CRC校验——SHT30返回的数据最后两个字节是CRC8校验值如果不校验偶尔会出现一个离谱的湿度值。但看完代码我立刻就发现了两个必须改的地方第一它的I2C读写函数用的是HAL_I2C_Mem_Read和HAL_I2C_Mem_Write看起来没问题但配合SHT30这种非寄存器式通信的传感器更标准的是用HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive。AI在这类细节上的选择有时候不会区分清楚。第二它的错误处理全是用HAL_I2C_IsDeviceReady轮询检测虽然是可行的方案但没考虑总线出错时的恢复机制这在实际硬件上很常见。这些不是AI没写好而是提示词里没提它按最常见的方式来。所以我说AI协同开发的核心工作是审代码而不是写代码。我花了大概五分钟把这两处改掉顺便加了一个超时退出机制整个驱动模块就足够稳了。3. 关键环节拆解AI在处理中断和状态机时到底行不行传感器驱动只是热身真正考验AI功力的是两件事中断处理逻辑和外设状态管理。这两个模块恰恰是最容易出看似正常、实则崩溃问题的地方。3.1 按键外部中断的需求表达与AI实现项目需求里有一个按键功能按一下切换采样模式从1Hz切换到10Hz长按3秒进入低功耗模式。这个逻辑用文字描述很简单但落到代码里涉及外部中断引脚配置、防抖处理、事件标记、主循环消费事件、长按计时等模块。我先让AI只负责外部中断配置这一小块提示词里明确了使用PA0引脚下降沿触发中断回调函数里只做一件事置一个全局标志位禁止在中断回调函数里做延时、轮询、日志输出AI给出的代码是对的——用HAL_GPIO_EXTI_Callback在回调里置一个volatile uint8_t标志位。这部分没有任何问题。但它默认没把防抖写进去。这不算AI的错因为很多硬件工程师的习惯是防抖在硬件电路用RC滤波解决项目里确实加了RC所以软件防抖可以省略。但如果你没有硬件防抖提示词里必须让AI加上软件防抖否则按键触发一次会确认好几次。然后是长按检测。我把这个逻辑单独拎出来让AI设计一个简单的状态机提示词里给了三个状态IDLE等待按下、PRESSED已按下、RELEASED已松开并说明判断条件。AI用了经典的switch-case状态机配合HAL_GetTick()做时间戳记录实现得很干净。它的状态转移图直接在注释里画出来了代码可读性反而比我之前自己写的版本好。3.2 用10个问题面试AI生成的HAL初始化代码有一类错误是编译器不报错、运行才崩溃的GPIO复用配置错、时钟源选错、DMA通道冲突。这些问题的根因是HAL库的初始化代码和芯片实际的引脚定义不匹配。我的习惯是在让AI生成初始化代码后自己对照芯片手册一项项面试它。下面是一份我常用的核对清单这次项目里也实际用了一遍核对项核对方式这次项目的结论引脚复用是否正确对照数据手册Alternate Function表AI正确选择了AF1给I2C1OKGPIO速度等级高速信号需要HIGH按键可LOWAI默认LOW串口需要HIGH我改了时钟源选择需要确认APB1/APB2外设时钟AI选对了它知道I2C1挂在APB1中断优先级分组NVIC优先级不能随意AI默认给了0我结合实际改成2串口波特率计算确认是否与上位机约定一致我用的是115200AI写成了9600改了上拉电阻配置开漏I2C必须有上拉芯片内部有上拉可开AI配置了内部上拉OKSysTick中断冲突如果用了HAL_Delay不能关本次未涉及但需要确认DMA外设请求如果开了DMA必须开启对应中断本次串口未用DMA忽略功耗模式相关外设低功耗前必须关闭不必要外设AI在低功耗分支里关了ADC和I2C我补了串口这套核对流程花不了多少时间但能避免大部分启动即死机的问题。不夸张地说AI生成的初始化代码80%是对的那20%的错误全在这种硬件细节上必须有个人拿着手册一页页对照。3.3 一定要让AI解释它写的每一行非常规代码AI有时候会写一些让人看不懂的代码。我遇到过几次它为了优化搞了一段位操作编译通过了、运行结果也对但没人知道为什么要这么写。这在大厂合规审查里是个大忌——代码必须有可维护性。所以我在提示词末尾加了固定的一条所有非常规的位操作和魔法数字必须在行注释里解释含义。这样一来AI生成的代码里不会出现莫名其妙的0x7F或1 8之类的裸数字。这个习惯建议大家从一开始就养成。4. 编译、烧录、调试AI帮不了的那部分才是项目成败的关键说实话AI协同开发最大的增量不在写代码而在改代码——编译——烧录——看现象——再改这个循环里。AI能帮你把代码量压缩一半但这个循环本身是省不掉的而且循环里到处是坑。4.1 首版编译一次性通过的代码我却高兴不起来这是我的真实经历。第一次把AI生成的所有模块合进工程后编译全工程只有两个warning没有任何error一次通过。我当时的反应不是AI太强了而是这肯定有问题。果不其然上板实测后发现了三个实际问题第一SHT30驱动读数异常温度偏高15度。查了一圈发现AI在计算温度时用了错误的移位方式。SHT30返回的16位数据是MSB在前AI写的是data[0] 8| data[1]这本身是对的但在HAL库接收数据时它用了小端序数组存储导致高低字节被颠倒了。这种字节序的坑在嵌入式里极其常见AI很难从代码上下文里判断出来只能靠你拿着逻辑分析仪或者串口打印去对比。第二串口输出经过USB转TTL模块时正常但直接用杜邦线接TTL电平的调试工具就乱码。原因是AI配置了串口的硬件流控引脚还启用了RTS/CTS。这个功能在开发阶段完全没必要反而会把调试工具的电平搞混。第三也是让我最头疼的——低功耗唤醒后系统卡死。这个后面单独展开讲。这几个问题都不是AI写错了而是它在没有光照到硬件实物细节时只能按标准答案来而嵌入式里根本没有标准答案。代码能不能用最终是板子说了算。4.2 低功耗唤醒Bug的完整排查链路这个Bug值得单独拿出来讲因为它完美展示了嵌入式AI开发的典型形态AI帮你缩小了排查范围但定位问题的逻辑能力还得靠人。现象是程序进入STOP模式后按按键唤醒系统可以跑但串口不再输出数据LED也不闪。复现率100%。我的排查思路分四步第一步确认唤醒源和时钟恢复。用示波器测了PA0引脚按键按下时电平有跳变EXTI中断应该触发。进入STOP模式后系统时钟默认切换到MSI唤醒后不会自动恢复到PLL——这是HAL库和底层CMSIS的默认行为所以我在唤醒后的代码里需要手动重新调用HAL_RCC_ClockConfig把时钟切回PLL。这步做了之后LED恢复闪烁说明CPU核和基本时钟已经恢复。第二步测串口引脚波形。发现TX引脚在唤醒后没有任何数据但引脚电平正常说明UART外设时钟可能没恢复。查代码发现AI在进入STOP模式前把USART1的外设时钟关了但唤醒恢复的函数里没有重新使能。这其实是HAL库的__HAL_RCC_USART1_CLK_ENABLE()调用问题加一行就解决。第三步看I2C总线状态。唤醒后继续读传感器第一次I2C通信超时。原因更隐蔽I2C总线在STOP模式前是挂着的唤醒后总线状态还停在忙需要发送一个STOP条件来恢复。AI生成的代码没有这个处理我是靠逻辑分析仪看到SCL在持续拉低才发现问题。第四步把所有恢复逻辑整理成一个System_Resume()函数让AI基于出错的完整日志生成一版恢复流程的代码最后实测通过。这个过程里AI唯一做得好的是帮我查了HAL库里STOP模式相关的API怎么调用它省了我翻手册的时间。但定位为什么唤醒后串口不工作的根因是外设时钟没恢复这种判断完全依赖我对STM32时钟树和HAL库执行流程的理解。所以别指望AI替代你做根因分析它只能做加速器。注意如果你在AI协同开发中做低功耗项目一定把唤醒后的时钟恢复和外设状态恢复写进验收清单。AI默认代码里至少有一半的概率不会处理这两件事。4.3 给AI建立错误反馈闭环我在这几轮调试里积攒了一个重要的经验当AI生成的代码出现Bug时不要直接把它修复后的结果贴回去而是要把错误现象、你的排查过程、最终定位到的根因整理成一段话放回AI的上下文里让它基于这个反馈重新生成代码。这么做有两个好处。第一后续对话里AI不会再犯类似的低级错误因为它看到过你给它纠偏的内容。第二它生成的后续代码会更注重你在排查中暴露出来的薄弱点比如字节序、外设时钟开关、总线状态恢复。我把这个过程叫错误反馈闭环是AI协同开发里最有价值的操作。如果说提示词设计决定了AI的下限那错误反馈闭环就决定了AI的上限。一套完整的失败案例反馈模板大概是这样的硬件环境STM32G030F6P6I2C总线接SHT30 错误现象系统从STOP模式唤醒后第一次I2C读取超时 排查过程 1. 确认时钟已恢复LED正常闪烁 2. 用逻辑分析仪观察SCL被拉低总线呈忙状态 3. 对比进入低功耗前的总线状态发现缺少释放总线的STOP条件 根因进入STOP模式前I2C外设未发送STOP条件总线状态残留 修复方法在唤醒后执行一遍I2C总线复位序列 请基于以上信息重新生成低功耗模式的进入和退出代码。看完这段内容AI生成的代码里自动加了一个I2C_Bus_Reset函数还顺带处理了MCU其他外设的总线残留。效果立竿见影。5. 软件架构层面的协同当AI面对的是一整个工程而不是单个函数前面几章讲的都是让AI写一个模块的用法。但在实际项目中AI的价值如果要最大化它得能看懂整个工程的代码结构和模块间依赖。嵌入式软件工程的代码组织方式决定了AI能不能在跨模块场景中帮上忙。5.1 工程结构怎么划分AI的视野才够大这是我的一个体会AI对单文件的处理能力强但跨文件的理解能力弱。所以你的工程结构越清晰——头文件职责单一、源文件的函数聚集性越强——AI给出的跨模块建议就越靠谱。我这次项目的模块划分如下main.c只做系统初始化和主循环调度bsp_i2c.cI2C总线初始化总线恢复函数sht30.c传感器驱动依赖bsp_i2capp_task.c应用逻辑状态机按键事件处理power.c低功耗进入与唤醒恢复debug_uart.c串口日志输出每个模块的函数声明都在各自的头文件里模块之间依赖关系明确。AI在收到让SHT30的采样逻辑在低频/高频模式之间切换这样的任务时能准确找到该改哪些文件、不该动哪些文件而不是把代码全堆在main里。5.2 让AI生成跨模块调用关系图再用自己的大脑复查我没有让AI直接画架构图项目小画图意义不大而是让它用文字描述从系统启动到首次数据输出的完整调用栈。它写出来的内容基本准确Reset_Handler → SystemInit → main → HAL_Init → SystemClock_Config → MX_GPIO_Init → I2C_SHT30_Init → 主循环 → UART_Log → 定时器调度。这个调用栈帮助我一眼就发现了SystemClock_Config里有一个时钟源初始化顺序的问题然后提前改掉。这种让AI描述、你来核对的协作模式比它输出一张流程图有用得多。5.3 代码审查不能省我的三层Review流程最后一道工序是Review。AI生成代码我不敢直接用必须走三层审查第一层功能审查。把AI生成的代码在逻辑上跑一遍模拟各种入参和边界条件确认功能符合需求。这一层大部分靠读代码。第二层硬件审查。对照芯片数据手册核实引脚、时钟、外设配置。这一层AI做不了必须人来。第三层规范审查。检查命名风格、注释完整性、是否存在无意义的宏定义。AI在命名上大多没问题但它喜欢生成几百行的一次性函数不利于维护我会拆开。三层review下来单个模块的代码从生成到合入大概会花我30到60分钟。一开始觉得慢但实战几周后我返工的次数大幅减少整体进度反而比以前自己硬搓代码、瞎改bug快得多。6. 实战中AI踩过的另外几个坑以及我的应对策略除了上面讲过的低功耗唤醒问题这轮项目里还遇到过几个值得记录的坑每个都代表一类常见问题拿出来单独说说。6.1 串口乱码的真相不是波特率不对是时钟频偏项目里遇到过串口接收端显示的字符是乱的起初以为是波特率算错了。用示波器量了TX引脚的波形发现每位的时间确实有点偏差但不大。后来查了SystemClock_Config里的时钟源AI配置了MSI作为系统时钟MSI在STOP退出后的精度不如HSI而且MSI的频率校准值没有在初始化后写入导致实际波特率偏差超过了串口容错的±2%。这个例子说明AI生成的时钟配置基本能跑但不一定跑得准。真正做产品的话时钟精度是必须验证的。我后来的做法是在提示词里固定要求系统时钟源首选HSI并注明校准流程。6.2 别让AI乱用printf重定向嵌入式调试最喜欢在串口上重定向printfAI对此也非常积极。问题是如果你只让AI写串口调试代码它很可能直接给你写一个fputc重定向到UART的思路但这个重定向会引入__io_putchar的依赖在某些IDE版本里会编译报错。而这种错误往往不是显性的它藏在链接阶段。我的建议是串口底层函数自己写不让AI代劳。因为这块代码非常稳定、各芯片之间差异大、而且一旦写好几年不用动让AI写反而要花时间校验。AI的优势场景是业务逻辑多变、方案多分支的代码这种万年不变、写错就崩的基础驱动自己来最稳妥。6.3 AI生成的宏定义必须警惕魔法数字陷阱AI在生成配置宏时喜欢直接给出数值比如#define I2C_TIMEOUT 1000。这个1000到底是毫秒、微秒还是HAL库循环的次数 AI不一定说得清。我遇到过把宏定义当成毫秒用实际HAL库内部对它做了循环换算导致超时时间短得离谱的情况。解决方法是所有这种宏定义必须让AI在注释里写明物理意义和单位同时我再结合HAL库的默认超时参数做一个大概的换算。这一步虽然烦琐但能省掉不少怎么跑一下就超时的怪问题。7. 这一轮项目跑下来AI协同开发教会我的事项目收尾时我统计了一下时间投入从零到全部功能跑通总共花了大概三天其中纯手写和调试的时间大约占一半。如果是以前这种规模的项目我自己写大概要五到六天。AI确实帮我省了时间但省的最多的是代码初稿和格式化的时间而不是定位问题的时间。我最大的体会是AI协同开发的真正价值不在于它能生成多少代码而在于它强迫你用更清晰的逻辑去拆解需求、设计接口。因为提示词写不清楚AI就一定给你写不清楚的代码——你在提示词上偷的懒后面会用十倍的Debug时间还回来。如果你正准备开始自己的第一个AI协同嵌入式项目我给你几条掏心窝的建议从外设驱动开始练手别一上来就让它写整个系统。先让AI写一个I2C驱动、串口驱动、PWM驱动摸清它的代码风格和错误模式。把提示词模板固定下来反复微调。我有一套自己的模板角色硬件信息功能需求输出格式每次写提示词只改核心内容其他部分不变效率高很多。每遇到一个新坑就把它记录成错误反馈段放回AI的上下文里。久而久之你手里的AI会越来越懂你的项目、越来越懂你的硬件、甚至越来越懂你的代码风格。保留随时退出AI、自己动手重写的能力。AI不是这条开发链路上不可替代的一环你才是。最后补一个实操心法。我现在写嵌入式代码的习惯已经变成了先让AI按我的思路出一版骨架我逐行Review、补充硬件感知的细节再让AI根据我的批注做修改最后我自己写测试脚本验证边界条件。这个流程走顺了之后AI再也不是偶尔帮我生成一段代码的工具而是真的像一个能帮我分担初稿工作的协作者。下一步我打算把AI接进编译报错解析的环节让它在每次编译失败后直接给出错误原因修改建议影响面分析。等跑完一个比较完整的闭环我再回来写这个系列的第三篇。如果你也在用AI做嵌入式开发欢迎在评论区分享你踩过的那些AI的坑咱们互相借鉴。
返回列表