1. 嵌入式场景下 Claude Code 的定位与核心价值
嵌入式软件开发和纯互联网应用开发有一个本质区别:代码跑在资源受限的硬件上,编译工具链五花八门,调试手段高度依赖硬件仿真器和串口输出。很多在 PC 端用得很顺手的 AI 编程工具,到了嵌入式场景就水土不服——生成的代码不考虑寄存器位宽、不区分大小端、对中断上下文和线程上下文的约束视而不见。Claude Code 之所以在嵌入式圈子里逐渐被接受,核心原因是它具备项目级上下文理解能力,能读取你工程里的头文件、链接脚本、启动文件,再结合对话给出贴合具体芯片的代码建议。
这一篇接着上一节的基础操作往下讲,重点放在嵌入式开发者最关心的几个环节:怎么让 Claude Code 真正理解你的工程结构、怎么用它做单元测试、怎么配置才能让它稳定输出可编译的代码,以及那些官方文档里不会写但实际用起来天天踩的坑。如果你刚开始接触 Claude Code,建议先把安装和基础对话跑通再来看这篇;如果你已经在用但觉得"它生成的代码总是差那么点意思",那这篇里的上下文管理技巧和提示词写法应该能帮到你。
需要先明确一点:Claude Code 不是万能的代码生成器,它更像一个随时在线的资深同事——你得把工程背景交代清楚,它才能给出有价值的建议。嵌入式领域的特殊性在于,同一个功能在不同 MCU 上的实现可能完全不同,所以"喂上下文"这件事比在其他领域更重要。
2. 让 Claude Code 读懂你的嵌入式工程
2.1 工程上下文的三层组织方式
很多人用 Claude Code 的方式是打开终端直接问"帮我写一个 SPI 驱动",然后得到一段通用性很强但根本编译不过的代码。问题出在上下文缺失。我的做法是把工程上下文分成三层来喂:
第一层是硬件抽象层信息,包括芯片型号、内核架构、主频、外设寄存器手册的关键章节。你不需要把几百页的参考手册全丢进去,但至少要让 Claude Code 知道这是 Cortex-M4 还是 RISC-V,有没有 FPU,中断优先级分几组。这些信息直接决定了它生成的代码能不能用。
第二层是工程结构信息,也就是你的目录树、构建系统(Makefile / CMake / Keil 工程文件)、已有的驱动框架。Claude Code 支持读取项目文件,你可以让它先扫描一遍工程再开始对话。实测下来,先执行一次工程扫描,后续对话中它引用已有函数和宏定义的准确率会明显提升。
第三层是编码规范约束,比如你们团队要求所有外设操作必须用封装好的宏、中断服务函数命名必须带_IRQHandler后缀、禁止在中断里调用阻塞函数。这些约束用自然语言写在对话开头,Claude Code 基本都能遵守。
三层上下文的组织顺序建议是:先给硬件信息,再让它读工程,最后补规范约束。顺序反了的话,它可能会基于通用假设先给出方案,后面再纠正反而更费劲。
2.2 用 CLAUDE.md 固化项目约定
Claude Code 支持在项目根目录放一个CLAUDE.md文件,它会自动读取并作为长期上下文。对嵌入式项目来说,这个文件的价值极高。我通常会在里面写这几类内容:
- 芯片平台和工具链版本(比如
arm-none-eabi-gcc 10.3,STM32CubeMX 生成的 HAL 库) - 目录结构说明(
Drivers/放外设驱动,App/放应用逻辑,Bsp/放板级支持) - 命名约定和代码风格(比如
模块名_功能名的函数命名,缩进用 4 空格) - 禁止事项(比如不允许动态内存分配、不允许使用浮点运算除非明确说明)
- 常用命令(编译命令、烧录命令、单元测试运行命令)
这个文件不需要写得很长,控制在 100 行以内效果最好。写太长反而会稀释关键信息的权重。我见过有人把整个编码规范文档贴进去,结果 Claude Code 在对话中经常忽略掉最关键的那几条约束。
提示:
CLAUDE.md里的内容会占用上下文窗口,建议只放"每次对话都需要知道"的信息。一次性的任务背景放在对话里说就行,不要往这个文件里塞。
2.3 上下文窗口的管理策略
嵌入式工程的文件数量往往很多,一个中等规模的 STM32 项目轻松上百个源文件。Claude Code 的上下文窗口虽然不小,但也不可能把整个工程都装进去。我的策略是按需加载:
开始一个任务前,先想清楚这个任务涉及哪些文件。比如要改 UART 驱动,那就让它读uart.c、uart.h、对应的寄存器定义头文件,以及调用这个驱动的上层模块。不要一上来就让它读整个Drivers/目录。
如果任务跨多个模块,可以分阶段进行。先让它理解模块 A 的接口,确认理解正确后再引入模块 B。这样虽然多几轮对话,但每次的输出质量都更高。我试过一次性丢进去十几个文件让它改一个跨模块的 bug,结果它改对了 A 模块却把 B 模块的接口用错了,返工成本反而更高。
另外一个小技巧:当对话轮次多了以后,上下文里会积累很多已经不需要的历史信息。这时候可以用/clear清空对话重新开始,但记得把关键结论先记下来。Claude Code 本身不保留跨会话记忆,每次清空都是全新开始。
3. 嵌入式单元测试的 AI 辅助实践
3.1 嵌入式单元测试的特殊性
"嵌入式软件单元测试怎么做"是热词里出现频率很高的问题,说明这是很多人的痛点。嵌入式单元测试和普通软件单元测试最大的区别在于代码和硬件强耦合。一个读取 ADC 的函数直接操作寄存器,你在 PC 上根本跑不起来,谈何测试。
常见的解决方案是引入硬件抽象层(HAL),把寄存器操作隔离到一层薄薄的接口后面,测试时用 mock 替换掉真实硬件。这个思路大家都知道,但实际做起来工作量大、容易遗漏。Claude Code 在这个环节能帮上大忙——它可以帮你分析哪些函数需要抽象、生成 mock 框架、甚至直接改写代码把硬件依赖抽离出来。
3.2 用 Claude Code 生成测试框架
我通常的操作流程是这样的:先让 Claude Code 分析目标模块的依赖关系,找出所有直接操作硬件的地方。提示词可以这样写:
请分析 App/adc_sample.c 中所有直接访问硬件寄存器的代码行, 列出它们操作的寄存器名称和用途,并给出将这些操作抽象为 接口函数的建议。接口函数命名遵循 bsp_ 前缀约定。它会输出一份依赖清单和抽象建议。确认无误后,再让它生成抽象层代码和对应的 mock 实现。这里有个细节:mock 实现要能模拟真实硬件的时序行为,比如 ADC 转换需要等待一段时间才有结果。如果 mock 直接返回固定值,测试就失去了意义。我会在提示词里明确要求"mock 函数需要支持注入延迟和返回值序列"。
测试框架的选择上,嵌入式领域常用的是 Unity + CMock 组合,或者 Google Test(需要把代码编译成 PC 可执行文件)。Claude Code 对这两套框架都很熟悉,你告诉它用哪套,它生成的测试代码基本能直接跑。我实测下来,让它生成 Unity 测试用例的准确率比 Google Test 更高一些,可能是因为 Unity 的 API 更简单、模式更固定。
3.3 测试用例设计的提示词技巧
让 Claude Code 设计测试用例时,最忌讳的是笼统地说"帮我写测试"。它需要知道边界条件和异常场景。嵌入式代码的测试重点通常在这几个方面:
- 输入参数的边界值(比如缓冲区长度为 0、最大长度、超长)
- 硬件返回异常时的处理(比如 I2C 通信超时、CRC 校验失败)
- 中断和主循环的并发场景(比如中断里修改的变量在主循环里读取)
- 资源耗尽的情况(比如队列满、内存池空)
我一般会把这些场景列出来,让 Claude Code 针对每个场景生成对应的测试用例。提示词示例:
针对 ring_buffer_write 函数,请生成以下测试用例: 1. 正常写入单个字节 2. 写入时缓冲区刚好满 3. 写入时缓冲区已满(应返回错误) 4. 写入长度为 0 5. 写入长度超过缓冲区剩余空间 每个用例用 Unity 框架实现,包含 setUp 和 tearDown。这样生成的测试用例覆盖度比让它自由发挥要高得多。而且因为场景是你指定的,不会出现它自己臆想出来的、实际不可能发生的测试场景。
4. 提示词工程:让 AI 输出可编译的嵌入式代码
4.1 嵌入式提示词的四个必备要素
"ai编程提示词"是个大话题,但在嵌入式场景下有四个要素是必须包含的,缺一个输出质量就明显下降:
第一,目标平台和工具链。明确告诉它芯片型号、编译器版本、C 标准(C99 还是 C11)。不同编译器对某些语法的支持不一样,比如 GCC 支持的一些扩展在 IAR 上就编译不过。
第二,代码风格约束。比如是否允许使用goto、是否要求所有函数有返回值检查、是否使用 MISRA C 规范。嵌入式领域对代码安全性要求高,这些约束能显著减少后续 review 的工作量。
第三,资源约束。栈空间多大、堆是否可用、Flash 和 RAM 的剩余量。这些信息会影响它选择算法和数据结构。比如栈只有 1KB 的时候,它就不应该生成递归实现。
第四,接口约定。函数命名规则、参数传递方式(指针还是值)、错误码定义。如果工程里已经有统一的错误码枚举,直接告诉它用哪个。
把这四个要素写进提示词,输出的代码基本能直接编译。我对比过,包含这四个要素的提示词和只说"帮我写个函数"的提示词,输出代码的可用率差距在 3 倍以上。
4.2 分步生成而非一步到位
嵌入式代码往往涉及多个层次:寄存器操作、外设驱动、业务逻辑。让 Claude Code 一次性生成所有层次,出错概率很高。我的做法是分层生成,逐层验证。
先让它生成最底层的寄存器操作函数,你人工检查一遍寄存器地址和位定义是否正确。确认后再让它基于这层接口生成驱动层,最后生成业务逻辑。每一层都验证通过再往上走,这样即使出错也能快速定位是哪一层的问题。
这个流程看起来慢,但实际上比"生成一大坨然后慢慢 debug"要快得多。尤其是寄存器操作这种一旦错了就很难查的问题,人工确认一遍能省下大量调试时间。
4.3 用示例驱动输出风格
Claude Code 有一个很好用的特性:你给它一个代码示例,它会模仿这个示例的风格。嵌入式工程通常有自己的一套编码风格,与其用文字描述,不如直接丢一个已有的、风格规范的函数给它看。
比如你要写一个新的外设驱动,可以先把它同目录下已有的、写得比较好的驱动函数贴给它,说"请按照这个函数的风格实现 XXX 功能"。这样生成的代码在命名、注释、错误处理方式上都会和现有代码保持一致,review 起来轻松很多。
我一般会在CLAUDE.md里放一两个"风格样板函数"的路径,让它需要的时候自己去读。这样不用每次对话都手动贴代码。
5. 常见问题与排查实录
5.1 连接与配置类问题
热词里出现了"unable to connect to anthropic"这类报错,这是新手最常遇到的问题。这类连接问题的排查思路是:先确认网络环境是否满足工具的运行要求,再检查配置文件里的参数是否正确,最后看版本是否匹配。具体到操作层面,建议按以下顺序排查:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 启动后立即报连接失败 | 配置文件缺失或路径错误 | 检查用户目录下的配置文件夹是否存在 |
| 对话中途断开 | 网络波动或会话超时 | 重新发起对话,检查是否有长任务阻塞 |
| 提示版本不兼容 | 客户端与服务端版本差异 | 更新到最新版本后重试 |
| 权限被拒绝 | 文件访问权限不足 | 检查工程目录的读写权限 |
需要强调的是,具体配置方法请以官方文档为准,不同版本的配置项名称可能有变化。我踩过的坑是:升级版本后旧的配置文件格式不兼容,导致一直连不上,删掉旧配置重新生成就好了。
5.2 代码生成质量问题
问题一:生成的代码用了工程里不存在的库函数。这是最常见的问题,尤其是它自作主张用了malloc或者某些标准库函数,而你的工程是禁用动态内存的。解决办法是在CLAUDE.md里明确列出可用的库函数白名单,或者明确禁止某些函数。
问题二:寄存器位定义和实际芯片不符。Claude Code 的训练数据里包含大量不同芯片的代码,它可能会混淆。解决办法是让它读你的芯片头文件,并且在提示词里强调"所有寄存器操作必须引用工程中已有的宏定义,不得自行编造"。
问题三:中断服务函数里调用了阻塞操作。这是嵌入式的大忌,但 AI 不一定每次都记得。解决办法是在提示词里明确"中断上下文禁止调用任何可能阻塞的函数,包括延时、信号量等待、动态内存分配"。
问题四:生成的代码没有考虑字节序。涉及多字节数据通信时,大小端问题很容易被忽略。如果你的芯片是小端而通信协议是大端,必须显式提醒它做转换。
5.3 上下文丢失与幻觉问题
对话轮次多了以后,Claude Code 可能会"忘记"前面说过的约束,开始生成不符合规范的代码。这不是 bug,是上下文窗口的固有限制。应对方法是:关键约束反复强调。每开始一个新任务,把最核心的两三条约束重新说一遍,不要指望它一直记得。
另一个问题是幻觉——它会编造不存在的函数名或宏定义。排查方法是生成代码后先做一次编译,编译错误里如果出现"未定义的引用",大概率就是幻觉。这时候把错误信息贴回给它,它通常能自己纠正。
注意:不要盲目相信 AI 生成的寄存器地址和位定义。这类信息一旦出错,轻则功能不工作,重则损坏硬件。我的习惯是:所有涉及寄存器操作的代码,必须对照芯片手册人工核对一遍再使用。
5.4 性能与资源占用问题
嵌入式开发对资源敏感,Claude Code 生成的代码有时候会"过度设计"。比如一个简单的状态机,它可能生成一个带动态内存分配和函数指针表的通用框架,而实际上用 switch-case 就够了。遇到这种情况,直接在提示词里加上资源约束,比如"栈空间限制 256 字节,禁止使用函数指针表"。
还有一个隐蔽的问题是代码体积。AI 生成的代码往往比较"啰嗦",同样的功能可能比手写代码多占 20% 到 30% 的 Flash。如果 Flash 空间紧张,生成后需要用size命令检查一下各个段的大小,必要时手动精简。
6. 工具链协同与工作流整合
6.1 与版本控制的配合
Claude Code 修改代码后,建议先用git diff看一下改了什么再决定是否接受。我见过有人直接让它改完就编译,结果它顺手"优化"了几个不相关的函数,引入了新的 bug。用 git 管理的好处是随时可以回退,改坏了也不怕。
一个实用技巧是用git worktree为 AI 编程开一个独立的工作目录。这样 Claude Code 在一个分支上折腾,不影响你当前的工作分支。等它改完了,你 review 通过再合并。热词里提到的git worktree ai编程说的就是这个用法,实测下来确实能避免很多"改着改着把主分支搞乱了"的问题。
6.2 与编辑器/IDE 的配合
Claude Code 有 CLI 版本也有编辑器插件版本。我的使用习惯是:探索性任务用 CLI,精确修改用编辑器插件。探索性任务比如"帮我分析这个模块的依赖关系",CLI 里对话更方便;精确修改比如"把这个函数的第 15 行改成 XXX",在编辑器里选中代码直接操作更直观。
如果你用 VS Code,插件版本可以直接读取当前打开的文件作为上下文,省去手动指定文件的步骤。但要注意,它默认可能只读取当前文件,跨文件的任务还是需要手动指定或者让它扫描工程。
6.3 团队协作中的注意事项
如果团队多人使用 Claude Code,建议统一CLAUDE.md的内容并纳入版本控制。这样每个人得到的代码风格建议是一致的,不会出现"张三生成的代码用驼峰命名,李四生成的用下划线命名"这种混乱。
另外,AI 生成的代码在提交时建议在 commit message 里注明,方便后续追溯。这不是为了"甩锅",而是当这段代码出问题时,review 的人知道它是 AI 生成的,会更有针对性地检查那些 AI 容易出错的点。
7. 我个人的实操体会
用了大半年 Claude Code 做嵌入式开发,最大的体会是:它的价值不在于替你写代码,而在于替你处理那些重复性的、模式固定的工作。比如生成外设初始化的样板代码、把一段裸寄存器操作改写成 HAL 风格、给已有函数补单元测试。这些工作技术含量不高但很耗时,交给它做能省下大量时间。
但涉及核心算法、时序敏感的代码、安全相关的逻辑,我还是坚持自己写。不是不信任 AI,而是这些地方的错误代价太高,人工 review 的成本可能比直接手写还高。把 AI 用在它擅长的地方,把人的精力留给真正需要思考的地方,这个分工目前来看是最合理的。
最后分享一个提高效率的小习惯:我会把每次让 Claude Code 做的任务和它的输出质量简单记在一个文档里,积累一段时间后就能看出它在哪些任务上靠谱、在哪些任务上容易翻车。比如我发现它生成 I2C 驱动比 SPI 驱动准确率高,生成状态机比生成通信协议栈靠谱。知道这些规律后,分配任务时心里就有数了。这个习惯看起来麻烦,但长期下来省的时间远超记录的成本。