返回

可爱的桌面机器人:ESP-VoCat 上会倾听的脸,以及浏览器中的模拟器

大多数桌面小玩意只是展示信息。我想要一个能对人做出反应的:一个在你说话时会看着你、会回答你、在没人时还会做点有趣事情的机器人。乐鑫 ESP-VoCat v1.2 具备实现这一切的硬件。它将 ESP32-S3 SoC、一块 360×360 的圆形触摸屏、两个麦克风、一个扬声器和一块电池集成在带有猫耳的机壳中。

VoCat Eyes 是我为它编写的脸部程序,也是 Cute Desktop Robot 系列的第一个机器人。它的主要特性包括:

固件、模拟器和工具均在 cute-desktop-robot 仓库中开源。

模拟器 3D 视图中的 VoCat Eyes 机器人:蓝色的猫耳 ESP-VoCat 机壳,圆形屏幕上显示着机器人的脸

上图是模拟器的 3D 视图:乐鑫发布的机壳模型,内部嵌入着被模拟机器人的实时屏幕画面。

下面是一台放在桌上的真实机器人,运行着同一套固件:

真实硬件上的机器人:在 YouTube 上观看。

在浏览器中体验机器人

下面的模拟器运行的是编译为 WebAssembly 的机器人 C++ 代码:与 ESP32-S3 上运行的是同一套脸部引擎、状态机和界面。只有硬件层被替换为模拟部件。无需账号,无需安装。只有语音对话需要真实机器人,因为模拟器没有网络。

如果画面太小,可以在全窗口中打开模拟器。

窗口分为三部分。左侧是 3D 机器人,下方是状态机,右侧是带有情绪图和传感器的面板。试用方法:

  1. 轻触机器人的屏幕。 在 3D 机器人的圆形屏幕上单击一次。机器人会播放下一个动画。
  2. 打开设置。 按住屏幕直到时钟和电池出现,然后单击时钟一次。设置菜单会打开。单击某一行的左半部分降低数值,单击右半部分提高数值。按住可关闭菜单。
  3. 从一侧说话。 在右侧面板的 SENSORS 下,将 from 旁边的滑块拖到某个角度,然后单击 talk [V]。地板上的紫色圆点显示模拟声音的来源,机器人的眼睛会转向它。
  4. 观察状态机。 每当机器人改变活动时,状态机中对应的方框会亮起,底部的条形图显示接下来可能改变状态的计时器。
  5. 转动机器人。 在屏幕外的任意位置拖动可旋转 3D 视图,使用鼠标滚轮缩放。按 M 切换到屏幕的平面视图。

单击模拟器后键盘也可使用:1 到 9 显示表情,Y 点头,U 摇头,H 显示对话气泡。轻触和设置在真实机器人上的操作方式相同。

下面的截图展示了桌面 PC 上整个模拟器窗口的样子:

完整的桌面模拟器:3D 机器人、情绪图、带滑块的传感器,以及带计时器的状态机

硬件

ESP-VoCat 出厂即已组装好,机壳、电池和屏幕均已就位。ESP-VoCat 用户指南详细介绍了该开发板。一个 ESP32-S3 模组承担所有工作。它拥有 16 MB 闪存和 16 MB PSRAM(外部 RAM)。PSRAM 使 360×360 的脸部成为可能:一帧 16 位色的完整画面需要 253 KB,超过了 SoC 内部 RAM 的容量。

下图展示了固件使用的部件以及每个部件连接到 ESP32-S3 的总线:

框图:ESP32-S3 通过 QSPI 驱动 ST77916 圆形 LCD,通过 I2C 读取 CST816S 触摸和 BQ27220 电量计,通过 ES7210 获取麦克风输入,并通过 I2S TDM 经 ES8311 及其放大器播放,同时通过 Wi-Fi 与 Gemini Live 通信

有两个部件需要额外处理:

机器人如何绘制它的脸

机器人每 33 ms 重绘一次脸部,大约每秒 30 次。下图展示了其中一帧从传感器到屏幕的过程:

帧管线:Sensors 为 Brain 生成 SensorState,Brain 为 FaceEngine 选择表情,FaceEngine 将形状绘制到 Canvas,Canvas 找出发生变化的 24 像素图块,VocatLcd 仅通过 QSPI 发送这些图块。Listener 任务将语音、敲击和唤醒词送入 Sensors,对话将 set_face 和 play_act 送入 Brain

