工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Manufacturing Technology >> 製造プロセス

EMGを使用したロボットハンドコントロール

コンポーネントと消耗品

uECGデバイス
× 3
inMoov hand
× 1
Arduino Nano R3
× 1
Adafruit PCA968516チャンネルPWMドライバー
× 1
nRF24モジュール(汎用)
× 1

このプロジェクトについて

私たちのチームには、ロボットの手に関する長い話があります。しばらくの間、信頼性の高い義手を作ろうとしましたが、このプロジェクトでは、既存のオープンソースハンドの良い例であるinMoovを使用しています。

手作業による組み立ての詳細については説明しません。プロジェクトのサイトで詳しく説明されており、非常に複雑です。これはまったく新しいので、ここでは制御に焦点を当てます:)
また、次のプロジェクトでこのテクノロジーが時間の経過とともにどのように進化したかを確認してください:https://www.hackster.io/the_3d6/seeing-muscles-at -work-8-channel-emg-with-leds-039d69

1.信号処理

制御はEMGに基づいています-筋肉の電気的活動。 EMG信号は、3つのuECGデバイスによって取得されます(ECGモニターであると想定されていますが、汎用ADCに基づいているため、EMGを含むあらゆる生体信号を測定できます)。 EMG処理の場合、uECGには、32ビンのスペクトルデータと「筋ウィンドウ」平均(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ピン1(GND)-ArduinoのGND
nRF24ピン2(Vcc)-Arduinoの3.3v
nRF24ピン3(チップイネーブル)-Arduinoの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
PCA9685SDA-ArduinoのA4
PCA9685SCL-ArduinoのA5
PCA9685 Vcc-Arduinoの5v
PCA9685GND-ArduinoのGND
PCA9685 V +-ハイアンプ5V
PCA9685GND-ハイアンプGND
フィンガーサーボ:PCAチャネル0〜4、私の表記ではサム-チャネル0、インデックスフィンガー-チャネル1など

3.EMGセンサーの配置

妥当な測定値を取得するには、筋活動を記録しているuECGデバイスを適切な場所に配置することが重要です。ここではさまざまなオプションが可能ですが、それぞれに異なる信号処理アプローチが必要です。そのため、私が使用したものを共有しています:

<図>

直感に反するかもしれませんが、親指の筋肉の信号は腕の反対側でよく見えるので、センサーの1つをそこに配置し、すべてのセンサーを肘の近くに配置します(筋肉はその領域に体の大部分を持っています) 、しかしあなたはあなたが正確にどこにいるのかを確認したい-かなり大きな個人差があります)

4.コード

メインプログラムを実行する前に、特定のuECGデバイスのユニットIDを見つけて(101行目のコメントを外し、デバイスを1つずつオンにすることで実行されます)、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; // nRF24CSピン
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パイプアドレスはスワップされたビットの順序を使用します
// 1バイトのビット順序を逆にします
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]; //着信RFパケットの配列
unsigned long unit_ids [3] ={4294963881、4294943100、28358}; //既知のuECGIDの配列-独自のユニット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モードでデータを送信します
br /> //これはnRF24では受信できません
Serial.begin(115200); //シリアル出力-デバッグに非常に便利です
pwm.begin(); // PWMドライバーを開始します
pwm.setPWMFreq(60); //アナログサーボは約60Hzの更新で実行されます
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; // 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); //この行のコメントを解除して、uECGIDのリストを作成します
if(in_pack [7]!=32)id =0; //間違ったパックタイプ:EMGモードでは、このバイトは32である必要があります
int val =in_pack [10]; //筋活動値
if(val!=in_pack [11])id =0; // RFノイズがパケットを破損する可能性があるため、値は2バイトで複製され、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行のコードを使用し、各チャネルから1つの値を使用します。 PCやスマートフォンで32ビンのスペクトル画像を分析することで、もっと多くのことができると思います。また、このバージョンは3つのuECGデバイス(EMGチャネル)のみを使用します。より多くのチャネルがあれば、本当に複雑なパターンを認識できるはずですが、それがプロジェクトのポイントであり、興味のある人に出発点を提供します:)このようなシステムのアプリケーションはハンドコントロールだけではありません。

コード

  • emg_hand_control2.ino
emg_hand_control2.ino Arduino
 #include  #include  #include  #include  #include  #include  #define SERVOMIN 150 / /これは「最小」パルス長カウント(4096から)#define SERVOMAX 600 //これは「最大」パルス長カウント(4096から)Adafruit_PWMServoDriver pwm =Adafruit_PWMServoDriver(); int rf_cen =9; // nRF24チップイネーブルpinintrf_cs =8; // nRF24 CS pinRF24 rf(rf_cen、rf_cs); //パイプアドレス-uECG側にハードコーディングpipe_rx [8] ={0x0E、0xE6、0x0D、0xA7、0、0、0、0}; uint8_t swapbits(uint8_t a) {// uECGパイプアドレスはスワップされたビット順序を使用します// 1バイトのビット順序を逆にしますuint8_tv =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; //サーボ値を最後に更新した時刻-これを頻繁に実行したくないbytein_pack [32]; //着信RFパケットの配列unsignedlong unit_ids [3] ={4294963881、4294943100、28358}; //既知のuECGIDの配列-独自のユニットIDを入力する必要がありますintunit_vals [3] ={0、0、0}; //これらのIDを持つuECG値の配列floattgt_angles [5]; // 5本の指のターゲット角度floatcur_angles [5]; // 5本の指の現在の角度floatangle_open =30; //開いたfingerfloatに対応する角度angle_closed =150; //閉じたfingervoidsetup()に対応する角度{// 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シリアルでは受信できません.begin(115200); //シリアル出力-pwm.begin();のデバッグに非常に役立ちます。 // PWMドライバを開始しますpwm.setPWMFreq(60); //アナログサーボは約60Hzの更新で実行されます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チャネルごとにフィルタリングされた筋活動値voidloop(){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; // RFノイズがパケットを破損する可能性があるため、値は2バイトで複製され、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]  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]; //生の値からスコアを計算するfloatscor0 =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)//弱い信号-open finger 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; }}} 

回路図

nrf24_hand_control_5jcEeCP8a3.fzz

製造プロセス

  1. 経口避妊薬
  2. IRセンサーを使用してワイヤレスロボット車両を作成する
  3. リファレンスデザインは、産業用ロボットモーター制御を簡素化します
  4. LM35を使用した温度ベースのデバイス制御システム
  5. AIを使用して光のプロパティを制御する|スーパーコンティニウムの生成
  6. 3DGロボットシミュレーションソフトウェアを使用したロボット自動化の計画
  7. 自動列車制御
  8. Arduino、1Sheeld、Androidを使用したユニバーサルリモコン
  9. IoTを使用してロボットアームをリモート制御する
  10. FirmataとXboxOneControllerを使用してArduinoRoverを制御する
  11. 学生はB&R技術を使用してロボットのごみ分別システムを構築します