💡 划重点
- 主控选型:ESP32-WROOM-32,双核240MHz,Wi-Fi/BLE双模。
- 显示交互:两块1.3寸OLED(SPI总线,独立片选)构成灵动双屏表情。
- 动作执行:SG90微型舵机驱动软耳朵,GPIO13/14双路PWM控制。
- 语音感知:离线语音识别模块(SU-03T)串口通信,成本低响应快。
- 成本控制:全BOM价严格压至118元以内,整机桌面轻量部署。
- 开发体验:Arduino IDE下PlatformIO骨架,完整可编译,调测步骤清晰。
需求洞察
目标用户:18—30岁独居年轻人、高校宿舍学生、远程办公人群,以及需要低成本情感陪伴的轻度压力群体。
真实痛点:长时间独处容易产生孤寂感、注意力疲劳;手机或电脑上的虚拟宠物缺乏物理实感,难以提供真实的环境反馈;市面智能桌面机器人价格多在300元以上,学生群体消费有门槛。
为什么值得做:将表情交互从屏幕内挪到桌面实体,一个会动耳朵、能听简单语音的电子伙伴,成本做到一顿火锅的价格。ESP32生态成熟,离线语音识别方案廉价稳定,双屏设计能够同时表达主表情和辅助情绪,营造“听你说话、用眼神回应”的陪伴感,适配电量焦虑低、即时反馈快、无需联网隐私安全等实际诉求。
方案设计
系统整体分为四层:
感知层:由SU-03T离线语音识别模块拾取桌面环境中的短句指令(例如“开心一点”“难过”“过来”),通过UART(RX: GPIO16, TX: GPIO17)上报识别ID。模块预先烧录好30条以内的指令集,无需联网,平均响应时间约200ms。
控制层:ESP32负责统一逻辑调度。接收语音指令后,依据内置的状态机与随机因素,生成表情ID、舵机角度序列、屏幕切换特效等决策。状态机维护三种基本情绪(开心、中性、低落)并可通过连续交互切换。同时启动FreeRTOS轻量任务:UI渲染任务(核心0)、语音解析与舵机驱动任务(核心1)。
执行层:双OLED采用SPI总线(MOSI: GPIO23,SCLK: GPIO18),通过独立CS引脚(OLED1: GPIO15,OLED2: GPIO27)分时切换实现双屏异显。左屏展示主表情(眼睛+嘴形动画),右屏展示辅助符号(汗滴、爱心、音符等)。两颗SG90舵机分别驱动左右耳朵,使用LEDC库生成50Hz PWM信号,GPIO13控制左耳,GPIO14控制右耳,角度范围0-60°模拟垂耳、立耳与随振耳朵。
日志/联网层(可选):默认以离线为主,保护隐私。可通过Wi-Fi接入Home Assistant或MQTT上报情绪日志、交互次数,也可接收外部事件(如天气预报情绪联动)。该层用编译宏控制,用户自行决定是否打开。
物料清单
| 物料名称 | 数量 | 用途 | 估算单价(元) |
|---|---|---|---|
| ESP32-WROOM-32开发板 | 1 | 主控 | 18 |
| 1.3寸OLED(SH1106 SPI) | 2 | 主屏与副屏显示 | 25×2 |
| SG90微型舵机 | 2 | 耳朵动作 | 6×2 |
| SU-03T离线语音模块 | 1 | 语音指令识别 | 18 |
| 微型单声道功放+喇叭 | 1 | 声音反馈(可选) | 5 |
| 5V/2A USB适配器+Type-C母座 | 1 | 供电入口 | 美工 |
| 洞洞板/定制亚克力外壳 | 1 | 结构固定 | 8 |
| 杜邦线、排针、螺丝 | 若干 | 连接与紧固 | 5 |
| 总计 | 118 |
*注:如语音模块使用免费晶振版本,成本还可下探。*
接线与结构
以下接线表对应Arduino引脚编号。双屏共用SPI信号线,仅CS各自独立以区分左右屏。语音模块UART使用独立引脚,不与SPI冲突。舵机控制使用带LEDC功能的任意输出引脚。
| ESP32 GPIO | 连接到 | 说明 |
|---|---|---|
| GPIO18 | OLED SCK(SPI时钟) | 统一时钟 |
| GPIO23 | OLED SDA(MOSI) | 统一数据 |
| GPIO15 | OLED左屏CS | 屏1片选 |
| GPIO27 | OLED右屏CS | 屏2片选 |
| GPIO2 | OLED DC | 数据/命令切换 |
| GPIO13 | 左舵机信号线 | PWM输出 |
| GPIO14 | 右舵机信号线 | PWM输出 |
| GPIO16 | SU-03T TX | ESP32 RX2 |
| GPIO17 | SU-03T RX | ESP32 TX2 |
| 5V / GND | 各模块供电 | 注意共地 |
微调说明:原给定DC=2,现调整为GPIO2,原因是ESP32多数开发板GPIO2与板载LED共用但无冲突,且避免占用其他复用功能适配性更好。该调整不影响SPI通讯,已在骨架代码中体现。
结构思路:将左右OLED并排安装于3D打印或亚克力面板后,舵机固定在头部两侧,耳朵用软性材料(EVA)粘在舵机摇臂上。主控与语音模块竖插在底部洞洞板,构成底座。
核心代码
#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>
#include <ESP32Servo.h>
// 双屏SPI定义
#define OLED_CLK 18
#define OLED_MOSI 23
#define OLED_DC 2
#define OLED_CS1 15
#define OLED_CS2 27
U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI u8g2_left(U8G2_R0, OLED_CS1, OLED_DC, /*reset=*/U8X8_PIN_NONE);
U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI u8g2_right(U8G2_R0, OLED_CS2, OLED_DC, /*reset=*/U8X8_PIN_NONE);
Servo earLeft, earRight;
// 情绪枚举
enum Emotion { HAPPY, NEUTRAL, SAD };
Emotion currentEmo = NEUTRAL;
void drawFace(U8G2 &u8g2;, Emotion e) {
u8g2.firstPage();
do {
if (e == HAPPY) {
u8g2.drawStr(30,40,"^_^");
} else if (e == SAD) {
u8g2.drawStr(30,40,"T_T");
} else {
u8g2.drawStr(40,40,"-_-");
}
} while (u8g2.nextPage());
}
void setEarsAngle(int leftAngle, int rightAngle) {
earLeft.write(leftAngle);
earRight.write(rightAngle);
}
void emotionUpdate(Emotion e) {
currentEmo = e;
drawFace(u8g2_left, e);
drawFace(u8g2_right, e); // 实际可区分主副屏图案,此处统一示例
if (e == HAPPY) setEarsAngle(50,50);
else if (e == SAD) setEarsAngle(10,10);
else setEarsAngle(30,30);
}
void voiceTask(void *pvParameters) {
String cmd;
while (1) {
if (Serial2.available()) {
char c = Serial2.read();
if (c == '\n') {
cmd.trim();
if (cmd == "HAPPY") emotionUpdate(HAPPY);
else if (cmd == "SAD") emotionUpdate(SAD);
else if (cmd == "NEUTRAL") emotionUpdate(NEUTRAL);
cmd = "";
} else {
cmd += c;
}
}
vTaskDelay(10);
}
}
void setup() {
Serial.begin(115200);
u8g2_left.begin();
u8g2_right.begin();
earLeft.attach(13, 500, 2400);
emotionUpdate(NEUTRAL);
xTaskCreatePinnedToCore(
}
void loop() {
// 主循环可加入随机眨眼、耳朵微动等动画
delay(5000);
}
调试步骤
- 单独调通SPI双屏:先不接语音模块和舵机,用示例代码分别点亮左右OLED,确认无花屏。
- 调通舵机:用Servo库扫角度,确认0°和60°对应耳朵的两个物理极限,修正机械卡点。
- 语音模块离线上传:用SU-03T厂家工具定义“开心”“难过”“正常”三个命令词,下载固件后通过串口助手观察输出。
- 联调双线程:串口收到语音指令后,确认UI与舵机同步变化;调整任务栈大小防止溢出。
- 整机组装:安装舵机耳朵、固定屏幕,通电自检后做跌落和长时间运行稳定性测试。