
那天晚上我盯着终端发呆,突然想:这都2026年了,嵌入式开发还是这副德性?
第二天,我了解到 Rust 在 ESP32 上的支持已经相当成熟了。做了个测试,感觉比我预期的好太多。
ESP32 不是什么都能干。它的双核 Xtensa 跑到 240MHz,SRAM 只有 520KB。你指望在上面跑 LLaMA,那属于想多了。
但它能干的事情其实不少:
唤醒词检测(比如"嘿小X"),传感器数据的异常检测,简单的图像分类(配合 ESP32-CAM),还有最实际的——做一个物理接口,把传感器数据和云端 AI API 串起来。
我最终做的东西不复杂:一个带麦克风的小盒子,本地做关键词检测,听到唤醒词后录音,发到云端做语音识别,再调大模型 API 返回结果。说白了就是个自制的小爱同学,但你可以完全控制数据的流向。
用 Rust 写这个,花了大概三天。同样的功能用 C 写,我估计得一周,而且大概率会有内存问题。
先说工具链。你需要装这些东西:
# 装 Rust 不多说了
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# esp-rs 的工具链管理器
cargo install espup
espup install
# 项目生成器
cargo install esp-generate装完之后,创建项目:
esp-generate --chip esp32 my-ai-device它会问你几个问题,选 std 模式(基于 ESP-IDF)还是 no_std(裸机)。如果你要用 WiFi 和各种外设,选 std。如果你要做极致优化、抠每一字节内存,选 no_std。
我选的 std,因为要联网调 API,没必要跟自己过不去。
项目结构长这样:
my-ai-device/
├── Cargo.toml
├── src/
│ └── main.rs
├── .cargo/
│ └── config.toml
└── sdkconfig.defaults嵌入式老规矩,先点灯,确认工具链没问题:
use esp_idf_svc::hal::delay::FreeRtos;
use esp_idf_svc::hal::gpio::*;
use esp_idf_svc::hal::peripherals::Peripherals;
fn main() {
// 初始化 ESP-IDF(必须)
esp_idf_svc::sys::link_patches();
let peripherals = Peripherals::take().unwrap();
let mut led = PinDriver::output(peripherals.pins.gpio2).unwrap();
loop {
led.set_high().unwrap();
FreeRtos::delay_ms(500);
led.set_low().unwrap();
FreeRtos::delay_ms(500);
}
}编译烧录一行命令搞定:
cargo run灯亮了,开始干正事。
ESP32 本地跑 AI,目前最现实的方案是 TensorFlow Lite Micro。Rust 这边可以通过 C FFI 调用,也有一些社区封装。
唤醒词检测我用的是 Google 的 MicroNet 模型,量化后大概 18KB,ESP32 跑起来帧率还行。核心逻辑就几步:
// 采集音频 -> 特征提取 -> 模型推理 -> 判断是否命中唤醒词
fn detect_wake_word(audio_buffer: &[i16], model: &mut TfLiteInterpreter) -> bool {
// 提取 MFCC 特征
let features = extract_mfcc(audio_buffer);
// 喂给 TFLite Micro 模型
model.set_input(&features);
model.invoke();
// 输出是一个概率值,0 到 1 之间
let confidence = model.get_output()[0];
confidence > 0.85
}实际代码比这个复杂得多,要处理环形缓冲区、采样率转换、特征窗口滑动这些细节。但核心就是这几步。
我测了一下,从音频进来到判断完成,大概 30ms 延迟。够用了。
检测到唤醒词后,开始录音,5 秒后发到云端:
use esp_idf_svc::http::client::*;
use std::io::Read;
fn send_audio_to_cloud(audio: &[u8]) -> anyhow::Result<String> {
let url = "https://your-api.com/transcribe";
let client = HttpClient::new()?;
let mut req = client.post(url)?;
req.header("Content-Type", "audio/wav")?;
req.header("Authorization", "Bearer YOUR_TOKEN")?;
let mut resp = req.send(audio)?;
let mut buf = String::new();
resp.read_to_string(&mut buf)?;
Ok(buf)
}拿到识别结果后,再调一轮大模型 API,把回复通过扬声器播出来。整个链路大概 2 到 3 秒,大部分时间花在云端。本地那 30ms 几乎可以忽略。
说点实在的,不是所有东西都顺。
内存是真紧张。 520KB SRAM,TFLite 模型占一点,音频缓冲区占一点,HTTP 客户端再占一点,留给你的空间没多少。我有一版代码音频缓冲区开大了,直接栈溢出,板子重启了。后来改用 PSRAM(外挂的 4MB 伪静态内存)才解决。如果你也遇到莫名其妙的重启,先检查内存。
no_std 生态还有缺口。 如果你选了裸机模式,很多 std 库的东西用不了,一些 crate 也不支持 no_std。我做音频处理的时候想用 rustfft,结果它不支持 no_std,最后自己手写了个简单的 DFT。不难,但浪费时间。
调试体验一般。 ESP32 的调试需要 JTAG,而且 Rust 的调试符号有时候对不上。大部分时候我还是靠日志输出排查问题,跟写 C 时候差不多。这点上 Rust 并没有带来明显改善。
编译慢。 第一次编译 esp-idf-svc 的时候,我等了快十分钟。后面增量编译快了,但第一次确实有点劝退。如果你网络不好,下载 ESP-IDF 的子模块也可能卡很久。
说几个我自己的判断。
如果你只是做原型验证,C/C++ 其实也够了。ESP-IDF 的文档和示例都是 C 的,社区更大,遇到问题搜得到答案。Rust 这边的资料还在积累中,有些边角问题你得自己啃源码。
但如果你打算做一个长期维护的项目,或者固件逻辑比较复杂(状态机多、并发多),Rust 的优势就出来了。编译器帮你挡掉了一大类内存安全问题,这一点在嵌入式上太重要了。嵌入式的 bug 不好调,因为你看不到崩溃日志,板子可能直接死给你看。Rust 让你在编译阶段就消灭大部分这类问题。
另外 esp-rs 的文档质量不错,esp-idf-svc 封装得很到位,基本上 ESP-IDF 能干的事情它都能干。如果你已经会写 Rust,上手成本不高。
我的建议是:如果你会 Rust 并且又对硬件感兴趣,可以花个周末试一下。ESP32 开发板三十来块,Rust 工具链免费,试错成本很低。万一你有了创意,可能会喜欢上这条路呢。