スタックチャン をArduinoFrameworkでスムーズに動かす[PWMサーボ版]

 Super-kawaii-robot!!! スタックチャンはししかわさん(@meganetaaan)が公開しているオープンソースのプロジェクトです。公開されているリポジトリではModdableというJavaScriptで動くファームウェアが公開されています。しかし、M5StackなのでArduinoFrameworkでも動かしたい方もいらっしゃると思うのでM5Stack-AvatarをベースにPWMサーボ(SG90等)で動作させる方法をまとめておきます。

 もくじ(Index)

M5Stack関連の目次へ戻る

スムーズに動かすためにServoEasingを使う

 ArduinoFrameworkにはPWMサーボをスムーズに動かすことが可能になるServoEasingというライブラリがあります。ESP32Servoでは移動する角度のみを指定してしますが、ServoEasingでは角度だけでなく移動時間やスピードを指定することができます。

ServoEasing – move your servo more natural

M5Stackの種類

本記事では下記のように分けます。

  • Core1
    M5Stack Basic/Gray/Go
  • Fire
    M5Stack Fire
  • Core2
    M5Stack Core2/Core2 for AWSIoT EduKit

この記事で使用するライブラリ

 下記のライブラリを利用します。

  • M5Stack-Avatar(v0.7.3)
    2021/12現在、最新のArduino-esp32ボードやM5Stackボードを使うと不具合がありライブラリを修正する必要があります。補足を参照してください。
  • ServoEasing(v2.4.0)
  • ESP32Servo(v0.9.0)
    ServoEasingが依存しているため必要です。

ServoEasingの基本的な使い方

①初期設定

下記のようにGPIOと開始角度を設定しておきます。

#define SERVO_PIN_X 13
#define SERVO_PIN_Y 14
#define START_DEGREE_VALUE_X 90
#define START_DEGREE_VALUE_Y 90
  • GPIO
    今回の記事ではM5Stack Core2 for AWSIoT EduKitのPort.C(G13,G14)で説明します。他のM5Stackやスタックチャン基板は下記のように接続してください。
    • スタックチャン基板(Ver.1.1)
      • Core1
        G5,G2
      • Core2
        G27,G19
    • Port.A
      • Core1・Fire(内部I2Cと共有)
        G21,G22
      • Core2(外部I2Cと共有)
        G32,G33
    • Port.B(片側がINPUTのみなので1つしか使えません。)
      • Core1・Fire・Core2共通
        G26,G36(INPUTのみ)
    • Port.C(FireはPSRAMと競合するため使用不可)
      • Core1
        G16,G17
      • Core2
        G13,G14
  • 開始角度
    初期化したときにこの角度まで一気に動きます。(※ここはスムースにはできません。)スタックチャンを組み立てる時に初期位置を90°にしてから組み立てましょう。

②サーボの初期化

 サーボの初期化はattach()を使います。下記のように記述してください。(attachするとサーボが初期位置にうごきます。)

if (servo_x.attach(SERVO_PIN_X, START_DEGREE_VALUE_Y, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE)) {
  Serial.print("Error attaching servo x");
}
if (servo_y.attach(SERVO_PIN_Y, START_DEGREE_VALUE_Y, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE)) {
  Serial.print("Error attaching servo y");
}

③スピードとモードの決定

 サーボを動かすスピードとモードを設定します。モードについてはいくつかあるのですが、下記の2つを設定してみると違いが分かりやすいと思います。

モードについて

  • EASE_LINEAR
    初期設定はこれです。一定の速度で動きます。
  • EASE_QUADRATIC_IN_OUT
    動きの最初と終わりがスムーズになります。

設定できるモードはこちらに一覧があります。

スピード

スピードは動かすときにも指定可能ですが、今回は一括で60degree/secで設定します。

  • setSpeedForAllServos(60)
servo_x.setEasingType(EASE_QUADRATIC_IN_OUT);
servo_y.setEasingType(EASE_QUADRATIC_IN_OUT);
setSpeedForAllServos(60);

④次の角度を指定して動かす。

 目的の角度を指定すると、その角度まで指定したスピードで動きます。最初は下記の3つだけ覚えれば良いでしょう。

  • easeTo(degree)
    角度を指定するとsetSpeedForAllServosで決めたスピードで動きます。
  • easeTo(degree, degrees/sec)
    角度とスピード(degree/sec)を指定して動きます。
  • easeToD(degree, millisForMove)
    角度と移動時間を指定します。例えばeaseToD(45, 1000)とすると45°まで1秒間で移動します。

