OGIMOノート ~家族のためのモノづくり~

8歳の娘と、6歳の息子(脳性麻痺)を持った父親エンジニアの備忘録。自作の電子工作おもちゃ/リハビリ器具/ロボット関係の製作記録、勉強記録を残していきます

車椅子を「ワクワク新幹線」に変える M5Stack×スピーカ×イルミモジュール製作

f:id:motokiinfinity8:20190831220648p:plain

はじめに

私は、障害を持った息子や家族が、日々楽しく過ごしていくためのサポートとして電子機器やおもちゃ等の「家族のためのモノづくり」として活動しています。そんな自分が製作していたものをtwitter等のSNSに発信し続けていたところ、
世界ゆるスポーツ協会 澤田 智洋さんより
「特別支援学校をもっと面白くするプロジェクトを一緒にやりませんか?」
とお声がけ頂き、
『特別支援学校の特別おもしろ祭り』
にスタッフ側として参加させて頂く事になりました!!

特別支援学校の特別おもしろ祭り

8/18 東京・府中市にある支援学校・けやきの森学園 で開催された
『特別支援学校の特別おもしろお祭り』

普通の学校の夏祭りではなく、
未来を感じるワクワクする技術・コンテンツをたくさん結集して特別支援学校の新たな可能性を提示するという超クリエイティブなイベント!   
特別おもしろい『スポーツ』『食』『音楽』『ファッション』『イタズラ』『仕事』『トイレ』『乗り物』『ディスコ』の合計9個のブース。
協賛4団体、協力15団体、
スタッフ150人以上、お客さん400名というすごい規模!

テレビのニュースでも取り上げられてました!
www.news24.jp


私の製作物に関して

きっかけ

今回私が担当させて頂いたのが、「特別面白い乗り物」ブース
その担当メンバーで議論したところ、
 「電車好きな多様な男子たちが多く参加する事が予想されるため
  普段の自分の車椅子をワクワクする様な電車にしてみたい」

という

『ウィルチェア・トレイン
 (Wheelchair Train)』

の構想が持ち上がりました。

「普通の乗り物に移乗困難なな子供でも楽しめ、
 普段の自分の車椅子が一日限りのワクワク乗り物に変身!」

のコンセプトが気に入り、夏休み期間を最大限使って製作に没頭しました!


[要求仕様]
 ・何よりも電車感がある事(見た目や音など)
 ・普段の車椅子・バギーに装着&脱着する事が容易である事
 ・指定コースを走行する想定で、発車時・停車時にアナウンスが欲しい。
  また、行中にモードが切り替わる事 (走っているとドンドン変化していく)
  ただし、車椅子などの制御情報(操作ボタンやタイヤエンコーダ)は使えない前提。
  (どんな車椅子が来るか分からないため)
 ・当日のお手伝いスタッフ(技術知識なし)でもお手軽に操作できる

完成品

当日はドクターイエロー版」「新幹線はやぶさ版」の二種類を作成しました。

はやぶさ版を歩行器(非電動)に取り付けた動画がこちら。


本番ではたくさんの子供たちの車椅子に取り付けて、コースを走行して楽しんで貰いました!
好きすぎた子供は、乗り物にすら乗らず新幹線部分を手持ちで楽しんでくれてました(笑)

