マイ アーム2

Created Date: 2025-09-01/ updated date: 2025-09-01
    Owner & Collaborators
    License
    Summary
    3Dプリント自助具コンテスト2025応募作品
    手の不自由な方のための食事介助ロボットです
    6年間続くAさんとの自助具作成の最新作です
    デジタル嫌いの私が何故かロボットアームを作成しています

    Materials

      Tools

        Blueprints

          Making

            • Aさん(筋ジストロフィー)
            • 障害者施設に勤務していた私が、そこで出会ったAさんに自助具スプーンを作ってほしいと頼まれました。試行錯誤の末、5年の期間を経て昨年ロボットアームが完成しました。
              「ラーメンを食べよう」という新たな目標のための、箸を使えるロボットの制作に取り組むこととなりました。

            • 6つのモーターを1つのボタンのみでコントロール。ボタンを押すタイミングによって動作が変わるように作成した。
            • 前回作よりも自ら食べ物を食べているという操作感が上がった。
            • ・サイズが大きく、置く位置を決めるのに時間がかかる
              (押す、引くの2動作を行うには、身体の正面に置きたいが、ロボットアームがあるため調整が必要)

              ・Aさんの車椅子の座り姿勢の崩れによって、力が出しづらくなる
            • ・お皿を回して食べ物をすくうので、自分自身でキャッチしているという実感が薄いように感じる
            • ・麺類がすくいにくく、ラーメンを食べることができたら嬉しい
          •    1.コントローラーの小型化

               2.コントローラーがどの位置であっても操作できる

               3.操作感の向上のため、自らキャッチしにいく動作を作る

               4.ラーメンを食べるため、箸への変更
            • 箸とスプーン2つのアームを持つロボットアームを制作



              問題点
              箸とスプーンのアームが動作中ぶつかることがある
              口を開ける時に反射的にどうしても手に力が入ってしまう。その際にボタンを押してしまい、箸が開いて口にあたってしまう
              やはり箸に対する恐怖感が取れない
              スプーンを動かしている間に箸が突然動くのではないかという恐怖感がある

              ロボットアーム自体が大きくなりすぎたため、コントローラーを置く位置がよりテーブルの端になり、押す動作ができなくなった

              結論
              箸をやめてみることに
              コントローラーの改善が必要


              • 良い点
                自分からつかみに行く動作が可能になったため、より操作している実感がある
                フォークが口から滑り出るようになったので、口元の違和感が少なくなった

                問題点
                肩の部分のモーターに負担がかかり、たまにアームが落ちてしまう。(関節の傾斜面の角度に改善が必要かも)
                皿の中の全ての位置の食べ物をつかむことができず、更にプログラムの改善が必要
                皿の回転をサーボモーターにしたため、独立した動きができず、ボタンを押すたびに初期位置に戻ってしまう。皿の回転の意味がなくなってしまった。(モーターをマイアーム1で使用したモーターに戻すか、プログラムの変更で改善できるかも)




                • より操作感が出て、楽しめる物になった。食べる分には以前のロボットアーム1の方が食べやすいかも。(スプーンが一番口当たりが良いため)

                  ボタンを押してからアームが動くまでの少しのタイムラグが気になる。(複雑なプログラムを入れたための影響)




                  • 操作感が出たことは良かった。2つのボタンを押すという手の動きで操作する方法(空間的側面)から、1つのボタンでタイミングによって操作する方法(時間的側面)に変えても操作感が落ちることはなかった。

                    BB弾を使用することによって、3Dプリンターで作れる物の幅が広がった。

                    Aさんの辛口の感想にショックは受けたが、「タイムラグを感じる」という、私自身は気にならなかった部分を指摘され、Aさんの感覚の鋭さを知り、新たな気づきになった。

                    今後はモールス信号のように、よりタイミングによって制御できる幅を広げてみたい。
                    • スプーンを口元に持っていく際に関節に電圧がかかる。マイアーム1は振り子の原理で負担を軽減できていたが、今回はそれができず口元に持っていく際に肩部分のモーターに過度な負担がかかっている。人の身体は口に物を入れる際、肩甲骨自体が下がる(下制)事で肩の負担を減らしているが、マイアーム2の肩甲骨部の動きは軸回転であるため、その動きが再現できていないための考える。
                    • ・Arduino
                      ・PCA9685 PWMサーボモーター 
                      ・ハウジングコネクター
                      ・マイクロスイッチ
                      ・ジャンパーワイヤー
                      ・MG996R
                      ・MG996R付属のサーボホーン
                      ・丸形サーボホーン
                      ・サーボコネクター延長ケーブル
                      ・BB弾
                      ・LEDライト
                      ・コントローラー用USBケーブル
                      ・Arduino用USBケーブル
                      ・ACDCアダプター
                      ・直径2mm幅ケーブル
                      ・直径18cm皿
                      ・輪ゴム(直径25mm)
                      ・箸 スプーン フォーク
                      ・工具類
                    • ①ベアリング制作 (4つ)
                       ベアリング内とベアリング外のへこみ部分を合わせ、そこからBB弾を挿入 
                       (動画参照) 
                      ②モーターとサーボホーンをつける前に、ベアリング部分を各パーツ(肩×2、前腕×1、手部×1)に
                       ねじでとりつける
                      ③MG996Rを取り付けねじで固定する
                      ④延長ケーブルを、各関節を通りながらからだ部分を通し台の中に入れ、PCA996⑤番から⑩番につける

                       

                       
                    • ①マイクロスイッチに半分に切断したコードをはんだ付けする(中央と右側の2か所)
                      ②コードとソケットに金具を取り付ける
                      ③コードを穴から通し、スイッチをコントローラーにはめ込む
                      ④ソケットメス部分をコントローラーふたに押し込む
                      ⑤100円USBを切断し、コントローラーケーブルを制作
                      ⑥コントローラーのレール部分にBB弾を挿入(動画参照)
                      ⑦両側のコントローラー壁部分を取り付けBB弾が外れないようにする
                      ⑧完成
                       ※コントローラーのスイッチ穴は2つあるが1つしか使わない

                    • ①回転部にサーボモーターと、サーボホーンを取り付け、レール部分のくぼみからBB弾を挿入
                      ②配線(配線図参照)
                    • ①各関節をワッシャーを入れねじでとめる
                      ②スライド部レールにBB弾を入れ、ふたをねじどめする
                      ③輪ゴム(ナンバー12、直径25㎜)を3か所(スライド部分×2 アーム部分×1)取り付ける
                      ④フォーク、スプーン(もしくは箸)は10円玉でねじ固定する
                    • #include <Wire.h>
                      #include <Adafruit_PWMServoDriver.h>

                      Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

                      #define SERVO_MIN 150
                      #define SERVO_MAX 600
                      #define LOGICAL_SERVO_COUNT 6      // 論理サーボ数 (0..5 → PCA9685 の 5..10)
                      #define BUTTON_PIN 2
                      #define LED_PIN 13
                    • #define WAIT_TIME 1000

                      // サーボ状態
                      float servoPositions[LOGICAL_SERVO_COUNT];
                      float servoTargetAngles[LOGICAL_SERVO_COUNT];
                      float servoStartAngles[LOGICAL_SERVO_COUNT];
                      unsigned long servoStartTimes[LOGICAL_SERVO_COUNT] = {0};
                      unsigned long servoMoveDurations[LOGICAL_SERVO_COUNT] = {0};
                    • bool servoIsMoving[LOGICAL_SERVO_COUNT] = {false};

                      // --- ポジション ---
                      int positionInitial[LOGICAL_SERVO_COUNT] = {170, 20, 100, 70, 160, 90};
                      int positionMouth[LOGICAL_SERVO_COUNT]   = {10, 40, 45, 90, 50, 90};

                    • int positionMiddles[7][LOGICAL_SERVO_COUNT] = {
                        {12, 80, 120, 60, 70, 80},
                        {12, 40, 160, 60, 70, 60},
                        {12, 20, 150, 70, 60, 100},
                        {12, 100, 120, 55, 130, 80},
                        {12, 100, 120, 65, 130, 80},
                        {12, 80, 150, 40, 100, 180},
                        {12, 30, 150, 50, 100, 80}
                      };
                    • int positionGoals[7][LOGICAL_SERVO_COUNT] = {
                        {12, 70, 160, 60, 60, 100},
                        {12, 70, 110, 60, 70, 60},
                        {12, 70, 140, 60, 70, 20},
                        {12, 70, 140, 60, 70, 30},
                        {12, 40, 130, 70, 130, 30},
                        {12, 80, 150, 40, 100, 0},
                        {12, 30, 150, 80, 100, 30}
                      };
                    • // --- スピード / ディレイ ---
                      unsigned long speedToMiddle[7][LOGICAL_SERVO_COUNT] = {
                        {500, 2000, 2000, 2000, 2000, 2000},
                        {500, 2000, 2000, 2000, 2000, 2000},
                        {500, 2000, 2000, 2000, 2000, 2000},
                        {500, 2100, 1900, 2100, 2000, 2000},
                        {500, 2200, 2000, 2000, 2000, 2000},
                    •  {500, 2100, 1800, 2000, 1900, 2000},
                        {500, 2000, 2000, 2000, 2000, 2000}
                      };
                      unsigned long delayToMiddle[7][LOGICAL_SERVO_COUNT] = {
                        {0, 0, 500, 0, 500, 500},
                        {0, 0,   0, 0, 500, 500},
                        {0, 0, 500, 0, 500, 500},
                        {0, 0, 500, 0, 500, 500},
                    •  {0, 0, 500, 0, 500, 500},
                        {700, 700, 700, 700, 700, 0},
                        {0, 0, 500, 0, 500, 500}
                      };
                    • unsigned long speedToGoal[7][LOGICAL_SERVO_COUNT] = {
                        {0, 2000, 4000, 4000, 4000, 2000},
                        {0, 2000, 4000, 4000, 4000, 2000},
                        {0, 4000, 4000, 4000, 4000, 2000},
                        {0, 1000, 4000, 4000, 4000, 2000},
                        {0, 1000, 4000, 4000, 4000, 2000},
                    • {0, 1000, 4000, 4000, 4000, 2000},
                        {0, 1000, 4000, 4000, 4000, 2000}
                      };
                      unsigned long delayToGoal[7][LOGICAL_SERVO_COUNT] = {
                        {0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0},
                    •  {0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 5, 0, 0}
                      };

                      // 戻りパラメータ
                    • unsigned long delayFromGoal[LOGICAL_SERVO_COUNT] = {0, 700, 700, 1200, 700, 500};
                      unsigned long speedFromGoal[LOGICAL_SERVO_COUNT] = {1000, 1500, 2000, 3000, 3000, 2000};

                      unsigned long delayToMouth[LOGICAL_SERVO_COUNT] = {4000, 50, 100, 150, 200, 0};
                    • unsigned long speedToMouth[LOGICAL_SERVO_COUNT] = {4000, 3000, 3000, 3000, 4000, 1500};

                      unsigned long delayFromMouth[LOGICAL_SERVO_COUNT] = {1000, 50, 100, 150, 200, 0};
                      unsigned long speedFromMouth[LOGICAL_SERVO_COUNT] = {4000, 3000, 3000, 3000, 3000, 1500};

                    • // 状態機械
                      enum State {
                        IDLE,
                        MOVING_TO_MIDDLE,
                        MOVING_TO_GOAL,
                        MOVING_TO_INITIAL,
                        MOVING_TO_MOUTH,
                        AT_GOAL,
                        AT_MOUTH,
                        RETURNING_FROM_MOUTH
                      };
                      State state = IDLE;
                    • int currentGoal = -1;
                      bool buttonPreviouslyPressed = false;
                      unsigned long lastReturnTime = 0;
                      unsigned long lastButtonPressTime = 0;
                      const unsigned long debounceDelay = 50;

                    • // プロトタイプ
                      void moveToPosition(int targetAngles[LOGICAL_SERVO_COUNT], unsigned long speeds[LOGICAL_SERVO_COUNT], unsigned long delays[LOGICAL_SERVO_COUNT]);
                      void setServoTarget(int i, float target, unsigned long duration, unsigned long delayTime);
                    • void updateServo(int i);
                      bool anyServoMoving();
                      void silentInitialize();

                      void setup() {
                        Serial.begin(115200);
                    • Wire.begin();
                        pwm.begin();
                        pwm.setPWMFreq(60);
                        pinMode(BUTTON_PIN, INPUT_PULLUP);
                        pinMode(LED_PIN, OUTPUT);
                        digitalWrite(LED_PIN, LOW);

                        silentInitialize();
                        moveToPosition(positionInitial, speedFromGoal, delayFromGoal);
                        state = MOVING_TO_INITIAL;
                      }
                    • void loop() {
                        unsigned long now = millis();
                        bool isPressed = digitalRead(BUTTON_PIN) == LOW;
                        bool isMouthReady = (state == IDLE && (now - lastReturnTime >= WAIT_TIME));

                        digitalWrite(LED_PIN, isMouthReady ? HIGH : LOW);
                    • if (isPressed && !buttonPreviouslyPressed && (now - lastButtonPressTime > debounceDelay)) {
                          lastButtonPressTime = now;
                          buttonPreviouslyPressed = true;

                    • // --- ボタン押下処理 ---
                          if (state == MOVING_TO_INITIAL) {
                            currentGoal = (currentGoal + 1) % 7;
                            moveToPosition(positionMiddles[currentGoal], speedToMiddle[currentGoal], delayToMiddle[currentGoal]);
                    • state = MOVING_TO_MIDDLE;
                            return;
                          }

                          if (state == MOVING_TO_MOUTH) {
                            // Mouth 未到達キャンセル → FromMouth
                            moveToPosition(positionInitial, speedFromMouth, delayFromMouth);
                    •  state = MOVING_TO_INITIAL;
                            return;
                          }

                          if (state == MOVING_TO_GOAL || state == MOVING_TO_MIDDLE) {
                            // Goal 未到達キャンセル → FromGoal
                            moveToPosition(positionInitial, speedFromGoal, delayFromGoal);
                    • state = MOVING_TO_INITIAL;
                            return;
                          }

                          if (state == AT_GOAL) {
                            moveToPosition(positionInitial, speedFromGoal, delayFromGoal);
                            state = MOVING_TO_INITIAL;
                            return;
                          }
                    • if (state == AT_MOUTH) {
                            moveToPosition(positionInitial, speedFromMouth, delayFromMouth);
                            state = RETURNING_FROM_MOUTH;
                            return;
                          }

                    •  if (isMouthReady) {
                            moveToPosition(positionMouth, speedToMouth, delayToMouth);
                            state = MOVING_TO_MOUTH;
                          } else if (state == IDLE) {
                            currentGoal = (currentGoal + 1) % 7;
                    •  moveToPosition(positionMiddles[currentGoal], speedToMiddle[currentGoal], delayToMiddle[currentGoal]);
                            state = MOVING_TO_MIDDLE;
                          }
                        }
                    • if (!isPressed) buttonPreviouslyPressed = false;

                        for (int i = 0; i < LOGICAL_SERVO_COUNT; i++) updateServo(i);

                        if (!anyServoMoving()) {
                          switch (state) {
                            case MOVING_TO_INITIAL:
                    •  case RETURNING_FROM_MOUTH:
                              state = IDLE;
                              lastReturnTime = millis();
                              break;
                            case MOVING_TO_MIDDLE:
                    • moveToPosition(positionGoals[currentGoal], speedToGoal[currentGoal], delayToGoal[currentGoal]);
                              state = MOVING_TO_GOAL;
                              break;
                    • case MOVING_TO_GOAL:
                              state = AT_GOAL;
                              lastReturnTime = millis();
                              break;
                            case MOVING_TO_MOUTH:
                    • state = AT_MOUTH;
                              break;
                            default:
                              break;
                          }
                        }
                      }

                    • void moveToPosition(int targetAngles[LOGICAL_SERVO_COUNT], unsigned long speeds[LOGICAL_SERVO_COUNT], unsigned long delays[LOGICAL_SERVO_COUNT]) {
                        for (int i = 0; i < LOGICAL_SERVO_COUNT; i++) {
                    • setServoTarget(i, targetAngles[i], speeds[i], delays[i]);
                        }
                      }

                      void setServoTarget(int i, float target, unsigned long duration, unsigned long delayTime) {
                    •  servoStartAngles[i] = servoPositions[i];
                        servoTargetAngles[i] = constrain(target, 0.0f, 180.0f);
                        servoMoveDurations[i] = duration;
                        servoStartTimes[i] = millis() + delayTime;
                        servoIsMoving[i] = true;
                      }
                    • void updateServo(int i) {
                        if (!servoIsMoving[i]) return;
                        unsigned long now = millis();
                        if (now < servoStartTimes[i]) return;

                    • unsigned long elapsed = now - servoStartTimes[i];
                        if (elapsed >= servoMoveDurations[i]) {
                          servoPositions[i] = servoTargetAngles[i];
                          servoIsMoving[i] = false;
                        } else {
                    •  float t = (float)elapsed / servoMoveDurations[i];
                          float easedT = t * t * (3 - 2 * t);
                          servoPositions[i] = servoStartAngles[i] + (servoTargetAngles[i] - servoStartAngles[i]) * easedT;
                        }
                    •  int pwmVal = constrain(map(servoPositions[i], 0, 180, SERVO_MIN, SERVO_MAX), SERVO_MIN, SERVO_MAX);
                        pwm.setPWM(i + 5, 0, pwmVal);
                      }
                    • bool anyServoMoving() {
                        for (int i = 0; i < LOGICAL_SERVO_COUNT; i++) {
                          if (servoIsMoving[i]) return true;
                        }
                        return false;
                      }

                    • void silentInitialize() {
                        for (int i = 0; i < LOGICAL_SERVO_COUNT; i++) {
                          int angle = positionInitial[i];
                          servoPositions[i] = angle;
                          servoTargetAngles[i] = angle;
                          servoIsMoving[i] = false;
                    •  int pwmVal = constrain(map(angle, 0, 180, SERVO_MIN, SERVO_MAX), SERVO_MIN, SERVO_MAX);
                          pwm.setPWM(i + 5, 0, pwmVal);
                        }
                      }


                    • 1ボタン操作に変えた
                      コントローラーにBB弾を使用し動作をスムーズに
                      コントローラーに傾斜をつけ、ボタンを押した後、腕の重さで元の位置に戻るようにした



                    • ・滑る部分にBB弾を入れ、スムーズに動くようにした
                      ・BB弾の厚みによって、部品同士が外れないようにした
                    • 食べ物がスプーンの上に乗るのを待つのではなく、自分から掴みにいくようにした
                      皿の中に7か所の目標位置を設定し、3秒以内にボタンを押すと次の目標位置にスプーンが移動する

                      1ボタンで全てを操作するために、ボタンを離している間の時間差によって動作が変わるよう変更した

                      3秒以内  次の目標位置(全部で皿内の7か所の目標位置)
                      3秒経過  LEDが点灯し口に移動

                  Add Card Order

                  References

                    Usages

                      Project comments