⑤2軸を同時に動かす。

setEaseToを使いX軸とY軸の角度を設定してから、その後でsynchronizeAllServosStartAndWaitForAllServosToStop()を実行します。

servo_x.setEaseTo(x);
servo_y.setEaseTo(y + 40);
synchronizeAllServosStartAndWaitForAllServosToStop();

⑥サーボを動かすサンプル

  • ボタンA
    X軸、Y軸とも90°になります。
  • ボタンB
    X軸は現在の角度→0→180→90、Y軸は現在の角度→40→90という動きを2回繰り返します。
  • ボタンC
    ランダムに動きます。
#include <Arduino.h>

#if defined(ARDUINO_M5STACK_Core2)
  #include <M5Core2.h>
  #define SERVO_PIN_X 13
  #define SERVO_PIN_Y 14
#elif defined( ARDUINO_M5STACK_FIRE )
  #include <M5Stack.h>
  #define SERVO_PIN_X 21
  #define SERVO_PIN_Y 22
#elif defined( ARDUINO_M5Stack_Core_ESP32 )
  #include <M5Stack.h>
  #define SERVO_PIN_X 21
  #define SERVO_PIN_Y 22
#endif

#include <Avatar.h> // https://github.com/meganetaaan/m5stack-avatar
#include <ServoEasing.hpp> // https://github.com/ArminJo/ServoEasing

#define START_DEGREE_VALUE_X 90
#define START_DEGREE_VALUE_Y 90

ServoEasing servo_x;
ServoEasing servo_y;

bool random_move = false;

void setup() {

#if defined(ARDUINO_M5STACK_Core2)
  M5.begin(true, true, true, false, kMBusModeOutput);
#elif defined( ARDUINO_M5STACK_FIRE ) || defined( ARDUINO_M5Stack_Core_ESP32 )
  M5.begin(true, true, true, false); // Grove.Aを使う場合は第四引数(I2C)はfalse
#endif
  if (servo_x.attach(SERVO_PIN_X, START_DEGREE_VALUE_X, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE)) {
    Serial.print("Error attaching servo x");
  }
  if (servo_y.attach(SERVO_PIN_Y, START_DEGREE_VALUE_Y, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE)) {
    Serial.print("Error attaching servo y");
  }
  servo_x.setEasingType(EASE_QUADRATIC_IN_OUT);
  servo_y.setEasingType(EASE_QUADRATIC_IN_OUT);
  setSpeedForAllServos(60);
}

void loop() {
  M5.update();
  if (M5.BtnA.wasPressed()) {
    random_move = false;
    servo_x.setEaseTo(90);
    servo_y.setEaseTo(90);
    synchronizeAllServosStartAndWaitForAllServosToStop();
  }
  if (M5.BtnB.wasPressed()) {
    random_move = false;
    for (int i=0; i<2; i++) {
      servo_x.easeTo(0);
      servo_x.easeTo(180);
      servo_x.easeTo(90);
      servo_y.easeTo(50);
      servo_y.easeTo(90);
    }
  }
  if (M5.BtnC.wasPressed()) {
    random_move = !random_move;
  }

  if (random_move) {
    int x = random(180);
    int y = random(40);
    M5.update();
    if (M5.BtnC.wasPressed()) {
      random_move = false;
    }
    servo_x.setEaseTo(x);
    servo_y.setEaseTo(y + 40);
    synchronizeAllServosStartAndWaitForAllServosToStop();
    int delay_time = random(10);
    delay(2000 + 100*delay_time);
  }

}

M5Stack-Avatarと合わせる。

 M5Stack-Avatarのbasicサンプルと組み合わせると下記のようになります。

#include <Arduino.h>

#if defined(ARDUINO_M5STACK_Core2)
  #include <M5Core2.h>
  #define SERVO_PIN_X 13
  #define SERVO_PIN_Y 14
#elif defined( ARDUINO_M5STACK_FIRE )
  #include <M5Stack.h>
  #define SERVO_PIN_X 21
  #define SERVO_PIN_Y 22
#elif defined( ARDUINO_M5Stack_Core_ESP32 )
  #include <M5Stack.h>
  #define SERVO_PIN_X 21
  #define SERVO_PIN_Y 22
#endif

#include <Avatar.h> // https://github.com/meganetaaan/m5stack-avatar
#include <ServoEasing.hpp> // https://github.com/ArminJo/ServoEasing       

using namespace m5avatar;
Avatar avatar;

#define START_DEGREE_VALUE_X 90
#define START_DEGREE_VALUE_Y 90

