这次项目我想验证一件事:嵌入式项目能不能“全程 AI 开发”。

答案是可以,但前提不是把一句需求直接丢给模型,然后等它自由发挥;真正有效的做法,是先把工程边界、协议契约、硬件真相和调试流程固化成 skill 与设计文档,再让 Claude Opus 4.7 和 GPT-5.4 在这个边界内持续产出。

这个项目最终落地的是一套双模智能家居网关:

  • STM32F103RCT6 作为主控网关
  • CC2530F256 作为 Zigbee 协调器
  • Zigbee 节点 1 负责温湿度采集与蜂鸣器/LED 控制
  • Zigbee 节点 2 负责光照采集与蜂鸣器/LED 控制
  • 网关通过 WiFi 把数据转成 JSON 上报给 PC
  • 网关本地 OLED 显示网络状态、节点状态和告警状态
  • 当光照过低、温湿度越限时,本地自动联动,不依赖上位机在线

如果只看功能,这就是一个典型的“STM32 + Zigbee + WiFi + 本地显示 + 自动控制”的课程设计升级版。但这次更有意思的地方,不是功能本身,而是整个开发过程几乎完全由 Claude Opus 4.7 和 GPT-5.4 驱动完成。

先写代码,还是先写 skill?

我这次最大的体会是:AI 时代的嵌入式开发,第一份核心资产不是 main.c,而是 skill

这个项目里,skill 不是一句泛泛的提示词,而是一份可复用的工程操作系统。它把常见动作拆成了明确的能力模块,比如:

  • build-keil
  • build-iar
  • flash-keil
  • serial-monitor
  • peripheral-driver
  • stm32-hal-development
  • workflow

更关键的是,它还定义了四个非常实用的共享约定:

  • Project Profile
  • Skill Handoff Contract
  • Command Outcome Schema
  • Failure Taxonomy

这四个约定听起来有点“方法论”,但它们在实际开发里非常重要。因为 AI 最怕的不是“不会写代码”,而是上下文漂移:上一轮还在改协议,下一轮已经忘了串口帧格式;刚修好 STM32,下一轮又把 CC2530 侧的字段长度搞错。

有了 skill 之后,模型收到的不是一句“帮我做 Zigbee 网关”,而是一整套被结构化过的上下文:

  • 当前项目是什么
  • 用什么工具链构建
  • 产物在哪里
  • 哪些错误是可恢复的
  • 哪些失败意味着要切换动作
  • 上一个 skill 的输出,如何直接变成下一个 skill 的输入

说白了,skill 做的事情,就是把“会做项目的人脑内经验”外置成 AI 可以稳定继承的工作流。

Skill 真正接管的,不是写代码,而是嵌入式最难的后半段

如果只把 skill 理解成“帮 AI 多记几个命令”,那其实低估了它。

传统 coding 在嵌入式里当然重要,但它通常不是最难的部分。最难的部分往往发生在代码写完之后:

  • 这个工程到底该用 Keil 还是 IAR,目标工程文件是哪一个
  • 编译产物到底是 hexaxf 还是 d51,路径在哪里
  • 报错到底是语法错、链接错、启动文件错,还是芯片配置错
  • 烧录完板子没反应,是代码逻辑问题,还是串口没起来,还是模块没复位
  • 串口看到的日志,到底是在证明系统正常,还是在暴露协议没对齐

这些问题,才是嵌入式开发最消耗经验、也最容易让 AI 失控的地方。也是这次 skill.md 真正发挥价值的地方。

1. Skill 先解决“能不能构建”,而不是“代码看起来对不对”

这个项目本身就是双工具链:

  • STM32 侧是 xxxx.uvprojx
  • CC2530 协调器侧是 xxxx.ewp

对应的产物也完全不同:

  • STM32 会生成 xxxx.axfxxxx.hex
  • CC2530 会生成 xxxx.d51

这恰好说明了 skill 的第一层作用:它不是让模型“会写 C”,而是让模型知道怎么在真实工具链里把代码变成可执行产物。

build-keilbuild-iar 这种 skill,真正解决的是这些问题:

  • 读取工程文件,而不是让模型猜“主工程大概在哪”
  • 用非交互方式调用编译器,而不是依赖 IDE 手动点按钮
  • 自动定位产物和 build log,而不是人工翻目录找文件
  • 把编译失败归类成可修复问题,而不是一句“编译失败了”

对于嵌入式项目,这比单纯写函数重要得多。因为很多代码“看起来像对的”,但只有在真实工程里通过编译、链接、产物生成之后,才算真正迈过第一道门槛。

2. Skill 解决的是验证闭环,不是单次生成

AI 写一个 router.cautomation.c 并不难,难的是写完之后能不能快速进入“生成 -> 编译 -> 修正 -> 再验证”的闭环。

