💡 划重点
- 主控 ESP32,双 OLED 分左右眼,舵机做情绪耳朵。
- 支持离线语音识别,喊“开心”“生气”“睡觉”即可切换表情与动作。
- 整机物料控制在 120 元内,适合桌面陪伴、情绪反馈与硬件入门。
- 接线核心:OLED SPI 共用 SCK/MOSI/DC,双片选 CS1=15、CS2=27;舵机 13/14。
需求洞察
目标用户是独居学生、远程办公者和喜欢桌面小物的年轻玩家。他们并不需要一台昂贵的机器人,而是想要一个“有回应”的桌面伙伴:写作业时抬头能看见它眨眼,加班时喊一声它会竖起耳朵,情绪低落时给一个简单反馈。
真实痛点有三点。第一,现有桌宠大多只有单屏或灯效,表情单调,缺乏生命感。第二,语音助手偏向工具型,交互冷冰冰,且依赖云端。第三,DIY 机器人门槛高,动辄三五百元,学生党难以下手。电子表情盒用双屏做“眼睛”,用舵机做“耳朵”,用离线语音做“耳朵和嘴巴”,把成本压到 120 元内,同时保留可编程、可扩展的乐趣。它值得做,因为它把“情绪陪伴”拆成可实现的硬件模块:感知、判断、表达、记录。
方案设计
感知层:SU-03T 离线语音识别模块负责唤醒与指令识别,输出串口字符串;ESP32 内部也可读取模拟麦克风做简单音量检测,作为“被吓到”的触发。
控制层:ESP32 作为主控,运行状态机。状态包括开心、生气、困倦、惊讶、待机。每个状态对应左右眼表情、耳朵角度、蜂鸣器音效。状态切换由语音指令、定时器或随机事件触发。
执行层:两块 0.96 寸 OLED 分别显示左右眼,通过 SPI 共享时钟与数据线,用不同 CS 片选;两个 SG90 舵机控制左右耳朵,角度 0 到 90 度表示下垂、竖起、抖动;无源蜂鸣器做提示音。
日志/联网层:ESP32 连接 WiFi 后,通过 MQTT 或 HTTP 上报情绪事件,例如“今天被夸了 3 次”“生气 1 次”。本地串口同时输出日志,方便调试。若不想联网,可仅保存在 SPIFFS 中,形成一周情绪小报。
物料清单
| 物料 | 数量 | 用途 | 估算单价 | 小计 |
|---|---|---|---|---|
| ESP32 开发板 | 1 | 主控与 WiFi | 25 元 | 25 元 |
| 0.96 寸 OLED SPI | 2 | 左右眼表情 | 15 元 | 30 元 |
| SG90 舵机 | 2 | 左右耳朵动作 | 6 元 | 12 元 |
| SU-03T 语音模块 | 1 | 离线语音识别 | 20 元 | 20 元 |
| 无源蜂鸣器 | 1 | 提示音 | 2 元 | 2 元 |
| 洞洞板/杜邦线 | 1 套 | 连接与固定 | 10 元 | 10 元 |
| 亚克力/3D 打印外壳 | 1 套 | 桌面结构 | 15 元 | 15 元 |
| USB 供电线 | 1 | 5V 供电 | 5 元 | 5 元 |
| 合计 | 119 元 |
接线与结构
OLED 采用 SPI 模式,两屏共用 SCK=18、MOSI=23、DC=2、RST=4。CS1=15 控制左眼,CS2=27 控制右眼。舵机左耳=13,右耳=14。SU-03T 的 TX 接 ESP32 GPIO16,RX 接 GPIO17,VCC 接 5V。蜂鸣器接 GPIO25。
微调说明:原建议 DC=2、CS1=15、CS2=27 保留;RST 共用 GPIO4,减少引脚占用。若屏幕无 RST 引脚,可接 ESP32 EN。舵机 13/14 保持,原因是这两脚支持 PWM 且不占用 SPI。注意舵机启动电流较大,建议 5V 独立供电并与 ESP32 共地,避免复位。
结构上,外壳分三层:底板放 ESP32 与语音模块,中板固定两个舵机,面板开两个圆孔露出 OLED。耳朵用轻质纸板或 3D 打印件固定在舵机臂上,左右对称。
核心代码
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32Servo.h>
#define SCREEN_W 128
#define SCREEN_H 64
#define OLED_DC 2
#define OLED_RST 4
#define CS_LEFT 15
#define CS_RIGHT 27
#define SERVO_L 13
#define SERVO_R 14
#define BUZZER 25
Adafruit_SSD1306 leftEye(SCREEN_W, SCREEN_H, &SPI;, OLED_DC, OLED_RST, CS_LEFT);
Adafruit_SSD1306 rightEye(SCREEN_W, SCREEN_H, &SPI;, OLED_DC, OLED_RST, CS_RIGHT);
Servo earL, earR;
enum Mood { HAPPY, ANGRY, SLEEPY, SURPRISED, IDLE };
Mood mood = IDLE;
void drawEye(Adafruit_SSD1306 &eye;, int cx, int cy, int r, bool blink) {
eye.clearDisplay();
if (blink) {
eye.drawLine(cx - r, cy, cx + r, cy, SSD1306_WHITE);
} else {
eye.fillCircle(cx + r / 3, cy - r / 3, r / 5, SSD1306_WHITE);
}
eye.display();
}
void setEars(int angleL, int angleR) {
earL.write(angleL);
earR.write(angleR);
}
void showMood() {
switch (mood) {
case HAPPY:
setEars(80, 80);
break;
case ANGRY:
leftEye.clearDisplay();
leftEye.display();
rightEye.clearDisplay();
rightEye.display();
setEars(20, 20);
break;
case SLEEPY:
setEars(10, 10);
break;
default:
setEars(45, 45);
}
}
void setup() {
Serial.begin(115200);
SPI.begin(18, -1, 23, -1);
earL.attach(SERVO_L);
earR.attach(SERVO_R);
pinMode(BUZZER, OUTPUT);
showMood();
}
void loop() {
if (Serial2.available()) {
String cmd = Serial2.readStringUntil('\n');
cmd.trim();
if (cmd == "happy") mood = HAPPY;
else if (cmd == "angry") mood = ANGRY;
else if (cmd == "sleep") mood = SLEEPY;
else if (cmd == "wake") mood = IDLE;
tone(BUZZER, 880, 120);
showMood();
Serial.println(cmd);
}
delay(20);
}
调试步骤
- 先只接一块 OLED,烧录测试程序,确认 SPI 引脚与 DC、RST 正确。
- 接入第二块 OLED,分别拉低 CS1 和 CS2,确认两屏能独立显示。
- 舵机接 5V 独立电源,测试 0、45、90 度,确认耳朵不卡壳。
- 连接 SU-03T,用串口助手查看识别输出,再与 ESP32 联调。
- 逐个语音指令测试表情、耳朵、蜂鸣器是否同步。
- 接入 WiFi 与 MQTT,检查情绪事件是否成功上报。
- 整机装入外壳,连续运行 30 分钟,观察是否复位或发热。