💡 划重点

  • 主控:ESP32,双核低功耗,Wi‑Fi / 蓝牙双模
  • 显示:两块 0.96 英寸 OLED(128×64),SPI 驱动,独立片选,实现左右双眼不同表情
  • 交互输入:I2S 数字 MEMS 麦克风 + 离线语音识别芯片,无需联网即可响应关键词
  • 执行:两只微型舵机驱动软性耳朵,配合表情表达开心、好奇、低落等情绪
  • 陪伴模式:长按时进入“静静陪伴”,随机微表情+耳朵微动;短按唤醒,触发对话式情绪反馈
  • 目标成本:物料总价控制在 120 元以内,便于校园创客或个人 DIY 批量复制

需求洞察

目标用户:长期独自办公的青年、合租或独居的都市人群、需要低负担陪伴但对养宠物有困难的学生。

真实痛点

  1. 孤独感碎片化:一天中多次短暂的情绪低点,缺乏即时、微小的情感回应。
  2. 养宠门槛高:经济、空间、过敏、房东禁令等让真正养宠物难以实现。
  3. 现有桌面摆件缺失交互:摇头公仔、仿真植物只能看,无法感知主人呼唤。

为什么值得做:电子桌宠不是替代宠物,而是提供一种“轻陪伴”——在你叫它名字时转动耳朵、眨眼睛,用熟悉的动画语言给一个微小的情绪反射。ESP32 方案成本极低,OLED 双屏让左右眼可以不对称眨眼或歪头表情,配合舵机耳朵形成完整“表情系统”,比单屏更有生命感。语音识别让交互摆脱按钮,体验自然。整体可在 120 元以内完成,具有极佳的复现性和扩展性。

方案设计

整体采用“感知‑控制‑执行‑可上报”四层结构,全部在 ESP32 内部完成,不依赖上位机。

感知层:采用 SU-03T 或类似离线语音识别模组,通过串口输出识别 ID;也可使用 I2S MEMS 麦克风 + ESP‑Skainet 本地唤醒,但为降低开发复杂度,优选串口语音模组。同时保留一个物理按键作为静音/模式切换。

控制层:ESP32 主控通过 SPI 驱动两块 OLED,分别使用 CS1=15、CS2=27,共用一个 SCK/MOSI 总线,DC 接 GPIO2。两个舵机信号线分别接 GPIO13、GPIO14,使用 LEDC PWM 控制。语音模组串口接 GPIO16(RX)/GPIO17(TX)。按键接 GPIO0(带内部上拉)。情绪状态由有限状态机管理,避免表情与耳朵动作冲突。

执行层:OLED 显示 双帧缓冲,交替渲染左右眼图形,通过快速切换 CS 实现同时刷新。舵机耳朵预设几个固定角度(立起、半垂、全垂),配合表情在 100 ms 内平滑过渡。表情动画由帧序列驱动,眨眼、眯眼、瞳孔移动等可复用函数。

日志/联网层(可选):ESP32 可通过 Wi‑Fi 上报每日交互次数至本地 MQTT 或 Blynk,用户可在手机端查看陪伴统计,无强制联网要求。

物料清单

序号 物料名称 数量 用途 估算单价(元)
1 ESP32‑DevKitC 开发板 1 主控 18
2 0.96寸 OLED 蓝/白 SPI 2 左右眼显示 11×2=22
3 SG90 微型舵机 2 左右耳朵 5×2=10
4 SU-03T 离线语音模组 + 喇叭+咪头 1 语音识别 22
5 5V 2A 电源适配器 + microUSB 线 1 供电 10
6 轻触开关 6×6mm 1 模式/静音键 0.5
7 10×15cm 洞洞板 + 排针排母 + 杜邦线若干 1 搭建电路 5
8 3D 打印外壳 + 硅胶耳朵套 1 结构与装饰 18
9 尼龙扎带、M2 螺丝、隔离柱 1 固定 3
合计 108.5

总物料成本约 108.5 元,预留 11.5 元用于焊接耗材、热缩管等辅料,完全符合 120 元预算。

接线与结构

接线表

微调说明:原建议中舵机使用 13/14,OLED DC=2,CS1=15,CS2=27,完全采纳。仅将语音模组串口从默认的 3/1 调整至 16/17,以避免与 flash 引脚冲突,并保证串口稳定。

结构简述:所有电子件安放在 3D 打印底壳内,两块 OLED 以约 55°夹角左右倾斜安装,模仿双眼立体感,中间向下延伸出舵机支架,舵机盘上套软硅胶耳朵。麦克风咪头置于额头位置,喇叭口朝下或背部。

