嵌入式软件这行有个特别拧巴的地方:代码跑在资源受限的板子上,调试靠串口打印和示波器,但写代码的方式却还停留在“手搓寄存器、翻数据手册、对着参考手册一行行抠”的阶段。我做了十多年嵌入式,从8位机裸跑到带RTOS的Cortex-M,再到最近两年开始把AI编程工具引入日常开发,最大的感受是——AI不会替你读懂时序图,但它能帮你把那些重复的、模板化的、容易写错的底层代码快速搭起来,让你把精力放在真正需要硬件直觉的地方。
这个系列写到第19篇,前面聊了怎么选AI编程工具、怎么写提示词、怎么让AI理解寄存器手册。这一篇是“第一个AI协同开发项目”的第三部分,也是收尾部分。前两部分我们把项目框架搭起来了,把外设驱动骨架生成了,这一部分要解决的是:怎么让AI生成的代码真正跑通、怎么排查AI写出来的坑、怎么把AI协同开发变成一套可复用的工作流。如果你正在学嵌入式,或者已经工作但想试试AI编程到底能不能用在正经项目上,这篇的内容应该能给你一些直接能抄的作业。
1. 项目收尾阶段的核心任务拆解
1.1 为什么收尾阶段比生成阶段更考验人
很多人对AI编程的想象是“输入需求,输出代码,编译通过,收工”。实际做过嵌入式项目的人都知道,编译通过只是万里长征第一步。AI生成的代码在语法层面通常没问题,但在嵌入式场景下,它可能踩的坑包括但不限于:寄存器位域顺序搞反、时钟使能顺序不对、中断优先级配置冲突、DMA和Cache一致性没处理、延时函数在中断里用了阻塞式实现。这些问题编译器不会报错,但板子就是跑不起来。
所以收尾阶段的核心任务不是“继续让AI写代码”,而是“验证AI写的代码在真实硬件上的行为”。这个阶段我把它拆成四块:代码审查与硬件对齐、编译与静态检查、板级调试与问题定位、工作流固化。每一块都有AI能帮上忙的地方,也有AI帮不上、必须靠人的地方。搞清楚这个边界,是AI协同开发能不能落地的关键。
1.2 收尾阶段的四个核心环节
先给一个整体视图,后面每个环节展开讲。
| 环节 | 主要目标 | AI能做的 | 必须人做的 |
|---|---|---|---|
| 代码审查 | 发现逻辑与硬件不符 | 对照手册检查位域、生成审查清单 | 判断时序是否满足硬件要求 |
| 编译检查 | 消除语法与链接问题 | 解释报错、建议修改 | 处理芯片特定的链接脚本 |
| 板级调试 | 让代码在硬件上跑通 | 分析日志、推测故障点 | 用示波器/逻辑分析仪实测 |
| 工作流固化 | 形成可复用流程 | 整理提示词模板、生成文档 | 根据项目特点调整流程 |
这张表是我踩了不少坑之后总结出来的。刚开始用AI编程的时候,我总想让AI把活全干了,结果发现它在“理解硬件真实行为”这件事上有天然短板——它没见过你的板子,不知道你的晶振是8M还是25M,不知道你的上拉电阻是4.7K还是10K。所以协同的正确姿势是:AI负责它擅长的模式化工作,人负责硬件相关的判断。
1.3 本部分要解决的具体问题
回到我们这个项目。前两部分已经完成了:项目需求定义(一个基于Cortex-M的传感器数据采集与串口上报系统)、外设驱动骨架生成(GPIO、UART、定时器、ADC的初始化代码)。这一部分要解决的具体问题是:
- AI生成的初始化代码,寄存器配置是否和手册一致
- 多个外设的初始化顺序是否有依赖问题
- 中断服务函数里有没有隐藏的阻塞操作
- 主循环的任务调度逻辑是否合理
- 怎么用AI辅助定位“代码看着对但跑不通”的问题
- 怎么把这套流程整理成下次能直接用的模板
这几个问题基本覆盖了嵌入式项目收尾阶段的主要痛点。下面逐个展开。
2. AI生成代码的审查与硬件对齐
2.1 寄存器配置审查:AI最容易出错的地方
AI生成寄存器配置代码时,最常见的错误不是语法错误,而是“位域理解偏差”。举个例子,某个状态寄存器里有一个3位的字段表示采样速率,AI可能会写成:
// AI生成的代码(有问题) ADC->CR |= (sample_rate << 8);看起来没问题,但如果手册里这个字段是从bit 9开始的,或者这个字段是“写1清除”而不是“写值设置”,这段代码就会出问题。更隐蔽的是,AI有时候会把“保留位”当成有效位来操作,或者把只读位当成可写位。
我的做法是:让AI生成代码之后,再让AI做一次“对照审查”。具体操作是,把手册里相关寄存器的描述贴给AI,然后问它:“请逐位对照以下寄存器描述,检查这段代码的位域操作是否正确,列出所有不一致的地方。”这个方法的有效性在于,AI在“对照检查”任务上的表现比“凭空生成”要稳定得多,因为前者有明确的参照物。
提示:贴手册描述的时候,尽量贴原文的位域表格,不要只贴文字描述。表格里的bit编号、字段名、读写属性、复位值这些信息,是审查的关键依据。
2.2 时钟树与初始化顺序的依赖检查
嵌入式系统里,外设初始化顺序是有严格依赖的。典型顺序是:系统时钟配置 → 总线时钟使能 → 外设时钟使能 → 外设寄存器配置 → 中断配置 → 使能外设。AI生成代码时,有时候会把这个顺序打乱,比如先配置了UART寄存器,才去使能UART时钟,结果配置全部无效。
这个问题在单外设的时候不明显,但多外设的时候就容易暴露。我让AI做了一次“初始化顺序审查”,提示词大概是这样的:
以下是一个Cortex-M项目的初始化代码片段,包含GPIO、UART、TIM、ADC四个外设。 请检查: 1. 系统时钟配置是否在所有外设配置之前 2. 每个外设的时钟使能是否在其寄存器配置之前 3. 中断优先级配置是否在中断使能之前 4. 是否存在外设之间的初始化依赖(如ADC依赖TIM触发) 列出所有顺序问题,并给出修正后的顺序。AI给出的结果里,确实发现了一个问题:ADC的初始化代码里,先配置了ADC的转换模式,然后才使能ADC时钟。虽然在某些芯片上这恰好能工作(因为时钟使能有延迟),但这是不可靠的。修正之后,代码的健壮性明显提升。
2.3 中断服务函数的隐藏陷阱
中断服务函数是AI生成代码的重灾区。常见问题包括:在ISR里调用了阻塞式延时、在ISR里做了浮点运算(在没有FPU的芯片上)、在ISR里访问了非原子性的共享变量、ISR执行时间过长导致其他中断丢失。
我让AI对生成的ISR做了一次专项审查,提示词是:“请检查以下中断服务函数,找出所有可能导致实时性问题的操作,包括阻塞调用、浮点运算、长循环、非原子访问,并给出修改建议。”AI找出了两个问题:一个是在UART接收中断里用了printf(阻塞式),另一个是在定时器中断里做了一个超过100次的循环。
修改方案也很直接:UART接收中断里只把数据放进环形缓冲区,主循环再去处理;定时器中断里的循环改成状态机分次执行。这两个修改都是嵌入式开发的基本功,但AI生成的时候不会主动考虑这些,需要你引导它去检查。
2.4 审查清单的固化
做完上面几轮审查之后,我把审查项整理成了一个清单,下次直接拿来用。这个清单包括:
- 寄存器位域是否与手册一致(bit编号、读写属性、复位值)
- 时钟使能是否在寄存器配置之前
- 中断优先级是否合理(嵌套、抢占、子优先级)
- ISR里是否有阻塞操作、浮点运算、长循环
- 共享变量是否有volatile修饰、是否原子访问
- DMA配置是否处理了Cache一致性(如果用了Cache)
- 延时函数是否在中断上下文里被调用
- 外设初始化顺序是否符合依赖关系
这个清单我让AI帮我整理成了Markdown格式,存在项目文档里。下次新项目直接让AI按这个清单审查,效率高很多。
3. 编译、链接与静态检查的AI辅助
3.1 编译报错的快速定位
嵌入式项目的编译报错有时候很隐晦,尤其是链接阶段的报错。比如“undefined reference to_sbrk”这种,新手看了完全不知道从哪下手。AI在这方面的价值是:它能快速解释报错的含义,并给出常见的解决方向。
我实测下来,对于GCC工具链的常见报错,AI的解释准确率很高。比如:
undefined reference to '_sbrk'→ 通常是没实现堆管理,或者链接脚本里没定义堆区域region 'RAM' overflowed→ 内存不够,需要优化变量或调整链接脚本section '.data' will not fit in region 'RAM'→ 初始化数据太大,考虑放到Flash里multiple definition of 'xxx'→ 头文件里定义了变量而不是声明
这些报错AI都能给出合理的解释和修改方向。但要注意,AI给的修改建议不一定适用于你的具体芯片,比如链接脚本的修改,不同芯片的地址映射完全不同,必须结合手册来改。
3.2 静态检查工具的配合使用
AI审查代码是“语义层面”的,静态检查工具是“规则层面”的,两者互补。我常用的组合是:
cppcheck:检查C/C++代码的常见缺陷clang-tidy:更严格的静态分析,能发现一些潜在的bug-Wall -Wextra -Werror:GCC的编译警告全开
实际操作中,我会先跑一遍静态检查工具,把报出来的问题贴给AI,让它解释每个问题的含义和修改方法。这样比单纯看工具的输出要快得多,因为工具只告诉你“哪里有问题”,AI能告诉你“为什么有问题”和“怎么改”。
注意:静态检查工具报出来的问题不一定都是真问题,有些是误报。让AI帮你判断哪些需要改、哪些可以忽略,能省不少时间。但最终判断还是要靠你自己对代码的理解。
3.3 链接脚本的AI辅助修改
链接脚本是嵌入式开发里比较“劝退”的部分,语法特殊,出错信息也不友好。AI在链接脚本方面的能力有限,因为它需要知道具体芯片的Flash和RAM地址、大小、以及各个段的布局要求。但如果你把这些信息都提供给AI,它能帮你生成一个可用的链接脚本模板。
我的做法是:把芯片手册里的内存映射表贴给AI,告诉它Flash起始地址、大小,RAM起始地址、大小,然后让它生成一个标准的链接脚本。生成之后,我再对照芯片的启动文件检查一遍,确认向量表、堆栈、堆的布局没问题。这个过程比从零写要快,但检查环节不能省。
3.4 编译优化等级的取舍
AI生成代码的时候,默认不会考虑编译优化等级的影响。但优化等级对嵌入式代码的影响很大,尤其是涉及volatile变量、延时循环、中断共享变量的时候。-O0和-O2下,同一段代码的行为可能完全不同。
我一般建议在调试阶段用-O0或-Og,方便单步调试;发布阶段用-Os或-O2,减小体积、提升速度。但切换优化等级之后,一定要重新测试,尤其是延时函数和中断相关的逻辑。AI可以帮你分析“哪些代码在优化后可能行为改变”,但实测还是必须的。
4. 板级调试与问题定位实录
4.1 “代码看着对但跑不通”的排查思路
这是嵌入式开发最经典的场景:代码逻辑没问题,编译通过,下载进去就是没反应。这时候AI能帮上忙的地方是“根据现象推测原因”,但前提是你要把现象描述清楚。
我遇到的一个具体问题是:UART初始化之后,发送数据没有输出。代码审查过了,寄存器配置和手册一致,时钟也使能了。我让AI帮我列了一个排查清单:
- 确认UART时钟源是否正确(有些芯片UART挂在APB1,有些在APB2)
- 确认GPIO的复用功能是否配置正确(AF编号)
- 确认波特率计算是否匹配实际时钟频率
- 确认TX引脚是否被其他外设占用
- 确认发送函数是否真的被调用(加个GPIO翻转做标记)
- 确认硬件连接是否正确(TX-RX是否交叉)
按这个清单逐项排查,最后发现是GPIO的复用功能编号配错了。AI在生成代码的时候,用了一个“常见值”,但这个芯片的UART TX引脚对应的AF编号不是那个值。这个问题很隐蔽,因为代码本身没有语法错误,只是硬件配置不对。
4.2 用GPIO翻转做“穷人的逻辑分析仪”
嵌入式调试有个特别实用的技巧:在关键代码位置翻转一个GPIO,然后用示波器或逻辑分析仪看波形。这个技巧在AI协同开发里同样重要,因为AI生成的代码你不可能完全信任,需要用它来验证“代码是否执行到了这里”。
我一般会预留一个调试GPIO,在初始化的关键节点、中断入口、主循环的关键分支都加上翻转操作。这样用逻辑分析仪一看,就知道程序卡在哪一步。AI可以帮你生成这些调试代码,但翻转的位置需要你自己判断——哪些节点是关键的,只有做过这个项目的人才知道。
4.3 常见问题速查表
下面这张表是我在实际项目中整理出来的,AI生成代码后经常遇到的问题和排查方法:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 程序不运行 | 启动文件、向量表、复位地址 | 检查链接脚本和启动文件 |
| 外设无输出 | 时钟未使能、AF配置错误 | 查时钟树、查GPIO复用表 |
| 中断不触发 | 优先级配置、NVIC使能、中断标志未清 | 查NVIC寄存器、查中断标志 |
| 数据错乱 | 共享变量未加volatile、非原子访问 | 加volatile、用临界区保护 |
| 偶尔死机 | 堆栈溢出、中断嵌套过深 | 查栈使用、查中断优先级 |
| 通信不稳定 | 波特率误差、时序不满足 | 算波特率误差、查时序图 |
| 功耗偏高 | 未使用的外设时钟未关、引脚悬空 | 关时钟、配置引脚状态 |
这张表我让AI帮我扩充过,加了一些它从常见问题里总结的条目。实际用下来,覆盖了八成以上的常见问题。
4.4 一个真实的调试案例
说一个我印象比较深的案例。项目里用到了ADC采集传感器数据,AI生成的代码看起来没问题,但采集到的数据一直跳动很大。我先用万用表量了传感器输出,是稳定的,说明问题在ADC配置或采样时序上。
让AI分析之后,它提出了几个可能:采样时间太短、参考电压不稳、DMA传输和ADC转换不同步。我逐一排查,最后发现是采样时间设置得太短,ADC的采样保持电容还没充够电就开始了转换。把采样时间从最小的几个周期改成几十个周期之后,数据就稳定了。
这个问题的教训是:AI生成ADC配置的时候,默认用的是“能工作的最小配置”,但实际硬件需要根据信号源阻抗来调整采样时间。这个计算过程AI不会主动做,需要你根据手册里的公式自己算。我后来把这个计算过程也整理成了提示词模板,让AI在生成ADC代码时自动带上采样时间计算。
5. AI协同开发工作流的固化
5.1 从“一次性使用”到“可复用流程”
前面几个环节做完,项目基本跑通了。但更重要的是把这套流程固化下来,下次新项目直接复用。我整理的工作流包括四个阶段:
- 需求阶段:用AI把项目需求拆解成外设清单和功能清单
- 生成阶段:用提示词模板让AI生成外设驱动骨架
- 审查阶段:用审查清单让AI逐项检查代码
- 调试阶段:用排查清单和AI一起定位问题
每个阶段都有对应的提示词模板和检查清单,存在项目文档里。下次新项目,先复制这套模板,改改芯片型号和外设列表,就能快速启动。
5.2 提示词模板的整理
提示词模板是这套工作流的核心资产。我整理了几个常用的模板:
外设驱动生成模板:
芯片型号:[型号] 外设:[外设名] 功能需求:[具体功能] 时钟频率:[频率] 请生成初始化代码和基本操作函数,要求: 1. 寄存器配置对照手册,标注每个配置的依据 2. 初始化顺序符合依赖关系 3. 中断服务函数避免阻塞操作 4. 关键配置附上计算过程代码审查模板:
请对照以下手册描述,审查代码中的寄存器配置: [贴手册位域表格] [贴代码] 检查项:位域编号、读写属性、复位值、时钟使能顺序、中断配置 列出所有不一致的地方和修改建议。问题排查模板:
现象:[描述现象] 已排查:[列出已排查项] 相关代码:[贴代码] 请列出可能的故障原因,按可能性排序,并给出排查方法。这几个模板我用了大半年,覆盖了大部分日常开发场景。当然,模板不是万能的,具体项目还需要根据芯片特点调整。
5.3 哪些环节不该交给AI
用了这么久AI编程,我越来越清楚它的边界。以下这些环节,我建议不要交给AI,或者只让AI做辅助:
- 硬件原理图设计:AI看不到你的原理图,不知道引脚怎么连的
- 时序关键路径的最终判断:AI能算,但最终要对照示波器实测
- 安全相关的代码:比如看门狗、故障保护,必须人工审查
- 芯片特定的勘误处理:手册里的errata,AI不一定知道
- 最终的性能优化:AI能给建议,但实测调优必须人工做
把这些边界搞清楚,AI协同开发才能既高效又可靠。
5.4 我个人的几点体会
最后分享几点我自己的体会。第一,AI生成的代码一定要审查,尤其是寄存器配置和中断相关部分,这是踩过坑的教训。第二,提示词的质量直接决定生成代码的质量,花时间打磨提示词模板是值得的。第三,AI在“解释”和“检查”任务上比“生成”任务更可靠,多用它做审查,少用它做从零生成。第四,嵌入式开发的核心能力——读懂手册、理解时序、调试硬件——AI替代不了,但AI能让你在这些核心能力上花更少的时间,把精力放在真正需要经验的地方。
这个系列写到这儿,第一个AI协同开发项目就算完整走了一遍。从需求拆解到代码生成,从审查到调试,再到工作流固化,这套流程我实际跑下来,效率提升大概在三成左右,主要省在模板代码编写和问题排查上。但前提是你要愿意花时间调提示词、做审查、整理模板。如果只是想让AI“一键生成能跑的代码”,那大概率会失望。嵌入式这行,硬件永远是最诚实的裁判,AI只是帮你更快地走到裁判面前。