图中的每个方块都是固件的一个模块:

所有表情和动画

模拟器包含一个命令行工具 facetool,它无需打开窗口即可渲染机器人的屏幕。下面的视频均由它生成。每个视频会同时播放同一组中的所有表情:先展示切换到每个表情的过程,再展示表情本身。

表情

平静与思考(16 种):普通、好奇、专注、怀疑、无聊、困惑、倾听等。

开心(15 种):从微微一笑到开怀大笑、爱心眼和星星眼。

难过(8 种):担忧、恳求、哭泣、孤独、内疚等。

生气(6 种):从恼怒到暴怒,以及厌恶。

惊讶与警觉(7 种):惊讶、害怕、眩晕、昏迷、被催眠等。

困倦(4 种):困倦、打哈欠、睡着和疲惫。

动画

动画在代码中称为 act,是一些短场景,其中许多会在眼睛旁边绘制一个物体:雨云、咖啡杯、雷达屏幕。Brain 会在机器人空闲时播放它们,轻触屏幕则会播放下一个。

肢体语言(9 种):点头、摇头、翻白眼、打喷嚏。

屏幕特效(5 种):故障、重启、扫描线、代码雨。

带物体(18 种):爱心、下雨、烟花、泡泡、球、跳舞。

日常事物(9 种):书、耳机、咖啡、火箭、雪。

游戏与圆屏特效(6 种):吃豆人、乒乓、彩虹、雷达、水族箱。

同样的网格图(以动画 GIF 形式)位于仓库 README 中。

机器人如何说话

对话使用 Gemini Live API,它通过一条 WebSocket 连接接收音频流,并以音频流作答。在撰写本文时,机器人使用的是 gemini-2.5-flash-native-audio 模型。下图展示了音频的传输路径:

语音管线:ES7210 麦克风以 16 kHz 向 Listener 任务提供音频,GeminiLive 任务将 100 毫秒的数据块发送给 Gemini Live API 并接收 24 kHz 音频,再重采样后交给 Player 和扬声器。工具调用交给 Talk,由它改变表情。Player 在播放时会静音 Listener

机器人将一只麦克风的声音以 16 kHz PCM 音频发送,每块 100 毫秒。Gemini 以 24 kHz 语音作答,机器人将其转换为 16 kHz 并播放。

对话是半双工的:机器人要么在听,要么在说,绝不会同时进行。扬声器距离麦克风只有几厘米,因此机器人在播放回答时会静音麦克风流。否则 Gemini 会听到自己的声音。由 Gemini 在其一侧判断对方是否已经说完。

Gemini 还会收到四个工具,即它可以在对话中调用的函数:set_face、play_act、look 和 show_text。当对话变得伤感时,Gemini 可以调用 set_face("sad")。当你让机器人表示同意时,它可以调用 play_act("yes"),机器人便会点头。每次会话开始时都会发送一条包含本地时间和日期的简短文本消息,这样 Gemini 就能回答相关问题。

有三个问题占用了大部分开发时间:

  1. 安全连接。 机器人通过 TLS 连接 Gemini。ArduinoWebsockets 库的 setInsecure() 函数在 ESP32-S3 上不起作用,因此 TLS 握手失败。固件现在内置了 Google 的两张根证书(GTS Root R1 和 R4,有效期至 2036 年)。它还会等待时钟通过 NTP 同步,因为验证证书需要当前日期。
  2. 内部 RAM。 “Hi ESP”唤醒词运行在 ESP-SR(乐鑫的语音识别库)上,它占用约 100 KB 内部 RAM。TLS 库在握手时需要内部 RAM,最初的尝试因 SSL - Memory allocation failed 而失败。机器人现在会在对话开始时停止 ESP-SR,从而释放 107 KB,并在对话结束时重新加载它。加载 ESP-SR 大约需要 1.2 秒。
  3. 缺失的麦克风流。 有一天,机器人向用户问好后便忽略了之后的一切。它的眼睛仍然跟随我的声音,所以麦克风显然工作正常。原因是固件从未启用发往 Gemini 的音频流。串口控制台现在会打印 Gemini 对它所听到内容的转写,这样同样的问题就能立刻被发现。

