スタックチャン を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
- Core1
- Port.A
- Core1・Fire(内部I2Cと共有)
G21,G22 - Core2(外部I2Cと共有)
G32,G33
- Core1・Fire(内部I2Cと共有)
- Port.B(片側がINPUTのみなので1つしか使えません。)
- Core1・Fire・Core2共通
G26,G36(INPUTのみ)
- Core1・Fire・Core2共通
- Port.C(FireはPSRAMと競合するため使用不可)
- Core1
G16,G17 - Core2
G13,G14
- Core1
- スタックチャン基板(Ver.1.1)
- 開始角度
初期化したときにこの角度まで一気に動きます。(※ここはスムースにはできません。)スタックチャンを組み立てる時に初期位置を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 スタックチャンはししかわさんが公開しているオープンソースのプロジェクトです。
- スタックチャンの紹介ページ
スタックチャン(Stack-chan)(ProtoPedia) - スタックチャンのGitHubリポジトリ
https://github.com/meganetaaan/stack-chan
スタックチャン プロジェクトへの貢献
スタックチャンはオープンソースのプロジェクトなので、開発・スポンサー・SNSでつぶやくなど、様々な貢献ができます。
- スタックチャンの機能追加要望や不具合報告はリポジトリのissueにてお願いいたします。
https://github.com/meganetaaan/stack-chan/issues… - GitHubでスタックチャンのスポンサーを募集しています。
https://github.com/sponsors/meganetaaan - M5Stack公式ストアで購入時にクーポンコードを使う。
M5Stack公式ストア(https://m5stack.com)で購入時に「STACKCHAN」というクーポンコードを使うと5%OFFになります。そしてししかわさんの活動の助けもなります。M5Stack公式ストアで製品購入時はぜひ使ってください。
※ 海外(中国・深セン)からの個人輸入となります。購入の注意事項はM5Stack、M5StickC、M5StickV、M5Atom、M5Paper、M5CoreInkなどM5Stack製品の買い方を見てください。 - SNSでつぶやく
頒布の相談をしたとき、ししかわさんの要望はスタックチャンをもっと多くの人に届けたいということだけでした。作成したスタックチャンの写真はTwitterや他のSNSでの公開を推奨します。その際はハッシュタグ(#スタックチャン または #stackchan)をご利用ください。
【補足】M5Stack-Avatarが再起動する時
2021/12/18現在、Arduino-esp32 Ver.2.0.0やM5Stackのボード最新版を利用していると、書き込んだ後再起動を繰り返すという障害が見つかっています。M5Stack-AvatarのAvatar.cppを修正してください。もしくはボードマネージャーからArduino-esp32 V1.0.6に戻すと動くようになります。
おわりに
ししかわさんの考えたスタックチャン及びM5Stack-Avatarのおかげで、毎日が楽しくなりました。ArduinoFrameworkで動かしている人はTwitterではロボさん(@robo8080)や私がいるのですが、「これ!」というファームウェアはまだありません。ランダムで動かすだけでも癒されるのですが、ぜひあなただけのスタックチャンを作ってみましょう!