高精度EMG制御ロボットハンド: InMoov統合 & ビルドガイド
部品と材料
| 部品 | 数量 |
|---|---|
| uECGデバイス (EMG/ECGセンサー) | 3 |
| InMoovハンド (オープンソース3Dプリントロボットハンド) | 1 |
| Arduino Nano R3 | 1 |
| Adafruit PCA9685 16チャネルPWMドライバー | 1 |
| nRF24モジュール (汎用) | 1 |
プロジェクト概要
私たちのチームは長年ロボットハンドに取り組んできました。信頼性の高い義手の開発を目指していましたが、今回は既存の優れたオープンソースハンド「InMoov」を使用します。ハンドの組み立てはプロジェクトサイトで詳しく説明されているため、ここでは完全に新しい制御システムに焦点を当てます。この技術の進化については次プロジェクト「8チャネルEMG with LEDs」も参照してください。
1. 信号処理
制御はEMG (筋電図) - 筋肉の電気的活動 - に基づきます。EMG信号は3台のuECGデバイスで取得します (本来はECGモニターですが、汎用ADCベースなのでEMGを含む生体信号全般を測定可能)。uECGにはEMG処理用の特別モードがあり、32ビンのスペクトルデータと「筋肉ウィンドウ」平均値 (75〜440Hzの平均スペクトル強度) を出力します。スペクトル画像は以下のようになります。

縦軸が周波数 (0〜488Hz、約15Hzステップ)、横軸が時間 (約10秒分)、色が強度 (青=低、緑=中、黄=高、赤=より高) を表します。確実なジェスチャー認識にはPCでの画像処理が必要ですが、ロボットハンドの指を単純に動かすだけなら、uECGがパケット特定バイトで提供する3チャネルの平均値だけで十分です。これらの値は以下のようにシンプルです。

赤・緑・青は親指、薬指、中指を握ったときの各チャネル生値です。人間には明確に区別できますが、プログラムで「指スコア」に変換する必要があります。問題は筋肉群の信号が「混ざっている」ことです。例えば第1と第3ケースで青信号強度が似ていても赤と緑が異なり、第2と第3では緑が同じでも青と赤が異なります。これを「分離」するため、以下の比較的シンプルな式を用いました:
S0 = V0² / ((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 (CE) → Arduino D9
- nRF24ピン4 (CSN) → Arduino D8
- nRF24ピン5 (SCK) → Arduino D13
- nRF24ピン6 (MOSI) → Arduino D11
- nRF24ピン7 (MISO) → Arduino D12
- PCA9685 SDA → Arduino A4
- PCA9685 SCL → Arduino A5
- PCA9685 Vcc → Arduino 5V
- PCA9685 GND → Arduino GND
- PCA9685 V+ → 大容量5V電源
- PCA9685 GND → 大容量電源GND
- 指サーボ: PCAチャネル0〜4 (親指=ch0, 人差し指=ch1, 中指=ch2, 薬指=ch3, 小指=ch4)
3. EMGセンサー配置
uECGデバイスを適切な位置に配置することが重要です。多くの選択肢がありますが、それぞれ異なる信号処理が必要になるため、私が使用した配置を共有します。

直感に反するかもしれませんが、親指の筋肉信号は前腕の反対側 (橈骨側) でより明確に取れます。すべてのセンサーを肘に近い位置に配置しています (筋肉の大部分がそこにあるため)。個人差が大きいので、自分の筋肉位置を確認して微調整してください。
4. コード
メインプログラム実行前に、各uECGデバイスのユニットIDを特定する必要があります (行101のコメントアウトを外し、デバイスを1台ずつオンにしてIDを確認)。取得したIDを 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 // 最小パルス長カウント (4096中)
#define SERVOMAX 600 // 最大パルス長カウント (4096中)
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
int rf_cen = 9; // nRF24 CEピン
int rf_cs = 8; // nRF24 CSNピン
RF24 rf(rf_cen, rf_cs);
// パイプアドレス (uECG側でハードコード)
uint8_t pipe_rx[8] = {0x0E, 0xE6, 0x0D, 0xA7, 0, 0, 0, 0};
// ビット順反転 (uECGとnRF24でビット順が異なるため)
uint8_t swapbits(uint8_t a) {
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];
float cur_angles[5];
float angle_open = 30;
float 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データを待ち受け
// 注: uECGは長押しボタンで「生データモード」に切り替える必要あり
// デフォルトはBLEモードでnRF24では受信不可
Serial.begin(115200);
pwm.begin();
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;
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;
// 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ノイズ対策、CRCなし)
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時間の実験でかなり信頼性の高い動作が得られました (動画は典型的なケース)。完璧ではなく、この処理では指の開閉のみ認識可能で、5本指それぞれではなく3筋肉群 (親指、人差し指+中指、薬指+小指) のみ検出です。しかし「AI」部分はわずか3行のコードと各チャネルの単一値のみ。32ビンのスペクトル画像をPC/スマホで解析すれば、もっと高度なことが可能でしょう。また今回は3チャネルのみですが、チャネルを増やせば複雑なパターン認識も可能になります。これが本プロジェクトの目的 - 興味を持つ誰かへの出発点を提供することです。ハンド制御はこのようなシステムの応用例の一つに過ぎません。
コード
- emg_hand_control2.ino (上記と同一)
回路図
Fritzingファイル: nrf24_hand_control_5jcEeCP8a3.fzz
製造プロセス
- 経口避妊薬:歴史、メリット、リスク、製造
- AT89S52マイコンとLM35センサーによる温度ベース機器制御システム
- DIY自動列車制御システム – Arduinoで実現するシンプル・信頼性・カスタマイズ自在なレイアウト運転
- Arduinoと1Sheeldで作る万能Androidリモコン──IR制御で家電を一括操作
- IoTで遠隔操作する自作ロボットアーム:Arduino MKR1000とSimulink、ThingSpeak活用
- FirmataとXbox OneコントローラーでArduinoローバーをシームレス操作
- 学生たちがB&R技術で高度なロボットゴミ分別システムを構築──Smart Green Island Makeathon
- AI駆動フォトニックチップがブロードバンド光を制御、スマート光学へ道筋
- 3DGの高度なロボットシミュレーションソフトでロボット自動化を効率化
- TrinamicとMaxim Integrated、産業用ロボットのエンドオブアームツーリング開発を加速するリファレンスデザインを発表
- ワイヤレス赤外線センサー駆動ロボット車両の構築