【免费下载链接】tiny-engineer
A small desktop robot that brings an AI coding assistant to life, physically acting out when it reads files, thinks, writes code, and finishes work.
Tiny Engineer 是一个桌面 AI 机器人,通过 ESP32-C3 固件驱动舵机、OLED 表情屏和蜂鸣器,把 Claude Code、Cursor 等编码助手的工作状态变成看得见的动作。本文完整拆解 Tiny Engineer 固件架构:如何用 PlatformIO + Arduino 框架构建 ESP32-C3 固件,又如何把 Vite 打包的 Web UI 压缩嵌入 Flash,一次pio run搞定应用、网页和音频资产的全部烧录。
一、先看整体:固件在系统里的位置
Tiny Engineer 的工作链路是:AI Agent(或 hook 脚本)通过 Wi-Fi HTTP 或 USB 串口发送姿态指令 → ESP32-C3 固件解析后驱动 5 个 SG90 舵机、SSD1306 OLED 表情屏、MAX98357A I2S 功放 → 机器人"读文件时张望、写代码时打字、完成时敲铃"。
硬件上默认使用一块自研主控板,把 ESP32-C3-Zero、PCA9685 舵机驱动、OLED 和功放插座集成在一起,免去飞线焊接:
主控板实物与在 3D 打印躯干中的安装位置如下:
引脚级接线关系(PCA9685 走 I2C、OLED 走 I2C、MAX98357A 走 I2S)见官方原理图:Tiny Engineer.drawio.png。
二、固件构建系统:PlatformIO + Arduino 框架
构建配置集中在 platformio.ini,核心环境esp32-c3-devkitm-1只有几个关键决策:
- 板级目标:
board = esp32-c3-devkitm-1(仅是构建目标名,实际硬件是主控板上的 C3-Zero 模块)。 - 框架:
framework = arduino,配合 pioarduino 维护的 Arduino-ESP32 3.x 平台,以使用ESP_I2S和rgbLedWrite等新版 API。 - 依赖(
lib_deps):Adafruit PWM Servo Driver(驱动 PCA9685)、Adafruit GFX + SSD1306(OLED 表情屏)、ArduinoJson(HTTP JSON 解析)。 - USB CDC:
ARDUINO_USB_CDC_ON_BOOT=1让 USB 直接作为串口,无需额外 UART 桥接芯片。
真正"有意思"的是extra_scripts挂载的四个钩子脚本,它们把 Web UI 嵌入、音频打包、版本烘焙、舵机保护串进了构建流水线:
| 脚本 | 时机 | 作用 |
|---|---|---|
| scripts/firmware_version.py | pre | 把git describe结果编译为FW_VERSION宏 |
| scripts/copy_assets.py | pre | 把 assets/ 的 WAV 音频打包进 LittleFS 镜像 |
| scripts/embed_ui.py | pre | 构建并嵌入 Web UI(下一节详述) |
| scripts/park_servos_before_upload.py | pre | 烧录前调用 CLI 把舵机摆到中间位,避免断电时舵机猛弹 |
另有[env:ota](双 OTA 应用槽,见下文分区表)和[env:native]环境——后者在宿主机用 Unity 框架对serial/、settings/、audio/等纯逻辑模块跑单测,pio test -e native一条命令完成,完全不碰硬件。
三、Web UI 如何"塞进" Flash:单文件 HTML + gzip + 头文件
固件自带一套完整的 Web 管理界面(配置向导、舵机控制、动画测试、API 参考页),源码在 ui/,技术栈是Preact(React 兼容)+ wouter-preact 路由 + TypeScript + Vite。
嵌入流水线分三步,全部由 scripts/embed_ui.py 自动完成:
- Vite 单文件构建:ui/vite.config.ts 中启用了
vite-plugin-singlefile插件——它把 JS、CSS 全部内联进一个dist/index.html。这是嵌入的前提:设备端没有静态文件服务器,页面必须自包含。 - gzip 压缩 + 转字节数组:构建出的 HTML 用
compresslevel=9压缩(固定mtime=0保证相同源码产出字节一致,避免无谓重编译固件),再转写为.pio/build/*/generated/web_ui.h中的const uint8_t kWebUiGzip[]数组。 - 固件侧直接吐出:HTTP 路由表 src/http/routes.cpp 的
handleIndex对所有前端路由(/、/animations、/servo、/config等)返回这个数组,并带上Content-Encoding: gzip头,浏览器自动解压。前端路由全部由 ui/src/App.tsx 的wouter-preactSwitch接管。
几个值得注意的工程细节:
- 脚本会比较
ui/源码与生成头文件的 mtime,UI 没改动时直接跳过 npm 构建,每次pio run不会都跑一遍 Node。 - 开发期用
npm run dev,Vite dev server 会把/health、/settings、/play等 API 正则代理到机器人(TINY_ENGINEER_URL或tiny-engineer.local),页面路由留在本地——开发体验与嵌入形态完全一致。 - 单文件 HTML 体积不小(约 1.38 MB),这正是 partitions.csv 采用"单应用大分区"的原因,见下节。
四、Flash 分区表:4MB 空间怎么分
ESP32-C3 模组是 4MB Flash,分区设计在 partitions.csv,注释写得很直白:双 1.5MB 的 OTA 应用槽装不下固件(光 HTML UI 就 ~1.38MB),所以默认 USB 烧录环境只用一个2.1MB 的app0,外加约 1.81MB 的 LittleFS 给音频资产,coredump分区保底调试:
nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, app0, app, ota_0, 0x10000, 0x210000, spiffs, data, spiffs, 0x220000,0x1D0000, coredump, data, coredump,0x3F0000,0x10000,想要 Wi-Fi 在线升级(OTA)时,用pio run -e ota切到 partitions_ota.csv:牺牲 coredump 分区,改回双 1.5MB 应用槽app0/app1,LittleFS 缩到 960KB,并定义TE_OTA宏由 src/network/ota.cpp 接管升级逻辑。这是"本地 USB 烧录优先、OTA 可选"的务实取舍。
五、LittleFS 音频资产:打包、覆盖与自动上传
铃铛声、欢迎声、报错声等 WAV 不放 Flash 固件里,而是放 LittleFS:
- scripts/copy_assets.py 调用 scripts/audio_pack.py 把 assets/ 下 6 个 WAV 打包成
data/镜像,并按分区表大小校验容量。 - 音频 Mod 机制:
platformio.ini里的custom_audio_mod变量默认为空(用原版音效);填上halloween后,mods/halloween/assets/ 里的同名 WAV 会覆盖原版,未提供的文件仍保留原版——同一个固件,换一套声音皮肤。 - scripts/upload_fs_after_upload.py 注册为
upload的 post 动作:固件烧完后自动再跑pio run -t uploadfs把littlefs.bin刷进去,保证一次pio run -t upload应用和音频资产齐活。若出现"动作正常但不发声",手动补一条pio run -t uploadfs即可。
六、运行时:启动流程与主循环
src/main.cpp 的setup()是一条清晰的初始化流水线,每一步都在 OLED 上显示进度:
- 初始化舵机输出引脚、USB CDC 串口(RX/TX 缓冲扩到 4KB,防止 ~1KB 的 COBS 数据帧打爆默认 256B 队列);
- 读取 NVS 设置(src/settings/);
- I2C 起总线 → 初始化 PCA9685 → 初始化 OLED 表情屏;
- I2S 初始化 MAX98357A 功放(失败会红屏 + RGB 灯闪烁错误码);
initAudioStorage()挂载 LittleFS 音频;- Wi-Fi 连接成功则
startHttpServer(),并startOta()开启在线升级;未配网则进入 AP 配网模式,把/config五步设置向导(舵机校准→OLED 方向→LED→喇叭→连接方式)直接托管给那套嵌入的 Web UI。
loop()只有四个调用,就是固件的全部心跳:
serialLinkPoll(); // USB 串口协议(COBS 分帧,src/serial/) pollHttpServer(); // WebServer 轮询,src/http/ pollOta(); // OTA 下载 tickRobot(); // 动画状态机:舵机插值、表情、RGBHTTP 层用WebServer监听 80 端口,src/http/server.cpp 挂 CORS 中间件,src/http/routes.cpp 用一张静态路由表声明 20 多条路由及各自的访问级别(Public / Token / Configured / Setup),Bearer token 鉴权、mDNS 主机名tiny-engineer.local、配网期 302 重定向到设置向导等细节都在这张表旁边闭环。API 完整文档见 docs/api.md。
七、不装工具链的烧录方式:浏览器直刷
对不想装 PlatformIO 的用户,web/flash/ 提供基于ESP Web Tools的网页烧录页:Chrome/Edge 里点 "Connect & install",通过 USB 直接写入 release 预编译的四件套(bootloader / partitions / firmware / littlefs)。而完整开发路径就是 docs/flash.md 里的三条命令:
pio run # 构建(自动触发 UI 嵌入与音频打包) pio run -t upload # 烧录固件 + LittleFS pio device monitor # 115200 串口监视八、关键文件索引
| 模块 | 路径 |
|---|---|
| 构建与分区配置 | platformio.ini · partitions.csv · partitions_ota.csv |
| Web UI 源码与构建 | ui/src/ · ui/vite.config.ts |
| UI 嵌入流水线 | scripts/embed_ui.py |
| 音频打包 | scripts/copy_assets.py · scripts/audio_pack.py |
| 固件主入口与心跳 | src/main.cpp |
| HTTP 服务与路由 | src/http/server.cpp · src/http/routes.cpp |
| 动画状态机 | src/animation/ |
| 串口协议 | src/serial/ · docs/serial.md |
| API 文档 | docs/api.md · docs/flash.md |
整套架构的精髓在于"一份源码,两条流水线":C++ 固件、TypeScript 界面、WAV 音频各自独立演进,却由 PlatformIO 的 pre/post 脚本钩子缝合成一次命令的产物;而env:native宿主机单测又让核心逻辑可以不碰硬件回归。想动手拆读代码,建议从 platformio.ini 的extra_scripts顺藤摸瓜,再看 scripts/embed_ui.py,半小时即可跑通"HTML → gzip → 头文件 → Flash"的完整旅程。
【免费下载链接】tiny-engineer
A small desktop robot that brings an AI coding assistant to life, physically acting out when it reads files, thinks, writes code, and finishes work.
相关推荐
在浏览器里烧录ESP32-C3固件:Tiny Engineer Web Flash教程,无需PlatformIO也能刷机
在浏览器里烧录ESP32 C3固件:Tiny Engineer Web Flash教程,无需PlatformIO也能刷机 Tiny Engineer(开源桌面
git-bug Web UI 前端架构深度解析:从 Vite + React 到嵌入式 SPA 的完整工程实践
git bug Web UI 前端架构深度解析:从 Vite + React 到嵌入式 SPA 的完整工程实践 git bug 是一个内嵌于 Git 仓库的分布
开发工具研发协作coze-studio 知识 IDE 适配器组件包 @coze-data/knowledge-ide-adapter 技术解读与实战指南
coze studio 知识 IDE 适配器组件包 @coze data/knowledge ide adapter 技术解读与实战指南 本指南以 fronte
嵌入式物联网智能硬件硬件开发嵌入式GUI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考