精准 EMG 控制机械手:InMoov 集成与构建指南
项目背景
我们团队长期研究机械手,曾尝试制作可靠的假肢手。本项目采用开源的 InMoov 手作为硬件平台,重点解决 EMG(肌电)信号控制问题。手部组装细节请参考 InMoov 官方文档,此处聚焦控制系统。技术演进可参考后续项目:8 通道 EMG 配合 LED 可视化。
1. 信号处理
控制基于 EMG——肌肉电活动。使用三个 uECG 设备采集 EMG 信号(uECG 本为心电监测仪,但其通用 ADC 可测量任何生物信号)。uECG 在特殊模式下输出 32 个频谱箱数据及“肌肉窗口”平均值(75–440 Hz 频谱强度均值)。频谱图如下:

垂直轴为频率(0–488 Hz,约 15 Hz 步进),水平轴为时间(约 10 秒),颜色编码强度(蓝低→绿中→黄高→红更高)。为实现可靠手势识别,需在 PC 端对频谱图做深度处理;但为简单驱动手指,仅用三通道平均值即可——uECG 在特定数据包字节提供该值,Arduino 可直接解析。三通道原始值随手指动作变化:

红、绿、蓝分别对应拇指、无名指、中指用力时的肌肉信号。肉眼可分辨,但需转化为程序可用的“手指得分”。问题在于肌肉信号混合:如拇指和中指动作时蓝色通道强度相近,但红/绿通道不同。为“解混”,采用如下公式:
S₀ = V₀² / ((V₁·a₀ + b₀)(V₂·c₀ + d₀))
其中 S₀ 为通道 0 得分,V₀,V₁,V₂ 为三通道原始值,a,b,c,d 为人工调整系数(a,c ∈ [0.3,2.0],b,d 为 15 和 20,需根据传感器贴位微调)。同理计算 S₁、S₂。处理后信号清晰分离:

此时可直接用阈值比较将得分映射为舵机角度。
2. 电路原理图

电路简洁:nRF24 模块、PCA9685(或同类 I2C PWM 驱动器)、大电流 5V 电源(≥5A 以稳定驱动所有舵机)。
连接清单
- nRF24: GND→GND, VCC→3.3V, CE→D9, CSN→D8, SCK→D13, MOSI→D11, MISO→D12
- PCA9685: SDA→A4, SCL→A5, VCC→5V, GND→GND, V+→大电流 5V, GND→大电流 GND
- 手指舵机:接 PCA 通道 0–4(拇指→0,食指→1,中指→2,无名指→3,小指→4)
3. EMG 传感器贴放

