亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Manufacturing Technology >> 制造工艺

使用 EMG 的机器人手控制

组件和用品

uECG 设备
× 3
inMoov 手
× 1
Arduino Nano R3
× 1
Adafruit PCA9685 16 通道 PWM 驱动器
× 1
nRF24 模块(通用)
× 1

关于这个项目

我们的团队在机械手方面有着悠久的历史。有一段时间我们试图制作一个可靠的假手,但对于这个项目,我使用了现有开源手的一个很好的例子:inMoov。

我不会深入了解手工组装的细节——它在项目网站上有很好的描述,而且非常复杂。我将在这里专注于控制,因为这是全新的 :)
此外,请在下一个项目中查看这项技术如何随着时间的推移而演变:https://www.hackster.io/the_3d6/seeing-muscles-at -work-8-channel-emg-with-leds-039d69

1. 信号处理

控制基于 EMG - 肌肉的电活动。 EMG 信号由三个 uECG 设备获得(我知道,它应该是 ECG 监视器,但由于它基于通用 ADC,它可以测量任何生物信号 - 包括 EMG)。对于 EMG 处理,uECG 有一种特殊的模式,它发送 32-bin 频谱数据和“肌肉窗口”平均值(75 到 440 Hz 之间的平均频谱强度)。光谱图像如下所示:

这里频率在垂直轴上(在 3 个图的每一个上,底部是低频,顶部是高 - 从 0 到 488 Hz,步长约 15 Hz),时间在水平轴上(此处左侧的旧数据整体在屏幕上大约 10 秒)。强度用颜色编码:蓝色 - 低,绿色 - 中,黄色 - 高,红色 - 甚至更高。为了可靠的手势识别,需要对这些图像进行适当的 PC 处理。但是对于机械手手指的简单激活,仅使用 3 个通道上的平均值就足够了 - uECG 方便地以某些数据包字节提供它,以便 Arduino 草图可以解析它。这些值看起来简单得多:

红色、绿色、蓝色图表是当我相应地挤压拇指、无名指和中指时,来自不同肌肉群的 uECG 设备的原始值。在我们看来,这些情况显然是不同的,但我们需要以某种方式将这些值转换为“手指分数”,以便程序可以将值输出到手舵机。问题是,来自肌肉群的信号是“混合的”:在第 1 种和第 3 种情况下,蓝色信号强度大致相同 - 但红色和绿色不同。在第 2 种和第 3 种情况下,绿色信号相同 - 但蓝色和红色不同。为了“分离”它们,我使用了一个相对简单的公式:

S0=V0^2 / (( V1 *a0 +b0)( V2 * c0+d0))

其中 S0 - 通道 0、V0、V1、V2 的分数 - 通道 0、1、2 和 a、b、c、d 的原始值 - 我手动调整的系数(a 和 c 从 0.3 到 2.0,b 和d 分别为 15 和 20,您需要更改它们以适应您的特定传感器放置)。通道 1 和通道 2 的得分相同。此后,图表几乎完全分开:

对于相同的手势(这次是无名指,中指,然后是拇指)信号清晰,只需与阈值比较即可轻松转化为伺服运动。

2. 原理图

原理图非常简单,您只需要 nRF24 模块、PCA9685 或类似的 I2C PWM 控制器,以及足以立即移动所有这些伺服器的高安培 5V 电源(因此它至少需要 5A 额定功率才能稳定运行)。

连接列表:
nRF24 pin 1 (GND) - Arduino's GND
nRF24 pin 2 (Vcc) - Arduino's 3.3v
nRF24 pin 3 (Chip Enable) - Arduino's D9
nRF24引脚 4 (SPI:CS) - Arduino 的 D8
nRF24 引脚 5 (SPI:SCK) - Arduino 的 D13
nRF24 引脚 6 (SPI:MOSI) - Arduino 的 D11
nRF24 引脚 7 (SPI:MISO) - Arduino 的 D12
PCA9685 SDA - Arduino 的 A4
PCA9685 SCL - Arduino 的 A5
PCA9685 Vcc - Arduino 的 5v
PCA9685 GND - Arduino 的 GND
VPCA96 高安培5V
PCA9685 GND - 高放大器 GND
手指舵机:到 PCA 通道 0-4,在我的符号中拇指 - 通道 0,食指 - 通道 1 等

