OGIMOノート

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

M5Stack(ESP32)を使い始めてみた

はじめに

 ちまたでブームになっている Arduino対応無線マイコンESP32が搭載されたモジュールキット”m5stack"!  2か月ほど前に衝動買いしていたものの、別件を優先してすっかり積みマイコン化していましたが、ようやく開封!

f:id:motokiinfinity8:20180722175725j:plain


M5Stackとは

 M5StackはEspressif社のArduino対応無線(Wifi/BL)マイコンESP32をベースに、ディスプレイ、スピーカ、バッテリー、SDカードスロット、ボタン、USBやGroveのコネクタを5cm四方の基板に搭載した統合開発モジュール。なので、ESP32マイコンを組み込む前のSDKボードとして、またM5stack単体としても工作品に導入できる。見た目もコンパクトで、液晶に顔を表現するだけでかわいくなる!
やはり、見た目は大事だ。

esp32マイコンは、Arduino UNOに比べて、
 ・マイコン周波数 → 30倍 (ESP32: 80MHz240MHz@2コア / Uno:16MHz)
 ・RAM容量 → 260倍(ESP32:520kB / Uno:2kB)
 ・ROM容量 → 128倍 (ESP32:4MB / Uno:32kB)
それでして、価格は esp32 の方が安いのだから、もう純正Arduinoマイコン系は出番がなくなるのではなかろうか。

個人的な使い道

ちょっと考えただけでも、いろんな工作品に即時導入できそう
 ① 小型ロボットのメインマイコンに出来る(液晶画面/スピーカがあるのは素敵)
   rosserialでHost PCからもROS制御できるので子ロボット向き
 ② リハビリ靴 ver.4のスピーカ部として使えそう
   (ver.3の課題になったディスプレイ課題がこれで解決する!)
 ③ 玄関お出迎えロボやボタン押しロボットにも使える
  (もともとESP32で簡易ロボを作ろうとしてたが、
   ディスプレイがある方が愛着がわきそう)
 ④ 業務でMEMSセンサーの性能実験をする際の簡易モニターに使える
  (シリアル値確認のためにPCを持ち運ばなくて良い!)
  ⑤ 子供のおもちゃ魔改造用!!

そんな事を視野に入れながら、今回のM5Stack 立ち上げで簡単にディスプレイ表示&スピーカ再生をしてみた。

うーん、いい感じ♪
これを自家製ピタゴラ装置の一つにしてしまったりも出来そう!(息子がピタゴラスイッチ大好きなので間違いなく喜ぶ!)
という訳で、さっそく模索開始!


環境構築

環境構築はそんなに難しくない。esp32環境が入っている前提であれば、M5stackのGitHubからダウンロードしてlibraryに加えてやればよい。
github.com


なお、esp32のArduino開発環境を入れるならば、こちらを参考するとよさそう
www.mgo-tec.com

M5stack(esp32)でサーボモータを動かしてみる

ここからは、M5stackで各モジュールを動かした際の注意事項を備忘録として記載しておく。意外と手間取ってしまった。

サーボーモータはArudinoでよく使われている"Servo.h"を使おうとしたが、結果としてはNGであった。

#include <Servo.h>
Servo myservo;

void setup() {
  myservo.attach( 1 );
}

void loop() {
  myservo.write(45);
  delay(5000);
}


で動かそうとしたら、、、

『警告:ライブラリServoはアーキテクチャ(avr, sam, samd, nrf52, stm32f4)に対応したものであり、アーキテクチャ(esp32)で動作するこのボードとは互換性がないかもしれません。 #error "This library only supports boards with an AVR, SAM, SAMD, NRF52 or STM32F4 processor."』

ですって。
ありゃま? ESP8266では使えてたのに。。。

どうやら調べてみたところ、ESP8266まではServo.h が使える ESP8266 等と Servo.h が使えない ESP32 だそうです。ソラシラナンダ

そこで、下記サイトを参考に、LEDを使用するledcWrite関数でPWM信号を使用するのがよさそうです。c( )関数でパラメータを設定し、ledcAttachPin( )関数で使用するピンを指定し、ledcWrite( )関数でPWM値を算出する。
ESP32: RCサーボの制御 | マイクロファン ラボ

ただし、使用CHには注意。M5stackの場合、CH0はスピーカ用、CH7はディスプレイのバックライト用で使っているので、それ以外を選ぶ必要がある。
間違って、CH0を使ってサーボを動かそうとしたら、スピーカノイズが発生してしまった。。

という訳で、CH3を使えばよさそう。このあたり、きちんとM5stackのハード構成を抑える必要がありそうだ。

#define LEDC_CHANNEL_3 3    //LEDCのチャンネル指定
#define LEDC_TIMER_BIT 16   //LEDCのPWMタイマーの精度設定
#define LEDC_SERVO_FREQ 50   //サーボ信号の1サイクル 50Hz:20ms
#define SERVO_PIN 2       //ServoPWMピン

int servo_pwm_count(int v)
{
  float vv = (v + 90) / 180.0 ;
  return (int)(65536 * (SERVO_MIN_WIDTH_MS + vv * (SERVO_MAX_WIDTH_MS -SERVO_MIN_WIDTH_MS)) / 20.0) ;
}

setup(){
   // servoモーター設定
  ledcSetup(LEDC_CHANNEL_3, LEDC_SERVO_FREQ, LEDC_TIMER_BIT) ; // 16ビット精度で制御
  ledcAttachPin(SERVO_PIN, LEDC_CHANNEL_3) ; // CH3をRC SERVOに
}

loop(){
    ledcWrite(LEDC_CHANNEL_3, servo_pwm_count(60)) ; 
}


M5stack(esp32)でディスプレイに表示を出してみる

