
做了这么多年嵌入式软件我越来越觉得这行最值钱的不是敢不敢焊板子而是踩坑的次数。寄存器怎么配、中断优先级怎么安排、状态迁移哪里容易翻车这些几乎全靠经验堆出来。最近我把 Claude Code 接进了平时的 STM32 工程说实话工作流确实变了。以前从需求到能跑的固件至少要折腾一整天现在把需求描述清楚AI 能把驱动框架、状态机、错误处理先搭出来我再重点做代码审查和硬件联调。这篇博文就记录我怎么用 Claude Code 做 STM32 开发包括安装配置、实战案例、踩坑实录适合手里有 STM32 板子、又想试试 AI 编程的朋友。全文不讲虚的所有流程都是我自己在真实工程里跑通过的操作。1. 为什么嵌入式开发需要AI编程助手1.1 先聊聊嵌入式开发的效率瓶颈嵌入式软件和纯互联网后端有个很大的区别代码和硬件强绑定很多问题在编译期根本暴露不出来。一个断针接触不良、一个时钟树配置错误表现出来可能就是串口全是乱码或者系统跑着跑着进 HardFault。这种“软硬结合”的排错过程极其消耗时间传统的补全型编程工具帮不上什么忙因为它们只懂语法不懂硬件语义。而 Claude Code 这类大模型编程工具不一样它可以一次性读入整个工程上下文包括 CubeMX 生成的初始化代码、HAL 库版本、寄存器配置再根据你描述的行为需求来生成代码。它不只是“自动补全”而是能理解“我现在要一个用定时器触发的软件定时器”、“按键需要消抖”、“状态机里 BREATHING 状态要平滑过渡”这种高层次的业务逻辑。另外嵌入式开发还有一个隐性成本芯片型号和库版本差异特别大。同样一段 GPIO 初始化代码F1 系列和 F4 系列写法不同HAL 库和标准库也不同。搜索引擎搜出来的答案经常过时或者干脆是错的。我现在遇到不确定的寄存器配置会直接把数据手册相关章节丢给 Claude Code让它按当前工程的 HAL 版本去推导比复制粘贴旧代码靠谱得多。1.2 Claude Code 和其他 AI 工具比强在哪我试过好几款 AI 编程工具包括 IDE 插件类的和命令行类的。Claude Code 在嵌入式方向上有几个点是真正打动我的。第一是它的上下文处理能力。嵌入式工程动辄几百个文件核心业务逻辑可能分散在多个驱动模块里。Claude Code 在对话中可以持续关联这些文件不会聊了两轮就把前面提到的硬件引脚定义忘了。这一点在做跨文件重构时特别有用比如把整个外设初始化从 main.c 抽到 board_init.c。第二是它会“动手”而不是只“动嘴”。很多 AI 插件只能在侧边栏给你代码建议你还要手动复制粘贴。Claude Code 可以直接创建文件、修改现有代码、执行编译命令形成一个完整的“说话—改代码—编译报错—再修”闭环。我经常让它自己跑 make然后把编译错误贴回对话框继续修改这种循环效率极高。第三是它对嵌入式工具链的理解。我在提示词里明确告诉它“这是 STM32 HAL 库 CMake 工程”它给出的代码就能严格遵守 HAL 库的命名习惯比如 HAL_GPIO_WritePin、__HAL_TIM_SET_COMPARE而不是给出一个 Arduino 风格或者纯寄存器操作的版本。当然这需要你在项目文件里写清楚约束后面我会详细说。特性Claude Code传统 AI 插件跨文件上下文强能关联整个仓库通常局限于当前打开文件执行命令支持直接编译/测试不支持或较弱嵌入式工程适配读 Makefile/CMakeLists 确定编译方式只看代码文本多人协作约束CLAUDE.md 固化规范难以沉淀项目规则1.3 嵌入式里 AI 真正能帮上忙的场景用了一段时间后我总结出五个 Clauude Code 在嵌入式开发中性价比最高的应用场景不是那种“让 AI 写整个项目”的幻想而是能明显省时间的实际环节。第一是初始化样板代码。CubeMX 虽然能生成初始化代码但很多人还是直接手写 HAL 库初始化。这种代码模式固定但细节繁多AI 生成速度极快。第二是状态机和业务逻辑建模。这是嵌入式软件架构的核心AI 能根据需求描述先给出状态划分和迁移条件方便人审阅纠偏。第三是外设驱动的移植适配。比如把一个 DHT11 驱动从标准库改成 HAL 库这种重复性强的工作AI 处理得很好。第四是调试信息分析。硬故障时把 PC 值和 LR 值丢给它配合 map 文件它能帮你缩小排查范围。第五是测试桩和模拟器代码生成。在主机上模拟传感器输入AI 可以快速生成 mock 层。2. Claude Code 环境准备与安装含避坑2.1 安装前需要准备什么不是装一个 npm 包就能直接用我建议先检查自己的基础环境。Claude Code 本身是 Node.js 编写的命令行工具因此机器上需要 Node.js 18 以上版本和 npm。你可以用 node -v 确认版本如果还没有装 Node.js去官网下载 LTS 版本就行Windows 用户安装时记得勾选 Add to PATH。然后是账号权限问题。Claude Code 需要登录 Anthropic 账号并且账号需要具备对应的模型访问权限。这个在你购买订阅或开通 API 后会自然拥有。我不推荐用任何非官方渠道的“破解版”或“转发服务”一方面有安全风险另一方面版本更新快非官方方案极易失效出了问题连排查入口都没有。直接走官方渠道最稳。最后是终端环境。Claude Code 在 Windows 上用 PowerShell 也能跑但如果你做 STM32 开发我强烈建议装 WSL2。原因很简单很多嵌入式构建工具链比如 arm-none-eabi-gcc、OpenOCD在 Linux 下更顺手Claude Code 在 WSL 里也能直接调用这些工具。没有 WSL 的情况下用 Windows 终端配好环境变量也行但偶尔会遇到路径分隔符和权限问题。2.2 安装与登录5分钟跑通安装本身非常直接全局安装官方 npm 包npm install -g anthropic-ai/claude-code安装完成后确认版本claude --version如果输出了版本号说明安装成功。下一步是登录claude login执行后会提示你在浏览器中打开一个链接授权后将本机终端和你的账号绑定。整个过程大概一两分钟。这里有个细节登录完成之后Claude Code 会在用户目录下生成配置文件记录 access token。千万别把这个 token 提交到 Git 仓库建议在 .gitignore 里显式忽略相关配置项。其实日常克隆新工程时我通常不需要重新登录但如果换了机器或者容器环境就得重复一遍 login。为了方便团队协作我们把 CLAUDE.md 放在工程仓库里提交这样每个成员用 Claude Code 打开工程时都能继承同样的项目约束。2.3 用 CLAUDE.md 告诉 AI 你的硬件约束Claude Code 支持项目级的指令文件默认叫 CLAUDE.md放在工程根目录。它会作为长期上下文在每次对话时被加载。对嵌入式项目来说这个文件是 AI 生成代码质量的关键一定要认真写。我给出一个 STM32 工程实际用到的 CLAUDE.md 示例# STM32F103C8T6 固件项目约束 - 芯片: STM32F103C8T6 (Cortex-M3, 64KB Flash, 20KB RAM) - 库: STM32Cube HAL (F1 系列, 版本 1.8.x) - 编译: CMake arm-none-eabi-gcc, C99 标准 - 协议: 使用 HAL 库函数不直接操作寄存器性能关键路径除外 - 引脚: - PA0: 按键输入, 内部上拉, 下降沿触发 - PA1: LED 输出, 推挽模式 - PA9/PA10: UART1_TX/RX, 115200-8-N-1 - 规范: - 函数命名: 模块名_动词_宾语, 如 led_driver_set_mode() - 头文件必须包含 extern C 保护 - 中断处理函数只置标志位业务逻辑放主循环你可以看到这里把硬件资源映射、技术栈、代码风格都写清楚了。Claude Code 后续生成的代码就会默认遵守这些约束极大减少“它生成了但根本跑不起来”的概率。当前工程里没有 CLAUDE.md 时你也可以在 claude 对话框中输入 /init它会根据你的仓库结构和代码风格自动生成一个初始版本你再人工补齐硬件约束。3. STM32 Claude Code 的工程级配合3.1 什么样的工程结构最顺手Claude Code 不是只能用在从零新建的工程里反而在既有工程里它更能发挥价值。它需要“看懂”你的工程结构才能针对性地修改代码。我现在的 STM32 工程目录长这样project/ ├── CMakeLists.txt ├── CLAUDE.md ├── core/ │ ├── main.c │ ├── stm32f1xx_hal_conf.h │ └── system_stm32f1xx.c ├── drivers/ │ ├── led_driver/ │ │ ├── led_driver.c │ │ └── led_driver.h │ ├── key_driver/ │ └── uart_driver/ ├── app/ │ ├── app_main.c │ └── app_state_machine.c └── Makefile这个结构把“硬件无关的业务逻辑”和“硬件相关的驱动”分开AI 在生成代码时能明确边界。比如它生成 app_state_machine.c 时只需要调用 led_driver 的接口而不用关心底层寄存器怎么操作。这样就算某一天换了个核心板驱动层重写业务层基本不用动。我还特别把 CMakeLists.txt 里对源文件的 glob 写法改成了显式列举方式。这不是为了其他目的而是让 Claude Code 在增删源文件时能明确知道要改哪个列表不会因为 gitignore 或者构建缓存搞得一头雾水。实测下来显式列举源文件后AI 自动添加新模块的成功率高很多。3.2 让 Claude Code 读懂 CubeMX 生成的代码很多人用 STM32CubeMX 生成初始工程工具栏里的 .ioc 文件是图形化配置的核心。Claude Code 是文本工具不能直接编辑 .ioc 文件那怎么办我的做法是分开处理CubeMX 负责时钟树和引脚复用这类图形化配置Claude Code 负责业务逻辑代码。但问题来了CubeMX 生成的代码里有很多 Banner 注释比如 “/* USER CODE BEGIN 3 */” 这种段内代码。Claude Code 偶尔会把这些保护区域以外的地方改掉重新生成后就被覆盖了。所以我给 CLAUDE.md 里加了一条规则只能修改用户代码区域或者新建独立模块文件不允许改动 CubeMX 自动生成区。只要这一条约束加进去冲突少了很多。另外在对话过程中你可以让 Claude Code 先阅读几个关键文件再下手。比如claude # 你请先读 core/src/main.c 和 app/app_state_machine.c # 然后告诉我整个系统的主循环结构。它会读完文件后给出一个概要你再一步步让它实现具体的功能。这样比一上来就丢一段需求更稳因为它已经理解当前代码的实际状态而不是凭空生成一套“看起来很美”的东西。3.3 编译与烧录的闭环高效循环Claude Code 不只写代码它还能执行终端命令。我会在工程里配好 make 命令对话中直接让它帮我编译# 你帮我编译一下并把错误信息整理出来。它会运行 make 或 cmake --build build看到错误后自动定位对应的源文件并给出修复方案。如果错误信息很明确我甚至可以直接说“按你的方案改吧”然后它又开始下一轮编译直到通过。这里要提醒一个细节嵌入式工程的编译错误经常不是语法错误而是链接错误比如 Flash 溢出、Undefined symbol。Claude Code 对这类错误的解读能力很强。我第一次故意让它把一个大数组定义在全局导致 Flash 溢出它能一眼指出是 L6242E 错误并建议把数组放到 const 段或者减小缓存。这个排查速度比我手动翻 map 文件快多了。至于烧录我一般不让 Claude Code 自动执行烧录命令因为调试器和目标板连接状态不稳定脚本自动执行可能烧到一半失败容易把环境搞乱。我的习惯是编译通过后我在终端手动执行烧录命令然后把硬件现象描述给 Claude Code让它辅助分析。烧录这一步保留人工操作多一道确认对板子也更安全。4. 实战案例Claude Code 写一个多模式 LED 驱动器4.1 需求拆解与第一轮对话光讲方法不下菜很容易飘我拿一个真实的小案例来演示整个过程。硬件平台是 STM32F103C8T6 小板外设包括一个 LEDPA1和一个按键PA0通过串口打印当前模式。需求是在三种工作模式之间切换常亮、闪烁500ms 翻转、呼吸灯PWM 占空比从 0 渐变到 100 再回落。我打开终端进入工程目录运行 claude第一轮对话这样发起请把 app_state_machine.c 里加上一个基于状态机的模式管理模块。 支持 MODE_SOLID、MODE_BLINK、MODE_BREATHING 三种状态 每次按键按下切换一次状态切换顺序是循环的。 按键在 key_driver 里已经提供了 is_key_pressed() 接口。 串口通过 uart_driver 的 uart_debug_print() 打印当前模式名。 LED 控制请调用 led_driver 里的接口预留 set_solid()、set_blink()、set_breathing()。这样描述有几个好处首先所有功能都有明确接口名不会让 AI 自己去发明不存在的函数其次我说明要用状态机它会按照状态建模的思路设计最后我明确指定文件范围不让它乱改驱动层。4.2 状态机设计AI 给方案我纠偏Claude Code 收到任务后会先给出一个设计说明再写代码。我印象很深的是它最初给出的状态枚举是这样的typedef enum { LED_MODE_SOLID, LED_MODE_BLINK, LED_MODE_BREATHING, LED_MODE_COUNT } led_mode_t;并且自己定义了一个 app_state_machine.c 和 app_state_machine.h。这个思路是对的但它在按键扫描上踩了个典型坑——它没有做消抖直接用 is_key_pressed() 的当前电平来切换状态。我当时就指出“这里还需要支持单击检测和防抖建议在状态机里加一个 20ms 延时确认再加软件边沿检测。”然后它马上修改了方案增加了一个短暂的 KEY_EVENT 判断等按键稳定后再读取一次状态确保按下一次只切一个模式。这就是人和 AI 配合的典型场景AI 能快速搭出框架但硬件相关的经验细节需要你作为“工程质检员”来把关。最终生成的关键代码结构如下typedef enum { LED_MODE_SOLID 0, LED_MODE_BLINK, LED_MODE_BREATHING, LED_MODE_COUNT } led_mode_t; static led_mode_t current_mode LED_MODE_SOLID; void app_state_machine_update(void) { uint8_t key_value key_driver_scan(); if (key_value KEY_PRESSED_ONCE) { current_mode (led_mode_t)((current_mode 1) % LED_MODE_COUNT); led_driver_switch_mode(current_mode); uart_debug_print(mode: %d\n, current_mode); } led_driver_cyclic_update(); }它把按键扫描、状态切换、驱动逐个更新分得很清楚主循环里只要持续调用 app_state_machine_update() 就行。模式计数循环用的取模方式也是嵌入式里最常用的做法简洁且不会越界。4.3 编译烧录后的实测结果我让它自己用 CMake 编译第一次遇到的问题是 LED 驱动的 PWM 通道初始化少配了 GPIO 复用功能。当时现象是呼吸灯模式里 LED 完全不变亮它检查代码后发现 TIM2_CH1 的复用推挽配置缺失加了一句 GPIOA-CRL 的配置或者 HAL_GPIO_Init 里 GPIO_MUX 配置才通过。修完之后编译零错误我用 ST-Link 烧录进去上电默认常亮按一下变成 500ms 闪烁再按一下呼吸灯效果出现全程串口都正确打印了当前模式。第二遍测试按键反复快速连按时出现了一次模式连续跳变的情况原因是状态机里只有边沿检测没有释放确认。我反馈给它之后它又引入了“必须检测到释放并且再延迟防抖后才算一次单击”的逻辑重复连按稳定了。这个案例说明AI 生成的代码可以作为 70 分的初稿但剩下 30 分的硬件时序和可靠性细节必须靠你定义和验证。5. 常见问题与排查技巧实录5.1 烧录连接类报错怎么破开发过程中最常见的一类问题就是调试器连接报错。搜索平台上被问烂的 “No STM32 Target Found” 基本可以分为三个层面物理链路、驱动权限、目标板状态。物理链路上优先检查 ST-Link 的 SWDIO、SWCLK、GND 三条线和目标板是否共地杜邦线接触不良在高速下载时特别容易出问题。驱动权限方面Windows 上如果设备管理器里 ST-Link 显示黄色感叹号需要重装驱动WSL 或 Linux 下需要给 ST-Link 配置 udev 规则否则 OpenOCD 没有访问权限。目标板状态方面芯片进入了低功耗模式或者复位引脚被拉低调试器也可能找不到内核。Claude Code 在这个排查过程里能帮上忙的部分是解释 OpenOCD 和 ST-Link 工具输出的调试日志。但物理接线和设备识别这类纯硬件问题还是要人来解决。我建议你在 CLAUDE.md 里把烧录命令、调试器型号、接线方式写成注释这样对话时它不会瞎猜你的环境。5.2 Claude Code 生成代码时最容易踩的坑我总结出三个比较常见的问题。第一个是库版本幻觉。你明明用的是 HAL 库它却可能生成标准库的代码。解决办法是在 CLAUDE.md 里写清楚“仅使用 STM32Cube HAL 1.8.x API禁用 SPL 标准库函数”并且在对话中强调一遍。第二个是不了解硬件时序约束。比如 I2C 读取传感器时它可能搞错 START 和 STOP 条件之间的延时或者在 DMA 传输未完成时就去读缓冲区。这种问题只能靠你在提示词里给出“设备手册第几页写明了需要 xx us 延时”这类信息或者让它对照你提供的手册片段来设计。第三个是代码膨胀。AI 喜欢生成大量防御性判断和组件封装对 PC 应用来说没问题但在 64KB Flash 的 MCU 上很致命。我遇到过一次它自动生成一个冗长的日志模块直接把 Flash 占用推到 98%。遇到这种情况你要补充约束条件“所有新模块增量 Flash 不超过 2KB禁用 printf 浮点支持”。嵌入式开发的资源预算意识必须通过约束传递给它。5.3 提升 AI 生成质量的提示词细节经过大量实测我整理出几个真正有效的提示词习惯。首先是“一次只做一件事”。不要让它“写一个完整的温控系统”而是“先写温度传感器读取模块只在 main 函数预留接口”。一个明确的小任务比十个模糊的大需求可靠得多。其次是“提供接口签名和错误场景”。比如你要生成一个传感器驱动把传感器型号、通信接口、寄存器地址都给它然后明确告诉它“当传感器无响应时返回 -1并在全局错误码里设置对应位”。这样做出来的代码几乎可以直接用。最后是“让它解释再写”。遇到复杂需求可以让 Claude Code 先输出设计方案说明状态划分、边界条件、资源占用预估你确认无误后再让它动手。我测试过在生成几千行代码之前先做方案讨论能减少至少两轮返工。这个习惯对嵌入式这种“改代码容易上板跑联调难”的开发场景尤其值得培养。我个人现在的体会是Claude Code 不是替代嵌入式工程师的外挂而是一个工作效率放大器。它把从空文件到核心逻辑成型的时间大幅压缩但你得具备判断它输出是否合理的能力。关键的状态机设计、硬件时序、资源预算这些环节最终还是需要你去把关。用它来生成初稿、做代码审查、处理重复移植再把自己的硬件经验反复喂给它这个工具会越用越顺手。