3.肌电传感器放置

为了获得合理的读数,将记录肌肉活动的 uECG 设备放置在正确的位置非常重要。虽然这里有许多不同的选择,但每个都需要不同的信号处理方法 - 所以我分享我使用过的:

这可能与直觉相反,但拇指肌肉信号在手臂的另一侧更清晰可见,因此将一个传感器放置在那里,并且所有传感器都放置在靠近肘部的位置(肌肉的大部分身体都在该区域,但您想检查您的确切位置-个体差异很大)

4.代码

在运行主程序之前,您需要找出您的特定 uECG 设备的单元 ID(通过取消注释第 101 行并逐个打开设备来完成)并将它们填充到 unit_ids 数组中(第 37 行)。

#include 
#include
#include
#include
#include
#include
#define SERVOMIN 150 // 这是“最小”脉冲长度计数(4096 中)
#define SERVOMAX 600 // 这是“最大”脉冲长度计数(共 4096 个)
Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();
int rf_cen =9; //nRF24芯片使能引脚
int rf_cs =8; //nRF24 CS pin
RF24 rf(rf_cen, rf_cs);
//管道地址 - uECG 侧硬编码
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数组——需要填写你自己的单元ID
int unit_vals[3] ={0, 0, 0}; //具有这些ID的uECG值数组
float tgt_angles[5]; //5个手指的目标角度
float cur_angles[5]; // 5 个手指的当前角度
float angle_open =30; //打开手指对应的角度
float angle_closed =150; //对应闭合手指的角度
void setup() {
//nRF24需要相对较慢的SPI,可能也可以在2MHz下工作
SPI.begin();
SPI .setBitOrder(MSBFIRST);
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
for(int x =0; x <8; x++) //nRF24和uECG有不同的位序对于管道地址
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数据
//注意uECG应该切换到原始数据模式(通过长按按钮)
//为了发送兼容的数据包,默认以BLE模式发送数据
//不能被nRF24接收
Serial.begin(115200); //串行输出——对调试非常有用
pwm.begin(); //启动PWM驱动
pwm.setPWMFreq(60); // 模拟舵机以 ~60 Hz 运行更新
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 + 角度 * 0.005556 * (SERVOMAX - SERVOMIN));
}
float angle_speed =15; //手指移动的速度
float v0 =0, v1 =0, v2 =0; //每3个通道过滤后的肌肉活动值
void loop()
{
if(rf.available())
{
rf.read(in_pack, 32 ); //处理包
byte u1 =in_pack[3];//32位单元ID,每个uECG设备唯一
byte u2 =in_pack[4];
byte u3 =in_pack[ 5];
byte u4 =in_pack[6];
unsigned long id =(u1<<24) | (u2<<16) | (u3<<8) | u4;
//Serial.println(id); //取消注释此行以列出您的 uECG ID
if(in_pack[7] !=32) id =0; //错误的包类型:在EMG模式下这个字节必须是32
int val =in_pack[10]; //肌肉活动值
if(val !=in_pack[11]) id =0; //值在2个字节中重复,因为RF噪声会破坏数据包,而且我们没有nRF24的CRC
//找到对应于当前ID的ID并填充值
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] 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];
//计算分数s 来自原始值
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 小时的实验,我得到了相当可靠的操作(视频显示了一个典型案例):

它的行为并不完美,并且通过这种处理只能识别张开和闭合的手指(甚至不能识别 5 个中的每一个,它只检测 3 个肌肉群:拇指、食指和中指,无名指和小指一起)。但是分析信号的“AI”在这里需要 3 行代码,并使用每个通道的单个值。我相信通过在 PC 或智能手机上分析 32-bin 光谱图像可以做更多的事情。此外,此版本仅使用 3 个 uECG 设备(EMG 通道)。有了更多的通道,应该可以识别真正复杂的模式 - 但是,这就是项目的重点,为任何感兴趣的人提供一些起点:) 手动控制绝对不是此类系统的唯一应用。

