☰
Tiny Engineer固件架构完整拆解:ESP32-C3+Arduino+Vite Web UI嵌入Flash全解析
2026/10/27 3:23:59 网站建设 项目流程

【免费下载链接】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.

项目地址:https://gitcode.com/gh_mirrors/ti/tiny-engineer
点击查看免费下载

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.pypre把git describe结果编译为FW_VERSION宏
scripts/copy_assets.pypre把 assets/ 的 WAV 音频打包进 LittleFS 镜像
scripts/embed_ui.pypre构建并嵌入 Web UI(下一节详述)
scripts/park_servos_before_upload.pypre烧录前调用 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 自动完成:

  1. Vite 单文件构建:ui/vite.config.ts 中启用了vite-plugin-singlefile插件——它把 JS、CSS 全部内联进一个dist/index.html。这是嵌入的前提:设备端没有静态文件服务器,页面必须自包含。
  2. gzip 压缩 + 转字节数组:构建出的 HTML 用compresslevel=9压缩(固定mtime=0保证相同源码产出字节一致,避免无谓重编译固件),再转写为.pio/build/*/generated/web_ui.h中的const uint8_t kWebUiGzip[]数组。
  3. 固件侧直接吐出: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 上显示进度:

  1. 初始化舵机输出引脚、USB CDC 串口(RX/TX 缓冲扩到 4KB,防止 ~1KB 的 COBS 数据帧打爆默认 256B 队列);
  2. 读取 NVS 设置(src/settings/);
  3. I2C 起总线 → 初始化 PCA9685 → 初始化 OLED 表情屏;
  4. I2S 初始化 MAX98357A 功放(失败会红屏 + RGB 灯闪烁错误码);
  5. initAudioStorage()挂载 LittleFS 音频;
  6. Wi-Fi 连接成功则startHttpServer(),并startOta()开启在线升级;未配网则进入 AP 配网模式,把/config五步设置向导(舵机校准→OLED 方向→LED→喇叭→连接方式)直接托管给那套嵌入的 Web UI。

loop()只有四个调用,就是固件的全部心跳:

serialLinkPoll(); // USB 串口协议(COBS 分帧,src/serial/) pollHttpServer(); // WebServer 轮询,src/http/ pollOta(); // OTA 下载 tickRobot(); // 动画状态机:舵机插值、表情、RGB

HTTP 层用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.

项目地址:https://gitcode.com/gh_mirrors/ti/tiny-engineer
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询