🧪 新教程持续更新中 · 从机械臂到传感器,跟着教程从零搭建
跳到主要内容

03-PWM舵机版本-使用手册 ​

目录 ​

  1. 概述

  2. 硬件接线

  3. 固件编译与烧录

  4. 串口通讯协议

  5. 命令参考

  6. 上位机使用教程

  7. 手势追踪

  8. 手势参数精调

  9. 常见问题


1. 概述 ​

本固件运行在 ESP32-S3 开发板上,通过 PWM 信号控制 8 路舵机驱动灵巧手执行手势。上位机(PC/树莓派/其他 MCU)通过 USB 串口发送二进制帧指令,ESP32 解析后执行对应手势并返回响应。

项目提供两套固件实现:

固件目录特点
ESP-IDF 版(推荐)esp-idf/AmazingHand_Serial/组件化工程结构,FreeRTOS 双任务,生产就绪

两套固件共享相同的串口协议和相同的命令集,手势参数可互相参考。

支持的手势(11 个手势命令) ​

手势命令说明
石头0x01猜拳:全指握拳
剪刀0x02猜拳:食指+中指伸出呈 V 字
布0x03猜拳:全指张开
真棒0x04拇指竖起,其余握拳
嘲讽10x05摇食指("不不不")
嘲讽20x06无名指伸展晃动(替代小拇指)
张开0x07全指张开
握拳0x08全指闭合
OK0x09OK 手势
捏合0x0A捏合手势
指向0x0C食指伸出做"指"动作
直驱0xF0直接控制 8 路舵机角度
设左右手0xF1切换左手/右手模式
重复0xFE重复执行上一次手势
停止0xFF立即终止当前手势
NOP0x00链路测试

注:命令 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说明
PWM14食指关节1
PWM25食指关节2
PWM36中指关节1
PWM47中指关节2
PWM515无名指关节1
PWM616无名指关节2
PWM717拇指关节1
PWM818拇指关节2
5V5V供电(从转接板引出)
GNDGND必须共地,至少接一根

左右手共用同一 GPIO 映射。切"左手"模式时,固件在手势内镜像拇指运动方向,引脚不变。

供电 ​

转接板有两组 5V/GND 供电口:

  • 一组由 Type-C 线引出,接 5V 3A 电源适配器

  • 另一组引出给 ESP32-S3 的 5V 引脚供电(开发板无需再通过 Type-C 供电)


3. 固件编译与烧录 ​

3.1 ESP-IDF 版(推荐) ​

警告:路径要求:ESP-IDF 编译不支持中文路径。请确保工程所在路径完全为英文(含用户文件夹、上级目录)。

工程结构 ​

Plaintext
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 环境变量

编译与烧录 ​

Bash
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(命令帧) ​

Plaintext
┌────────┬────────┬──────────┬────────────────┬──────────┐
│  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 → 主机(响应帧) ​

Plaintext
┌────────┬────────┬────────┬──────────┐
│  0xBB  │ CMD_ID │ STATUS │ CHECKSUM │
│ 1 Byte │ 1 Byte │ 1 Byte │  1 Byte  │
└────────┴────────┴────────┴──────────┘
 帧头      命令ID    状态码    校验和
  • 帧头: 固定 0xBB

  • CMD_ID: 原始命令编号

  • STATUS: 状态码(见下表)

  • CHECKSUM: CMD_ID ^ STATUS

状态码 ​

STATUS含义说明
0x00OK命令已接受,开始执行
0x01无效命令CMD_ID 不在命令表中
0x02参数错误数据长度或内容不正确
0x03忙手势执行中,暂不接受新命令
0x10完成手势执行完毕

4.3 通信时序 ​

Plaintext
主机                          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 01BB 01 00 01 → BB 01 10 11全指握拳
剪刀AA 02 00 02BB 02 00 02 → BB 02 10 12食指+中指伸出
布AA 03 00 03BB 03 00 03 → BB 03 10 13全指张开
真棒AA 04 00 04BB 04 00 04 → BB 04 10 14拇指竖起
嘲讽1AA 05 00 05BB 05 00 05 → BB 05 10 15摇食指(约 2.5s)
嘲讽2AA 06 00 06BB 06 00 06 → BB 06 10 16无名指晃动(约 2.5s)
张开AA 07 00 07BB 07 00 07 → BB 07 10 17全指张开
握拳AA 08 00 08BB 08 00 08 → BB 08 10 18全指闭合
OKAA 09 00 09BB 09 00 09 → BB 09 10 19OK 手势
捏合AA 0A 00 0ABB 0A 00 0A → BB 0A 10 1A捏合手势
指向AA 0C 00 0CBB 0C 00 0C → BB 0C 10 1C食指伸出做"指"动作

5.2 直驱命令 (0xF0) ​

直接控制 8 路舵机角度,8 字节数据分别对应舵机 1-8,每字节取值范围 0-180。

示例:全部舵机归中位(90°)

Plaintext
发送: 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°)

Plaintext
发送: AA F0 08 AA 0A 5A 5A 5A 5A 5A 5A 58
               └─170°  └─10°

5.3 设左右手 (0xF1) ​

1 字节数据:0x01 = 右手,0x02 = 左手。

Plaintext
设右手: AA F1 01 01 F1
设左手: AA F1 01 02 F2

5.4 控制命令 ​

命令HEX 帧说明
NOPAA 00 00 00链路测试,立即返回 BB 00 00 00
重复AA FE 00 FE重复执行上一次手势
停止AA FF 00 FF立即终止当前手势