代码

  • emg_hand_control2.ino
emg_hand_control2.inoArduino
#include #include #include #include #include #include #define SERVOMIN 150 / / 这是“最小”脉冲长度计数(4096 中)#define SERVOMAX 600 // 这是“最大”脉冲长度计数(4096 中)Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver();int rf_cen =9; //nRF24 芯片使能 pinint rf_cs =8; //nRF24 CS pinRF24 rf(rf_cen, rf_cs);//管道地址 - 硬编码在 uECG sideuint8_t pipe_rx[8] ={0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};uint8_t8 交换位{ //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;返回 v;}long last_servo_upd =0; //我们上次更新伺服值的时间 - 不想经常这样做byte in_pack[32]; //传入射频数据包的数组unsigned long unit_ids[3] ={4294963881, 4294943100, 28358}; //已知uECG ID数组——需要填写你自己的单元IDsint unit_vals[3] ={0, 0, 0}; //带有这些ID的uECG值数组float tgt_angles[5]; //5个手指的目标角度float cur_angles[5]; // 5 个手指的当前角度float angle_open =30; //对应于打开fingerfloat的角度angle_closed =150; //对应于闭合fingervoid setup()的角度{ //nRF24需要相对较慢的SPI,可能也可以在2MHz下工作 SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); for(int x =0; x <8; x++) //nRF24 和 uECG 对管道地址有不同的位顺序 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数据//注意uECG应该切换到原始数据模式(通过长按按钮)//为了发送兼容的数据包,默认情况下它以BLE模式发送数据//nRF24串行无法接收.开始(115200); //串行输出 - 对调试 pwm.begin() 非常有用; //启动PWM驱动pwm.setPWMFreq(60); // 模拟舵机以 ~60 Hz 运行更新 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; //手指移动的速度有多快浮动 v0 =0, v1 =0, v2 =0; //每3个通道过滤的肌肉活动值void loop() { if(rf.available()) { rf.read(in_pack, 32); //处理数据包字节 u1 =in_pack[3];//32 位单元 ID,每个 uECG 设备字节唯一 u2 =in_pack[4];字节 u3 =in_pack[5];字节 u4 =in_pack[6]; unsigned long id =(u1<<24) | (u2<<16) | (u3<<8) | u4; //Serial.println(id); //取消注释此行以列出您的 uECG ID if(in_pack[7] !=32) id =0; //错误的包类型:在EMG模式下这个字节必须是32 int val =in_pack[10]; //肌肉活动值 if(val !=in_pack[11]) id =0; //值在 2 个字节中重复,因为 RF 噪声会破坏数据包,并且我们没有使用 nRF24 的 CRC //找到对应于当前 ID 的 ID 并填充值 for(int n =0; n <3; n++) if (id ==unit_ids[n]) unit_vals[n] =val;长毫秒 =毫秒(); 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; } 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));浮动 scor1 =4.0*v1*v1/((v0*2.0 + 20)*(v2*2.0 + 20));浮动 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] =角度闭合; } 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; } }}

示意图

nrf24_hand_control_5jcEeCP8a3.fzz

制造工艺

  1. 避孕药
  2. 使用红外传感器制作无线机器人车辆
  3. 参考设计简化了工业机器人电机控制
  4. 使用 LM35 的基于温度的设备控制系统
  5. 使用 AI 控制光的属性 |超连续谱生成
  6. 使用 3DG 机器人仿真软件规划机器人自动化
  7. 自动列车控制
  8. 使用 Arduino、1Sheeld 和 Android 的通用远程控制
  9. 使用物联网远程控制机械臂
  10. 使用 Firmata 和 Xbox One 控制器控制 Arduino Rover
  11. 使用 Arduino 的简单智能机械臂
  12. 学生使用 B&R 技术构建机器人垃圾分类系统