提到AI协同开发STM32程序,我第一反应不是某个自动补全插件的界面,而是一次实实在在的项目复盘。那是一个电池供电的采集节点,我花了三天排查一块启动不了的主板,最后把报错信息、电路上电时序和参考手册片段一起丢给大模型,它指出了时钟源切换顺序的问题。从那天起,我把AI正式纳入了日常工作流,也逐渐形成了一套相对稳定的协作流程。
很多人以为AI协同开发就是让AI生成整个工程,复制粘贴然后点编译。我试过,生成完看起来完美,编译也能过,但烧进MCU之后一切静悄悄。问题不在于AI写得不好,而在于嵌入式开发里的时钟树、外设寄存器、中断优先级、内存布局和功耗指标,恰恰是通用大模型最容易“想当然”的部分。没有流程约束,AI的想象力就是风险。
这篇文章适合两类人:一类是刚接触AI辅助嵌入式开发的工程师,想找一套能落地的协作方式;另一类是已经在用AI写代码,但总被它“一本正经胡说八道”坑到的人。下面是我在实际项目中反复调整后的完整流程,以及踩坑之后的判断标准。
1. AI协同开发STM32程序,本质是一场分工重构
1.1 我把协作方式拆成了四个阶段
与其说AI改变了写代码的方式,不如说它把嵌入式开发的时间分配打乱了。以前我做STM32项目,“想清楚怎么做”占三成时间,“写代码和调bug”占七成。现在正好反过来了——AI在编码外层的完成度很高,但在需求和验证层面帮不了我太多。这意味着人必须花更多精力把需求说清楚,把验收条件定清楚。
我把整个协作流程拆成四个阶段:
- 阶段A:需求与方案设计。人给出功能、功耗、成本、接口约束;AI负责列选项、做对比、补盲区。
- 阶段B:工程与资源准备。AI辅助查手册、生成工程配置建议;人负责选芯片、选外设、跑一遍官方工具。
- 阶段C:编码与调试。AI生成大量样板代码、解读编译错误、推断Bug原因;人负责取舍、修改、实测。
- 阶段D:评审与优化。AI做代码评审、注释补全、功耗与结构优化建议;人负责接受或否决。
这套分工看起来只是把工作步骤重新归位,但真正的区别在每个阶段的“责任主体”变了。阶段A和D的责任几乎全在人,阶段B和C可以大量交给AI,但所有交给AI的结论都必须留一条人工验证通道。没有这条通道,AI协同开发就会变成AI主导开发,返工成本远高于自己写。
1.2 哪些任务适合AI,哪些必须人工接管
具体到STM32,AI最适合的其实是信息密集、重复度高、逻辑相对封闭的活儿。我整理了一张分类表,方便大家预先判断是否该交给AI。
| 任务类型 | 适合AI? | 原因 |
|---|---|---|
| 外设初始化样板代码 | 高 | 语法固定、结构相似,AI可以快速生成 |
| 编译/链接报错解读 | 高 | 错误信息有规律,AI擅长检索和联想 |
| 参考手册要点检索 | 中高 | AI能快速提炼,但存在张冠李戴风险 |
| 状态机框架搭建 | 高 | 逻辑明确,AI实现效率高 |
| 中断与实时性分析 | 低 | 涉及优先级、延迟和硬件行为,AI经常出错 |
| 低功耗模式切换 | 低 | 不同型号差异很大,必须以手册和实测为准 |
| 硬件时序与电路问题 | 低 | AI看不到波形,只能给出排查思路 |
我个人的经验是:任何“能用文字准确描述”的任务,AI的可用性都高;任何需要“感受波形、读示波器、看电流”的任务,AI只能当参谋。你在任务开始前先问自己一句:这个任务的结果能不能用客观文字描述出来?能,就大胆交给AI;不能,你就得自己守在现场。
另一个容易忽略的点:AI适合一次性生成,不适合在同一个会话里反复修改同一段代码而不产生劣化。上下文越长,它越容易把早期的一些错误决策当成既定事实,然后继续在错误基础上扩展。所以我的习惯是:每个模块开一个新对话,把公共约束放在每段提示词的开头。
2. 动手之前:把AI当作架构顾问,而不是代码虫
2.1 从“做一个节点”到可执行的模块清单
我最近在做的项目是一个低功耗环境监测节点,供电是两节AA电池,目标是每60秒采集一次温湿度并通过无线模块上报。这样的需求听起来已经足够清晰,但直接让AI写代码依然会翻车,因为缺少模块边界和性能指标。我现在会在写任何代码之前,先让AI参与需求拆解。
我会把提示词写成这样:
请扮演嵌入式架构师,帮我梳理一个基于STM32的低功耗数据采集节点。已知条件:两节AA电池供电,每60秒唤醒一次,需要采集温湿度和电池电压,通过无线模块发送数据。请输出硬件模块清单、软件模块划分、关键风险点和待确认问题。不要写具体代码,先做系统边界设计。
AI往往会给出:需要RTC或低功耗定时器唤醒、传感器I2C接口、无线模块SPI或UART、电压采样ADC、电源管理、预留烧录与调试接口、电池低电量报警、发送失败重试……这些内容看起来都是常识,但它能帮我把容易漏的“备用启动方式”和“看门狗策略”补进清单。
这里最关键的动作是“筛选和补问题”。AI列出的模块不一定全要,也不一定够。比如它可能会漏掉出厂校准参数的存储区域,或漏掉无线重发时的随机退避要求。我会拿着它生成的清单逐条问自己:这个模块如果不存在,会不会导致产品无法交付?然后人工补上硬件版本号读取、参数持久化、日志擦除保护等条目。
2.2 配置工具与参考手册:AI是加速器,不是替代品
接下来我会先用芯片厂商提供的图形化工程生成工具,把芯片型号、时钟树、外设引脚和中断优先级的基础工程搭出来,生成代码骨架。这个过程AI能帮的忙,是解读那些密密麻麻的时钟树选项。比如APB1和APB2最高频率分别是多少、某个外设挂在哪条总线上、系统时钟从PLL切换到HSI需要注意什么,AI都可以根据公开资料给出比较可靠的解释。
但你要有意识地区分“通用知识”和“型号专属知识”。像Cortex-M的时钟树一般结构、APB分频关系,AI基本答得不错;而某款具体型号的低功耗定时器是否支持从外部时钟源计数,内部高速时钟的校准值放在哪个寄存器,这些细节AI经常把不同型号混着答。我把这类问题称为“高幻觉区”,解决方式很笨但很有效:对话开始时固定一句——“请基于某STM32超低功耗系列芯片回答,如果你不确定,请明确说不知道,不要猜测。”并且要求它给出参考手册的章节号,我再核实。这句话能显著减少一本正经的编造。
对于工程生成工具本身,AI也不能完全替代。工具生成的芯片初始化代码虽然啰嗦,但它包含的引脚映射、时钟树参数和中断向量都与选型强相关,是后续所有代码的地基。AI生成的驱动代码必须建立在这个地基之上,而不是自己另起炉灶。
2.3 目录和版本规划:让AI产出第一张地图
代码写得越久,越发现AI协同开发最大的敌人不是AI错误,而是工程混乱。AI没有长期记忆,如果你自己都说不清某个文件是干嘛的、当前改到哪个分支,它给出的建议就会互相矛盾。所以我的习惯是在动手前就把项目目录和版本策略固定下来,这一项同样让AI参与。
我一般要求AI这样输出:
请为STM32的GCC工程生成一套目录结构建议,采用bsp/drivers/app/arch分层,要求包含头文件路径规划、静态库和可执行文件的分离,以及推荐的Git分支模型。
AI生成的目录不会完全符合我的项目习惯,但我会在上面改。比如它可能把配置文件扔进app,我则会单独建一个config目录,把硬件版本宏、编译选项、链接脚本都归进去。然后让AI根据目录生成.gitignore,排除build、IDE临时文件、日志输出等。这个动作看起来很基础,但它决定了后续AI代码补全能不能找到正确文件。如果你在多个分支里飘来飘去,AI生成的代码很可能来自记忆里另一个版本,这种问题几乎无法排查。
3. 编码阶段:提示词就是替代翻手册的撬棍
3.1 驱动代码:AI能写,但你要会“喂”约束
写外设驱动时,同样一个问题,提示词不同结果完全不同。我给大家看一个反例和正例。
反例:给AI一句话“帮我生成读取温湿度传感器温度的代码”。AI大概率会返回一套基于某型号芯片的HAL库代码,主函数、I2C初始化、延迟函数全给你。看起来能用,但一旦遇到具体传感器地址、寄存器长度、超时策略,就是你开始返工的地方。
正例:我会写:
在STM32某低功耗系列上,使用硬件I2C外设和HAL库,读取地址为0x44的温湿度传感器。传感器温度寄存器是16位,一次读取4字节,采用阻塞式读取方式,但要加超时和总线故障重试。请输出:
- 引脚初始化函数
- I2C发送接收函数
- 错误码定义
- 调用示例 要求:不使用动态内存分配,函数内不超过50行,关键参数用宏定义并说明依据。
这样AI就能在有限约束下生成可用的驱动骨架。给它限制非常重要,嵌入式的硬约束(不能malloc、函数长度、超时、重试)会让生成结果从一开始就贴近现场。这些约束是哪来的?不是AI告诉你的,是你根据自己的RAM、栈、代码规范定的。所以AI只是替代你翻手册和敲样板代码,不能替代你定边界。
3.2 业务逻辑:状态机可以给AI,边界条件必须自己定
驱动代码只是金字塔底层,真正麻烦的是业务逻辑。我习惯把业务逻辑拆成状态机来描述,因为状态机对AI太友好了,转移条件明确、动作块独立。
例如低功耗采集节点的主循环:
typedef enum { NODE_POWER_DOWN, NODE_WAKE_UP, NODE_SENSOR_READ, NODE_RADIO_PREPARE, NODE_RADIO_SEND, NODE_RETRY_WAIT } NodeState; static NodeState state = NODE_POWER_DOWN; while (1) { switch (state) { case NODE_POWER_DOWN: enter_stop_mode(); state = NODE_WAKE_UP; break; case NODE_WAKE_UP: if (clock_ready()) state = NODE_SENSOR_READ; else state = NODE_POWER_DOWN; break; case NODE_SENSOR_READ: // AI可以补全读传感器逻辑 break; default: break; } }这个骨架让AI补全完全没有问题。但真正体现工程能力的,是状态机之外的边界条件:传感器读失败三次后是否继续上报?无线发送失败后是重发还是丢弃?重发之间有没有随机退避?电池电压低于阈值时,是否降低采样频率?每一个问题都直接影响产品可靠性,而这些信息在原始需求里往往没有。
AI很可能自主加上一个我认为不合适的答案,比如“发送失败就无限重试”。这个判断离谱吗?在无线通信里,无限重试会加重信道拥塞和功耗,可能让整批节点在同一时刻反复发送,造成冲突。我必须在代码框架里把这种规则写成参数:最大重试次数、退避窗口、传感器失败是否进入低功耗模式。AI协同开发的流程里,这部分必须由人来输入约束,否则代码跑起来越“聪明”越危险。
3.3 寄存器、DMA和中断的回答,哪些能信
在AI给出的STM32代码里,最需要提防的是DMA请求映射和中断号。不同系列、不同封装、不同复用功能下,DMA通道完全不同。AI经常会把另一个型号的DMA映射表套到你的型号上。比如串口发送请求映射到DMA1通道4还是DMA2通道7,如果你的芯片恰好不同,代码编译能过,运行却永远不发数据。
我的处理方法分三步。第一步,写提示词时声明具体型号和具体外设:“请只为某STM32超低功耗系列回答,不要跨型号移植。”第二步,让AI的答案附上“如何自检”——它应该指出应该查看参考手册的哪一张DMA请求映射表,或者哪个寄存器的哪一位。第三步,拿到代码后,我用调试器打开外设寄存器视图,逐项核对DMA源地址、目的地址、传输长度、使能位。整个过程只需要几分钟,却能挡掉最隐蔽的一类Bug。
另外,中断优先级也是高幻觉区。AI建议的优先级数字可能来自它训练集里某个项目的经验值,但你的项目中A、B两个中断谁先谁后,必须由实际的系统延迟需求决定。AI不知道你的实时性指标,所以你应当给出目标,比如“串口接收中断不能阻塞无线发送中断超过1毫秒”,让AI在这个约束下推荐优先级,而不是反过来让AI替你决定。
4. 调试战场上,AI是第二块屏幕
4.1 编译报错排查:喂给AI的信息要成组
很多开发者把AI当成搜索引擎,只发一行报错文字,然后抱怨回复是废话。原因很简单:报错信息的语义依赖上下文。我会把报错时相关的几样东西一起发给AI:
- 芯片型号和编译器版本,因为不同编译器的语法差异很大。
- 完整错误日志,不只是最后一行error,最好带上发生错误之前的20行。
- 触发报错的文件和代码片段,尤其是宏定义和结构体定义。
- 当前优化等级,有些警告只在-O2或-Os时出现。
一个典型例子是项目中编译时出现一大堆“undefined reference”。AI从头到尾看一遍日志后指出,某个驱动文件的函数名写错,导致链接找不到符号,而错误列表里真正显示的却是另一个库文件。这比人眼从几百行日志里挑更高效,但前提是日志完整。
如果遇到HardFault,AI的作用更像一个诊断助手。你可以把进入异常时的寄存器快照交给它:PC指针、LR、堆栈地址、中断状态寄存器。AI会告诉你最可能是栈溢出、野指针,还是试图访问未使能外设。但它不认识你的具体状态,所以必须由你把“当前在执行哪段代码”的信息补上。有一次就是因为DMA中断里写了一个传感器读取函数,AI根据中断优先级和函数耗时推断出主循环被饿死,直接建议把读取逻辑移到任务里执行。这种问题靠经验也能查出来,但AI把定位时间从小时级缩短到了分钟级。
4.2 运行期BUG:日志、波形到AI推理的完整链路
运行期的问题,调试链路长且依赖现场信息。我的做法是先把问题变成文字可描述的现象,再用AI推断原因。所谓“文字可描述现象”,包括:什么时候发生、多长时间一次、触发前有什么外部事件、是否固定频次。
举个例子,节点每60秒唤醒一次,运行十分钟后开始偶发死机。我把日志时间戳做了统计,发现死机时间总是出现在无线发送成功之后、进入Stop模式之前。把这段日志丢给AI,它很快就判断可能是无线模块发出的中断在Stop模式下没有被正确屏蔽,导致进入低功耗后被中断唤醒,但系统还没有准备好。这个判断我再用示波器抓无线模块的IRQ脚确认,最后确实是IRQ引脚悬空、误触发电平中断。
这里我想强调一个原则:AI给的是“假设”,不是“结论”。它擅长把你输入的现象和它见过的模式做匹配。你要接着做的是设计一个实验去验证匹配是否正确。不要因为AI说“很可能是”,就省掉实测那一步。波形和寄存器现场,才是嵌入式调试的最终裁判。
4.3 硬件与软件交界的问题:AI如何教你设计实验
纯软件问题AI很擅长,但硬件无法正视。遇到硬件交互问题,AI的价值在于教你设计排查实验。有一次I2C总线SDA一直为低,板子上只有一个传感器,我原本准备直接换传感器,但AI建议我先断开传感器电源,测量主控I2C引脚是否被拉低,再检查上拉电阻。断开传感器后SDA恢复正常,说明问题出在传感器侧或外部上拉,而不是主控配置。最后发现是传感器地址跳线被焊错。
你看,AI并没有直接给出答案,但它给了一个“隔离变量”的实验思路,让我迅速缩小范围。嵌入式调试里最难的不是修代码,而是确定问题在哪一边。AI能给出工程上最常见的排查顺序,即使它对你手里的电路一无所知。
我常用的提问格式是:
我现在遇到XX现象。已确认:电源电压正常、主控程序可以运行、传感器供电由GPIO控制。请列出从最可能到最不可能的排查步骤,并在每一步说明要量测的波形或电压值。不要轻易建议换硬件。
这样得到的答案针对性会强很多。AI的排查建议可能按概率排列,但有些概率很低的步骤因你的板子特例反而会成为关键,所以把已知条件写得越具体越好。
5. 收尾阶段:让AI帮你把编码过程沉淀下来
5.1 代码评审:AI挑出的问题,分三级看
代码能跑之后,我还会让AI帮忙做一遍代码评审。提示词类似:
请以嵌入式专家身份评审这个C文件,重点检查中断安全、资源泄漏、状态机重入、可变长度数组、全局变量使用、错误处理是否完备。
AI会返回几十条意见。你不能全采纳,我会按三级处理。
第一级,明显正确且有价值。比如发现中断服务函数里调用阻塞函数会导致主循环延迟,或者结构体字节对齐可能造成Cortex-M访问异常,这种直接采纳。嵌入式的很多并发问题,AI确实比人扫得快。
第二级,有可能是本项目的“有意为之”。比如某段代码为了兼容旧编译器写了未初始化变量的处理,或者为了让某外设快速响应设置了非推荐参数,AI建议改成更“整洁”的形式,但其实会影响时序。这种我不采纳,但我保证心里清楚AI的改进意见,并考虑是否有迁移价值。
第三级,完全错误或误导。比如AI建议用malloc处理传感器缓冲区,在资源紧张的STM32上这是不合适的,我会直接忽略。
关键是把AI评审当成“同事提意见”,而不是“测试员下结论”。我通过表格把每一条意见标上严重程度、影响路径、处理动作,这样发布前可以逐条确认,既不让问题漏掉,也不让AI误改正确设计。
5.2 注释与设计文档:AI的价值是帮你留下“为什么”
AI生成的注释,很容易变成两种极端:一种逐行翻译代码,毫无价值;一种完全没有说明。我现在会让AI生成模块级注释和函数级注释,但会明确指示:写注释时多解释“为什么”,少解释“是什么”。
我给AI的提示词是:
为以下函数补充注释,内容包括:函数输入、输出的物理量含义与范围;调用该函数的时机和限制条件;为什么采用当前的实现方式;失败模式和调用者的处理责任。不需要为每条赋值语句写注释。
AI执行这样的要求后,生成的文档比大部分程序员手写的更清晰。不过它并不了解你的业务背景,所以最后我还要把一些决策过程补进注释里,比如“因为在发送失败时需要重试,所以缓冲区内不允许写入新的采样值,直到重试完成”。
另外,我会利用AI辅助生成设计文档模板:需求追踪矩阵、接口变更记录、功耗预算表,然后用真实测量数据填充。这里的重点是AI生成的模板能省格式化的时间,但数据必须源于实测或人工确认。千万别把AI估算的功耗当最终交付数据,否则后面产品测试会构成坑。
5.3 一次完整案例复盘:低功耗采集节点的AI协作流程
最后把前面内容放进一个实际项目流程里,方便你照抄。
项目背景:某低功耗环境监测节点,STM32主控,电池供电,60秒唤醒一次,I2C温湿度传感器,sub-GHz无线模块上报数据。
| 阶段 | 人做什么 | AI做什么 | 输出 |
|---|---|---|---|
| 需求拆解 | 定义唤醒周期、电池容量、重试次数 | 列出模块、风险、待确认问题 | 需求清单和硬件模块图 |
| 资源配置 | 选择具体芯片型号、跑厂商工具 | 解释时钟树、校验I2C/SPI地图 | 工程骨架、配置说明 |
| 驱动开发 | 确定传感器寄存器地址、无线模块时序 | 生成驱动骨架和错误重试代码 | I2C、SPI、ADC驱动 |
| 业务逻辑 | 设定状态机边界、看门狗策略 | 生成状态机代码和日志框架 | 主循环、唤醒流程 |
| 调试排错 | 抓波形、看电流、复现故障 | 分析日志、解释寄存器快照 | Bug清单与定位 |
| 评审优化 | 测功耗、调整策略 | 代码复审、重构建议、注释补全 | 最终代码和功耗表 |
整个流程耗时大约是我以前手工开发的一半左右,但真正节省的时间并不是敲键盘那部分,而是查手册和试错。最大的一个坑依然来自AI的自信:它把某款芯片的RTC校准寄存器写到了另一款型号的代码里,幸好我在评审阶段坚持逐项核对寄存器地址,才没有把一批板子全部刷废。AI协同开发是流程,不是点金术,这句话我在每一个项目结束之后都会再确认一遍。
6. 边界感:AI协同开发的信任模型和我的最终工作流
6.1 必须人工复核的五个场景
综合多轮项目经验,我会强制自己人工复核以下五类问题,不直接信AI输出。
一是时钟树与复位配置。这一块配置错了,芯片可能上电后不启动,而且很难从逻辑上排查。每次都要对照厂商工具生成的时钟值或手动计算的寄存器值。
二是中断优先级与延时预算。AI不知道你的系统的实时性要求,所以一切涉及ISR调度的代码,都要有明确的延迟预算,不能让它随意定优先级。
三是低功耗模式切换。不同型号的Sleep、Stop、Standby行为差异极大,尤其是唤醒源和时钟恢复时序,必须看手册和实测。
四是Flash编程与磨损均衡。擦写次数有限,AI可能生成“每次上电都把整个扇区写一遍”的代码,这在开发板没问题,但在产品上就是隐患。
五是硬件时序与电平匹配。比如无线模块的IRQ、复位时序、上电不可靠。AI给的代码可能在模块级成立,但不代表你的硬件连接正确。任何硬件相关时序必须以示波器和逻辑分析仪为准。
我习惯把这五类问题写在项目根目录的README中,并在每次和AI对话前复制一遍。这相当于给AI提前设置边界,也是提醒自己不要因为AI回答得流畅就放松警惕。
6.2 我的最终工作流:如何在每一轮对话里保持上下文
最后分享一个我在实践中总结出的高频操作流。
每次开始AI对话前,我固定提供这样一段上下文:
当前项目:低功耗无线采集节点。主控:STM32超低功耗系列。编译器:GCC工具链。当前阶段:编写传感器读取驱动。约束:不使用动态内存分配,主堆栈4KB,所有外部库函数都要有错误码返回。请回答时标注哪些结论是基于公开资料假设的,哪些需要我查阅参考手册确认。
这一段只有五句话,但它把AI从“全知心态”拉回到“工程师助手心态”。之后我针对每个小任务提问,比如让AI生成一个带超时重试的I2C读取函数,或者解读一段HardFault寄存器快照。一次会话只围绕一个模块,做完后把结果整理进文档,再开启新任务的新会话。
整套流程走下来,我的体感是:AI替我承担了查资料、写样板、整理文档和部分排错的脑力劳动,而我集中全部精力做三件事——定义边界、设计实验、确认事实。这大概就是AI协同开发STM32程序的正确打开方式。我踩过最大的坑和最大的收获都来自同一件事:AI的流畅输出不等于可靠性,可靠性只能由按流程验证来保障。你可以把我的清单抄走,再根据你自己的项目和脾气改一版,应该会比我原来的“裸奔式AI编程”好用得多。