这时候 workflow 和共享约定的价值就出来了。

Project Profile 会告诉模型:

  • 这个仓库有 STM32 和 CC2530 两个固件面
  • 两边工具链不同
  • 两边产物不同
  • 两边验证动作不同

Command Outcome Schema 会告诉模型:

  • 这次构建成功没有
  • 产物路径是什么
  • 失败发生在哪个阶段
  • 下一步应该继续烧录、抓串口,还是先修编译错误

Failure Taxonomy 会进一步把失败拆成工程能处理的类型,比如:

  • 工具链缺失
  • 工程配置错误
  • 产物未生成
  • 串口阻塞
  • 协议联调失败

这样 AI 就不再只是“写了一版代码”,而是能够在失败后选择正确动作。嵌入式开发真正难的地方,从来不是某一轮生成,而是连续十几轮验证都不丢上下文。

3. 串口 skill 解决的不是“看日志”,而是“拿到系统真相”

嵌入式里最有价值的验证入口,经常不是单元测试,而是串口。

这次项目里,STM32 和 CC2530 之间靠 UART4 跑二进制帧协议,PC 和网关之间又靠 WiFi 透传 JSON。很多关键问题最后都要回到“线上到底发了什么”:

  • ZB_PING 有没有真正收到响应
  • ALLOW_JOIN 是不是只发了命令,但协调器其实没进窗口
  • Node1 的温湿度上报有没有进到 router_on_zb_frame()
  • Node2 的光照联动为什么没触发,是阈值没过,还是串口帧没过
  • OLED 显示和 PC 看到的状态为什么不一致

serial-monitor 这种 skill 的价值,不是简单打开串口工具,而是把串口验证标准化:

  • 选择正确串口
  • 固定波特率和抓取方式
  • 持续采集而不是肉眼盯屏
  • 把启动日志、心跳、异常帧和状态跳变关联起来

这就是为什么我会说,skill 更像嵌入式开发的“验证层”而不是“代码补全层”。在桌面软件里,日志通常只是辅助;但在嵌入式里,串口经常就是系统事实本身。

4. 真正让 AI 好用的,是把“编译、烧录、串口验证”串成流水线

单个动作并不难,难的是把动作串起来。

如果没有 skill,AI 很容易停在这样一个状态:

  • 代码改完了
  • 语法看起来没问题
  • 解释也说得通
  • 但没有确认产物是否真的生成
  • 没有确认板子是否真的跑起来
  • 没有确认串口和协议是否真的对上

而 skill 的意义,就是把这条链闭合:

build-keil/build-iar -> 定位产物 -> 烧录 -> serial-monitor -> 判断是否进入下一轮修改

这条链一旦建立,AI 在嵌入式项目里的角色就变了。它不再只是传统意义上的 coding assistant,而是开始接手原本最依赖人工经验的那部分工程工作:构建验证、运行态观测、联调闭环和失败后的动作切换。

项目怎么拆,AI 才不会失控

这个项目的系统结构其实很清晰:

PC <-> WiFi/TCP/JSON <-> STM32 网关 <-> UART 二进制帧 <-> CC2530 协调器 <-> Zigbee 节点

其中最关键的不是某个外设驱动,而是边界定义。

1. STM32 不直接碰 Zigbee 细节,只做网关和业务路由

router.c 是整个网关侧的中心。它负责两件事:

  • 把 CC2530 发来的 ZB_REPORTZB_NODE_INFOZB_NET_STATUS 解析成 PC 可读的 JSON
  • 把 PC 下发的 cmdset_thresholdallow_join 等 JSON 指令翻译成发给 CC2530 的串口帧

这个设计很重要,因为它把“无线协议”和“业务协议”隔开了。

对上位机来说,它永远只需要关心:

{"t":"cmd","seq":1,"node":1,"target":"led","op":"on"}

对 CC2530 来说,它只需要处理统一的二进制帧协议:

AA 55 LEN CMD PAYLOAD CRC 0D

这样一来,STM32 就像一个翻译层:上面说 JSON,下面说 Zigbee 控制帧,双方都不需要知道对方内部实现。

2. 协议必须双端同源,不要让 AI “各写各的”

嵌入式项目里最容易被 AI 写崩的,往往不是业务逻辑,而是协议细节。

这个项目里最稳的一步,是把帧协议在 STM32 和 CC2530 两端做成近乎同源的实现:

  • STM32侧的frame.c
  • CC2530侧的frame.c

两边都使用同样的:

  • AA 55 作为帧头
  • LEN 表示 CMD + PAYLOAD
  • CRC8-ITU 做校验
  • 0x0D 作为帧尾
  • 字节间隔超过 20ms 就重置解析状态机

这是一个特别典型的 AI 工程化经验:协议代码不要只在一端“看起来对”,而要让两端尽可能共享同一套结构和语义。否则模型一旦在某一边把长度、大小端或者 CRC 范围改偏,问题会非常难查。