5.5 响应速查 ​

收到无效命令时(以 0xFC 这个不存在的命令为例):

Plaintext
发送: 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(给客户) ​

  1. 拿到 AmazingHand控制台.exe(或解压后目录)

  2. 双击 exe 直接运行,无需安装 Python

  3. 按下面步骤连接和使用

方式 B:从源码运行 ​

Bash
# 1. 安装依赖
pip install -r requirements.txt

# 2. 运行
python hand_gui.py

GUI 使用步骤 ​

  1. 选串口:顶部下拉框选择 ESP32 对应的 COM 口(Windows 设备管理器查看)

  2. 点"连接":状态灯变绿,日志区显示"已连接",并自动发送 NOP 链路测试

  3. 手势命令:点击「石头」「剪刀」「布」「真棒」「OK」…等按钮,机械手执行对应手势

  4. 左右手:勾选「右手」/「左手」切换拇指镜像方向

  5. 舵机直驱:拖动 8 个滑块,实时控制单个舵机角度(0-180°)

  6. 手指差动控制(推荐):每根手指两个进度条——弯曲/伸直 控制该手指两个舵机反向差动(弯曲伸张),右摆/左摆 控制同向摆动。两个自由度独立,同步驱动

  7. 重复 / 停止:重复上一次手势 / 立即中断当前手势

  8. 通信日志:底部实时显示收发帧和响应状态

手指差动控制说明 ​

每根手指由两个舵机差动驱动,两个自由度正交:

进度条作用机械效果
弯曲◀▶伸直两个舵机反向旋转(差动)手指弯曲或伸直
右摆◀▶左摆两个舵机同向旋转手指左右摆动
  • 弯曲/伸直滑块范围 -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) ​

命令行用法 ​

Bash
# 查看帮助
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         # 全部舵机逐个测试

交互模式 ​

Bash
python serial_test.py COM3

进入 REPL,直接输入简写命令(如 servo 3 180、rock、mid、quit)。

6.3 串口工具手动测试(可选) ​

CoolTerm (macOS/Windows/Linux):

  1. 打开 CoolTerm,Options → 设置波特率 115200, 8N1

  2. Connection → Send String → 选择 Hex

  3. 输入 AA 01 00 01 → 发送 → 机械手执行"石头"

  4. 观察响应区显示 BB 01 00 01 ... BB 01 10 11

SerialTool (macOS):

Bash
brew install serialtool
echo -ne '\xAA\x01\x00\x01' > /dev/cu.usbserial-0001

6.4 Python 控制脚本(自定义开发) ​

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 世界坐标):

依赖版本
Python64 位 3.12
mediapipe0.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 一键部署 ​

  1. 安装 64 位 Python 3.12(从 python.org 下载 64 位 installer,装到 D:\Python312-64)

  2. 双击运行项目根目录 setup_tracking.bat

    • 自动查找 64 位 Python

    • 创建 tracking_env 虚拟环境

    • 安装 mediapipe 0.10.14 等依赖

    • 验证安装

7.4 使用步骤 ​

  1. 用追踪环境启动 GUI:
Plaintext
tracking_env\Scripts\python hand_gui.py
  1. 连接串口(选择 ESP32 对应的 COM 口)

  2. 在"手势追踪"面板选择摄像头编号(默认 0)

  3. 点击 "开始追踪" → 摄像头画面显示在面板中

  4. 手掌对准摄像头:

    • 手指弯曲/伸直 → 灵巧手对应手指弯曲/伸直

    • 手掌左右翻转 → 灵巧手手指左右摆动

  5. 点击 "停止追踪" 结束

若未检测到手,面板显示"未检测到手";检测到后显示"检测到手: Right/Left"。

7.5 参数标定 ​

映射系数在 hand_tracking.py 底部(FLEX_SCALE / BASE_SCALE):

Python
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 参数含义 ​

C
// 例:石头手势
#define ROCK_IDX_OFF1    70    // 食指关节1 偏移量
#define ROCK_IDX_OFF2   -70    // 食指关节2 偏移量
  • 正值 = 弯曲握紧,负值 = 伸展打开

  • 每根手指 2 个偏移量,相对 middle_pos(默认 90°)

  • 差动结构:两舵机偏移差值 = 伸展/收缩,同向分量 = 左右偏

8.3 调参步骤 ​

  1. 找到对应手势的 #define 宏

  2. 修改数值(增大 → 幅度更大;减小 → 幅度更小)

  3. 重新编译烧录,用上位机测试效果

  4. 反复微调直到动作自然


9. 常见问题 ​

Q1: 上位机连不上串口? ​

  1. 确认 ESP32 已通过 Type-C 连接电脑

  2. 检查设备管理器中的 COM 口号是否与 GUI 选择一致

  3. 确认波特率 115200

  4. 断开其他占用串口的软件

Q2: 发送命令没反应? ​

  1. 先发 AA 00 00 00(NOP),应收到 BB 00 00 00

  2. 确认固件已烧录且目标芯片是 ESP32-S3

  3. 检查接线(GND 是否共地)

Q3: 手势动作幅度不对或方向反了? ​

进入手势参数精调(见 第 7 节)调整对应宏。

Q4: 左右手模式影响哪些手势? ​

石头/剪刀/真棒/OK/捏合等含拇指的手势,切换左右手后拇指镜像方向。

Q5: 手指卡住、伸不出来? ​

所有收缩类手势执行前会自动"先全手展开再收拢",避免手指被上一手势挡住。若仍卡住,检查机械装配或减小收缩幅度。