ServoEasing servo_x;
ServoEasing servo_y;

bool random_move = false;

const char* lyrics[] = { "BtnA:MoveTo90  ", "BtnB:ServoTest  ", "BtnC:RandomMode  "};
const int lyrics_size = sizeof(lyrics) / sizeof(char*);
int lyrics_idx = 0;

void setup() {

#if defined(ARDUINO_M5STACK_Core2)
  M5.begin(true, true, true, false, kMBusModeOutput);
#elif defined( ARDUINO_M5STACK_FIRE ) || defined( ARDUINO_M5Stack_Core_ESP32 )
  M5.begin(true, true, true, false); // Grove.Aを使う場合は第四引数(I2C)はfalse
#endif
  if (servo_x.attach(SERVO_PIN_X, START_DEGREE_VALUE_X, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE)) {
    Serial.print("Error attaching servo x");
  }
  if (servo_y.attach(SERVO_PIN_Y, START_DEGREE_VALUE_Y, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE)) {
    Serial.print("Error attaching servo y");
  }
  servo_x.setEasingType(EASE_QUADRATIC_IN_OUT);
  servo_y.setEasingType(EASE_QUADRATIC_IN_OUT);
  setSpeedForAllServos(60);
  avatar.init();
}

void loop() {
  M5.update();
  if (M5.BtnA.wasPressed()) {
    random_move = false;
    servo_x.setEaseTo(90);
    servo_y.setEaseTo(90);
    synchronizeAllServosStartAndWaitForAllServosToStop();
  }
  if (M5.BtnB.wasPressed()) {
    random_move = false;
    for (int i=0; i<2; i++) {
      avatar.setSpeechText("X 90 -> 0  ");
      servo_x.easeTo(0);
      avatar.setSpeechText("X 0 -> 180  ");
      servo_x.easeTo(180);
      avatar.setSpeechText("X 180 -> 90  ");
      servo_x.easeTo(90);
      avatar.setSpeechText("Y 90 -> 50  ");
      servo_y.easeTo(50);
      avatar.setSpeechText("Y 50 -> 90  ");
      servo_y.easeTo(90);
    }
  }
  if (M5.BtnC.wasPressed()) {
    random_move = !random_move;
  }

  if (random_move) {
    int x = random(180);
    int y = random(40);
    M5.update();
    if (M5.BtnC.wasPressed()) {
      random_move = false;
    }
    servo_x.setEaseTo(x);
    servo_y.setEaseTo(y + 40);
    synchronizeAllServosStartAndWaitForAllServosToStop();
    int delay_time = random(10);
    delay(2000 + 100*delay_time);
    avatar.setSpeechText("Stop BtnC");
  }
  if (!random_move) {
    const char* l = lyrics[lyrics_idx++ % lyrics_size];
    avatar.setSpeechText(l);
    avatar.setMouthOpenRatio(0.7);
    delay(200);
    avatar.setMouthOpenRatio(0.0);
    delay(2000);
  }

}

謝辞

 スタックチャンという可愛いロボットを生み出してくださったししかわさん(@meganetaaan)さんに感謝いたします。

 M5GoBottom版の作成にあたり、DMM.makeで実際にモデルを作成し助言を頂いたロボさん(@robo8080)さんに感謝いたします。

スタックチャン について

 Super-kawaii-robot スタックチャンはししかわさんが公開しているオープンソースのプロジェクトです。

スタックチャン プロジェクトへの貢献

スタックチャンはオープンソースのプロジェクトなので、開発・スポンサー・SNSでつぶやくなど、様々な貢献ができます。

【補足】M5Stack-Avatarが再起動する時

 2021/12/18現在、Arduino-esp32 Ver.2.0.0やM5Stackのボード最新版を利用していると、書き込んだ後再起動を繰り返すという障害が見つかっています。M5Stack-AvatarのAvatar.cppを修正してください。もしくはボードマネージャーからArduino-esp32 V1.0.6に戻すと動くようになります。

https://github.com/meganetaaan/m5stack-avatar/pull/66/commits/f28efa87d482a730237565a666d67d7422e638f4

おわりに

 ししかわさんの考えたスタックチャン及びM5Stack-Avatarのおかげで、毎日が楽しくなりました。ArduinoFrameworkで動かしている人はTwitterではロボさん(@robo8080)や私がいるのですが、「これ!」というファームウェアはまだありません。ランダムで動かすだけでも癒されるのですが、ぜひあなただけのスタックチャンを作ってみましょう!

コメントを残す

メールアドレスが公開されることはありません。