3. 自动联动逻辑不要写成 if-else 堆,要写成状态机

automation.c 是我很喜欢的一块。它没有把“光照低就开灯”“温湿度超限就报警”粗暴地写成一串条件判断,而是做成了带防抖、滞回和人工覆盖窗口的状态机。

这里面有几个很实用的工程细节:

  • 光照联动带 hysteresis,避免临界值反复抖动
  • 温湿度告警要持续满足一段时间才触发,避免瞬时误报
  • 手动控制后,自动联动会进入 override 窗口,避免 AI 写出来的自动控制和人工操作互相打架
  • 节点离线或数据过期时,联动逻辑自动撤销,不会保持错误状态

这其实就是嵌入式项目里最典型的“看起来简单,写不好会很烦”的部分。GPT-5.4 在这类跨文件、偏实现层的重构上推进很快,而 Opus 4.7 更适合先把规则和边界定义清楚,再把逻辑压实。

4. 持久化和本地显示,决定了它是不是“产品”而不是“实验”

很多课程项目做到传感器上报、远程控制就收工了,但这个项目又往前走了一步。

thresholds.c 把联动阈值持久化到了 Flash,保存结构里还带了 magicversionCRC32。这意味着:

  • 参数可以在线修改
  • 重启后不会丢
  • 读到坏数据时能自动回退默认值

gw_main.coled_view.c 则把网关状态、节点在线情况、最近传感器值和告警状态统一收敛到本地 OLED 上。这样即使 PC 不在线,网关本身依然是可观测的。

这一步的意义其实不小:它让系统从“演示链路通了”变成“本地也能独立工作”。

CC2530 这一侧,AI 最容易写错什么

如果说 STM32 这边更像业务网关,那么 CC2530 这边更像一个受约束很强的协议桥。

对应的几个核心文件分别是:

  • gw_coord.c:接收 Zigbee AF/ZCL 数据,转成串口帧上送 STM32
  • uart_link.c:串口收发、环形缓冲、帧解析
  • zb_net.c:节点表、入网窗口、在线状态管理
  • cmd_map.c:把 STM32 下发的串口命令转成 Zigbee 行为

这部分是最考验 AI 是否真的理解嵌入式约束的地方。因为在 CC2530/8051 这种平台上,很多“桌面开发里无所谓”的写法都会变成隐患,比如:

  • 在中断里做太多工作
  • 想当然地动态分配内存
  • 对 buffer 长度不敏感
  • 忽略状态机超时
  • 把协议栈和业务层耦合得过紧

这次项目里比较稳的做法是:

  • UART 接收中断只做搬运和置位,不在 ISR 里做复杂解析
  • 帧解析放到任务上下文执行
  • 节点表静态分配
  • 允许入网窗口和节点在线状态都通过 tick 驱动维护

比如 zb_net.c 里做了一个很实用的细节:当协调器已经建网但还没有任何节点加入时,会自动周期性重新打开入网窗口。这种设计特别适合真实调试环境,因为板子上电顺序经常不是理想化的,节点慢几秒进网是很常见的事情。这个补偿机制一加,整套系统的“可调通性”会高很多。

Claude 和 GPT,分别适合干什么

Opus-4.7 更适合

  • 把需求压成一份完整设计文档
  • 统一协议、引脚、状态机和边界条件
  • 做长上下文的一致性维护
  • 帮你判断“这是不是一个合理的分层”

gateway_design_spec.mdCC2530.md 这种“既是设计文档,又是 AI 的系统约束”的内容,非常适合交给 Claude 先打底。

GPT-5.4 更适合

  • 快速把设计拆成代码文件
  • 在多个 C 文件之间持续推进实现
  • 根据现有工程结构补代码、修补丁、对齐细节
  • 把文档约束落成实际函数、结构体和状态机

如果说 Opus-4.7 更像“架构和约束的生成器”,那 GPT-5.4 更像“推进实现的主力编码器”。

真正效率高的方式,不是谁替代谁,而是两者分工明确:

  • Claude 先把边界钉死
  • GPT 再沿着边界持续出代码
  • Skill 负责把构建、烧录、监控、调试这些动作串起来

这次实践里,我得到的几个结论

第一,嵌入式项目并不适合“裸奔式 vibe coding”

网页前端可以边跑边看,错了立刻改;但嵌入式项目一旦涉及串口协议、Zigbee、Flash 持久化、状态机和中断边界,AI 只要有一处理解漂移,代价就会迅速上升。

所以真正有效的不是“让 AI 更自由”,而是“给 AI 更硬的工程边界”。

第二,skill 的价值不在于多会写 C,而在于把验证上下文继承下去

很多人理解 skill,会把它看成“构建脚本索引”。

