03-PWM舵机版本-使用手册
目录
概述
硬件接线
固件编译与烧录
串口通讯协议
命令参考
上位机使用教程
手势追踪
手势参数精调
常见问题
1. 概述
本固件运行在 ESP32-S3 开发板上,通过 PWM 信号控制 8 路舵机驱动灵巧手执行手势。上位机(PC/树莓派/其他 MCU)通过 USB 串口发送二进制帧指令,ESP32 解析后执行对应手势并返回响应。
项目提供两套固件实现:
| 固件 | 目录 | 特点 |
|---|---|---|
| ESP-IDF 版(推荐) | esp-idf/AmazingHand_Serial/ | 组件化工程结构,FreeRTOS 双任务,生产就绪 |
两套固件共享相同的串口协议和相同的命令集,手势参数可互相参考。
支持的手势(11 个手势命令)
| 手势 | 命令 | 说明 |
|---|---|---|
| 石头 | 0x01 | 猜拳:全指握拳 |
| 剪刀 | 0x02 | 猜拳:食指+中指伸出呈 V 字 |
| 布 | 0x03 | 猜拳:全指张开 |
| 真棒 | 0x04 | 拇指竖起,其余握拳 |
| 嘲讽1 | 0x05 | 摇食指("不不不") |
| 嘲讽2 | 0x06 | 无名指伸展晃动(替代小拇指) |
| 张开 | 0x07 | 全指张开 |
| 握拳 | 0x08 | 全指闭合 |
| OK | 0x09 | OK 手势 |
| 捏合 | 0x0A | 捏合手势 |
| 指向 | 0x0C | 食指伸出做"指"动作 |
| 直驱 | 0xF0 | 直接控制 8 路舵机角度 |
| 设左右手 | 0xF1 | 切换左手/右手模式 |
| 重复 | 0xFE | 重复执行上一次手势 |
| 停止 | 0xFF | 立即终止当前手势 |
| NOP | 0x00 | 链路测试 |
注:命令 0x0B 已停用(原"拇指朝下"与"真棒"动作重复,已移除)。
2. 硬件接线
适用硬件
| 项目 | 型号 |
|---|---|
| 主控 | ESP32-S3 开发板(优信 YX-ESP32-S3 或同类) |
| 舵机 | 8 路 PWM 模拟舵机 (SG90 或同类) |
| 转接板 | PWM 舵机转接板 |
ESP32-S3 开发板有两个 Type-C 接口:
内置 USB Serial/JTAG:直连 ESP32-S3 芯片内置 USB 控制器
外挂 FT232:通过 FT232 转串口芯片通信
两个接口均可用于串口通讯,任选其一即可。上位机选择对应的串口设备名。
舵机 → 转接板
8 个舵机的 3P 插头按 ID 号插入转接板的舵机 1-8 排针。
转接板 → ESP32-S3
| 转接板 | ESP32-S3 GPIO | 说明 |
|---|---|---|
| PWM1 | 4 | 食指关节1 |
| PWM2 | 5 | 食指关节2 |
| PWM3 | 6 | 中指关节1 |
| PWM4 | 7 | 中指关节2 |
| PWM5 | 15 | 无名指关节1 |
| PWM6 | 16 | 无名指关节2 |
| PWM7 | 17 | 拇指关节1 |
| PWM8 | 18 | 拇指关节2 |
| 5V | 5V | 供电(从转接板引出) |
| GND | GND | 必须共地,至少接一根 |
左右手共用同一 GPIO 映射。切"左手"模式时,固件在手势内镜像拇指运动方向,引脚不变。
供电
转接板有两组 5V/GND 供电口:
一组由 Type-C 线引出,接 5V 3A 电源适配器
另一组引出给 ESP32-S3 的 5V 引脚供电(开发板无需再通过 Type-C 供电)
3. 固件编译与烧录
3.1 ESP-IDF 版(推荐)
警告:路径要求:ESP-IDF 编译不支持中文路径。请确保工程所在路径完全为英文(含用户文件夹、上级目录)。
工程结构
esp-idf/AmazingHand_Serial/
├── CMakeLists.txt # 顶层工程配置
├── sdkconfig.defaults # 默认 Kconfig 配置
├── main/
│ ├── CMakeLists.txt
│ └── main.c # FreeRTOS 双任务 + 初始化(胶水层)
└── components/
├── hand_servo/ # 舵机驱动(LEDC PWM + 校准数据)
├── hand_gestures/ # 手势参数宏 + 手势函数 + 左右手控制
└── hand_protocol/ # 串口帧解析 + 命令分发构建环境
ESP-IDF v6.0.1
目标芯片:ESP32-S3
已配置
idf.py环境变量
编译与烧录
cd esp-idf/AmazingHand_Serial
# 1. 设置目标芯片(首次或更换芯片时)
idf.py set-target esp32s3
# 2. 编译
idf.py build
# 3. 烧录(Windows: 用 COM 口,如 COM3)
idf.py -p COM3 flash
# 4. 串口监控(可选,波特率 115200)
idf.py -p COM3 monitor修改任何
components/或main/下的源码后,重新idf.py build && idf.py -p COM3 flash即可。
3.3 校准(可选,首次使用建议)
舵机中心位和脉宽需按实际机构校准。有两种方式:
- ESP-IDF 版:编辑
components/hand_servo/hand_servo.c中middle_pos[8](第 40 行)和min_pw/mid_pw/max_pw[8](第 45-47 行)
校准后需重新编译烧录。
4. 串口通讯协议
4.1 物理层
| 参数 | 值 |
|---|---|
| 接口 | USB Serial (UART0) |
| 波特率 | 115200 |
| 数据位 | 8 |
| 校验位 | 无 (None) |
| 停止位 | 1 |
| 流控 | 无 |
4.2 帧格式
主机 → ESP32(命令帧)
┌────────┬────────┬──────────┬────────────────┬──────────┐
│ 0xAA │ CMD_ID │ DATA_LEN │ DATA[0 .. N-1] │ CHECKSUM │
│ 1 Byte │ 1 Byte │ 1 Byte │ N Bytes │ 1 Byte │
└────────┴────────┴──────────┴────────────────┴──────────┘
帧头 命令ID 数据长度 数据负载 校验和帧头: 固定
0xAA,标识一帧的开始CMD_ID: 命令编号(见 命令参考)
DATA_LEN: 数据负载的字节数(0-8,超过 8 的帧无效)
DATA: 数据负载,长度由 DATA_LEN 决定
CHECKSUM:
CMD_ID ^ DATA_LEN ^ DATA[0] ^ ... ^ DATA[N-1](XOR 校验)
如果 DATA_LEN = 0,则 CHECKSUM = CMD_ID。
ESP32 → 主机(响应帧)
┌────────┬────────┬────────┬──────────┐
│ 0xBB │ CMD_ID │ STATUS │ CHECKSUM │
│ 1 Byte │ 1 Byte │ 1 Byte │ 1 Byte │
└────────┴────────┴────────┴──────────┘
帧头 命令ID 状态码 校验和帧头: 固定
0xBBCMD_ID: 原始命令编号
STATUS: 状态码(见下表)
CHECKSUM:
CMD_ID ^ STATUS
状态码
| STATUS | 含义 | 说明 |
|---|---|---|
| 0x00 | OK | 命令已接受,开始执行 |
| 0x01 | 无效命令 | CMD_ID 不在命令表中 |
| 0x02 | 参数错误 | 数据长度或内容不正确 |
| 0x03 | 忙 | 手势执行中,暂不接受新命令 |
| 0x10 | 完成 | 手势执行完毕 |
4.3 通信时序
主机 ESP32
│ │
│──── [AA 01 00 01] ────────→│ 发送"石头"命令
│ │
│←─── [BB 01 00 01] ─────────│ ACK: 命令已接受
│ │
│ (执行手势中)
│ │
│←─── [BB 01 10 11] ─────────│ 完成: 手势执行完毕
│ │
│──── [AA 02 00 02] ────────→│ 发送"剪刀"命令
│ │
│←─── [BB 02 00 02] ─────────│ ACK
│ │4.4 停止手势 & 帧超时
发送
[AA FF 00 FF]可随时中断正在执行的手势ESP32 在 200ms 内未收完一帧会自动丢弃(防止丢字节导致永久失步)
校验和不匹配的帧会被静默丢弃,上位机应实现超时重发
4.5 左右手模式
默认右手模式。发送 [AA F1 01 02 F2] 切换左手,[AA F1 01 01 F1] 切回右手。左右手影响拇指的运动方向(石头/剪刀/真棒/OK/捏合等含拇指手势)。
5. 命令参考
5.1 挥手势命令 (0x01-0x0A, 0x0C)
这些命令无需数据负载 (DATA_LEN=0),ESP32 收到后立即执行对应手势。
| 命令 | HEX 帧 | 响应 | 说明 |
|---|---|---|---|
| 石头 | AA 01 00 01 | BB 01 00 01 → BB 01 10 11 | 全指握拳 |
| 剪刀 | AA 02 00 02 | BB 02 00 02 → BB 02 10 12 | 食指+中指伸出 |
| 布 | AA 03 00 03 | BB 03 00 03 → BB 03 10 13 | 全指张开 |
| 真棒 | AA 04 00 04 | BB 04 00 04 → BB 04 10 14 | 拇指竖起 |
| 嘲讽1 | AA 05 00 05 | BB 05 00 05 → BB 05 10 15 | 摇食指(约 2.5s) |
| 嘲讽2 | AA 06 00 06 | BB 06 00 06 → BB 06 10 16 | 无名指晃动(约 2.5s) |
| 张开 | AA 07 00 07 | BB 07 00 07 → BB 07 10 17 | 全指张开 |
| 握拳 | AA 08 00 08 | BB 08 00 08 → BB 08 10 18 | 全指闭合 |
| OK | AA 09 00 09 | BB 09 00 09 → BB 09 10 19 | OK 手势 |
| 捏合 | AA 0A 00 0A | BB 0A 00 0A → BB 0A 10 1A | 捏合手势 |
| 指向 | AA 0C 00 0C | BB 0C 00 0C → BB 0C 10 1C | 食指伸出做"指"动作 |
5.2 直驱命令 (0xF0)
直接控制 8 路舵机角度,8 字节数据分别对应舵机 1-8,每字节取值范围 0-180。
示例:全部舵机归中位(90°)
发送: AA F0 08 5A 5A 5A 5A 5A 5A 5A 5A F8
│ │ │ └── 8 个 0x5A (90°) ──┘ │
│ │ │ └── CHECKSUM
│ │ └── DATA_LEN = 8
│ └── CMD_DIRECT_DRIVE
└── 帧头校验和 = F0 ^ 08 ^ 5A ^ 5A ^ 5A ^ 5A ^ 5A ^ 5A ^ 5A ^ 5A = F8
8 个相同的 0x5A 两两 XOR 得 0x00,最终
0xF0 ^ 0x08 ^ 0x00 = 0xF8
示例:食指张开(舵机1=170°, 舵机2=10°),其余归中(90°)
发送: AA F0 08 AA 0A 5A 5A 5A 5A 5A 5A 58
└─170° └─10°5.3 设左右手 (0xF1)
1 字节数据:0x01 = 右手,0x02 = 左手。
设右手: AA F1 01 01 F1
设左手: AA F1 01 02 F25.4 控制命令
| 命令 | HEX 帧 | 说明 |
|---|---|---|
| NOP | AA 00 00 00 | 链路测试,立即返回 BB 00 00 00 |
| 重复 | AA FE 00 FE | 重复执行上一次手势 |
| 停止 | AA FF 00 FF | 立即终止当前手势 |
5.5 响应速查
收到无效命令时(以 0xFC 这个不存在的命令为例):
发送: AA FC 00 FC
响应: BB FC 01 FD (STATUS=0x01 无效命令)校验和验证:
FC ^ 00 = FC,响应FC ^ 01 = FD
6. 上位机使用教程
项目根目录提供两个上位机工具:
| 工具 | 文件 | 类型 | 用途 |
|---|---|---|---|
| 图形界面 | hand_gui.py / 打包的 exe | 可视化 | 点按钮做手势、滑块直驱、日志 |
| 命令行测试 | serial_test.py | 指令化 | 发送手势/单舵机/扫频,自动化测试 |
两者都只需依赖
pyserial。安装:pip install -r requirements.txt
6.1 可视化 GUI(推荐)
方式 A:运行打包好的 exe(给客户)
拿到
AmazingHand控制台.exe(或解压后目录)双击 exe 直接运行,无需安装 Python
按下面步骤连接和使用
方式 B:从源码运行
# 1. 安装依赖
pip install -r requirements.txt
# 2. 运行
python hand_gui.pyGUI 使用步骤
选串口:顶部下拉框选择 ESP32 对应的 COM 口(Windows 设备管理器查看)
点"连接":状态灯变绿,日志区显示"已连接",并自动发送 NOP 链路测试
手势命令:点击「石头」「剪刀」「布」「真棒」「OK」…等按钮,机械手执行对应手势
左右手:勾选「右手」/「左手」切换拇指镜像方向
舵机直驱:拖动 8 个滑块,实时控制单个舵机角度(0-180°)
手指差动控制(推荐):每根手指两个进度条——弯曲/伸直 控制该手指两个舵机反向差动(弯曲伸张),右摆/左摆 控制同向摆动。两个自由度独立,同步驱动
重复 / 停止:重复上一次手势 / 立即中断当前手势
通信日志:底部实时显示收发帧和响应状态
手指差动控制说明
每根手指由两个舵机差动驱动,两个自由度正交:
| 进度条 | 作用 | 机械效果 |
|---|---|---|
| 弯曲◀▶伸直 | 两个舵机反向旋转(差动) | 手指弯曲或伸直 |
| 右摆◀▶左摆 | 两个舵机同向旋转 | 手指左右摆动 |
弯曲/伸直滑块范围 -70 ~ +70(0 = 中立,+70 = 完全伸直,-70 = 完全弯曲)
左右摆动滑块范围 60 ~ 120(90 = 中立,60 = 向右摆,120 = 向左摆)
舵机角度 =
摆动 ± 弯曲,两个舵机同步更新并发送直驱命令
例(食指 GPIO4/5):弯曲滑块拖到 +70、摆动保持 90 → 舵机4=160°、舵机5=20°(完全伸直);弯曲拖到 -70 → 舵机4=20°、舵机5=160°(完全弯曲)。
6.2 指令化测试(serial_test.py)
命令行用法
# 查看帮助
python serial_test.py
# 链路测试
python serial_test.py COM3 nop
# 发送手势
python serial_test.py COM3 rock # 石头
python serial_test.py COM3 thumbs_up # 真棒
python serial_test.py COM3 index # 指向
python serial_test.py COM3 open # 张开
python serial_test.py COM3 close # 握拳
# 单舵机直驱
python serial_test.py COM3 servo 1 90 # 舵机1 → 90°
# 全部归中
python serial_test.py COM3 mid
# 设左右手
python serial_test.py COM3 hand L # 左手
python serial_test.py COM3 hand R # 右手
# 扫频 / 自检
python serial_test.py COM3 sweep 1 # 舵机1 扫频
python serial_test.py COM3 test # 全部舵机逐个测试交互模式
python serial_test.py COM3进入 REPL,直接输入简写命令(如 servo 3 180、rock、mid、quit)。
6.3 串口工具手动测试(可选)
CoolTerm (macOS/Windows/Linux):
打开 CoolTerm,
Options→ 设置波特率 115200, 8N1Connection→Send String→ 选择Hex输入
AA 01 00 01→ 发送 → 机械手执行"石头"观察响应区显示
BB 01 00 01 ... BB 01 10 11
SerialTool (macOS):
brew install serialtool
echo -ne '\xAA\x01\x00\x01' > /dev/cu.usbserial-00016.4 Python 控制脚本(自定义开发)
#!/usr/bin/env python3
"""灵巧手串口控制 - Python 上位机示例"""
import serial
import time
SERIAL_PORT = "/dev/cu.usbserial-0001" # 修改为实际端口
BAUD_RATE = 115200
# 命令定义(与固件命令集一致)
CMD = {
"nop": 0x00,
"rock": 0x01,
"scissors": 0x02,
"paper": 0x03,
"thumbs_up": 0x04,
"taunt1": 0x05,
"taunt2": 0x06,
"open": 0x07,
"close": 0x08,
"ok": 0x09,
"pinch": 0x0A,
"index": 0x0C,
"direct": 0xF0,
"set_side": 0xF1,
"repeat": 0xFE,
"stop": 0xFF,
}
def calc_checksum(cmd_id, data=b""):
"""计算 XOR 校验和 (CMD ^ LEN ^ DATA[0..N])"""
result = cmd_id ^ len(data)
for b in data:
result ^= b
return result & 0xFF
def send_command(ser, cmd_id, data=b""):
"""发送命令帧,返回 (ack_status, completion_status)"""
data_len = len(data)
checksum = calc_checksum(cmd_id, data)
frame = bytes([0xAA, cmd_id, data_len]) + data + bytes([checksum])
ser.write(frame)
print(f"发送: {frame.hex(' ').upper()}")
def read_response(ser, timeout=1.0):
"""读取一个响应帧 [0xBB CMD STATUS CKSUM]"""
ser.timeout = timeout
while True:
b = ser.read(1)
if not b:
return None
if b[0] == 0xBB:
buf = ser.read(3)
if len(buf) == 3:
expected = buf[0] ^ buf[1]
if expected == buf[2]:
return bytes([0xBB]) + buf
return None
def set_side(ser, side):
"""设置左右手: side='R' 右手, side='L' 左手"""
val = 0x01 if side.upper() == 'R' else 0x02
send_command(ser, CMD["set_side"], bytes([val]))
def direct_drive(ser, angles):
"""直驱 8 路舵机: angles 为 8 个 0-180 的角度列表"""
data = bytes([min(180, max(0, a)) for a in angles[:8]])
send_command(ser, CMD["direct"], data)
# ===== 使用示例 =====
if __name__ == "__main__":
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(1) # 等待 ESP32 复位完成
# 1. 链路测试
print("=== NOP 链路测试 ===")
send_command(ser, CMD["nop"])
# 2. 猜拳游戏
print("\n=== 猜拳: 石头 → 剪刀 → 布 ===")
for name in ["rock", "scissors", "paper"]:
send_command(ser, CMD[name])
time.sleep(0.5)
# 3. 真棒手势
print("\n=== 真棒 ===")
send_command(ser, CMD["thumbs_up"])
# 4. 停止测试
print("\n=== 停止测试 ===")
send_command(ser, CMD["taunt1"]) # 开始摇食指
time.sleep(0.3)
send_command(ser, CMD["stop"]) # 立即停止
# 5. 直驱模式: 全部归中
print("\n=== 直驱: 归中 ===")
direct_drive(ser, [90] * 8)
ser.close()7. 手势追踪
用摄像头实时识别手掌,驱动灵巧手手指弯曲/伸直和左右摆动。基于官方 AmazingHand 的手部追踪算法(MediaPipe 21 点关键点 + 3D 世界坐标旋转)。
7.1 原理
摄像头对准手掌 → MediaPipe 识别 21 个手部关键点
构建手部局部坐标系,计算 4 指指尖的 3D 向量
指尖向量 → 每根手指的 (flex, base) 差动参数 → 复用直驱协议发往舵机
7.2 环境要求
手势追踪依赖 64 位 Python + mediapipe 0.10.14(旧版 solutions API,只有它能做 3D 世界坐标):
| 依赖 | 版本 |
|---|---|
| Python | 64 位 3.12 |
| mediapipe | 0.10.14 |
| numpy | <2.0 |
| scipy | >=1.9 |
| opencv-python | >=4.10 |
| Pillow | >=10.0 |
注意:现有默认环境是 32 位 Python,无法安装 mediapipe。需另装 64 位 Python 3.12(装到 D 盘如
D:\Python312-64,与现有 32 位完全并存,不冲突)。
7.3 一键部署
安装 64 位 Python 3.12(从 python.org 下载 64 位 installer,装到
D:\Python312-64)双击运行项目根目录
setup_tracking.bat自动查找 64 位 Python
创建
tracking_env虚拟环境安装 mediapipe 0.10.14 等依赖
验证安装
7.4 使用步骤
- 用追踪环境启动 GUI:
tracking_env\Scripts\python hand_gui.py连接串口(选择 ESP32 对应的 COM 口)
在"手势追踪"面板选择摄像头编号(默认 0)
点击 "开始追踪" → 摄像头画面显示在面板中
手掌对准摄像头:
手指弯曲/伸直 → 灵巧手对应手指弯曲/伸直
手掌左右翻转 → 灵巧手手指左右摆动
点击 "停止追踪" 结束
若未检测到手,面板显示"未检测到手";检测到后显示"检测到手: Right/Left"。
7.5 参数标定
映射系数在 hand_tracking.py 底部(FLEX_SCALE / BASE_SCALE):
FLEX_SCALE = 80.0 # 指尖 z 分量 → 弯曲/伸直 (flex)
BASE_SCALE = 30.0 # 指尖 x 分量 → 左右摆动 (base)若弯曲/伸直幅度不够或方向反了,调整 FLEX_SCALE;左右摆幅度不够或反了,调整 BASE_SCALE(正负号调方向)。
8. 手势参数精调
8.1 参数位置
每个手势的角度偏移量用 #define 宏定义,无需改逻辑代码,只调数值。
- ESP-IDF 版:
components/hand_gestures/hand_gestures.c顶部"手势参数(用户可调)"区
8.2 参数含义
// 例:石头手势
#define ROCK_IDX_OFF1 70 // 食指关节1 偏移量
#define ROCK_IDX_OFF2 -70 // 食指关节2 偏移量正值 = 弯曲握紧,负值 = 伸展打开
每根手指 2 个偏移量,相对
middle_pos(默认 90°)差动结构:两舵机偏移差值 = 伸展/收缩,同向分量 = 左右偏
8.3 调参步骤
找到对应手势的
#define宏修改数值(增大 → 幅度更大;减小 → 幅度更小)
重新编译烧录,用上位机测试效果
反复微调直到动作自然
9. 常见问题
Q1: 上位机连不上串口?
确认 ESP32 已通过 Type-C 连接电脑
检查设备管理器中的 COM 口号是否与 GUI 选择一致
确认波特率 115200
断开其他占用串口的软件
Q2: 发送命令没反应?
先发
AA 00 00 00(NOP),应收到BB 00 00 00确认固件已烧录且目标芯片是 ESP32-S3
检查接线(GND 是否共地)
Q3: 手势动作幅度不对或方向反了?
进入手势参数精调(见 第 7 节)调整对应宏。
Q4: 左右手模式影响哪些手势?
石头/剪刀/真棒/OK/捏合等含拇指的手势,切换左右手后拇指镜像方向。
Q5: 手指卡住、伸不出来?
所有收缩类手势执行前会自动"先全手展开再收拢",避免手指被上一手势挡住。若仍卡住,检查机械装配或减小收缩幅度。

