从零做一只桌面陪伴机器人:双自由度云台、电源避坑与上位机驱动
市面上很多桌面陪伴机器人,要么是套壳的带屏智能音箱,机械结构基本为零;要么是结构过于复杂的双足或四足,打印和调参门槛直接劝退。
这次我们做一只最基础、但动作表现力足够的桌面陪伴机器人原型。目标很简单:基于双自由度(Pan-Tilt)结构,实现头部转动、俯仰,外加一块 OLED 显示状态,能通过上位机 Python 脚本做视线跟随和简单表情联动。这套结构也是后续接入 Hugging Face LeRobot 数据采集或语音多模态的基础底座。
1. 硬件 BOM 清单
做桌面项目,优先考虑低成本和易维护性,耗材与器件都按标准件选型。
| 模块 | 型号 / 规格 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | ESP32-S3-DevKitC-1(或标准 ESP32) | 1 | 负责底层 PWM、屏幕刷新与串口通讯 |
| 舵机 | MG90S 金属齿轮舵机(180 度) | 2 | 别买塑料齿的 SG90,扫齿概率极高 |
| 舵机驱动板 | PCA9685 16 路 I2C 模块 | 1 | 隔离主控负载,避免直接用单片机引脚输出 PWM |
| 供电模块 | MP1584EN 降压模块(调至 5.2V) | 1 | 给舵机独立供电 |
| 屏幕 | 0.96 寸 SSD1306 OLED(I2C) | 1 | 用于显示眼睛表情或系统状态 |
| 结构件 | 3D 打印件(PLA / PETG) | 1 套 | 包含底座、偏航轴支架、俯仰头部外壳 |
| 紧固件 | M26、M28 自攻螺丝,M3 螺丝螺母 | 若干 | 固定舵机盘和结构件 |
2. 机械与供电设计逻辑
为什么不能直接用 ESP32 的 5V 引脚带舵机
很多新手刚接线时,喜欢把舵机的 VCC 直接接到单片机的 5V 输出引脚上。只要两个 MG90S 同时启动,瞬间堵转电流能冲到 1.5A 以上。ESP32 板载的 LDO 根本扛不住这种脉冲电流,结果就是板子不断 Brownout 重启,或者 I2C 通讯直接被拉死。
正确的做法是电源分路,信号共地:
- 外部 7.4V(2S 锂电)或 12V 适配器输入给 MP1584EN,降压到 5.2V 单独送给 PCA9685 的 V+ 接口(专门给舵机供电)。
- ESP32 走电脑 USB 供电,或者由另一路降压供电。
- 必须把降压模块的 GND、PCA9685 的 GND 和 ESP32 的 GND 连在一起。如果不接共地线,PWM 信号没有参考电平,舵机会出现无规律的狂抖。
3D 打印装配细节
- 打印参数建议:层高 0.2 mm,填充 25% 以上。舵机摇臂受力处需要适当加厚外壁(Wall Loops 设为 3 或 4)。
- 安装舵机前,先用舵机测试仪或单片机发送 90 度的中位信号,把舵机转轴固定到行程中点,再压入摇臂和结构件。如果盲装,装好后会发现一边转动行程受限,甚至直接顶坏结构。
3. 接线拓扑
接线尽量集中在 I2C 总线上,减少杂乱引线:
- ESP32:
GPIO 21-> I2C SDA(接 PCA9685 和 SSD1306)GPIO 22-> I2C SCL(接 PCA9685 和 SSD1306)GND-> 系统公共地
- PCA9685:
VCC-> ESP32 3.3V(逻辑供电)V+-> 降压模块输出的 5.2V(舵机动力电)CH0-> 偏航轴舵机(Yaw,水平左右)CH1-> 俯仰轴舵机(Pitch,上下仰俯)
- SSD1306:
VCC-> 3.3VGND-> 公共地
4. 下位机固件与协议设计
下位机主要负责解析串口传来的角度指令,并驱动 PCA9685。这里给出一个精简的 Arduino 框架代码。通信协议采用简单的 ASCII 字符串格式:Y<yaw_angle>P<pitch_angle>\n,例如 Y90P45。
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);
#define SERVOMIN 120 // 对应 0 度
#define SERVOMAX 490 // 对应 180 度
int angleToPulse(int ang) {
return map(ang, 0, 180, SERVOMIN, SERVOMAX);
}
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
pwm.begin();
pwm.setPWMFreq(50); // 模拟舵机常用 50Hz
// 初始化回中
pwm.setPWM(0, 0, angleToPulse(90));
pwm.setPWM(1, 0, angleToPulse(90));
}
void loop() {
if (Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
input.trim();
int yIndex = input.indexOf('Y');
int pIndex = input.indexOf('P');
if (yIndex != -1 && pIndex != -1) {
int yaw = input.substring(yIndex + 1, pIndex).toInt();
int pitch = input.substring(pIndex + 1).toInt();
// 物理限位保护,防止打死卡裂结构
yaw = constrain(yaw, 15, 165);
pitch = constrain(pitch, 30, 130);
pwm.setPWM(0, 0, angleToPulse(yaw));
pwm.setPWM(1, 0, angleToPulse(pitch));
}
}
}
5. 上位机 Python 控制示例
在电脑端,我们可以直接用 Python 发送控制指令。这里写一个平滑插值的呼吸摆头示例,演示如何驱动关节自然转动,而不是直来直去的机械生硬摆动。
import serial
import time
import math
class DesktopCompanion:
def __init__(self, port='/dev/ttyUSB0', baudrate=115200):
self.ser = serial.Serial(port, baudrate, timeout=1)
time.sleep(2) # 等待 ESP32 复位完成
self.current_yaw = 90
self.current_pitch = 90
def send_target(self, yaw: int, pitch: int):
cmd = f"Y{int(yaw)}P{int(pitch)}\n"
self.ser.write(cmd.encode('utf-8'))
def idle_motion(self, duration=10):
"""让机器人在空闲状态下做平滑的头部微动"""
start_time = time.time()
print("开始空闲姿态循环...")
while time.time() - start_time < duration:
t = time.time() - start_time
# 用正弦函数叠加制造不对称的运动轨迹
target_yaw = 90 + 25 * math.sin(t * 0.8)
target_pitch = 90 + 15 * math.cos(t * 1.2)
self.send_target(target_yaw, target_pitch)
time.sleep(0.02) # 50Hz 更新率
def close(self):
self.send_target(90, 90)
self.ser.close()
if __name__ == '__main__':
# 替换为你自己的串口端口,Windows 上一般为 COM3 / COM4 等
robot = DesktopCompanion(port='/dev/ttyUSB0')
try:
robot.idle_motion(duration=15)
except KeyboardInterrupt:
pass
finally:
robot.close()
6. 常见翻车点记录
-
舵机异响与发热严重: 如果舵机运动到特定位置时发出刺耳的滋滋声且表面发烫,说明机械结构干涉导致堵转了。必须在下位机代码里加
constrain()做软限位,不能完全依赖舵机的 0 到 180 度标称范围。 -
串口发送过密导致延迟堆积: 上位机发送频率不要无限高。如果每秒发几百条指令,ESP32 的串口缓冲区会被塞满,导致动作滞后 1 到 2 秒。上位机循环加一个
time.sleep(0.02)(对应 50Hz 控制频率)就完全足够。 -
PCA9685 驱动频段偏差: 廉价的 PCA9685 内部振荡器通常有 5% 到 10% 的时钟误差。如果你设了 50Hz,实际输出可能是 54Hz。如果发现映射出的角度和真实角度偏离很大,拿示波器或逻辑分析仪测一下真实频率,微调库里的预分频系数。
7. 下一步规划
这个原型搭好后,桌面端就有了最基本的物理执行机构。接下来可以挂载两个方向:
- 引入视觉闭环:在头部中央加一颗广角摄像头(如 OV2640 或 USB 免驱模组),用 MediaPipe 抓取人脸坐标,把坐标偏差转换成 PID 控制量给 Yaw / Pitch,实现目光追随。
- 迁移至 LeRobot:如果后续把关节升级为带绝对编码器的总线伺服(如飞特 STS 系列),就可以接入 Hugging Face 的 LeRobot 框架,利用双机械臂或者遥操作手柄录制 Teleoperation 数据,做桌面层面的模仿学习。
License: CC BY-NC 4.0
Updated 13 hours ago
Was this article helpful? Give it a like.
0 comments