手動バギーや手動車いすについては、株式会社るーと様(https://root-ca.jp/)が持ってこられた簡易電動ユニット CarryLoco 及び その子供の症例に合わせた操作スイッチ を使い、「子供たちが自分の意志で操作する体験」をサポートされていました!

特に電車好きの子供達が自分の作ったモジュールを使い、楽しそうにコースを走ってる姿を見て、すごく胸熱く感じました!
作って良かったー!  

上記動画をTwitter上に投稿したところ、かなり拡散されて、気が付けば 2019/8/31 21:00時点で動画再生回数 3.5万回、いいね2000回以上、たくさんの人に興味を持ってもらい暖かいコメントを頂けたのがとにかく嬉しいですね。

みんなで、子ども達の笑顔、そしてやってみたいという気持ちをを引っ張り出す
暖かい世界だなぁ~

概要仕様

本番コースでの走行を想定して、以下のシナリオでの運用を想定しました。
f:id:motokiinfinity8:20190831222427j:plain


[手順]
・子供の車椅子にモジュールを装着
・コーススタート位置でボタンを押すと「発車アナウンス」が流れる
・ ボタンを押して「ドアを閉める」操作をする
・ 車椅子を前に走らせると「発車音」が流れる
・ 走行中のコース途中で表示変更
・ ゴールが近づくと「停車音」が流れる
・ 車椅子を停止させると「終了アナウンス」が流れる

ハードウェア

今回のコンセプトは、「普段の車椅子に簡単に脱着可能である事」です。なので、制御部や走行状態センシングはモジュール内で完結させる必要があります。更に、当日は大勢の人に取り付ける必要があり、操作の簡易さ/起動の速さ/安定性を加味した結果、マイコンを使ったシステムに決めました。
いつもお馴染み「M5Stack」を中心とした制御モジュールで進めます。


システム構成図になります。
f:id:motokiinfinity8:20190831222500j:plain

M5Stack Grayを使い、全体的な制御 及び 内蔵加速度センサを使っての走行状態センシングをおこないます。また、液晶に電車コクピット映像を映して気分を高めます!

アナウンス音については、M5Stackのスピーカではボリューム不足 かつ 他操作の影響を排除してキレイな音声再生を安定再生させるためDF Player miniモジュール+8Wスピーカを別回路で用意しました。NeoPixel LEDテープはおなじみで、今回は144個/mの密度の高いモノを使い、LEDグラデーションを表現しました。また、長時間の使用に耐えるために、モバイルバッテリーを使用します。NeoPixel LEDテープは非常に大きな電流(2A以上)を流すため、2.4A出力のモノを使いました。

完成したモジュールは以下です。すべてを簡単に持ち運べる様に3Dプリンタで制御部分の外装を作りました。



新幹線の筐体ですが、柔軟に取り付け・取り外しが可能な様に、段ボールタイプを使用しました。

f:id:motokiinfinity8:20190831223412j:plain

こちらの市販品を購入 & 組み立てして、背面部に荷物運搬用のロープ&マジックテープを取り付けました。車椅子へは背面でくくりつける形で固定します。なお、モジュールは新幹線の上に配置しました。

ソフトウェア

走行検出/モード切替について

今回の一番の悩みは、走行中でのモード切替方法。
せっかくなので「自分が動いた事によって動作が変わったんだという達成感」を持って欲しかったので、
一定時間でモード自動遷移する案は保留。

カメラやタイヤエンコーダ情報を使ってオドメトリを取得し自己位置認識を行い、一定場所に到着したらモードが切り替わる、という案も検討しましたが、残念ながら本番当日までコース試走が困難であったため、安定性を優先してこの案も保留。


という事で、ここはシンプルに
・走行開始有無はモジュールの加速度センサで検出
・モード切替はジャイロセンサからのYAW角情報をもとに、スタート時点からの回転方向の変化(例えば90度曲がった場合)に行う
・ゴール到達は、ボタン長押しで手動表示 (次回以降の課題)

 
としました。
※次回は、ライントレース等によるコース認識や、Jetson Nano等を使った自己SLAMにもトライしてみてみたい!

発進検出

M5Stack内部の加速度センサーの値を取得しました。
モジュールの取り付け方法が相手の車椅子に応じて異なる事が予想されたため、
 ・X,Y,Zどの方向に対しても一定以上の閾値を一定期間(800ms以上)超えた場合
と定義して行いました。

幸いな事に、電動車椅子駆動時には必ず振動が発生するので、うまく発車/停車検出できました

※課題として、車椅子の種類によっては移動時の振動が少なく検知できないケースもあったため、
 走行/停止の閾値判定をキャリブレーションする仕組みはあっても良かったかなぁ。

旋回検出

これが曲者でした。
一般的にRoll/Pitch角の検出は重力の変化が起きるのでやりやすいのですが、
Yaw角検出は難しいのです。

ちなみに、M5Stackのサンプルコードである
MPU9250BasicAHRS
を使っても、YAW角は不安定でした。

そこで様々に調査した結果、M5Stack内部で使用している MPU9250という加速度センサは
バイス内部でDMPというYAW角算出のハード回路を有していますので、
加速度/ジャイロから計算するのではなく、ハードレジスタを読み出す形で行います。



これに対応したライブラリが以下にあるので、これを M5Stack用に改修してやるイメージです。
github.com


M5Stackへの適用方法は以下を参照ください。一部ライブラリの書き換えは必要になります。

qiita.com

#include <M5Stack.h>
#include <NeoPixelBrightnessBus.h> // instead of NeoPixelBus.h
#include <SparkFunMPU9250-DMP.h>


MPU9250_DMP IMU;


double comAccX,comAccY,comAccZ=0;  
double comAccX_old,comAccY_old,comAccZ_old=0;  
double gravityX, gravityY, gravityZ=0;

const double alpha = 0.9;//lowpassfilter
uint32_t timer;

float y_org=0.0;
float p_org=0.0;
float r_org=0.0;
float y_mod, y_mod_old, y_raw = 0.0;
float p_mod, p_raw = 0.0;
float r_mod, r_raw = 0.0;
float y_base = 0.0;


(中略)

void setup(){
    M5.begin();
    dacWrite(25, 0); // Speaker OFF
    Serial.begin(115200);

    Wire.begin();
    if (IMU.begin() != INV_SUCCESS){
      while (1){
         Serial.println("Unable to communicate with MPU-9250");
         Serial.println("Check connections, and try again.");
         Serial.println();
         delay(5000);
       }
    }
    IMU.setSensors(INV_XYZ_GYRO | INV_XYZ_ACCEL | INV_XYZ_COMPASS);
    IMU.setGyroFSR(2000); // Set gyro to 2000 dps
    IMU.setAccelFSR(2); // Set accel to +/-2g
    IMU.setLPF(5); // Set LPF corner frequency to 5Hz
    IMU.setSampleRate(10); // Set sample rate to 10Hz
//    IMU.setCompassSampleRate(10); // Set mag rate to 10Hz

    IMU.dmpBegin(DMP_FEATURE_6X_LP_QUAT | // Enable 6-axis quat
               DMP_FEATURE_GYRO_CAL, // Use gyro calibration
              10); // Set DMP FIFO rate to 10 Hz
 
}

void loop() {

  // Check for new data in the FIFO
  if ( IMU.fifoAvailable() ){
    // Use dmpUpdateFifo to update the ax, gx, mx, etc. values
    if ( IMU.dmpUpdateFifo() == INV_SUCCESS){
      // computeEulerAngles can be used -- after updating the
      // quaternion values -- to estimate roll, pitch, and yaw
      IMU.computeEulerAngles();

      // 前値からの差分が0.5°以内の変動は温度ドリフト分として 原点想定位置(y_org)を更新
      float y_raw_old = y_raw;
      y_mod_old = y_mod;
      y_raw = IMU.yaw;
      if(abs(y_raw-y_raw_old) < 0.5) y_org += (y_raw - y_raw_old);
      if(y_raw-y_raw_old > 355.5)    y_org  += (y_raw - y_raw_old)-360;
      if(y_raw-y_raw_old < -355.5)   y_org  += 360+(y_raw - y_raw_old);
      y_mod = IMU.yaw   - y_org;
      if(y_mod >= 360) y_mod -= 360;
      if(y_mod < 0) y_mod += 360;
      r_mod = IMU.roll  - r_org;
      if(r_mod >= 360) r_mod -= 360;
      if(r_mod < 0) r_mod += 360;
      p_mod = IMU.pitch - p_org;
      if(p_mod >= 360) p_mod -= 360;
      if(p_mod < 0) p_mod += 360;

//      Serial.print("R/P/Y: " + String(r_mod) + ", "
//            + String(p_mod) + ", " + String(y_mod)+ "   ");
    }
  }
    
   // 加速度センサのデータ受信
   acc_val_max = 0;
   //if ( IMU.dataReady()){
     IMU.update(UPDATE_ACCEL | UPDATE_GYRO | UPDATE_COMPASS);
     //printIMUData();

    double dt = (double)(micros() - timer) / 1000000; // Calculate delta time
    timer = micros();

    /*速度を求める*/
    // 重力加速度を求める
    gravityX = alpha * gravityX + (1 - alpha) * IMU.calcAccel(IMU.ax);
    gravityY = alpha * gravityY + (1 - alpha) * IMU.calcAccel(IMU.ay);
    gravityZ = alpha * gravityZ + (1 - alpha) * IMU.calcAccel(IMU.az);

    // 補正した加速度
    comAccX = IMU.calcAccel(IMU.ax) - gravityX;
    comAccY = IMU.calcAccel(IMU.ay) - gravityY;
    comAccZ = IMU.calcAccel(IMU.az) - gravityZ;

    velX = velX + (comAccX + comAccX_old)/2*dt;
    velY = velY + (comAccY + comAccY_old)/2*dt;
    velZ = velZ + (comAccZ + comAccZ_old)/2*dt;
   
    comAccX_old = comAccX;
    comAccY_old = comAccY;
    comAccZ_old = comAccZ;

    // 動いているときのみ点灯
    //if(comAccX > 0.02 || comAccX < -0.02 || comAccY > 0.02 || comAccY < -0.02 || comAccZ > 0.02 || comAccZ < -0.02){
    if(comAccX > 0.02 || comAccX < -0.02 || comAccY > 0.02 || comAccY < -0.02){
    //if(comAccY > 0.03 || comAccY < -0.03){
      move_flg = true;
      g_stopping_time = millis();  //時間リセット
    }else{
      move_flg = false;
      g_moving_time = millis();  //時間リセット
    }





}



上記以外のコードについては、
 ・状態遷移の記載
 ・LEDユニットの点灯処理
 ・スピーカ再生音設定
に関しては、過去のソフトウェアを流用していますので、特にブログに特記なしとします。
(もし興味がありましたら、個別にお問い合わせください)


最後に

息子のために作ってきた各種工作品、これが他の子供達の笑顔に繋げられるのかと思うと…
なんだか感慨深いです。。。

息子のためにモノづくりを始め1年半。趣味の活動が、多くの子供たちを喜ばせる事に繋がるなんて…
エンジニアとしての幸せなんやろなぁ、って思います。
モノを作るのは純粋に楽しい。
色々と苦労を乗り越えて、完成した時・思い通り動いた時の達成感は堪らない。
だけど、作った満足感だけで終わるのでなく、その先にある
「自分が作ったものが誰かの役に立つ嬉しさ」
「誰かをちょっぴり笑顔にする楽しさ」

を自分は大事にしていきたいな(^-^)

残りの夏休み宿題をこなしている中で、夏休みの絵日記を書いて貰ったら、
父「この夏休み、何が一番楽しかった?」
娘「お父さんと東京にいったときの、支援学校のお祭り!

娘にとっても色々と得るものが多い東京遠征やったのかなぁ、と思うと、なんだか嬉しい!



そして、これがご縁となり、なんとテレビから取材依頼がくることになったのです!
その話については、また別記事で書きたいと思います。

f:id:motokiinfinity8:20190901021746j:plain