用 Cursor / Claude 写嵌入式代码:提示词与上下文技巧
- 说清嵌入式代码相比普通代码对 AI 提问的特殊要求
- 套用一份提示词配方,让 AI 产出完整可编译的固件
- 用迭代技巧把残缺代码逐步逼成可用代码
凌晨一点,你让 Cursor「写个 ESP32 读温度的代码」,它刷刷给了三十行,复制进 Arduino IDE,编译居然过了。你接上板子一烧,串口窗口要么一片空白,要么疯狂刷 nan,要么板子直接进了重启循环。AI 给的代码不是错的——它引用了一个并不存在的库函数,或者用了一个跟你板子引脚对不上的 I2C 地址。它看起来对,但它没在你的硬件上跑过,它也跑不了。
这就是嵌入式领域用 AI 写代码最大的坑:编译通过 ≠ 能跑。普通的 Web 后端代码写错了,跑起来会报错给你看;嵌入式代码写错了,往往是硬件层面悄无声息地不工作,你连个像样的报错都拿不到。所以让 AI 写嵌入式代码,对「怎么问」的要求比写普通代码高得多。
这篇就讲一套能反复用的提问配方。开始前,如果你还没给 AI 配好工作上下文(板型、库、芯片手册这些喂给它的背景材料),先看 aiwf-context-setup,那是这篇的地基。对 AI 编程整体还陌生的,回 /guide/l1-ai-coding/ 补一下。
为什么嵌入式代码对 AI 提问要求更高
先说清楚嵌入式代码跟你平时写的网页、脚本到底差在哪,这决定了你该怎么喂信息给 AI。
它直接操作硬件。 一行 digitalWrite(2, HIGH) 背后是真的有一个引脚被拉到高电平。引脚号写错了,不会报错,只是接在那个引脚上的 LED 不亮。AI 不知道你这块板子的 GPIO2 是不是接了灯,除非你告诉它。
它对时序敏感。 I2C、SPI 这些总线有自己的时序要求,传感器上电后要等几毫秒才能读。AI 容易漏掉这种「等一下」的细节,给出的代码一上来就读,读到的全是垃圾值。
它不能轻易 print 调试。 你写 Python 卡住了,到处加 print 就能定位。嵌入式上你可能连串口都还没初始化好,或者一加打印就改变了时序,bug 反而消失了。调试成本高,所以更要一次问对。
它资源受限。 一块 ESP32 的内存以 KB 计。AI 写惯了服务器代码,张口就是大数组、字符串拼接、动态分配,在单片机上轻则卡顿,重则栈溢出重启。
把这四点记住,你就明白:给 AI 的信息越具体,它踩坑的概率越低。
一套能反复用的提示词配方
别再说「帮我写个读温度的代码」了。一个能用的提示词应该有四块:目标 + 硬件上下文 + 约束 + 期望输出。
- 目标:要做什么,越具体越好。不是「读温度」,而是「用 I2C 读 BMP280 的温度,每秒一次,打到串口」。
- 硬件上下文:板型、芯片、引脚、用哪个库。「ESP32 DevKitC,BMP280 接 I2C,SDA=21 SCL=22,地址 0x76,用 Adafruit_BMP280 库」。
- 约束:框架和工程要求。「Arduino 框架,循环里不要用 delay 阻塞,加上传感器初始化失败的错误处理」。
- 期望输出:你想拿到什么形态。「完整可编译的 .ino,关键行加中文注释,最后附接线说明」。
坏 prompt vs 好 prompt 的产出对照
坏 prompt:「ESP32 读温度代码」
你大概率会拿到这种东西——能编译,但充满假设:
#include <BMP280.h> // 库名是猜的,未必存在
BMP280 bmp;
void setup() {
Serial.begin(115200);
bmp.begin(); // 不检查是否成功,传感器没接也不报
}
void loop() {
Serial.println(bmp.readTemperature());
delay(1000); // 用 delay 阻塞,后面想加别的事就卡死
}
引脚没指定、地址没指定、初始化不检查、库名靠猜。烧进去八成读出 nan,而你完全不知道为什么。
好 prompt:
我用 ESP32 DevKitC,外接一颗 BMP280,I2C 接法,SDA=GPIO21,SCL=GPIO22,I2C 地址 0x76。请用 Arduino 框架和 Adafruit_BMP280 库写代码:每秒读一次温度打到串口(波特率 115200)。要求:loop 里不要用 delay 阻塞,用 millis 做非阻塞定时;传感器初始化失败要在串口打印明确错误并停住;关键行加中文注释;最后附上接线说明和需要装的库。
这种 prompt 该产出的骨架是这样的:
#include <Wire.h>
#include <Adafruit_BMP280.h>
Adafruit_BMP280 bmp; // 使用 Adafruit 官方库
const uint8_t BMP280_ADDR = 0x76;
unsigned long lastRead = 0;
const unsigned long INTERVAL = 1000; // 非阻塞定时间隔
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // 显式指定 SDA/SCL,对上你的接线
if (!bmp.begin(BMP280_ADDR)) {
Serial.println("BMP280 初始化失败:检查接线和 I2C 地址");
while (1) delay(10); // 失败就停住,不往下跑
}
}
void loop() {
unsigned long now = millis();
if (now - lastRead >= INTERVAL) { // 非阻塞,不卡 loop
lastRead = now;
Serial.print("温度: ");
Serial.print(bmp.readTemperature());
Serial.println(" °C");
}
}
差别一目了然:引脚对得上、地址明确、初始化检查、非阻塞、有错误提示。这才是能直接烧进去跑的代码。多花二十秒把 prompt 写清楚,省下的是半小时对着空白串口抓狂。
让产出更稳的迭代技巧
一次问对很难,所以更现实的做法是分步逼近:
- 先要方案再要代码。先问「读 BMP280 有几种接法,各有什么坑,推荐哪种」,看 AI 给的思路对不对,再让它落地成代码。方案错了,代码再漂亮也白搭。
- 分函数逐个实现。复杂功能别让它一口气写完。先让它写「初始化传感器」一个函数,跑通了,再写「读取并打印」。一次只验一件事,bug 好定位。
- 把编译错误贴回去。编译报错别自己硬啃,整段贴回给 AI:「编译报这个错:xxx,帮我改」。它修错的能力比从零写强。这部分调试套路单独讲,见 aiwf-debug。
- 让它逐行解释。代码能跑了但你看不懂,是隐患。让 AI 解释每行在干嘛,尤其是寄存器操作和位运算那些。怎么系统地读懂 AI 给的代码,见 /guide/l1-read-code/。
什么时候别用 AI
有个反直觉的判断:当你自己都说不清需求时,别开 AI。
如果你连「要读什么、多久读一次、读到了干嘛」都讲不明白,AI 只会把你的模糊放大成一堆看着像那么回事、实则跑不通的代码,你还得花更多时间去甄别它哪里错了。这种时候先关掉编辑器,拿张纸把需求和接线画清楚,甚至先翻翻芯片手册(见 aiwf-datasheet-wiring)。需求清楚了,再回来问 AI,效率天差地别。
我的观点很明确:AI 是个高级实习生,不是甩手对象。 它打字快、知识广、不喊累,但它没碰过你的板子,不知道你的引脚怎么接,也不会对结果负责。你的活是「出题」和「验收」——题出得清楚,它就干得漂亮;你不验收直接烧板,出了事是你的板子冒烟,不是它的。会用 AI 的人不是会聊天的人,是会出题的人。
你应该看到什么
把上面那个好 prompt 喂给 Cursor 或 Claude,理想情况下你会看到:
- 代码里显式指定了引脚和 I2C 地址,跟你说的接线对得上,而不是用库的默认值。
- 有一段初始化失败的处理,传感器没接好时串口会打印明确提示,而不是闷头读
nan。 - loop 里用
millis()做非阻塞定时,没有delay(1000)把整个循环卡住。 - 附了接线说明和需要装的库,你照着接、照着装就能复现。
复制进 IDE,编译通过,烧录,串口每秒稳定打出一个合理的温度值。这就是「结构化提问」该有的回报:拿到的不是玩具,是能跑的东西。
故障与质量排查表
AI 给的嵌入式代码出问题,多半逃不出这几类。对照着排查:
| 现象 | 多半是什么原因 | 怎么办 |
|---|---|---|
编译就报错,xxx not declared |
AI 用了不存在的库或函数,凭印象编的 | 把报错贴回去,并补一句「我装的是 Adafruit_BMP280,请只用这个库的 API」 |
| 编译过但烧进去没反应 | 引脚/地址跟实际接线对不上 | 在 prompt 里写死真实引脚号和 I2C 地址,让它别用默认值 |
| loop 卡死、别的功能加不进去 | 用了 delay() 阻塞 |
要求改成 millis() 非阻塞定时 |
读出 nan 或乱码,不报错 |
没做初始化检查,传感器没就绪就读 | 要求加 if (!sensor.begin()) 错误处理并停住 |
| 跑一会儿就重启 | 内存/栈溢出,常见于大数组、频繁字符串拼接 | 让它用定长缓冲、避免 String 频繁拼接,明确「这是单片机,内存只有几十 KB」 |
| 数值能读但明显不对 | 时序漏了,上电后没等传感器稳定 | 提醒它「传感器上电后需要等待 xx ms 再读」 |
两个变体玩法
变体一:让 AI 写状态机。 嵌入式里很多逻辑本质是状态机——比如按钮长按短按、设备的「待机/工作/报警」切换。直接问「帮我写按钮逻辑」会得到一堆缠在一起的 if。换个问法:「用状态机实现,状态有 IDLE / PRESSED / LONG_PRESS,请先列状态转移表再写代码」。先要表,能让它(和你)把逻辑理清,代码也更好维护。
变体二:让 AI 重构既有代码。 你手上有一段能跑但乱的代码,可以让 AI 整理:「这段代码能跑,但 loop 里全是 delay,帮我改成非阻塞,并把读传感器和打印拆成两个函数,行为保持不变」。重构时一定强调「行为保持不变」,并自己烧一遍验证——AI 重构时偶尔会顺手「优化」掉你依赖的副作用。
动手挑战
用本篇的配方,让 AI 写一个带去抖的按键计数器:
- 目标:一个按键接在某个 GPIO,每按一次计数加一,并把当前计数打到串口。
- 硬件上下文:写清你的板型、按键接哪个引脚、是上拉还是下拉。
- 约束:要做软件去抖(按一下别因为抖动算成好几次),loop 用非阻塞,别用
delay去抖。 - 期望输出:完整可编译,关键行注释,附接线说明。
写完别急着满意,按这篇的迭代技巧追问:让它解释去抖那几行的逻辑;故意把按键接错,看错误处理够不够;再让它把计数逻辑拆成单独函数。不知道怎么从零搭一个按键工程的,可以先做一遍 /guide/l2-button/ 打底,再回来用 AI 加速。
小结
嵌入式代码对 AI 提问的要求高,是因为它直接碰硬件、对时序敏感、难调试、资源紧。应对的办法不复杂:用「目标 + 硬件上下文 + 约束 + 期望输出」四块写清提示词,先要方案再要代码、分函数实现、把报错贴回去、让它逐行解释。记住 AI 是高级实习生,你负责出题和验收,板子冒烟的是你不是它。
需求讲不清的时候,最好的提速不是问 AI,而是先把芯片手册和接线搞明白。下一步就去 aiwf-datasheet-wiring,学会读手册、对引脚,给 AI 喂出真正靠谱的硬件上下文。想看更多 AI 工作流的文章,回 /aiworkflow/ 这个汇总页。