但这次做完之后我更确定:skill 真正的价值,是把工程上下文变成可传递对象。它让一次构建失败、一次串口抓日志、一次烧录动作,不再是孤立事件,而是可以被下一个动作直接理解和继承的状态。对嵌入式来说,这种“验证上下文继承”比单纯多写几百行代码更重要。

这对 AI 编程尤其关键。

第三,最该先文档化的,不是接口说明,而是 Ground Truth

项目里最有价值的文档不是“功能介绍”,而是像 gateway_design_spec.mdCC2530.md 这种带强约束性质的文档。

因为它们定义的不是“我们想做什么”,而是:

  • 哪些引脚是真相
  • 哪些协议字段不能乱改
  • 哪些行为必须双端一致
  • 哪些代码模式在这个平台上禁止出现

这类 Ground Truth 一旦写清楚,AI 的表现会稳定很多。

最后

这次项目让我对“AI 能不能做嵌入式”这件事有了更具体的答案。

能做,而且能做得很快;但前提不是把工程交给模型自由生成,而是把人的经验先固化成 skill、契约和设计文档。真正让开发效率提升的,不是一句更聪明的 prompt,而是一整套能约束 AI、复用 AI、交接 AI 的工程机制。

从这个角度看,skill 不是开发的附件,它本身就是开发的一部分。

如果要我用一句话总结这次实践,那就是:

不是 Opus-4.7 和 GPT-5.4 把项目“写出来”了,而是它们在 skill 和文档约束下,把一个原本高度依赖人工经验的嵌入式开发流程,尤其是最难标准化的编译、验证、串口联调和失败恢复,第一次变成了可以稳定复用的 AI 工作流。

吐槽

嵌入式开发真的很烧token(1天蹬了快3E的input/output,小4E的cached tokens,这还是我非常高的命中率情况下),总费用小300刀,但是在套workflow下vibe coding是真的爽。

附录

技能列表

技能说明
build-cmake配置并构建基于 CMake 的 MCU 固件工程
build-keil配置并构建基于 Keil MDK 的固件工程
build-iar配置并构建基于 IAR EWARM 的固件工程
build-platformio配置并构建基于 PlatformIO 的固件工程
flash-keil通过 Keil MDK 内置调试器烧录固件
flash-openocd通过 OpenOCD 烧录 ELF/HEX/BIN 产物
flash-platformio通过 PlatformIO 上传机制烧录固件
debug-gdb-openocd通过 OpenOCD 附着 GDB,支持下载后调试、仅附着和崩溃现场排查
debug-platformio通过 PlatformIO 内置 GDB 调试
serial-monitor选择串口并抓取运行日志
modbus-debugModbus RTU/TCP 寄存器读写、从站扫描和持续监控
can-debugCAN 总线帧监听、发送和节点扫描
visa-debugVISA 仪器 SCPI 通信、波形捕获和截图
peripheral-driver搜索并适配开源 BSP 外设驱动到目标工程
stm32-hal-developmentSTM32 HAL 库开发指导与最佳实践
workflow串联多个 skill 的流水线编排(编译+烧录+监控/调试)

仓库结构

.
├── skills/                     # 技能模块
│   ├── build-cmake/            # CMake 构建
│   ├── build-keil/             # Keil 构建
│   ├── build-iar/              # IAR 构建
│   ├── build-platformio/       # PlatformIO 构建
│   ├── flash-keil/             # Keil 烧录
│   ├── flash-openocd/          # OpenOCD 烧录
│   ├── flash-platformio/       # PlatformIO 烧录
│   ├── debug-gdb-openocd/      # GDB 调试
│   ├── debug-platformio/       # PlatformIO 调试
│   ├── serial-monitor/         # 串口监视
│   ├── modbus-debug/           # Modbus 调试
│   ├── can-debug/              # CAN 总线调试
│   ├── visa-debug/             # VISA 仪器调试
│   ├── peripheral-driver/      # 外设驱动适配
│   ├── stm32-hal-development/  # STM32 HAL 开发
│   └── workflow/               # 流水线编排
├── shared/                     # 共享约定
│   ├── contracts.md            # 上下文交接合约
│   ├── failure-taxonomy.md     # 失败分类
│   ├── platform-compatibility.md
│   └── references/
├── templates/                  # Skill 模板
│   └── skill-template/
└── scripts/
    ├── validate_repo.py        # 结构校验
    └── em_config.py            # 工具路径配置 CLI

共享约定

所有 skill 围绕同一套核心上下文进行输入与输出:

  • Project Profile — 工作区、目标、构建系统、探针和产物的标准化元数据
  • Skill Handoff Contract — 下游 skill 可直接继承的上下文
  • Command Outcome Schema — 成功、失败或阻塞结果的统一格式
  • Failure Taxonomy — 标准失败分类及推荐后续动作