
不用非得等发货芯片、屏幕和几根杜邦线到位一个能跟着 AI 变脸的哈基米桌面机就能自己攒出来。Microduck 的预售期一拖再拖我等得实在心痒干脆参照开源社区的 HachimoDock 思路自己手搓了一个出来。项目从硬件选型到表情渲染都是常用开源方案整套流程走下来大概花了一个周末整体难度不算高适合玩过 Arduino 或 ESP32 的朋友直接抄作业。这个“哈基米机”本质上是一个带小尺寸彩色屏幕的桌面底座设备屏幕上会显示一张拟人化猫猫脸根据 AI 对话内容或本地的情绪判断不断切换表情、眨眼、换嘴型“小屏跟着 AI 变脸”说的就是这套联动机制。我复刻的目标很明确低成本、可复现、逻辑透明并且后续能方便接入各种大模型 API。下面把选型思路、软件架构和实操过程完整记录下来给也在等发货的朋友做个参考。1. 先搞清楚要复刻的到底是什么1.1 HachimoDock 的定位不是电子相框是“情绪可视化终端”第一眼看到哈基米机的成片很多人觉得这就是个会动的小表情播放器实际上它的核心价值不在“显示”而在“连接”。设备上的小屏幕只是一个输出窗口真正有意思的是背后那套 AI 决策链路——你对着机器说话模型理解语义把情绪状态映射成表情指令屏幕上的猫脸才会跟着变化。所以复刻 HachimoDock不只是复刻一个屏幕外壳而是复刻一条“人 → 模型 → 表情 → 画面”的完整回路。这个定位很关键因为很多新手复刻时会陷入“把表情做好看”的误区花大量时间画素材、磨动画却忽略了整个设备最核心的交互逻辑。我建议先画一张数据流图麦克风采集音频 → 转成文本 → 模型判断情绪 → 输出 JSON 指令 → 单片机解析 → 屏幕渲染表情。只要这条链路打通即便表情是几个简单的几何图形这台机器也已经“活”了。1.2 “小屏跟着 AI 变脸”背后的三个真实需求拆开这句话会看到三层需求。第一层是视觉反馈也就是把 AI 的思考状态可视化模型在分析时转圈圈、输出结果时换表情用户能直接感知到“它在工作”。第二层是情绪映射把抽象的文字情绪转成具体的脸部状态开心就是嘴角翘起困惑就是眉头微皱这种映射关系构成了产品的灵魂。第三层是内容持续更新也就是表情包不能永远只有三张得根据对话内容动态组合出不同的表情效果。理解这三层需求之后功能设计就清晰了必须有音频输入模块、必须有一个稳定的情绪识别接口、必须设计一套足够灵活的表情渲染引擎。三者缺一不可。很多简化版复刻之所以“看着像、用着不像”就是因为把第一层做到了第二层第三层直接砍掉了。1.3 复刻这件事难在模块间协作从纯技术角度看屏幕驱动、摄像头识别音频等任何一个单项都有大量现成库可用真正难的是把模块拧在一起时出现的“全局问题”。比如 WiFi 连接不稳定导致 API 请求超时、屏幕刷新不够快导致动画撕裂、模型返回的情绪字段格式不统一导致解析出错这些跨层问题才是花时间最多的部分。我的建议是不要一上来就做全家桶先把“本地关键词识别 固定表情动画”跑通让设备哪怕断网也能像模像样地变脸再逐步接入云端 AI 能力。这个阶梯式做法能极大降低调试难度也能保证成品在任何环境下都有基础体验兜底。2. 手搓前的物料准备与硬件选型2.1 主控芯片ESP32-S3 是这个场景最优解复刻哈基米机主控选择基本决定项目难易。我从算力、通信、图形驱动三个维度对比了常见方案结论非常明确ESP32-S3 是这个场景的最优解。首先是算力。ESP32-S3 有双核 240MHz且带向量指令加速虽然不能直接吞吐大模型但跑简单的中文关键词分类或极小的端侧模型完全够用。其次是通信自带 2.4GHz WiFi 和 BLE可以直接联网调用云端大模型接口减掉外接通信模块的麻烦。最后是图形生态TFT_eSPI、LVGL 等图形库对 ESP32-S3 的 SPI 屏幕驱动支持非常成熟开箱即用。STM32F4 系列也不是不行但连 WiFi 得加模块AI 接口的 JSON 解析也吃内存整体成本反而更高。RP2040 价格便宜、资料多但缺原生 WiFi如果非要用还得加 Pico W 版本整体体验不如直接用 ESP32-S3 顺手。主控方案WiFiAI 能力屏幕驱动生态开发效率推荐度ESP32-S3原生向量指令加速TFT_eSPI/LVGL 完善高首选STM32F407外挂无需手写较多中备选RP2040需 Pico W无可用但资源少中一般K210自带 KPU有资料较少低不推荐2.2 屏幕选型尺寸、驱动 IC 和刷新率小屏是哈基米机的“脸”选屏直接影响观感。我的经验是优先选择1.54 英寸到 2.0 英寸的 IPS 全彩屏分辨率 240x240 或 240x280 都够用。在这个尺寸下单颗像素间距肉眼看不明显色彩表现足够细腻同时 SPI 接口数据传输压力也不大。驱动 IC 方面最常见的两类是 ST7789 和 ST7735。ST7789 一般匹配 240x240 或 240x320 分辨率的屏ST7735 更多是 128x160 或 160x80 的小屏。复刻哈基米机我强烈建议选 ST7789因为它支持更高的刷新频率在 40MHz SPI 时钟下可以轻松跑到 30fps 以上表情切换流畅度明显更好。ST7735 也不差但部分型号像素初始化参数比较诡异网上资料混乱新手容易卡在“花屏”这一步。还有一个容易忽略的点是屏幕模组是否自带电平转换。很多裸屏模块是 3.3V I/O但部分带触摸的型号是 5V 供电、3.3V 逻辑接线前务必查清楚模块资料否则 GPIO 电平不匹配轻则显示乱码重则烧坏屏幕背光。2.3 拾音、按键与外壳交互完整度决定“像不像真机”要让哈基米机具备“跟着 AI 变脸”的互动能力光有屏幕和主控还不够。我加了 INMP441 数字麦克风模块走 I2S 接口采集声音用 ESP32-S3 的专用外设做语音数据流处理。如果不接麦克风设备只能通过按键或者上位机推送文字来变脸互动性会大打折扣。按键方面保留一个实体按钮功能是“跳过当前表情”和“切换交互模式”。外壳我用 3D 打印做了个小鸭子造型的底座中间留出屏幕槽位屏幕朝上倾斜 30 度左右放在桌面上刚好可以平视。手头没有 3D 打印机也可以先用硬纸板或亚克力拼一个核心是快外形随时可以迭代。2.4 完整物料清单序号物料规格/型号作用参考成本1主控开发板ESP32-S3-DevKitC计算与通信中枢30-502TFT 屏幕1.69 英寸 IPS240x280ST7789表情显示20-403数字麦克风INMP441拾取语音10-204TYPE-C 数据线支持数据传输供电与烧录5-105按键轻触开关交互控制2-563D 打印外壳自定 STL外观支撑10-307杜邦线/排针若干接线5-103. 软件架构让 AI 决定屏幕上的表情3.1 表情的本质一套可动态组合的脸部数据在做哈基米机的表情系统之前我自己先花了两小时画设计稿结果发现效率很低。后来改成数据驱动的方式把一张脸拆成眼睛、眉毛、嘴巴、腮红几个独立部件每个部件用若干参数控制。比如眼睛由圆心坐标、半径和眨眼系数决定嘴巴由圆心、起始角度、结束角度决定。这样一套参数组合就是一种表情。用数据描述表情最大好处是AI 模型返回的可以直接是参数而不是图片大大节省了小屏的存储和渲染开销。一张 240x240 的 RGB565 位图大约需要 112KB而一个表情参数 JSON 可能不到 200 字节。对只有几百 KB RAM 的 MCU 来说这个差距是决定性的。我设计了一个简化版的表情状态结构如下{ mood: happy, intensity: 0.8, eye_radius: 8, mouth_curve: 0.6, blink: true, color_theme: warm }“intensity”控制表情的夸张程度“mouth_curve”正负号控制嘴角上扬或下垂“blink”控制是否触发眨眼动画。屏幕渲染层拿到这个 JSON直接更新画布上的几何参数即可。3.2 三条表情切换路线本地关键词、云端大模型、端侧推理加入 AI 变脸能力现阶段有三大路线可选成本、实时性和灵活度各不相同。路线一是本地关键词匹配完全不依赖网络。把用户语音切割成片段用 AC 自动机或简单的字符串匹配查找“开心”“难过”“生气”等关键词命中后映射到相应表情。优点是零延迟、绝对稳定缺点是理解不了复杂语义说“今天涨工资了真不错”它只能识别出“不错”识别出“今天涨工资”背后的喜悦还需要更多规则。路线二是云端大模型 API 调用把语音转成文本后发给大模型让模型返回结构化 JSON。这种路线最能体现“AI 变脸”的完整潜力模型能准确理解上下文语境和情绪甚至能根据对话内容动态生成一段“表情脚本”。缺点是依赖网络且每次对话都有几十毫秒到几秒的延迟需要在设备端做超时兜底。路线三是端侧轻量推理在 ESP32-S3 上跑一个小型意图分类模型使用 TensorFlow Lite Micro 或 ESP-DL 部署。这种方案可以在离线状态下实现语义级判断但模型体积和 RAM 占用需要严格控制适合对隐私要求高、不希望语音上云的场景。我最终的复刻版采用了“本地关键词 云端大模型”双通道模式联网时优先走大模型断网或超时时自动降级到本地关键词匹配。这套切换逻辑用状态机实现代码量不大但体验提升非常明显。3.3 完整链路从一段语音到一张变脸上的详细流程我把哈基米机的 AI 链路分解成六个环节方便大家逐个复现。第一环拾音。INMP441 麦克风采集音频数据通过 I2S 接口送入 ESP32-S3 的 DMA 缓冲区。第二环唤醒检测。检测到超过设定阈值的语音能量或特定唤醒词就开始录制片段典型时长为 2-3 秒。第三环语音转文本。把录到的音频数据上传到在线 ASR 服务或者使用本地经过蒸馏的 Whisper 小模型得到文字。第四环情绪判断。文本送入大语言模型提示词里固定要求“只返回 JSON包含 mood 和 intensity 字段”避免模型输出多余文字导致解析失败。第五环表情映射。设备端解析 JSON把情绪值映射到表情参数比如happy自动对应嘴角上扬、眼眶变圆、背景色变为暖色调。第六环渲染刷新。主控把参数传给 TFT_eSPI 绘图函数以 30fps 刷新动画并在眨眼关键帧插入过渡状态。整个链条里最容易出问题的是第四环和第五环之间的“协议对齐”。我在调第一版时模型偶尔返回一段带解释的文字而不是纯 JSON直接把 MCU 的 JSON 解析器干崩溃了。后面学乖了在解析之前先用正则表达式抽取大括号内的内容做容错处理再传入解析器。3.4 数据协议设计模型和单片机之间的“共同语言”协议设计这一步很多人不重视等到接入不同模型时才发现处处要改。我定义了一个简单但通用的“表情指令协议”目标是无论换哪个大模型只要模型能学会按格式输出设备端代码几乎不用动。完整的协议字段如下{ schema_version: 1, target: face, action: set_expression, params: { mood: surprised, intensity: 0.9, blink: true } }其中schema_version用于版本兼容action支持set_expression设置表情、sequence播放一段表情序列、idle回到默认待机态三种操作。这样设计的好处是动作可以扩展且模型不需要理解屏幕分辨率或绘图逻辑只需要输出语义级别的指令。把协议文档固定下来之后后续接新模型只需要在提示词里附上协议示例让模型照着输出就行。我前后换了三四个模型测试设备端没有任何改动全靠协议层做了隔离。4. 实操过程从接线到第一张笑脸点亮4.1 接线与引脚分配接线是整个项目里最“物理”的部分也是新手最容易出问题的地方。我用的 ESP32-S3-DevKitC 和 1.69 英寸 ST7789 屏幕引脚分配如下屏幕信号ESP32-S3 引脚说明SCLKGPIO12SPI 时钟MOSIGPIO11SPI 数据CSGPIO10片选低电平有效DCGPIO7数据/命令选择RSTGPIO8复位BLKGPIO38背光控制可调亮度VCC3.3V电源GNDGND地麦克风 INMP441 的接线稍微复杂一些主要有 5 根线L/R 接地SD 接 GPIO4WS 接 GPIO5SCK 接 GPIO6VDD 接 3.3VGND 接地。给麦克风供电时我额外并联了一颗 10uF 和一颗 0.1uF 的滤波电容能明显减少电源纹波对录音质量的干扰。连接技巧上建议所有杜邦线尽量短SPI 时钟线和高频数据线不要靠近电源线布置否则屏幕刷新时容易串扰导致显示异常。有条件的话直接用排针焊接在开发板与屏幕排线转接板上比杜邦线稳定得多。4.2 屏幕驱动的移植与配置屏幕驱动我直接用 TFT_eSPI 库不需要自己从头写 ST7789 初始化序列。但在 Arduino IDE 中首次使用前需要修改User_Setup.h配置文件把驱动芯片、引脚、SPI 频率定义好。我的配置片段如下#define ST7789_DRIVER #define TFT_WIDTH 240 #define TFT_HEIGHT 280 #define TFT_MOSI 11 #define TFT_SCLK 12 #define TFT_CS 10 #define TFT_DC 7 #define TFT_RST 8 #define TFT_BL 38 #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 20000000 #define SPI_TOUCH_FREQUENCY 2500000这里有个关键细节屏幕的分辨率是 240x280但 ST7789 控制器的默认扫描范围是 240x320如果不做偏移设置屏幕下方会出现一段“看不见”的区域或者出现图像错位。TFT_eSPI 在ST7789_DRIVER模式下会自动处理 240x320 到 240x280 的偏移但前提是配置文件里没有显式关闭那个 80 行的偏移补偿。SPI 频率方面我实测在 40MHz 下长时间运行稳定图像没有雪花点。如果线材质量较差或者接线太长降到 20MHz 也能接受只是刷新率会从 30fps 掉到 20fps 左右整体看还是在可接受范围内。4.3 表情动画渲染的核心逻辑表情渲染我用的是“直接绘制几何图形”的方式而不是贴图。原因很简单一张 RGBA 图片在 MCU 里解码和存储都占资源几何图形则完全由参数实时生成既能做无缝过渡动画又适合配合 AI 返回的动态参数。核心伪代码如下void drawFace(Expression exp) { // 背景色 tft.fillScreen(exp.bgColor); // 左眼 tft.fillCircle(eyeLx, eyeLy, exp.eyeRadius, exp.eyeColor); // 右眼 tft.fillCircle(eyeRx, eyeRy, exp.eyeRadius, exp.eyeColor); // 嘴巴用圆弧模拟上翘/下垂 int startAngle exp.mouthCurve 0 ? 0 : 180; int endAngle exp.mouthCurve 0 ? 180 : 360; tft.drawArc(mouthCx, mouthCy, mouthR, startAngle, endAngle, exp.mouthThick, exp.mouthColor); // 腮红 if (exp.intensity 0.5) { tft.fillCircle(cheekLx, cheekLy, 4, 0xF81F); // 粉色 tft.fillCircle(cheekRx, cheekRy, 4, 0xF81F); } // 眨眼周期性画细线代替圆 if (exp.blink millis() % 4000 150) { tft.drawLine(eyeLx - 5, eyeLy, eyeLx 5, eyeLy, exp.eyeColor); tft.drawLine(eyeRx - 5, eyeRy, eyeRx 5, eyeRy, exp.eyeColor); } }实际开发中我建议把每个表情参数预先计算成结构体数组避免每次渲染时做太多浮点运算。比如“开心”“难过”“惊讶”“疲惫”“待机”五种基础表情各存一份再根据 AI 返回的intensity在相邻表情之间做线性插值这样表情过渡会更自然。渲染性能方面drawArc函数比较吃 CPU高频率调用会影响主循环的响应速度。如果发现 UI 卡顿可以把嘴巴改画成若干fillCircle拼接或者直接用drawSmoothArc优化。我的最终方案里嘴巴部分是提前生成一个 64x64 的离屏画布把嘴型像素数据算好并缓存主循环只负责按位拷贝帧率提升明显。4.4 固件编译、烧录与在线调试我开发环境选的是 Arduino IDE因为 TFT_eSPI、ArduinoJson、WiFiClientSecure 等库安装最省事。新建工程后在库管理器里搜索并安装以下库TFT_eSPIArduinoJsonWiFiHTTPClientI2S 相关库INMP441 用烧录前在 Arduino IDE 的“开发板”菜单里选择 ESP32S3 Dev ModuleFlash Size 选 8MB 或根据实际模组大小来分区方案我推荐选“Huge APP (3MB No OTA/1MB SPIFFS)”因为哈基米机的表情参数不太好放进 1MB 的 App 分区而且暂时不需要 OTA。代码编译通过后按住开发板上的 BOOT 键插入 USB 线进入下载模式点击上传即可。烧录命令等价于esptool.py write_flash但这部分 IDE 已经封装好了不需要手动敲。调试环节除了串口打印我还建议开启 TFT_eSPI 的ESP32_DMA编译选项打开后屏幕刷新会使用 DMA 传输主循环不会被刷屏阻塞AI 回调的响应速度会快很多。4.5 调参记录刷新率、背光与动画流畅度真正让哈基米机“顺眼”起来靠的是几组参数的反复调试。我的调参记录如下参数初始值最终值调参原因SPI 频率20MHz40MHz初始值 30fps 下有明显拖影提高主频后流畅屏幕背光 PWM 频率1kHz5kHz1kHz 时有轻微频闪提高后肉眼不可见表情插值步长无8 步无插值时表情切换突兀8 步过渡自然且不卡顿眨眼周期3s4s3s 太频繁像抽搐4s 更接近自然眨眼频率麦克风增益默认低12dB默认值下远距离收音音量太小影响语音识别调参不能一次改很多要一次只改一个变量每改完就对着实际画面观察一两分钟。尤其是眨眼频率和表情插值步长前者影响自然感后者影响流畅度都属于“参数审美”没有绝对标准只能对着不同场景反复打磨。5. 常见问题与故障排查实录5.1 屏幕白屏或花屏的常见原因白屏问题大概率出在初始化序列没有执行成功优先排查引脚配置是否正确、驱动 IC 是否选对。如果用的是 ST7789 但代码里误配成 ST7735初始化时寄存器指令不匹配屏幕自然是白屏。花屏则多半是 SPI 时钟频率过高或接线过长导致信号完整性问题。解决办法是先把SPI_FREQUENCY降到 10MHz 测试如果正常再逐步提高同时检查 MOSI 和 SCLK 之间是否被电源线平行穿过。另一个高阶坑是 ST7789 的部分批次需要额外的madctl参数非标准分辨率屏幕可能在行列扫描方向设置上不同遇到图像颠倒或镜像时试着调整TFT_MAD_COLOR_ORDER和TFT_RGB_ORDER两个宏。5.2 WiFi 频繁掉线和连接不上的排查ESP32-S3 连接路由器后长时间运行掉线大多不是代码问题而是供电不足。屏幕刷新瞬间电流峰值可能达到 200-300mA如果 USB 口供电能力弱电压跌落会触发 WiFi 模块复位。我在 Type-C 供电线上串了一个带电流计的测试板实测屏幕全白画面刷新时电流峰值确实高出平均电流一大截。解决办法是在 3.3V 和 GND 之间并联一颗大电容比如 470uF 电解电容和 0.1uF 陶瓷电容组合增加储能缓冲。软件层面开启 WiFi 的自动重连回调通过WiFi.onEvent监听断开事件在ARDUINO_EVENT_WIFI_STA_DISCONNECTED时主动发起重连避免一直停在半连接状态。5.3 表情切换卡顿与画面撕裂的优化表情切换卡顿通常来自渲染线程被网络请求阻塞。因为 HTTP 请求的client.readString方法是阻塞式的等待模型响应时屏幕会停在上一帧画面整体就像卡死了一样。我的优化方案是引入“渲染线程”概念虽然没有用真正的多线程但在主循环中把联网请求拆成异步状态发请求后不再一直等待每轮循环检查是否有数据可读同时继续绘制待机动画。画面撕裂则和没有开启 DMA 或者单缓冲有关。TFT_eSPI 默认直接写屏幕缓存时如果和屏幕刷新频率不同步画面会从中间劈开。打开ESP32_DMA后用双缓冲可以彻底解决代价是 RAM 占用增加约 100KBESP32-S3 完全扛得住。5.4 大模型返回内容不稳定的兜底策略模型接口在高峰期可能延迟到 10 秒以上也可能返回格式错误哈基米机如果直接卡在等待中交互体验会非常糟糕。我的兜底策略分三层。第一层设置 3 秒超时如果超时就进入本地关键词模式先给一个贴合关键词的表情。第二层对模型返回做宽松解析用正则提取 JSON 大括号内容解析失败则默认moodneutral。第三层内置一个“心情恢复计划”如果连续三次超时屏幕显示打瞌睡的表情并降低背光亮度告诉用户当前处于离线兜底状态避免用户对着无响应的屏幕不知所措。5.5 故障速查表现象可能原因解决办法上电白屏引脚配置错误或驱动 IC 选错核对 User_Setup 中的引脚和驱动宏花屏/雪花点SPI 频率过高或接线过长降频、缩短杜邦线、开启 DMA文字乱码中文字库未加载或编码问题改用带字库的屏幕或外接存储字库WiFi 反复断连供电不足或信号弱加电容、调整天线位置、开启断线重连表情切换卡顿网络请求阻塞主循环异步化网络请求绘制与请求解耦声音识别不准麦克风增益过低或环境噪声大提高增益、加降噪、调整录音阈值模型返回解析失败响应不是纯 JSON正则提取大括号、加默认值兜底5.6 这类项目到底适合谁来做复刻哈基米机这件事不同基础的人收获完全不同。如果你已经有 ESP32 开发经验那这个项目的重点应该在 AI 链路设计上你可以尝试接不同大模型、设计不同协议、扩展表情序列做成一个极具个人风格的作品。如果是零基础小白我建议先不要考虑 AI 部分把点亮屏幕和显示几个固定表情作为第一目标运行成功后再一步步加麦克风、接大模型这样体验会更顺畅。一组实用建议第一外壳里加一个微型风扇虽然 ESP32-S3 温升不严重但屏幕和电源模块贴在一起时间久了还是会影响稳定第二屏幕表面贴一张防窥膜或钢化膜防止划痕毕竟桌面设备经常会有钥匙等杂物碰到第三别在代码里写死 API Key用esp32的 NVS 分区存储方便随时改。写在最后等 microduck 发货的那段时间我反而因为“等不起”而收获了一个更完整的动手项目。从画电路、写协议、调参数到打磨动画整个过程学到的知识远超过直接买一台成品。现在这台自制哈基米机已经在我桌面上连续跑了一周每天上班时它会根据我说话的语气切换表情中午还会自动切到“待机打盹”模式像一个有脾气但又不会抱怨的小桌宠。最后分享一个实际操作中的小技巧如果你也想做类似的小屏 AI 设备第一步别急着买最贵的屏幕或麦克风先用手头现有的开发板和一块普通 OLED 屏把“AI 输出到屏幕显示”这条链路跑通再回来优化画质和音质。链路通了后面的升级就是水到渠成的事。祝各位也能早日拥有自己的哈基米机屏幕上的每一张脸都是你和 AI 之间独一无二的对话痕迹。