ディスプレイドライバはILITEK 社の TFT LCD 用 ドライバー ILI9341 との事。詳細はこちらのサイトが詳しそう。
www.mgo-tec.com

どうやら SPI接続+バックライト制御の模様。M5stackの描画ライブラリは、ESP32のSPIレジスタに直接アクセスしてHW転送を使っている様で高速転送が期待できる模様。

SDカード上のJPEGデータを表示

一番楽な使い方。

  M5.Lcd.clear();
  M5.Lcd.drawJpgFile(SD, "/dora.jpg");

これだけでSDカードのルート直下にある"dora.jpg"が表示されるので簡単。
これは画面左上を原点とした場合の描画。任意の位置に描画させるためには以下の引数を設定すればよさそうな模様(まだ未実証)

M5.Lcd.drawJpgFile(fs::FS &fs, const char *path, uint16_t x, uint16_t y, uint16_t maxWidth, uint16_t maxHeight, uint16_t offX, uint16_t offY, jpeg_div_t scale);


公開されている顔アバターを使う

ロボットの顔としてディスプレイを使うため、顔の表示は必要!できれば、瞬きとかちょっとしたアクションの変化は付けたいなぁ。と探していたら、既にライブラリを作成されている方を発見!

github.com

サンプルコードを使わせてもらったが、とても使いやすく可愛い♪ 呼吸などのアクション動作をMainソフトから意識させないライブラリの作り方がとてもありがたい。一旦、ありがたく使わせていただきつつ、将来的には家族好みのキャラに作り替えていきたいなぁ、と。
是非とも、ドラえもん風のキャラクタ顔をここに表示させたい!

M5stack(esp32)でスピーカから音を鳴らしたい

ここが少しばかり苦労した。まず、M5stackとしてのwav/mp3ファイル再生のライブラリはなさそうで、ESP32で動作実績のあるライブラリを持ってくる必要がありそうだ。
ESP8266Audio / ESP8266_Spiramライブラリを使うのが推奨の様だ。M5stackのサンプルプログラムにも本ライブラリのヘッダ記載があった。

https://github.com/earlephilhower/ESP8266Audio
https://github.com/Gianbacchio/ESP8266_Spiram

一旦、
M5 Speaker and WAV files · Issue #40 · m5stack/M5Stack · GitHub
を参考に、ボタンを押す度にwavファイルが再生するコードを作成。

しかし、うまくいなかい。
課題1 : 1回目の再生終了後にノイズが発生して2回目に移行せず。
 ⇒ 試行錯誤の結果、都度 file = new AudioFileSourceSD関数を作り直したらうまくいった。ファイルポインタの位置を戻さないといけないのか?

課題2: WAV再生の場合、一度再生した後に無音ノイズが残る(MP3再生なら問題なし)
 ⇒ ライブラリのソースを見たところ、ライブラリ側の間違い発見!
AudioGeneratorWAV.cpp内のstop関数で停止処理がされておらず

bool AudioGeneratorWAV::stop()
{
  if (!running) return true;
  running = false;
  free(buff);
  buff = NULL;
  output->stop();  ★ここを追加
  return file->close();
}


なるほど、Output-> stop()が抜けていたから、無音再生しっぱなしだったのね。
という訳で、ローカルでライブラリ修正する事で無事に音源ファイルの連続再生ができた! (後ほどGithubに書いておこう)

という訳で、無事に音楽再生もクリア。参考コードは以下の形となった


AudioGeneratorWAV *wav;
AudioGeneratorMP3 *mp3;
AudioFileSourceSD *file_w,file_m;
AudioOutputI2S *out_w, out_m;
AudioFileSourceID3 *id3;

void setup() {
  M5.begin();

  //MP3の場合
  file_m = new AudioFileSourceSD("/sample.mp3");
  id3 = new AudioFileSourceID3(file_m);
  out_m = new AudioOutputI2S(0,1);
  out_m->SetOutputModeMono(true);
  out_m->SetGain(0.3);
  mp3 = new AudioGeneratorMP3();

  //WAVの場合
  file_w = new AudioFileSourceSD("/sample.wav");
  out_w = new AudioOutputI2S(0,1); 
  out_w->SetOutputModeMono(true);
  out_w->SetGain(0.3); 
  wav = new AudioGeneratorWAV(); 
}

void loop(){
  M5.update();
  if(M5.BtnC.wasPressed()){
    //MP3の場合
    file = new AudioFileSourceSD("/sample.mp3");
    id3 = new AudioFileSourceID3(file_m);
    out_m = new AudioOutputI2S(0,1);
    out_m->SetOutputModeMono(true);
    out_m->SetGain(0.3);
    mp3 = new AudioGeneratorMP3();

    mp3->begin(id3, out_m);
    while(mp3->isRunning()){
     if (!mp3->loop()) mp3->stop();
    }

    //WAVの場合
    file_w = new AudioFileSourceSD("/sample.wav");
    out_w = new AudioOutputI2S(0,1); 
    out_w->SetOutputModeMono(true);
    out_w->SetGain(0.3); 
    wav = new AudioGeneratorWAV(); 

    wav->begin(file_w, out_w);
    while(wav->isRunning()){
     if (!wav->loop()) wav->stop();
    }
  }

}

最後に

M5stackを扱った事のある電子工作技術者から見たら初歩的な内容だったかもしれないが、自分への備忘録もかねて記載してみた。いろんなマイコン/プロセッサ/PCを並行して触っていると細かい部分はすぐに忘れてしまうからなぁ。。「一週間前の自分は他人である」ってヤツですね(笑)

今回で M5stackの良さはかなり実感できたので、実際に我が家の工作物に導入していきたいと思う!