ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用ESP32+小智AI驱动舵机,打造会对话的桌面宠物狗

用ESP32+小智AI驱动舵机,打造会对话的桌面宠物狗 一直想在桌面上养一只“活的”AI宠物——不用喂食、不用铲屎但喊它名字会回头跟它聊天它会歪着脑袋听回话的时候尾巴还能跟着节奏晃。市面上的桌面音箱大多只会发声放在一堆机械结构旁边显得特别呆。后来我在玩开源小智AI框架的时候发现它把“唤醒词—语音识别—云端大模型—语音合成—播放”这条链路已经做得相当完整而ESP32作为主控正好能同时承担联网和音频传输。剩下的问题只有一个怎么给这个会说话的AI助手装上肢体答案就是舵机。这篇文章记录的就是我在小智AI框架之上用ESP32驱动多路舵机做出一只桌面宠物狗的完整过程。整个项目包括三块核心小智AI负责语音对话ESP32负责大脑和联网舵机组负责把状态变成可见的动作。硬件选型、固件编译、PWM舵机原理、动作编排、供电方案和实测踩坑都会放到后面展开。如果你手上正好有一块ESP32开发板想做一个能聊天的桌宠或者只是好奇小智AI和舵机到底怎么配合这篇文章值得你读完再动手。1. 为什么是小智AI语音链路已经齐全只差一副身体很多人一开始的思路是麦克风拾音、调用大模型接口、再把返回的文字用TTS朗读出来。听起来简单但真正做进嵌入式设备就会发现光是“稳定地检测到唤醒词再进入对话”这一步就够你调上两个礼拜。麦克风阵列的波束成形、回声消除、打断唤醒、流式音频上传、TTS播放的时序衔接每一环都有坑。小智AI这个开源框架的价值在于它把这些语音前端问题基本都封装好了。小智AI目前主跑在乐鑫ESP32-S3这类带音频能力的芯片上开发者只需要按照自己的板子调整配置烧录完就能直接得到一个能联网对话的语音终端。我选它做桌宠狗的大脑不是因为它花哨而是因为它的状态机很清晰空闲、唤醒、聆听、思考、回复框架都会以事件的形式暴露给应用层。这就给了我一个天然的动作联动入口——只要在这些状态切换时给舵机下发对应动作狗就能“活”起来。另外一个现实原因是小智AI官方已经适配了一批音频开发板从带屏幕的ESP32-S3-BOX系列到各种第三方音频底板都有现成配置。如果你不想在音频通路上折腾直接选适配列表里的板子是最稳的。我当时手里有一块ESP32-S3-DevKitC搭配INMP441麦克风和MAX98357A功放模块也能跑只是需要自己对照sdkconfig把所有I2S引脚检查一遍。这一版的整体结构可以分成上下两层上层是语音交互层跑小智AI固件负责麦克风采集、唤醒词识别、连接云端服务端、TTS播放。下层是动作执行层可以是PCA9685舵机驱动板也可以是另一块ESP32/Arduino小板负责接收上层发来的动作指令再驱动各个舵机。我最终采用的是“双板分离”方案主控小板只跑语音另一块小板专门管理舵机。原因后面会细说但核心思想是——别让舵机PWM刷新和WiFi协议栈抢CPU时间否则你会看到狗一边说话一边抽风。2. 硬件物料清单与供电架构这版桌宠狗最关键的决定桌宠狗的体积不大但对电流的需求一点不小。整机物料不算外壳和螺丝我用了这么一套配置部件型号数量说明主控板ESP32-S3-DevKitC-11跑小智AI固件负责语音链路I2S数字麦克风INMP4411数字输出抗干扰强I2S功放模块MAX98357A1D类功放直推3W喇叭喇叭3W 8Ω 28mm1桌面场景足够舵机驱动板PCA9685116路PWMI2C控制腿部舵机SG904轻量便宜坏了不心疼头部舵机MG90S1金属齿轮头是重点活动部件尾部舵机SG901尾摆动作不需要大力矩舵机供电5V/3A直流电源1独立供电和ESP32分开滤波电容1000uF电解0.1uF瓷片若干吸收舵机浪涌防复位这里最想强调的其实是供电架构因为这个项目的“翻车点”几乎都和电有关。SG90这种小舵机正常运行时电流在200mA左右但堵转瞬间能冲到400mA以上四个腿舵机同时动一下总电流很容易超过2A。如果你试图用USB口同时给ESP32舵机和舵机供电瞬间电压跌落会让你看到一串很经典的故障现象板子重启、串口掉线、WiFi反复重连。正确的做法是分两路供电。ESP32用独立的5V/USB口供电舵机单独用一个5V/3A电源两块电源的GND必须接到一起。为什么必须共地因为舵机的PWM信号是以GND为参考电平的主控板如果和舵机板各用各的地信号参考点不一致舵机就会乱抖甚至完全不动。PCA9685的接线也有一点讲究。板子上的VCC接舵机电源V引脚同样接舵机电源逻辑部分由板载稳压供电。如果你用的是3.3V逻辑信号SCL/SDA直接接ESP32的GPIO即可。还有个容易被忽略的细节PCA9685的OE引脚默认低电平使能如果你不控制它就直接接地。我第一版忘了接OE舵机完全没反应排查了半天才发现是OE悬空导致的。关于外壳市面上有不少四足机器人STL模型可以直接下载。我的经验是如果只是验证动作先用硬纸板或者亚克力做一个简易骨架把所有舵机固定好跑通动作再考虑3D打印。因为舵机安装孔位很容易量错直接打印一次成型的代价太高。等动作逻辑稳定之后再根据实际需要的体积和孔距打印正式外壳一次成功率高得多。3. 编译与烧录在小智AI固件里填对板子和服务端参数小智AI的固件编译不复杂用的就是PlatformIO。先装好VSCode和PlatformIO插件然后从GitHub把xiaozhi-esp32工程clone下来。工程里面的platformio.ini预置了很多开发板的编译环境你只要找到自己板子的型号注释掉其他环境编译上传就行。以ESP32-S3-DevKitC为例对应环境大概长这样[env:esp32-s3-devkitc-1] platform espressif32 board esp32-s3-devkitc-1 framework arduino monitor_speed 115200如果用的是官方适配的音频开发板比如BOX-3环境名里直接就有。选错环境会导致引脚不匹配麦克风拾音异常、喇叭无声都是从这里开始的。固件编译完成后烧录前要确认两件事USB线是数据线而不是充电线COM口选对。ESP32-S3进入下载模式的方式是按住BOOT键按一下EN键松开BOOT然后点击PlatformIO的Upload。如果串口提示连接超时十有八九是USB线只有供电没有数据或者BOOT时序操作不对。第一次刷完可以打开串口监视器查看日志波特率115200看到启动信息就说明固件已经跑起来了。接下来是配网和配置服务端。小智AI首次开机会进入配网模式用手机连接它创建的热点WiFi在浏览器里输入配置页面填上家里的WiFi账号密码。这一步完了它会自动重启并连接路由器。但只连上WiFi还不够语音对话需要一个后端服务来接入大模型。小智AI官方给了服务端部署代码你可以部署在自己的NAS或者云服务器上拿到一个WebSocket服务地址。然后在固件配置里把这个地址填上。这一步比较绕因为我一开始以为“烧录完就能聊天”结果一直等到配完后端、绑定了模型API密钥它才真正能说出完整的话。如果你部署有困难官方社区也提供了一些公开测试服务不过为了稳定和隐私还是建议自己部署一套。配置好之后在串口日志里看到“WebSocket connected”再用唤醒词喊一句听到它回应语音链路就算通了。到了这一步你的小智AI还是一个只能待在桌上的音箱。接下来要做的才是让它“长出”身体。4. 舵机驱动原理50Hz周期、脉宽角度换算与两套PWM方案桌宠狗能动的核心是舵机而舵机控制的核心是PWM。大部分桌面舵机比如SG90、MG90S、MG996R都是以50Hz为基准频率的。50Hz意味着PWM信号的周期是20ms舵机看的是这个周期里面高电平持续的时间脉宽而不是占空比的百分比。这个区别很关键很多人直接把占空比概念套进来结果舵机要么不动要么以奇怪的方式抖动。标准舵机的脉宽和角度映射关系是这样脉宽0.5ms → 0度脉宽1.5ms → 90度中位脉宽2.5ms → 180度所以任意角度A对应的脉宽P毫秒可以用一个线性公式算出来P 0.5 (A / 180) × 2.0换算成占空比因为周期是20ms所以0度对应2.5%占空比180度对应12.5%占空比。每次有人问我“为什么舵机不转到180度只转了一点点”基本都是没算这个脉宽关系直接用50%占空比去驱动了——50%占空比对应10ms高电平早就超出舵机识别的范围了。用ESP32驱动舵机有两套方案。第一套是用ESP32自带的LEDC硬件PWM控制器。LEDC可以直接设置频率和分辨率输出非常稳定不会占用CPU。下面是一段用LEDC输出50Hz、12bit分辨率PWM的代码#define PWM_FREQ 50 #define PWM_RES 12 #define PWM_MAX 4095 uint32_t angleToDuty(float angle) { float pulseMs 0.5 (angle / 180.0) * 2.0; float duty pulseMs / 20.0; return (uint32_t)(duty * PWM_MAX); } void setup() { ledcSetup(0, PWM_FREQ, PWM_RES); ledcAttachPin(13, 0); ledcWrite(0, angleToDuty(90)); }这段代码把角度转成占空比值再直接写给LEDC通道。12bit分辨率下4095个刻度对应20ms0.5ms脉宽对应约102个刻度2.5ms对应约512个刻度。这个算法不依赖任何库理解了它你就能明白后面所有舵机库的底层原理。第二套方案是外接PCA9685舵机驱动板。为什么要有它因为当你要同时控制四个腿、一个头、一条尾巴总共6路舵机的时候直接用ESP32的LEDC当然也能做但GPIO的分配会变得非常吃紧。尤其小智AI固件已经占用了很多引脚再把6路PWM全部派给舵机留给麦克风、功放、按键的引脚就捉襟见肘了。PCA9685只需要占I2C总线上的两个引脚就能输出16路PWM而且它的PWM是硬件产生不依赖主控循环稳定性和实时性都有保障。PCA9685的使用代码更简单Adafruit的库已经封装好了。关键是把脉宽值算对#include Wire.h #include Adafruit_PWMServoDriver.h Adafruit_PWMServoDriver pwm Adafruit_PWMServoDriver(); #define SERVOMIN 102 // 0.5ms对应值 #define SERVOMAX 512 // 2.5ms对应值 void setup() { Wire.begin(8, 9); // 自定义I2C引脚 pwm.begin(); pwm.setPWMFreq(50); } void setAngle(uint8_t channel, float angle) { uint16_t pulse SERVOMIN (uint16_t)((angle / 180.0f) * (SERVOMAX - SERVOMIN)); pwm.setPWM(channel, 0, pulse); }PCA9685内部是12bit分辨率4096个刻度对应20ms。SERVOMIN和SERVOMAX这两个数值就是用4096乘以0.5/20和2.5/20算出来的。注意不同品牌的舵机脉宽范围会有差异比如有些支持0.4ms到2.3ms你直接用默认值可能到不了极限角度。我实测中顺手校准了几个舵机把每个通道的SERVOMIN实际值单独微调过这样动作精度会好很多。如果你用Arduino的ESP32Servo库内部原理也是一样的。它底层会分配LEDC通道然后根据write()传入的角度换算脉宽。但它默认的脉宽范围不一定是0.5ms到2.5ms某些情况下你需要自己在attach里指定servo.attach(13, 500, 2500);两个数字分别代表最小脉宽和最大脉宽单位是微秒。如果舵机中位不正或者转不到指定角度优先检查这两个参数。5. 让小狗动起来AI状态联动、平滑动作与UART指令协议舵机能转了下一步就是把舵机动作和小智AI的语音状态绑定起来。这块是整个项目的灵魂狗要让人感觉“活”的动作不能像机器人一样生硬地瞬间跳到指定位置而是要有平滑过渡。小智AI框架内部会把对话过程拆成几个状态空闲、唤醒、聆听、思考、回复。状态变化会触发上层事件。我们需要做的就是监听这些事件然后给舵机控制端下发对应的动作指令。动作目标可以根据狗的肢体设计随意发挥我用的动作表是这样的AI状态小狗动作关键舵机角度变化空闲缓慢甩尾偶尔眨眼尾巴在60-100度之间缓慢扫动唤醒抬头竖耳头部从低头转向平视聆听歪头身体前倾头部左右倾斜15度思考尾巴高频小幅度摆动尾巴快速摆动10度范围回复头部随语音节奏点头头部小幅上下5度为了让动作流畅我写了一个简单的“目标姿态插值”的驱动结构。主循环不直接写舵机目标角度而是维护两个状态当前姿态和目标姿态。每一帧比如每20ms把当前姿态朝目标姿态方向移动一小步这样舵机运动就是匀速且平滑的不会因为突然跳变产生猛砸的顿挫感。typedef struct { float head; // 头部舵机角度 float tail; // 尾巴舵机角度 float earL; // 左耳舵机角度 float earR; // 右耳舵机角度 } Pose; Pose currentPose {90, 90, 90, 90}; Pose targetPose {90, 90, 90, 90}; float moveTowards(float current, float target, float maxStep) { if (abs(target - current) maxStep) return target; return current (target current ? maxStep : -maxStep); } void updatePose() { static uint32_t lastUpdate 0; uint32_t now millis(); float step (now - lastUpdate) * 0.08f; // 每毫秒移动0.08度 lastUpdate now; currentPose.head moveTowards(currentPose.head, targetPose.head, step); currentPose.tail moveTowards(currentPose.tail, targetPose.tail, step); currentPose.earL moveTowards(currentPose.earL, targetPose.earL, step); currentPose.earR moveTowards(currentPose.earR, targetPose.earR, step); setAngle(0, currentPose.head); setAngle(1, currentPose.tail); setAngle(2, currentPose.earL); setAngle(3, currentPose.earR); }这套插值逻辑的好处是不管目标姿态从哪里来舵机都会以可预测的速度平滑过渡。你甚至可以直接把狗从“抬头”状态切到“低头”状态中间过程完全不用处理它自己会走出一条平滑的轨迹。如果想让动作更有力度就把0.08这个速度因子调大如果想更慵懒就调小。再来说说UART指令协议。我采用的是双板方案ESP32-S3主控板跑小智AI另外一块STC8/ESP32/Arduino小板专门驱动PCA9685。主控板通过UART把动作ID发给舵机小板。为什么不用I2C直接接主控的SCL/SDA因为小智AI固件本身占用了不少外设资源I2C总线上如果再挂PCA9685容易和音频外设冲突。UART是点对点干净利落而且硬件串口在ESP32-S3上有多个可用。动作指令协议设计得尽量简单方便双端解析// 5字节帧帧头 命令字 动作ID 保留位 校验和 // 0xAA 0x01 [动作ID] 0x00 [校验和] void sendAction(uint8_t actionId) { uint8_t packet[5] {0xAA, 0x01, actionId, 0x00, 0x00}; packet[4] packet[0] ^ packet[1] ^ packet[2] ^ packet[3]; Serial2.write(packet, 5); }接收端解析的思路也很直接不断从Serial读字节攒够5个字节就检查帧头和校验校验通过就根据动作ID切换到对应的目标姿态。整个协议没有用复杂的JSON或者字符串就是为了在单片机上解析更高效、更不容易出错。void parsePacket(uint8_t* buf) { if (buf[0] ! 0xAA || buf[1] ! 0x01) return; if (buf[4] ! (buf[0] ^ buf[1] ^ buf[2] ^ buf[3])) return; switch (buf[2]) { case ACTION_IDLE: targetPose idlePose; break; case ACTION_LISTEN: targetPose listenPose; break; case ACTION_THINK: targetPose thinkPose; break; case ACTION_SPEAK: targetPose speakPose; break; } }主控板这边只需要在小智AI的状态回调里调用sendAction。比如唤醒词命中时发送ACTION_WAKEUP进入聆听时发送ACTION_LISTEN开始TTS播放时发送ACTION_SPEAK。动作执行完全交给舵机小板主控不关心舵机有没有到位这样就实现了语音和动作的解耦。实际联调时我遇到过一个初期问题静态状态下PCA9685输出的PWM会让舵机保持一定角度但主控和舵机小板上电顺序不固定狗有时候会做出一个诡异的“定格”姿势。解决方法是舵机小板在初始化时先把所有目标姿态设成预设的初始Pose再等主控下指令覆盖。这样即使主控还没起来小狗也是一个正常的站立/趴伏姿势而不是四脚乱飞。6. 实测中的四个硬坑供电浪涌、WiFi断连、舵机抖动、烧录失败这一节要讲的是我调试过程中实际遇到的坑每个都花了不少时间才定位到根因。发现这些问题比写代码本身更有价值所以把它们完整记录下来。第一个坑是USB供电带不动舵机这是初学者最容易踩到、也最隐蔽的坑。现象是插上USB后ESP32能正常启动但舵机一旦动作板子马上重启或者整个系统反复重启。用万用表量5V引脚能看到舵机动作瞬间电压掉到2.8V左右。根因很简单USB口最大输出500mA多条腿同时摆动瞬时电流可能超过1A直接把5V拉垮。解决方法是给舵机独立供电ESP32和舵机电源地线共接问题立刻消失。如果你用的是电池供电同样要保证电池能提供至少2A以上的持续电流而不是只看标称容量。第二个坑是上电复位和烧录失败。我第一次给舵机小板烧程序时一插USB线串口芯片正常枚举但一点Upload就报“Failed to connect to ESP32: Timed out”无论怎么按BOOT都没反应。排查了一圈发现是舵机的电源线和USB共用了同一个5V舵机上电瞬间的电流浪涌把USB的5V拉低导致串口芯片也复位了。烧录期间把舵机电源跳线拔掉问题马上好了。后来我把舵机供电做成独立的接口烧录时直接断开再也不折腾了。第三个坑是WiFi断连这个在桌宠狗场景里特别容易发生。现象是狗动着动着语音延迟越来越高然后日志里出现WiFi重连信息。一开始我以为是路由器信号问题后来发现问题的根源在动作执行阶段舵机电流快速变化会引入高频电磁干扰而ESP32的2.4G天线离舵机线太近就可能导致信噪比下降、丢包率升高。解决思路有几个把舵机线和天线拉开距离在舵机电源端并联1000uF电解电容和0.1uF瓷片电容吸收电流突变如果还不行可以给WiFi换一个更空闲的信道。另外还有个容易被忽视的点ESP32的蓝牙和WiFi可以共存但共用2.4G射频前端如果日常开着BLE广播语音包时延会明显上升。我实测在语音对话时关掉BLE广播时延降低了大概40%。所以我把BLE广播改成只在配网或调试阶段打开。第四个坑是舵机静止状态下的抖动。问题是这样的狗在空闲模式尾巴舵机已经稳定在90度了但能听到“滋滋”的声音从侧面能看到尾尖轻微发抖。我一开始怀疑是舵机本身质量问题换了新舵机还是抖。然后用示波器看PWM输出PCA9685的波形其实是稳定的问题出在电源纹波上。舵机静止时仍然有一个维持力矩电源纹波会让脉宽检测产生微小波动。解决方式是加大电容滤波并且把舵机供电的纹波控制在50mV以内。这个坑在SG90这类低价舵机上尤其明显金属齿轮舵机因为阻尼更大相对好一些。这些坑有一个共同点都是“电”的问题而不是“程序”的问题。所以我的建议是调试桌宠狗时先从供电架构下手把每路电源的地和电压都确认好再动代码。不然程序写得再好硬件供电不稳现象永远千奇百怪。7. 从“会动的音箱”到“真桌宠”可玩的下一个方向当你的小狗能对话能摇尾巴之后它已经算是一个作品了。但桌宠这类项目的奇妙之处在于它永远有下一步可以玩。我自己列了几个接下来的方向按难度排序供参考。第一个方向是把普通舵机换成总线舵机。SG90这类舵机的控制是单向的你发角度它就转但它到底转没转到、遇到阻力没你是不知道的。总线舵机比如飞特FEETECH等品牌的串行总线舵机通过一根线就能串联多个舵机能回读角度、设置速度还能实时反馈电流和温度。这对于要走出复杂步态的四足桌宠来说价值很大相当于给狗装了本体感觉。我试过用总线舵机做一条机械臂回来和桌宠狗的腿部控制做了对比最大的感受是——不用再为每一路舵机单独拉PWM线整机布线清爽太多了而且动作精度确实高一个档次。第二个方向是加传感器。桌宠能不能感知周围环境决定它够不够“灵”。我在小狗头顶加了一个触摸传感器人摸一下它就进入互动状态加了人体红外模块有人走近时它会主动抬头看。这些传感器都很好接占用一两个GPIO就行。如果你想让对话内容更丰富还能加温湿度传感器、空气质量传感器。小智AI的对话链路里可以通过自定义工具tool calling让模型读取传感器数据这样你说“今天房间热不热”它是真的去读温湿度数据而不是瞎编。第三个方向是接入智能家居控制。小智AI本来就有一定的控制能力如果你部署的是完整服务端可以把它接入HomeAssistant。让桌宠狗成为桌面上的家庭助手终端你说“打开客厅灯”语音通路识别意图之后通过MQTT/API把指令下发到设备。这个玩法把桌宠从“玩具”变成了“管家”实用性一下就上来了。我目前的版本只接了语音和动作还没有串HomeAssistant但架构上已经留好了回调接口下一步就是补这块。第四个方向是3D打印定制外壳。网上现成的四足机器人模型很多但真正装舵机时你会发现孔位、舵机臂方向和重心都需要反复试。我的经验是先用PLA打印一个初版骨架装上舵机跑动作根据实际卡顿或者重心偏移去改3D模型第二版再追求外观。为了追求颜值直接打印成品外壳、装上舵机却发现重心太高站不稳这种返工成本太高了。从软件到硬件从语音到动作这个项目把“AI桌宠”这个概念拆得非常具体。它的乐趣不完全在于最终成品有多完美而是在于每一个模块单独测试通过、联调成功之后你看着它从一堆零件变成一个会回应你的小生命那种成就感是真实的。如果你也想动手做一只我的建议是先把舵机和外壳搭好用一段简单的测试程序确认每个动作都顺滑再去灌小智AI语音固件。语音和动作分开调通了再合到一起出问题的时候好定位。等它第一次因为听到你叫名字而抬起头来的时候你就知道这个周末花得值。
返回列表