使用 Gemini 需要来自 Google AI Studio 的 API 密钥,并且该 API 会根据 Google 的 Gemini API 定价 按使用量计费。密钥保存在本地的 secrets.h 文件中,绝不会提交到仓库。

机器人屏幕上的设置

机器人设置完成后就不再需要电脑。长按屏幕会显示时钟、电池电量和 Wi-Fi 状态。点击时钟会打开设置,你可以在其中更改:

Wi-Fi 一行会打开范围内网络列表,以及一个包含大写字母、数字和符号的屏幕键盘。ESP32-S3 在连接某个网络时无法扫描网络,因此固件会在扫描期间暂停连接尝试,并在扫描结束后恢复。如果密码错误,时钟屏幕会将其显示为原因。ESP32-S3 仅支持 2.4 GHz 网络。

机器人、PC 和浏览器共用一套代码

机器人的代码并不直接调用显示、触摸或音频驱动。表情、Brain 和各屏幕都通过 src/hw 和 src/audio 中的一层薄硬件层。模拟器替换了该层,因此其余代码无需改动即可为三个目标构建:

src/ 中的共享 C++ 代码以三种方式编译:arduino-cli 生成 ESP32-S3 固件,g++ 配合模拟的 Arduino 头文件生成 raylib 桌面模拟器,em++ 为浏览器生成同一个模拟器

在模拟器中,Arduino.h、Wire.h 和 SPI.h 的小型替代版本会写入一个代表硬件的结构体。该结构体保存显示内存、触摸控制器寄存器和电池读数。使用 raylib 图形库编写的模拟器窗口会绘制该结构体,并允许你更改传感器数值。

浏览器版本是用 Emscripten 编译的同一个模拟器程序,Emscripten 将 C++ 转换为 WebAssembly。三项改动使之成为可能:

  1. 面向 Web 的 raylib。 raylib 使用 Emscripten 构建为一个通过 WebGL 绘制的静态库。
  2. 每次调用绘制一帧。 桌面版本运行在一个无限循环中。该循环变成了一个绘制单帧的函数。桌面版本在循环中调用它,浏览器则在每次屏幕刷新时调用一次。
  3. 固定的窗口尺寸。 在浏览器中,模拟器绘制到一个 1380×900 的画布上,页面会缩放画布以适应显示。可调整大小的画布会破坏鼠标与画布坐标之间的映射。

3D 外壳来自乐鑫。ESP-College 在 OSHWHub 上以 GPL-3.0 许可证发布了 ESP-VoCat(EchoEar)外壳的 STL 文件。一个简短的 Python 脚本将 STL 文件转换为一个 3D 模型文件,并降低其细节,以便浏览器快速加载。模拟器将实时屏幕绘制到外壳圆形窗口中的圆盘上。当你点击 3D 屏幕时,模拟器会在该圆盘上找到点击点,并将其转换为对应像素处的触摸。

自己动手构建

要构建 VoCat Eyes,你需要一块 ESP-VoCat v1.2 开发板,以及在撰写本文时版本为 3.3 的 ESP32 Arduino 核心。VoCat Eyes README 列出了所需的库和确切的命令。简而言之:

  1. 将 secrets.example.h 复制为 secrets.h,并添加你的 Gemini API 密钥。你也可以在那里输入 Wi-Fi 网络,或稍后在机器人上设置。
  2. 使用 esp_sr_16 分区方案和 sketch 文件夹中的 partitions.csv 文件编译 sketch。此布局为应用程序提供 6 MB 闪存。
  3. 将固件与 srmodels.bin(“Hi ESP”唤醒词的模型文件)一起上传。
  4. 在机器人上,按住屏幕,点击时钟,选择 Wi-Fi,选择你的网络并输入密码。
  5. 说“Hi ESP”,或点击屏幕底部的小麦克风,然后开始说话。

如果你正在为 ESP32-S3 或其他开发板设计固件,Velxio 可以在你烧录任何硬件之前,在浏览器中对其进行模拟。


分享到:
David Montero

作者

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

相关文章


上一篇
七个可以在浏览器中构建并运行的万圣节项目
下一篇
M5Stack Basic 上的 Claude 用量仪表盘——桌面上的套餐限额与 API 花费