贴放位置对信号质量至关重要。看似反直觉,拇指肌肉信号在前臂对侧更清晰,因此一个传感器贴于前臂背侧靠近肘部;所有传感器均贴近肘关节(肌肉腹最丰满处),但个体差异大,需自行寻找最佳位置。
4. 代码实现
运行主程序前,需先获取各 uECG 设备的唯一 ID(取消注释第 101 行,逐个开机记录),填入 unit_ids 数组(第 37 行)。
#include <SPI.h>
#include <RF24.h>
#include <RF24_config.h>
#include <nRF24L01.h>
#include <Adafruit_PWMServoDriver.h>
#include <Wire.h>
#define SERVOMIN 150
#define SERVOMAX 600
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
int rf_cen = 9, rf_cs = 8;
RF24 rf(rf_cen, rf_cs);
uint8_t pipe_rx[8] = {0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};
uint8_t swapbits(uint8_t a) { // uECG 管道地址位序反转
uint8_t v = 0;
if(a & 0x80) v |= 0x01; if(a & 0x40) v |= 0x02; if(a & 0x20) v |= 0x04;
if(a & 0x10) v |= 0x08; if(a & 0x08) v |= 0x10; if(a & 0x04) v |= 0x20;
if(a & 0x02) v |= 0x40; if(a & 0x01) v |= 0x80; return v;
}
long last_servo_upd = 0;
byte in_pack[32];
unsigned long unit_ids[3] = {4294963881, 4294943100, 28358}; // 替换为你的 uECG ID
int unit_vals[3] = {0,0,0};
float tgt_angles[5], cur_angles[5];
float angle_open = 30, angle_closed = 150;
void setup() {
SPI.begin(); SPI.setBitOrder(MSBFIRST);
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
for(int x=0;x<8;x++) pipe_rx[x] = swapbits(pipe_rx[x]);
rf.begin(); rf.setDataRate(RF24_1MBPS); rf.setAddressWidth(4);
rf.setChannel(22); rf.setRetries(0,0); rf.setAutoAck(0);
rf.disableDynamicPayloads(); rf.setPayloadSize(32);
rf.openReadingPipe(0, pipe_rx); rf.setCRCLength(RF24_CRC_DISABLED);
rf.disableCRC(); rf.startListening();
// 注意:uECG 必须长按切换至原始数据模式(默认 BLE 模式 nRF24 无法接收)
Serial.begin(115200);
pwm.begin(); pwm.setPWMFreq(60);
for(int i=0;i<5;i++) { tgt_angles[i]=angle_open; cur_angles[i]=angle_open; }
}
void setAngle(int n, float angle) {
pwm.setPWM(n, 0, SERVOMIN + angle * 0.005556 * (SERVOMAX - SERVOMIN));
}
float angle_speed = 15;
float v0=0, v1=0, v2=0;
void loop() {
if(rf.available()) {
rf.read(in_pack, 32);
byte u1=in_pack[3], u2=in_pack[4], u3=in_pack[5], u4=in_pack[6];
unsigned long id = (u1<<24)|(u2<<16)|(u3<<8)|u4;
if(in_pack[7] != 32) id = 0; // 非 EMG 模式包
int val = in_pack[10];
if(val != in_pack[11]) id = 0; // 双字节校验防射频干扰
for(int n=0;n<3;n++) if(id == unit_ids[n]) unit_vals[n] = val;
}
long ms = millis();
if(ms - last_servo_upd > 20) { // 限制舵机更新频率
last_servo_upd = ms;
for(int n=0;n<5;n++) { // 平滑插值到目标角度
if(cur_angles[n] < tgt_angles[n] - angle_speed/2) cur_angles[n] += angle_speed;
if(cur_angles[n] > tgt_angles[n] + angle_speed/2) cur_angles[n] -= angle_speed;
}
for(int n=0;n<5;n++) setAngle(n, cur_angles[n]);
// 指数滑动平均滤波
v0 = v0*0.7 + 0.3*(float)unit_vals[0];
v1 = v1*0.7 + 0.3*(float)unit_vals[1];
v2 = v2*0.7 + 0.3*(float)unit_vals[2];
// 计算三通道得分(解混公式)
float scor0 = 4.0*v0*v0/((v1*0.3+20)*(v2*1.3+15));
float scor1 = 4.0*v1*v1/((v0*2.0+20)*(v2*2.0+20));
float scor2 = 4.0*v2*v2/((v0*1.2+20)*(v1*0.5+15));
Serial.print(scor0); Serial.print(' '); Serial.print(scor1); Serial.print(' '); Serial.println(scor2);
// 阈值判断驱动手指
if(scor2 < 0.5) tgt_angles[0] = angle_open; // 拇指
if(scor2 > 1.0) tgt_angles[0] = angle_closed;
if(scor1 < 0.5) { tgt_angles[1]=angle_open; tgt_angles[2]=angle_open; } // 食指+中指
if(scor1 > 1.0) { tgt_angles[1]=angle_closed; tgt_angles[2]=angle_closed; }
if(scor0 < 0.5) { tgt_angles[3]=angle_open; tgt_angles[4]=angle_open; } // 无名指+小指
if(scor0 > 1.0) { tgt_angles[3]=angle_closed; tgt_angles[4]=angle_closed; }
}
}
5. 实测效果
约 2 小时调试后获得相当可靠的操控(视频展示典型案例)。当前仅能识别手指张合,且仅区分三组肌肉(拇指、食中指合一、无名小指合一)。核心“AI”仅 3 行代码,使用单通道单数值。若在 PC/手机端分析 32 箱频谱图、增加 EMG 通道,可识别更复杂模式。这只是起点,同类系统应用远不止机械手控制。
原理图文件
Fritzing 原理图文件:nrf24_hand_control_5jcEeCP8a3.fzz
制造工艺
- 避孕药:历史、获益、风险与生产
- 基于 AT89S52 单片机与 LM35 传感器的温控系统设计
- Arduino自动列车控制器 – 可编程调度/加减速/双向运行
- 基于 Arduino、1Sheeld 与红外控制的万能安卓遥控器
- 基于 Arduino MKR1000 与 ThingSpeak 的远程 IoT 机械臂控制系统
- Firmata + Xbox 手柄无线遥控 Arduino 小车:UWP 应用桥接方案
- DIY 智能 Arduino 机械臂:主从控制与动作记忆教程
- 学生构建B&R驱动先进机器人垃圾分拣系统
- AI驱动光子芯片塑造宽带光实现智能光学
- 3DG高级机器人仿真软件:精简机器人自动化集成
- Trinamic 与 Maxim Integrated 发布参考设计,加速工业机器人末端执行器开发
- 构建无线红外传感器驱动的机器人小车