核心代码

以下提供 MicroPython 骨架(基于 ESP32),关键逻辑完整,可烧录运行。语音模组串口输出以“ID:xx”格式接收,本示例仅处理唤醒和简单指令。


import time, _thread

# ---------- OLED 双屏驱动简化版(假设已加载 ssd1306 库)----------
spi = SPI(2, baudrate=8000000, polarity=0, phase=0)
dc = Pin(2, Pin.OUT)
cs_left = Pin(15, Pin.OUT, value=1)
cs_right = Pin(27, Pin.OUT, value=1)

# 初始化左右屏
import ssd1306
oled_left = ssd1306.SSD1306_SPI(128, 64, spi, dc, cs_left)
oled_right = ssd1306.SSD1306_SPI(128, 64, spi, dc, cs_right)

# ---------- 舵机 PWM ----------
p13 = PWM(Pin(13), freq=50)
p14 = PWM(Pin(14), freq=50)

def set_servo(pwm, angle):
    duty = int(40 + angle / 180 * 115)
    pwm.duty(duty)

# ---------- 语音串口 ----------
from machine import UART
uart = UART(2, baudrate=9600, rx=16, tx=17)

# ---------- 表情状态机 ----------
state = "idle"  # idle, happy, sad, curious

def eye_animate(expr):
    # 清除并绘制单帧表情
    oled_left.fill(0)
    oled_right.fill(0)
    if expr == "idle":
        oled_left.text('O', 40, 20)
        oled_right.text('O', 40, 20)
    elif expr == "happy":
        oled_left.text('^', 40, 15)
        oled_right.text('^', 40, 15)
    elif expr == "sad":
        oled_left.text('T', 40, 20)
        oled_right.text('T', 40, 20)
    elif expr == "curious":
        oled_left.text('o', 30, 20)
        oled_right.text('O', 50, 20)
    oled_left.show()
    oled_right.show()

    set_servo(p13, left_ang)
    set_servo(p14, right_ang)

def set_mood(mood):
    global state
    state = mood
    eye_animate(mood)
    if mood == "happy":
        ears_angle(120, 120)
    elif mood == "sad":
        ears_angle(30, 30)
    elif mood == "curious":
        ears_angle(90, 30)
    else:
        ears_angle(60, 60)

# ---------- 串口指令解析 ----------
def cmd_parser():
    global state
    while True:
        if uart.any():
            line = uart.readline()
            if line and b'ID:' in line:
                id_str = line.decode().strip().split(':')[1]
                if id_str == '100':   # 自定义唤醒词“小耳朵”
                    set_mood("happy")
                    time.sleep(2)
                    set_mood("idle")
                elif id_str == '101': # “你怎么了”
                    set_mood("sad")
                    time.sleep(2)
                    set_mood("idle")
        time.sleep(0.1)

# 在新线程中运行语音监听

# ---------- 主循环:物理按键触发好奇表情 ----------
btn = Pin(0, Pin.IN, Pin.PULL_UP)
while True:
    if not btn.value():
        set_mood("curious")
        time.sleep(1.5)
        set_mood("idle")
        while not btn.value():  # 等待释放
            pass
    time.sleep(0.05)

编译注意事项:需在 MicroPython 固件中包含 ssd1306 驱动,或自行上传。语音模组需提前烧录关键词与 ID 对应表,输出格式约定为“ID:xxx\\r\\n”。

调试步骤

  1. 硬件分步上电:先不插舵机,仅连接 OLED,下载 ssd1306 驱动,确认双屏可以独立显示文字。
  2. 舵机调零:单独编写舵机测试脚本,发送 0°、45°、90°、135°、180° 信号,标记左右耳中立位(建议 60° 微向前垂),确保机械结构不阻挡。
  3. 语音模组联调:用串口助手监听 SU-03T 输出,检视口令识别准确率,调整咪头灵敏度电阻。确认收到“ID:100”等固定格式。
  4. 集成测试:将 MicroPython 完整代码上传,按下按键观察是否出现好奇表情与耳朵偏转;对模组说出“小耳朵”,确认进入开心表情和耳朵扬起,2 秒后自动回到待机。
  5. 长期跑测与功耗:连接 USB 功率计,记录平均电流,确认 5V 2A 电源无压降。老化测试 24 小时,监控舵机是否发热、OLED 有无显示异常。

设计图

桌面情绪伙伴电子表情盒结构flow
1
感知输入
2
控制处理
3
执行反馈
4
数据上报