筋電センサMyoWareを使って腕とサーボモーターを連携させる

#はじめに
この記事は
[Arduinoと筋電センサMyoWareで始める筋電計測](https://qiita.com/imaimai17468/items/3f0048cd1c80311c0921)
の実践的な内容になります。
筋電センサMyoWareの基本的な使い方はこちらの記事を参照してください。

また、この記事は [木更津高専 Advent Calendar 2021](https://qiita.com/advent-calendar/2021/nit_kisarazu) 12 日目の記事です。
前の記事は takamasa272 さんの [学寮ネット委員 珍走記](https://qiita.com/takamasa272/items/74dbb85fb7d89ff4799b)
次の記事は takamasa272 さんの 学寮ネット委員 珍事件簿
です。


#つかったもの
前回の記事と同様

+

* [サーボモーター SG90](https://www.amazon.co.jp/dp/B016FKJJ8M/ref=cm_sw_r_tw_dp_27PHYNKPF2AX0QMHDM6S)

* (ポテンショメータ)
サーボモータがちゃんと動くか確かめるためにいちいち電極を腕に貼るのは面倒くさいので、ポテンショメータの値を腕の筋電信号の代わりにしました。要は腕のデバッグ要員です。

# なにをするか
**腕に力を入れない場合はサーボモータの値を0°に**
**腕に力を入れれば入れるほど180°に近づく**

という動作を実装します。
![sinni.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481328/1ebcaa12-b764-03d9-191e-dfe343cc4af7.png)

#どのように制御するか
![seigyozu.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481328/a03ffd88-3c83-83e6-439e-2f16a2d29dd0.png)
Arduinoのアナログ入力からMyoWareの信号を受け取り、その信号の大小でサーボモータを制御します。

少し詳しく説明すると
① まず、あらかじめに力を入れていない時の平均と入れている時の平均をとります。
② このとき、力を入れていない時の平均を0°、入れている時の平均を180°と換算し、計測した筋電位を角度に変換します。
③ その後、筋電位を100msごとに計測します。
④ そして、変換した角度の位置までサーボモータを回転させることで、サーボモータと腕を連携させます。

![setumei.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481328/e294883c-fe35-2420-d77a-e0edf9c3d8da.png)


#前準備
前回の記事を参考に、ArduinoとMyoWareを接続してください。
また私は電極を腕の図の位置に貼りました。間隔は3cmぐらいです。アース電極は肘に貼りました。
![haruiti.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481328/00050358-3552-3572-9dad-61b43f0d336f.png)

電極を貼りながら、センサの値の上振れと下振れの幅が大きい箇所を探ってみてください。

#実際に制御する
##使用したコード
[GitHub](https://github.com/imaimai17468/musclesensor_arduino)

```c
#include <Servo.h>  // マイクロサーボモータ用のライブラリ


#define MAX_SIZE 2000 // 受け取るデータ数
#define PIN_SERVO 9   // D9：サーボモータ
#define PIN_METER 1   // A1：ポテンショメータ(角度)
#define PIN_SENSOR 0  // A0：筋電位センサ

//*******//
// DEBUG //
//*******// (使用しない場合はコメントアウト)
// #define METER_ON   // ポテンショメータを使用するか
// #define METERMODE  // センサ値をポテンショメータからの値にするか

// function(prototype)----------------------------------------------------------------------

void countdown(int t); // カウントダウン用関数
int culc_ave(void);       // グラフの下辺と上辺の平均を求める関数

// global-----------------------------------------------------------------------------------

int i = 0;                    // カウント変数
unsigned long t = 0;          // ループ毎の時間を保持する変数
unsigned long dis_t = 0;      // スタートからの時間を保持する変数
unsigned long delay_t = 0;    // サンプリング周期を調整するための遅延時間を保持する変数
int value = 0;                // センサの値を保持する変数
char moji;                    // シリアル通信の入力用変数
int angle = 0;                // シャフトの角度を保持する変数
double upper = 0, lower = 0;  // グラフの上辺と下辺の平均値を保持する関数

// Servoオブジェクトを作成
Servo myservo;

// setup------------------------------------------------------------------------------------

void setup() {
  Serial.begin(115200);

  // サーボモータにピンに割り当て0～180度のパルス幅をマイクロ秒で指定
  myservo.attach(PIN_SERVO);
  myservo.write(0); // サーボに角度を書き込む
  delay(500);       // シャフトがその位置に達するのを待つ
}

// main-------------------------------------------------------------------------------------

void loop() {
  // =======================================================================================
  // ポテンショメータを使うモード
  // サーボモータの動作確認用
  // =======================================================================================
  #ifdef METER_ON // (METER_ONが定義されていれば実行)
  // ポテンショメータから角度(0-180)を読み込む
  value = analogRead(PIN_METER);
  Serial.println(value);
  angle = map(value, 0, 1023, 0, 180);
  myservo.write(angle); // サーボに角度を書き込む
  delay(10);            // シャフトがその位置に達するのを待つ

  
  // =======================================================================================
  // 通常モード
  // 筋電位を読み取りサーボモータを制御する
  // =======================================================================================
  #else  
  if ((moji = Serial.read()) != -1) {
    // 制御に用いる値の測定
    // グラフの上辺と下辺の平均値を計測
    Serial.println("静止時の筋電位を測定します。");
    Serial.println("測定開始まで");
    countdown(5);
    lower = culc_ave();
    Serial.println("力を入れた時の筋電位を測定します。");
    Serial.println("測定開始まで");
    countdown(5);
    upper = culc_ave();

    // 結果を表示
    Serial.print("lower = ");
    Serial.println(lower);
    Serial.print("upper = ");
    Serial.println(upper);
    
    Serial.println("5秒後に制御モードへ移行します。");
    countdown(5);
    
    // 再び入力されるまで繰り返す
    while((moji = Serial.read()) == -1){
      // 動作内容============================================================================
      
      // センサ値をポテンショメータからの値にしたい場合(動作テストなど)
      // 動作テストとは：閾値や平均などのサーボモータを動かす要素が機能しているかのテスト
      #ifdef METERMODE
      value = analogRead(PIN_METER);
      
      // 通常動作で動かす場合(筋電位センサから受け取る)
      #else
      value = analogRead(PIN_SENSOR);

      #endif
      
      // ===================================================================================

      
      // サーボモータの制御====================================================================
      
      // 範囲外の値は範囲内に収める
      value = constrain(value, lower, upper);
      // グラフの下辺から上辺までを0°から180°に変換
      angle = map(value, lower, upper, 0, 180); 
      myservo.write(angle);

      // ===================================================================================

      Serial.println(value);
      delay(100);
    }
  }
  #endif
}

// function(prototype)----------------------------------------------------------------------

// カウントダウン用関数
void countdown(int t){
  for(i = t; i > 0; i--){
    Serial.println(i);
    delay(1000);
  }
}

// 筋電位の平均を求める関数
int culc_ave(void){
  double ave = 0;
  unsigned long start_t = micros();
  
  for(i = 0; i < MAX_SIZE; i++){
    t = micros();
    dis_t = t - start_t;
    
    #ifdef METERMODE
    value = analogRead(PIN_METER);
    #else
    value = analogRead(PIN_SENSOR);
    #endif

    ave += value;
    
    Serial.println(value);
    delay_t = micros() - t + (dis_t % 1000);
    delayMicroseconds(1000 - delay_t);
  }
  return ave / MAX_SIZE;
}

// -----------------------------------------------------------------------------------------
```

デバッグ用のポテンショメータを使わない場合は該当箇所を除いてください。

###デバッグの説明
* METER_ON
ポテンショメータの回しぐあいに応じてサーボモータがちゃんと動くかテストするモード

* METERMODE
実際にMyoWareの信号の代わりにポテンショメータの信号を使用するモード


#おわりに・発展
お気づきの方もいるかもしれない

**サーボモータ1つ動かした程度で何ができるというのだ！！**

その通りである。これだけでは筋電義手には程遠い。
腕の力の入れ具合しか判定できておらず、腕の曲げ、回転などまったく考慮されていない。
なので、より筋電義手に近づくにはどうすればいいか考えよう

* 筋電センサをいっぱい使って複数の状態を読み取れるようにする

* 筋電センサを一個にする代わりに機械学習でゴリ押して複数の状態を読み取る

私は前者は金銭面、後者は技術面で頓挫してしまったが、興味を持った人はぜひチャレンジしてみてほしい。