
刚开始接触AI协同开发的时候我心里是不太信的。你让一个搞了十年嵌入式底层的人去接受“把需求丢给模型再人工把代码改到能跑”听起来就像是开玩笑。但做到第三个项目阶段我真正改观了——不是AI写得有多对而是它把我在“搜索-验证-试错”这条链路上消耗的时间压缩掉了一大半。这节是“第一个AI协同开发项目”的第三篇。前两篇我们搭建了工作区、整理了芯片手册、跑通了最小工程模板。这篇要讲的是最硬核的阶段从“让AI生成驱动代码”开始到“与AI一起联调并最终跑通硬件”。整个过程包括提示词该怎么写给嵌入式场景、AI生成的寄存器代码怎么审查、联调中崩了几次、以及最后我是怎样用反编译工具加AI去分析一份没有文档的旧固件。我的建议是直接对照这篇文章的操作流程在你自己板子上走一遍比存了再吃灰强。1. 从“让AI写Hello World”到“让AI写驱动”——这步怎么迈过去的1.1 前两章做完了什么为什么第三章反而是最难的一章第一章做完的是环境准备Windows下用WSL搭的交叉编译链、CMake工程骨架、以及VS Code里面各种AI插件与嵌入式插件的联配。第二章做的是“硬件最小系统”把一颗常用MCU的启动代码、链接脚本和串口驱动先跑通确认能通过调试器下载程序、能在终端看到输出。看起来万事俱备但到了第三章你会发现前两章写的都是“确定性代码”——模型参考资料的样板非常多网上随手一搜能搜到一堆现成例子。而第三章要写的驱动代码例如外部传感器的I2C通信、DMA传输、中断优先级配置每一行都要对着具体芯片的寄存器手册和具体外设的时序要求来写。这类代码最大的难点在于它的正确性不是“语法正确”能保证的而是“时序和寄存器位域必须完全正确”才能保证的。我自己最开始犯的错误就是太高估AI的能力。那时候我给了它一大段“帮我初始化SPI通信并读取陀螺仪数据”它生成得倒是有模有样仔细一看它把片选引脚号和SPI外设的配置寄存器都搞混了。当时我真的觉得这条路走不通。但后来我发现问题不在AI在“提问方式”——这引出了这一章的核心嵌入式AI编程的提示词必须当作一份正式的需求规格书写而不是当作聊天问答。1.2 工具链选型怎么在项目里同时驾驭AI和嵌入式工具做嵌入式AI协同开发工具链不用太豪华但每个环节必须稳。我自己的搭配是作用工具为什么选它代码编写与对话Cursor / VS Code Continue能直接看工程上下文不用反复复制粘贴文件运行与调试OpenOCD GDB命令行可控脚本化调试方便记录日志硬件调试逻辑分析仪 示波器验证I2C/SPI/UART时序AI帮不了这一步代码分析IDA Pro / Ghidra配合AI分析无文档固件时用得上Ghidra免费知识库自建Markdown笔记 向量检索把芯片手册重点、报错现象、修复记录沉淀下来方便AI引用这里有个很重要的心得AI编程工具的选择不要追版本号更新而是看它能否“把仓库里的文件当作上下文”。很多在线版AI工具你只能贴代码片段一碰到跨文件的结构体定义和宏定义它就瞎猜。真正效率高的做法是让工具直接读你仓库里的头文件和驱动代码它给出的答案才有据可依。另外一个不能忽视的硬件工具是逻辑分析仪。AI能帮你写代码但它看不到示波器上那些毛刺。I2C总线上拉电阻没焊好导致时钟线SDA异常这类问题AI再强也发现不了。所以我对这个项目的定位是AI负责代码脑我负责硬件眼二者缺一不可。2. AI协同开发的提示词工程——我给AI下“需求文档”2.1 嵌入式提示词的格式模板照着填就行做嵌入式场景的AI提示词我踩了很长时间的坑最后总结出一套固定模板。不管你要AI生成什么底层代码按这个结构写出错的概率会大大下降【角色】你是一个熟悉XXX芯片具体型号嵌入式开发的专家熟悉该芯片参考手册第XX章外设部分。 【任务】请完成以下外设驱动XXX例如通过I2C读取压力传感器数据每100ms读取一次并做软件滤波。 【硬件约束】 - MCU型号XXX - 使用的引脚SCLPAx / SDAPAy - 外设地址0xXX - 时钟频率系统主频72MHzI2C时钟要求400kHz 【寄存器约束】 - 只能用标准外设库/固件库或反过来只能用寄存器操作 - 必须启用NACK中断处理超时 【期望输出】 - 初始化函数原型、读写函数原型 - 注释标明寄存器位域含义与配置流程 - 附加一个最简调用示例有同事问过我为什么要这么啰嗦直接说“写个I2C读取代码”不就行了吗我后来的回答是因为AI不知道你的工程里用的是哪颗芯片的哪个固件库、时钟还分了AHB和APB两条总线更不知道你板子的I2C引脚有没有复用功能。你假设它“应该知道”它就用它训练数据里最通用的方案来猜而这个方案大概率跟你板子匹配不上。格式上还有个小技巧把约束条件放在一个“constraints”小标题下AI在解析时比混在一大段文字里要敏锐得多。这就像需求评审时你那句话被淹没在闲聊里根本不算数单独写在“验收标准”里才算数。2.2 芯片外设怎么让AI理解寄存器手册、时序图与代码间的翻译很多嵌入式工程师问得最多的问题是AI有没有读过这颗芯片的手册答案通常是“不知道但它很可能读过类似芯片的大量代码”。所以你不能只给它芯片型号还得给它几个关键文件芯片用户手册对应外设章节的寄存器描述页截取成文本或PDF给AI引脚复用表那一页你自己工程的时钟配置代码主要是SystemInit相关代码这些材料给它之后AI才能在生成代码时准确判断该配置哪个复用功能位、该往哪个频率寄存器填值。比如STM32F103的I2C你给它同时看了APB1时钟频率和RCC寄存器定义它生成出来的“CCR”配置值才可能是对的不然它只会给你一个模糊的“配置为400kHz”注释——你得自己查头文件里的位域再去填。这里有一个非常实用的操作方法不要直接把整个PDF手册丢给AI那通常会超过上下文窗口反而是交给它精简后的“相关章节文本”。我一直保留着一个习惯就是把芯片参考手册中关于外设的寄存器表、位域说明做成一份Markdown摘要再用AI工具以文件形式加载。这样模型能精确记住“I2C_SR1的bit0是SB位需要在START条件后置位”这类细节。这一步准备得越充分后面AI生成代码的一次正确率越高。另外我还会把外设的“时序图”翻译成文字描述再喂给AI。例如“在向从机发送地址后必须等待SB位为1然后写入数据寄存器等TXE为1后再次写入。”把图画成一段流程AI很容易理解。虽然有点原始但实测下来效果明显好于直接把图片丢给它。2.3 实测一个可复现的提示词案例生成I2C驱动我做项目时刚好要新加一个气压传感器于是完整走了一遍流程。下面就把这版能一次跑通的提示词与代码结构分享出来。提示词原文关键部分请为STM32F103C8T6生成I2C1主机模式驱动用于读取BMP280传感器的温度和气压。 要求 1. 使用标准外设库时钟接口不得使用HAL库。 2. 引脚SCL PB6SDA PB7复用开漏输出上拉电阻启用。 3. I2C1使用APB1的PCLK1系统时钟72MHz要求I2C时钟400kHz计算CCR值。 4. 采用连续读模式读校准参数时跳过温度寄存器0xFA压力寄存器0xF7配置寄存器0xF4。 5. 必须处理总线忙等待超时时间为100ms超时后返回错误码。 6. 生成初始化代码、单次读取函数、以及头文件的宏定义。AI返回的代码结构大概是一个BMP280_Init()里面分三步GPIO复用配置、I2C1初始化、传感器配置寄存器写入一个BMP280_ReadAll()负责写地址命令、读6字节、再把温度压力量化为实际物理量。关键的两个计算点都对了CCR填了0x09因为I2C时钟是PCLK1/2以及BMP280的7位地址左移一位后放在首字节。最令我欣慰的是它自动处理了I2C读时序里最容易被忽略的“最后一个字节前不应发送ACK”这个逻辑代码里用了I2C_NACKNextConfig()这一步对不同平台的工程师来说经常要在查资料上花掉半小时。这不代表AI智能得能看懂时序图但它见过大量类似代码里“倒数第二个字节前需要调用NACK配置”这个模式于是自动补上了。3. 核心环节实现AI生成代码与硬件联调的完整流程3.1 生成代码前的边界条件定义越细越不容易翻车AI代码生成后第一个出错的点往往不是因为语法而是你没有把“边界条件”说清楚。嵌入式开发的边界条件包括三件事时钟来源、引脚复用、中断优先级。我见过太多AI代码在初始化里只写了外设时钟却忽略GPIO复用时钟或者开了中断但中断优先级分组模式没设置。所以在做驱动生成的前一步我习惯先手写一份“硬件连接与运行边界清单”然后把它加在提示词的最后面。清单内容如下主控芯片运行频率72MHzAPB136MHzAPB272MHzI2C1挂在APB1上I2C时钟36MHzPWM输出引脚PA8用作TIM1_CH1复用推挽输出频率20kHz占空比可按需调整外部中断PE0作为按键输入下降沿触发中断分组2抢占优先级2子优先级0串口USART1PA9/PA10波特率115200用于输出调试日志这些信息一旦写清楚AI在生成初始化函数时就会自己查表判断“RCC_APB1PeriphClockCmd应该开哪几个位”而不是试图用一个统一模板硬凑。我把这个做法命名为“给AI画一张硬件拓扑图”对它的上下文理解帮助极大。你会觉得“这不像编程更像写需求文档”。确实如此AI协同开发最大的变化就是你需要花更多时间把自己的工程边界思考清楚因为AI不会替你思考“如果你没说它就不猜”但它也不会像人一样不耐烦地吐槽你啰嗦。3.2 轮到AI写第一版从代码初稿到工程落地当我把I2CBMP280的提示词完整送出去以后AI给出了一份约200行的驱动代码。我并没有直接把它塞进工程而是先做了三个动作把代码里与硬件相关的宏统一抽到bmp280_conf.h头文件里方便后续换引脚时不清扫整个驱动文件。在关键寄存器操作行夹上“寄存器注释”把位域含义、配置值来源写上防止后续维护时看不懂。在初始化函数上补充了“调用条件”——必须放在系统时钟配置之后调用且不能在中断中进行软件延时。工程里加入这些新文件之后我用交叉编译工具链做了一个编译测试。第一次编译报了两个error一个是因为引脚复用函数中宏名写错GPIO_Remap_I2C1没定义另一个是因为它引用了delay_ms()函数而我的裸机工程里没有这个函数。这都很正常前者AI可以修正后者需要我自己提供一个微秒级延时函数接口。这里有一个个人非常受用的点我不会让AI直接去修改整个工程而是让它针对“单文件”出改进补丁。因为嵌入式工程的构建依赖非常脆弱一个头文件的路径不对就可能连锁爆炸让AI上手改整个工程出错率会急剧上升。用Git先创建分支、单文件替换、编译过再合并这套流程最稳妥。3.3 联调实录AI生成代码第一次跑通是什么体验编译过了只是第一步接下来上板联调才真正“刺激”。我先把烧录脚本写好通过OpenOCD连上目标板下载程序然后打开串口助手查看输出。首次跑串口没有任何应答。我第一反应是硬件问题拿示波器量了一下I2C引脚发现SCL线上没有时钟跳变。这说明程序根本没跑到初始化I2C那一步。于是我用调试器加断点发现执行到BMP280_Init()时程序卡在了一个while循环里——就是提示词里要求加的那个“总线忙等待”。我检查了一下总线是否真的上报忙发现I2C_SR2的BUSY位被置位这是典型的上电时序问题I2C上拉电阻没在复位期间保持稳定导致总线误判为忙状态。解决方法是复位I2C外设后做一次软件复位或者在配置GPIO之前把总线电平拉高至少1ms。我和AI的描述是“总线BUSY位被置位虽然没有任何设备在通信。我已确认上拉正常需要一段代码在初始化I2C外设前强制清除BUSY状态或重建总线。”AI给出的方案是先把I2C外设复位再重新初始化外设时钟和GPIO。我照着改再跑一次时钟线立刻有了波形。于是我知道前面那个坑已经绕过去了。接着又遇到一个更隐蔽的问题读出来的温度和气压数值永远是FFFF。排查过程延伸到下一小节。这段经历给我的启发是AI在写代码逻辑方面已经很能打但在硬件时序的“现场排查”上确实无能为力。你必须用示波器和调试器把问题缩小到一个范围再把现象、寄存器状态、你尝试过的方案交给AI它才有用。这其实就是“AI嵌入式”协同开发的真正分工。3.4 协同调试AI不是万能的追问与纠错才是核心读到FFFFFF我脑子里第一时间闪现的是“读时序没对”但具体哪里没对我没法确定。于是把现象整理成一句话连同一个读取函数片段发给AI读到的寄存器值全是0xFF但I2C通信本身没有报错总线上也没有NACK。AI给出的第一个建议是检查首字节地址是否正确——它建议我用逻辑分析仪抓一下I2C波形看看从机地址字节发出的到底是0xEC还是0xEE。我照着检查后发现发送的地址完全正确不是这个问题。于是我把逻辑分析仪的波形图抓下来人工确认是“主机发地址→收到ACK→读数据→收ACK→读最后一个字节→发NACK→停止”整体时序看不出问题。重新回到对话我补充了一个信息“从机在寄存器地址写入后并没有回复ACK但主机仍然继续读取数据。”这时候AI才给出关键判断可能是我发送24位命令寄存器地址读命令时没有检查I2C_SR1的EV5、EV6事件标志就仓促设置了后续操作导致从机根本没锁存寄存器地址。于是我去翻AI生成代码里的发送逻辑果然发现它在发送寄存器地址后少一步等待BTF标志位置位的环节。是那种写寄存器顺序导致状态机错乱的典型情况。我手动补上等待BTF的两行代码把所有再读取的数据打印出来温度和气压值就全对了。从那次开始我总结出一个经验AI对话中的第二次、第三次追问价值比第一次生成大得多。第一次生成的代码只是“基准线”后续围绕实际现象的纠错才是让AI融入工作流的关键。不要把AI当搜索引擎一个问题一个答案就完事要不断地把“示波器截图-寄存器快照-错误代码”反馈回去就像和资深同事做硬件联合调试一样。4. 反编译与代码分析——AI帮我读懂没有文档的旧固件4.1 嵌入式软件反编译的边界什么场景需要它法律上要注意什么项目推进到一半我接到一个额外需求分析一个已经停产的外设传感器模块固件其代码藏在旧芯片的Flash里而原厂早已不提供寄存器说明和协议文档。要复用它最直接的手段就是把芯片内的二进制导出来做嵌入式软件反编译分析。这里必须强调一下反编译本身是一种中性的软件工程技术常见于固件兼容性分析、安全漏洞研究、数据格式逆向等场景但前提是你要拥有该二进制文件的合法使用权或授权。我在这块的经验是过程完全合规只在公司内部做用途是兼容旧产品绝不涉及对第三方商业软件的解密或抄袭。在正式开始之前把“授权范围确认”这一条写进文档存档避免后续麻烦。从技术角度讲我用的工具是Ghidra——免费开源对ARM Cortex-M支持较好配合AI来分析反汇编代码非常顺。Ghidra可以把二进制加载成静态项目自动识别函数边界、解析字符串、画调用图。接下来重点不是让AI“看懂”所有汇编而是让它配合伪代码快速筛选出关键逻辑。4.2 用AI分析反编译代码的一次实践Ghidra反编译输出的C伪代码有一个特点变量名几乎都是局部变量local_20、函数名都是FUN_08001234可读性很差。如果靠人工一行一行读效率很低但把这段伪代码交给AI让它从控制流和寄存器的读写模式里推测用途准确率意外地高。我拿了一段约80行的反编译伪代码给AI附带一句说明“这是某Cortex-M0芯片的Flash反编译代码请根据寄存器和常数判断它可能在做什么并给出关键寄存器地址的注释。”AI很快识别出几个可疑地址其中一个0x40010800在STM32体系里正好是GPIOA寄存器组另一个0x40022000是Flash接口控制寄存器。看到这些我立刻意识到这段代码是进入Bootloader前对固件所在的Flash进行擦除和重编程的一个序列。接下来我采用了一种人机协同的方式让AI为每一段反编译代码生成“行为摘要”和“调用前提”再由我人工核对几个关键分支。到了这一步我基本从80行汇编中提取出了和传感器通信协议相关的参数表波特率115200、数据格式为8N1、写寄存器命令固定为0xAA。配合外部逻辑分析仪采样整个协议一下子清晰了。这种工作流的意义在于AI不会取代你理解系统但它能把你从“从汇编到人的翻译”的强度里解放出来。很多嵌入式工程师一听到反编译就头大觉得那是逆向高手才能做的活。实际上当你有了AI你只需要做到“能够提问、能够验证、能够把AI的分析对到硬件行为上”就已经足够应付80%的合法逆向需求了。4.3 从代码学习嵌入式软件新思路——AI让我少走弯路的几个启发通过这段时间与AI协同工作我有几个关于“AI下嵌入式软件怎么学”的直接体会正好对应一些朋友关心的“要不要学汇编、要不要学寄存器、要不要背手册”的问题。寄存器层面你依然要懂但不用背。重点是要知道“去哪本手册查什么位域”。AI能快速生成代码但你没能力验证寄存器配置正确性就会一直被“ AI生成的能不能信”困住。汇编层面不用精通但至少要看得懂基本指令。否则Ghidra给你反编译出来的伪代码出现晦涩写法时你做不了交叉验证。学习路径从AI生成的代码里学习设计模式比从教科书里学得更快。每一次让AI生成新驱动我都要求它给注释把每行关键配置的原理讲明白这等于请了一个随叫随到的手把手家教。另外我发现一个好的“AI编程提示词”本身就是学习工具。你有明确目标时把要实现的协议和技术细节以提示词形式梳理出来相当于强迫自己系统化思考了一遍。这比打开芯片手册漫无目的地逛有效得多。把整个硬件系统分割成小模块逐块用提示词理清边界再交给AI实现——这套方法我称为“提示词驱动式学习”。5. 常见问题与排查技巧实录5.1 AI生成代码跑不通如何判断是自己问题还是AI问题每次AI生成代码挂了我都会按固定顺序排查避免瞎改看编译错误里是不是头文件路径、宏定义冲突相关这通常是工程环境问题AI背不了锅。看运行卡死的位置在哪一行。如果是在等待事件标志的循环里大概率是状态机没推进先回去查寄存器配置顺序。用调试器读出外设寄存器快照和芯片手册规范对比。比如I2C配置完检查控制寄存器值是否和你填写的期望值一致。如果寄存器和配置都对但仍无波形那就别在AI代码里绕了拿示波器检查引脚电平、焊接、上拉。过去我总想“一次跑通”现在心态变成“接受第一次不完美把它看作人脸识别验证流程中的一部分”。在一个AI协同开发的工作流里正确率不是第一评价指标你处理错误的速度才是。5.2 嵌入式工程的文件规整AI生成的代码如何不被同事骂AI生成代码有个特点文件名长短、内部是否拆分为头文件和源文件、宏定义可配置性完全取决于你的要求。如果提示词写得松散它可能把几百行代码全塞在一个文件里。这在个人实验项目里没问题放进团队代码库就是灾难。我的处理办法是在提示词里直接给“工程文件结构要求”驱动文件要拆成io_config.h、xxx.h、xxx.c三个文件所有可调参数要集中在头文件宏定义区函数注释要包含“入参、返回值、出错码”。AI生成后我再逐文件检查命名和依赖关系确认没有循环include。另外我坚持把AI生成的代码当成一个新员工写的初稿必须经过自己的代码评审才允许合入main分支。这套流程跑顺以后不仅代码可读性提高了后面继续做功能迭代时给AI描述新需求也容易很多因为它能直接读取工程里已经规范化的头文件理解你的代码风格。5.3 我的提示词仓库整理方法经过三个项目阶段我积累了几十条提示词。它们不是散落在聊天记录里的而是被保存成一个标准Markdown文档按功能分类芯片初始化类针对不同MCU型号的GPIO、时钟、中断配置模板外设驱动类UART、I2C、SPI、ADC、PWM的初始化读写模板调试协助类如何描述示波器波形、寄存器快照、崩溃现场让AI快速定位反编译分析类Ghidra伪代码的角色定义、行为摘要求、协议猜测线索每次新项目需要用到某类模板时我会先把模板复制到当前工程的docs目录下再根据硬件型号微调约束条件才发给AI。这样做的好处是即使过了很久你也能知道某段代码当时是照着哪些前提生成的将来维护回查时不会出现“这代码为什么这么写谁改的”的框。我个人最大的建议是不要用单条孤立的提示词而是建立一个“提示词-模板-工程脚本”的配套体系。例如工程里有一个prompts/文件夹每个提示词旁边对应一个test/样例用来验证AI生成结果是否满足要求。这样才能把AI协同开发的收益固化下来而不是每次从零开始摸索。在写这一篇文章的过程中我多次回想起自己最开始对AI编程的排斥。从心底说我依然认为嵌入式工程师的硬功夫——看时序图、查寄存器手册、调示波器——不能丢。但经历了这次“第一个AI协同开发项目”的第三阶段我確确实实体会到把那些繁琐的“搬运式编码”和“翻译式学习”交给AI之后我的精力可以更集中在理解系统边界、规划硬件行为、验证数据流这些更有创造性的工作上。后面的项目阶段我计划继续把模块更复杂的通信协议和实时性优化逐步交出去也准备尝试让AI参与测试用例的自动生成。希望这篇文章里的实操细节也能在你自己的AI协同开发里少走几个弯路。