メインページに戻る

※最初コンパイルに時間がかかるのでボードのインストールを実施し、可能なら最初のコードのコンパイルをスタートさせておく。

このページのゴール

「M5StickC Plus2の基本的な機能を理解し、それらを組み合わせたインタラクティブなプロジェクトを自分で作れるようになること」

今日のねらい

  • M5Stackプラットフォームの全体像を把握する
    • シリーズや拡張モジュールの関係性を理解することで、今後どのようなハードウェアを組み合わせれば自分のアイデアを実現できるかの見通しを立てます。
  • M5StickC Plus2の主要機能を体験する
    • ディスプレイ、日本語フォント、ボタン、LED、ブザー、マイク、そしてAvatarライブラリといった、内蔵機能や外部ライブラリの基本的な使い方を習得します。
  • 「インタラクティブな体験」の基礎を学ぶ
    • ユーザーの操作や周囲の環境にプログラムが反応する仕組みを、具体的なサンプルコードを通じて実践的に学びます。

なぜフィジカルコンピューティングを学ぶのか

これから扱うテーマのキーワードは「データ」です。
AIが急速に発展している今、学習に使える高品質なデータは「いずれ枯渇する」という議論さえあります。つまり、これから価値の高いものの一つは自分の手で集めた現実世界のデータかなと思っています。

世の中には、ランニング、楽器演奏、調理、介護、伝統芸能、犬の散歩……といった無数のアクティビティが存在します。ただ、その大半はまだ十分に計測・記録されていません。もちろん例えばジョギングなどのGPSデータや心拍などを取得するアプリは提供されていますが、走っている最中の自分の身体の詳細な状態、自分が見ている景色、感情やその時考えていたことなどは取りこぼされ続けています。
そこで必要になるのがフィジカルコンピューティングです。
フィジカルコンピューティングとは、コンピュータと現実世界をセンサやアクチュエータ(モーターやLEDなど)を使ってつなぎ、相互に作用するシステムを設計・制作することです。単にデータを集めるだけでなく、「現実世界をコンピュータで制御する」という双方向のやり取りが可能になります。

現実世界にセンサや小さな機器を置く/身につけることで、これまで流れ去っていた活動をデータとして捉え直す。この「計測の設計と実装」が、他に簡単には真似できない価値を生む可能性があります。

データを集めたら終わりではありません。
集めたデータをどう処理し、誰に、どの形で提供するかまで設計してはじめて、人や社会に役立つ仕組みになります。
ここに関わってくるのがHCI(Human–Computer Interaction)です。
HCI(ヒューマン・コンピュータ・インタラクション)は、人がコンピュータとどのように関わるかを研究する学問分野です。単に技術的に動かすだけでなく、「ユーザーにとって直観的で、意味がある体験をいかに設計するか」という視点を提供します。
HCIは、収集 → 処理 → 提供 のパイプライン全体を、人にとって使いやすく意味のある体験としてデザインする学問ともいえます。

文化情報デザイン工学では、主にM5StickC Plus2を用いながら、次の点を実践的に学びます。
・どんなセンサを選び、どうやってデータを収集するか
・得たデータをどう処理し、どんな体験として提供するか

基礎パートは3回でまとめ、中盤ではM5StickC Plus2を用いてネットワークの活用方法を学びます。
後半は 3Dプリンタ/レーザーカッターを活用した筐体づくり自主制作に時間を配分する予定です。
15回を通じて「作る→試す」を実践し、様々な情報を扱いながら、人の体験へ還す道筋を自分の手で設計できるようになることを目指します。

※ ちなみに収集したデータに対してデータサイエンスの知識を活用すると、より豊かな体験が作れる可能性が高いと思いますが、今回の講義でそこまで考慮すると中途半端になってしまうので、データサイエンスの知識はあまり扱いません。ただ、独学でできる方は是非講義後半の自主制作のタイミングなどで試してみてください。


0. 導入

文化情報工学基礎演習では、Arduino Nano を使って、ブレッドボード配線をしながらLEDやセンサーを動かす、という体験をしてきました。
これから扱うM5StickC Plus2(M5シリーズ) は、その配線と周辺機能の多くを最初から内蔵したモジュール群です。
ポイントは「すぐ動く」こと。
電源、液晶、ボタン、無線通信などをケース一体で持っていて、Groveケーブルでセンサーをカチッと挿すだけでOKです。一部ブレッドボードのようにジャンバーワイヤで繋げられます。ハード実験の初動が一気に短縮できます。
個人的にはIoTに必要な無線通信構築の部分がArduinoシリーズと比べて圧倒的に楽ちんです。


1. M5Stack(M5シリーズ)とは?

画像参照元

M5Stackは、本体シリーズ(Core/Stick/ATOM)と、拡張シリーズ(Module/Base/Unit/Hat)でできた積み木のような開発プラットフォームです。
心臓部は ESP32 になります。
ESP32は、Wi-FiとBluetooth機能を内蔵した高性能なマイコンです。
低消費電力でありながら高い処理能力を持つため、IoTデバイス開発の分野で広く利用されています。
M5Stackシリーズのほとんどのモデルに搭載されており、無線通信を使ったプロジェクトを簡単に実現できます。
Wi-Fi/Bluetoothが標準で、Arduino IDE/MicroPythonなどで開発できます。

  • 本体
    • Core:液晶大・スピーカー等を備えた“全部入り”寄り
    • Stick:細長く小型、携帯・装着しやすい
    • ATOM:極小サイズ、IoTノードに最適
  • 拡張
    • Module:Coreの下に挟む物(通信、GPSほか)
    • Base(Bottom):電源や端子の土台
    • Unit:Groveケーブルで横に足す小物(各種センサー)
    • Hat:Stick専用のかぶせ物

詳しく知りたい方は公式HPを見てみてください。
※ ちなみに私はM5StackとM5Stickしか触ったことないです。


2. 本体シリーズ

画像参照元

Core(Basic/Grayなど)

Core シリーズ Core シリーズはディスプレイ、ボタン、スピーカー、マイク、バッテリー、拡張インターフェースを統合しています。

画像参照元

Stick(StickC/StickC Plus/StickV)

スリムでコンパクトな形状で、ウェアラブルや携帯用途向けに設計されています。

AIプロセッサ(Kendryte K210)とカメラを搭載したM5StickVというものもあります。

画像参照元

ATOM(Matrix/Lite)

最小限の機能に絞った超小型モデルです。

画像参照元


3. 拡張の考え方

Module/Base

LLM など複雑な機能を追加できる。

画像参照元

以下のようにM5stackと重ね合わせることで、簡単にローカルLLMが利用できる。

画像参照元

Unit

温湿度・距離・ボタン・LED・リレー等のセンサがあり、繋げばすぐに使える。

画像参照元

Hat

Stick専用、最小スペースで機能を足せる。センサーなど繋げばすぐに使える。

画像参照元


4. M5StickC Plus2(今回の講義で使う)

Stickカテゴリの現行モデルで、小型液晶+IMU+Grove+内蔵バッテリー+ESP32をワンパッケージ化。
Arduino Nanoのような配線は最小限で、「表示」「計測」「無線通信」をコード中心にすぐ試せる。

  • 主要内蔵(モデル差あり)
    • カラー液晶(約1.14″/高視認性)
    • 6軸IMU(加速度+ジャイロ)
    • ボタンGroveポート、(一部モデルでIRマイク等)
    • ESP32によるWi-Fi/Bluetooth
    • 内蔵バッテリー(USB充電)

画像参照元


5. 実習

一部引用:https://qiita.com/RikuSawada/items/d24d97c768505edcf293

では、早速 M5sticC Plus2 を使っていきましょう。

開発環境のセットアップ

必要なもの

  • M5StickC Plus2 本体
  • USB-C ケーブル
  • 開発用PC(Windows / macOS / Linux)
  • Arduino IDE

Arduino IDE の準備

  1. Arduino IDE をインストール
    • 公式サイト(https://www.arduino.cc/en/software)からダウンロードしてインストール。
    • まだインストールしていない方はアクセスしてダウンロードし、デフォルト設定で良いのでインストールしてください。すべて同意、許可で大丈夫です。
      (余談)UI変わりますが、CloudバージョンだとPCが変わっても引き継げます
  2. ボードのインストール
    • 左側のアイコンからボードマネージャを開く(上から二番目)( or [ツール] → [ボード] → [ボードマネージャ] を開く。)
    • esp32 を検索し、esp32 by Espressif Systems をインストール。(※ 3~4分かかります
    • Macのみ注意:3.2.1のバージョンに変更しないと”m5unified.cpp ‘touch_pad_init’ was not declared in this scope” というエラーが出るようです。
  3. M5StickC Plus2 用のライブラリをインストール
    • 左側のアイコンからライブラリマネージャを開く(上から三番目) (or [スケッチ] → [ライブラリを管理] を開く。)
    • m5Stickcplus2」 を検索してインストール(1分もかからない)。

  4. M5stickC Plus2をPCに接続してみる
    • 以下のような加速度センサに反応するデモが最初から入ってます。

5.1 ディスプレイに文字を表示してみる

以下のコードを Arduino IDE にコピーし、M5StickC Plus2 に書き込みます。
コードは基本 Arduino Nano の時と一緒です。

  • setup() は「準備」フェーズ(電源を入れた時に一度だけ実行)
  • loop() は「実行」フェーズ(繰り返し実行される部分)
コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ

//M5StickCPlus2.h は、M5StickC Plus2専用の機能を簡単に使うためのライブラリです
//M5.begin()のように、画面、ボタン、IMUなどの内蔵ハードウェアを初期化・制御するための関数が含まれています。

// 最初に一度だけ実行される処理
void setup() {
    M5.begin();  // M5StickC Plus2 の初期化(LCD, ボタン, I2C, IMUなどを含む)
    M5.Lcd.setRotation(3);  // 画面の回転方向を設定(横向きに表示)
    M5.Lcd.fillScreen(BLACK);  // 画面全体を黒で塗りつぶす(背景クリア)
    M5.Lcd.setTextColor(WHITE);  // 文字色を白に設定
    M5.Lcd.setTextSize(2);  // 文字サイズを2倍に設定
    M5.Lcd.setCursor(10, 10);  // テキスト表示の開始位置を (x=10, y=10) に設定
    M5.Lcd.println("Hello, M5StickC Plus2!");  // 文字列を表示
}

// 繰り返し実行される処理(ループ)
void loop() {
    // メインの処理をここに書く
    // 例: センサー値の読み取り、表示、通信など
}

書き込み手順(こちらも Arduino Nano と流れは一緒です)

  • 「Select Board」=>「Select other board and port…」を選択

  • BOARDSは「M5StickCPlus2 」、PORTSは M5StickC Plus2 のポートを選択。

  • 上の左から2番目の「→」マーク(Upload) をクリック。※初回コンパイルは5分くらいかかります。

書き込みが完了すると、M5StickC Plus2 の画面に Hello, M5StickC Plus2! が表示される。
こんな感じで文字が表示されたらOKです!

※ エラーでた方はesp32のボードパッケージのバージョン間違えているかもしれません。

エラー対処法(2024年度生 宮崎さん)
In file included from /Users/username/Documents/Arduino/sketch_sep29a/sketch_sep29a.ino:1:
/Users/username/Documents/Arduino/libraries/M5StickCPlus2/src/M5StickCPlus2.h:21:5: error: 'RTC8563_Class' does not name a type; did you mean 'RTC_Class'?
   21 |     RTC8563_Class &Rtc     = M5.Rtc;
      |     ^~~~~~~~~~~~~
      |     RTC_Class
exit status 1

Compilation error: exit status 1

上記のようなエラーが出た方は以下を参考に対応お願いします。

VS Codeで保存する流れ

1. ファイルを開く

  • VS Code を起動する
  • メニューから 「ファイル」→「開く…」 を選ぶ
  • エラーメッセージにあった場所に移動します: /Users/momo/Documents/Arduino/libraries/M5StickCPlus2/src/M5StickCPlus2.h
  • M5StickCPlus2.h を選んで開きます

2. ファイルを編集する

開いたら 21 行目あたりにある以下のコードを探します👇

RTC8563_Class &Rtc     = M5.Rtc;

これを次のように書き換えます:

RTC_Class &Rtc     = M5.Rtc;

3. 保存する

  • 編集したら、Command + S(⌘ + S)を押す
    または
    メニューの 「ファイル」→「保存」 をクリック

これでファイルが保存されます。


4. Arduino IDE で再コンパイル

Arduino IDE に戻ってスケッチをコンパイル → 書き込みしてください。
修正が反映されて、エラーが解消するはずです。


⚠️ 補足

  • この方法はライブラリファイルを直接書き換える応急処置です。
  • 今後 Arduino IDE でライブラリをアップデートすると、この修正が消えてしまうので、その場合はもう一度同じ変更をする必要があります。

👉 VS Code での操作、ファイルを「開く」ところからやってみますか? それとも Finder から右クリック → 「Visual Studio Codeで開く」にしたほうがやりやすいですか?

5.2 画面の向きを回転させる

※ 講義中に作業をする際は、「新規でファイルを作成する」のは避けましょう。
 新しいファイルを作成してコンパイルを始めると、終了まで約5分ほど時間がかかってしまいます。時間を大幅に節約するため、必ず既存ファイル(上で実行したコード)を開いた状態で、そこにコードやテキストをコピー&ペーストで貼り替える方法を推奨します。この方法なら、処理は約30秒で完了します。

M5Unified は M5Stackシリーズ(M5Stack, M5StickC, Core2, etc.)をまとめて扱えるライブラリです。

以下のコードを書き込んでみましょう。

コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ

// 最初に一度だけ実行される処理
void setup(void) {
  auto cfg = M5.config();  // 設定オブジェクトを取得
  M5.begin(cfg);           // 設定に基づいて初期化
  M5.Display.setTextSize(2);  // 文字サイズを2倍に設定
}

// 繰り返し実行される処理(ループ)
void loop(void) {
  M5.delay(1000);  // 1秒待つ(=表示が1秒ごとに更新される)

  static int rotation = -1;  // 画面の回転方向を管理する変数(最初は -1)

  M5.Display.clear(TFT_RED);  // 画面を赤で塗りつぶす
  rotation = (rotation + 1) % 8;  // 回転値を0〜7の範囲で循環させる
  M5.Display.setRotation(rotation);  // 回転方向を設定
  M5.Display.setCursor(0, 0);        // テキストの開始位置を左上に設定
  M5.Display.printf("Rotation = %d\n", M5.Display.getRotation());  
  // 現在の回転方向を数値で表示
}

  • setup() では一度だけ初期化を行い、表示文字のサイズを設定。
  • loop() では 1秒ごとに画面を赤でクリアしつつ、表示の回転方向(M5.Display.setRotation()で設定可能。4以降は左右反転した鏡面指定になる)を0〜7の間で順番に切り替えて表示している。
    • 実行すると、画面が毎秒「ぐるぐる」回転していくデモになる。

5.3 日本語を表示させる

フォントを指定することで日本語も描画可能です。
以下のコードを書き込んでみましょう。

コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ

void setup(void) {
  // 設定オブジェクトを取得(電源、ディスプレイなどの設定をまとめる)
  auto cfg = M5.config();
  // デバイスを初期化(ディスプレイやセンサー等も含む)
  M5.begin(cfg);

  // 文字表示位置を左上(x=0, y=0)に設定
  M5.Display.setCursor(0, 0);

  // 日本語フォントを設定(M5Unified内蔵の日本語ゴシック24ドット)
  M5.Display.setFont(&lgfxJapanGothic_24);

  // 日本語文字列をLCDに表示
  M5.Display.print("日本語描画");
}

void loop(void) {
  // イベント処理などを入れたい場合に備えて1msの待ち時間
  M5.delay(1);
}

このプログラムの一番のキモは、M5.Display.setFont(&lgfxJapanGothic_24);という行です。

これは、M5Stack向けのライブラリM5Unifiedの中に、あらかじめ日本語の文字の形(フォントデータ)が用意されているので、それを指定してこれを使ってと教えてます。

5.4 ボタン入力で画面の色を変更してみる

M5StickC Plus2 には、プログラムから制御可能な 3つのボタン(BtnA, BtnB, BtnPWR が搭載されています。
ボタンの活用例としては、画面を切り替える操作や、ストップウォッチのリセットのように状態を初期化する処理を実行したり、ボタンを押したタイミングでセンサー値を取得したり、インターネットから情報を読み込むといった様々な使い方が考えられます。

ボタンC(電源ボタン)を長押しすることで電源オフにできます。覚えておいてください。

画像参照元

以下のコードを書き込んでみましょう。押したボタンによって画面の色が切り替わります。

コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ

void setup() {
    auto cfg = M5.config();     // 設定オブジェクトを取得(画面、電源、ボタンなどの初期化に使う)
    M5.begin(cfg);              // 設定に基づいてM5デバイスを初期化する
    // 文字サイズを指定(標準より大きめの文字で表示する)
    M5.Lcd.setTextSize(4);
}

void loop() {
    // ボタンの状態を更新(押されたか、離されたか、長押しかを検出するために毎回呼ぶ)
    M5.update();

    // テキストを表示する位置を (x=0, y=0) にセットする
    M5.Lcd.setCursor(0, 0);

    // ボタンA(左の物理ボタン)が「離されたとき」の処理
    if (M5.BtnA.wasReleased()) {
        M5.Lcd.fillScreen(RED);   // 画面を赤色で塗りつぶす
        M5.Lcd.print("red");      // 画面に "red" と表示する
    }
    // ボタンB(右の物理ボタン)が「1000ms(=1秒)以上押された後に離されたとき」の処理
    else if (M5.BtnB.wasReleasefor(1000)) {
        M5.Lcd.clear();           // 画面をクリア(真っ黒にする)
    }
    // ボタンB(右の物理ボタン)が「離されたとき」の処理
    else if (M5.BtnB.wasReleased()) {
        M5.Lcd.fillScreen(GREEN); // 画面を緑色で塗りつぶす
        M5.Lcd.print("green");    // 画面に "green" と表示する
    }
    
    // loop() は常に繰り返し呼ばれるので、毎フレームこの条件分岐が実行される
}

ポイント

ボタンの状態チェック(M5.update():まずM5.update()を使って、「今、どのボタンが押されたか、離されたか」という最新の情報を取得します。これが、ボタン操作をマイコンに認識させるための重要なステップです。void loop()(繰り返し実行)の中で常にボタンの状態をチェックしてます。

5.5 LEDライトを光らせる

M5StickC Plus2 には標準で赤色のLEDライトが搭載されています。
このLEDは、ボタンを押したタイミングで光らせたり、処理が完了した際に点灯させたりと、任意のタイミングで制御できます。例えば「処理が終わった通知」や「ユーザーに操作を促す合図」といった場面で活用できます。

M5StickC Plus2 の赤色LEDは GPIO(汎用入出力ピン) を使って制御します。
GPIOはピン番号で指定しますが、このLEDは本体に固定されており、ピン番号は19番に割り当てられています。これは購入時から決まっている仕様で、本体背面のラベルにも「LED G19」と記載されています。

以下のコードを書き込んでみましょう。「0.5秒点灯 ➡ 0.5秒消灯」を繰り返す、M5StickC Plus2版のLチカです。

コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ

// M5StickC Plus2 のLEDは GPIO19 に接続されている
#define LED_PIN 19

void setup() {
  auto cfg = M5.config();     // 設定オブジェクトを取得(画面、電源、ボタンなどの初期化に使う)
  M5.begin(cfg);              // 設定に基づいてM5デバイスを初期化する
  pinMode(LED_PIN, OUTPUT);   // LED用のピンを出力モードに設定
}

void loop() {
  digitalWrite(LED_PIN, HIGH);  // LEDを点灯
  delay(500);                   // 0.5秒待つ
  digitalWrite(LED_PIN, LOW);   // LEDを消灯
  delay(500);                   // 0.5秒待つ
}

ポイント

  1. #define LED_PIN 19
    • Arduino Nanoのときに#define LED_PIN 13などと定義したのと同じです。
  2. pinMode(LED_PIN, OUTPUT);
    • Arduino Nanoと同じく、LEDを制御するためにピンを出力モードに設定します。
  3. digitalWrite(LED_PIN, HIGH); / digitalWrite(LED_PIN, LOW);
    • Arduino Nanoと同じく、HIGHで点灯、LOWで消灯です。

5.6 ブザーを鳴らす

M5StickC Plus2 には 内蔵スピーカー(ブザー) が搭載されています。
Arduino Nano では圧電スピーカーを外付けして tone() 関数を使って鳴らすこともできますが、M5StickC Plus2 では基板内にすでに接続されているため、ピン番号を指定するだけですぐに音を鳴らせます。

以下のコードを書き込んでみましょう。ボタンを押すと音が鳴ります。

コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリを読み込む

// 最初に一度だけ実行される処理
void setup(void) {
  auto cfg = M5.config();     // 設定オブジェクトを取得(画面、電源、ボタンなどの初期化に使う)
  M5.begin(cfg);              // 設定に基づいてM5デバイスを初期化する
  M5.delay(500);              // シリアル出力が安定するまで少し待つ

  // 画面にログ出力を表示する設定
  M5.setLogDisplayIndex(0);       // ログの表示先をLCD画面に設定(インデックス0がLCD)
  M5.Display.setTextScroll(true); // 画面のテキストをスクロール表示可能にする
}

// 繰り返し実行される処理
void loop(void) {
  M5.delay(1);    // 1ミリ秒待機(無駄にCPUを占有しないための小休止)
  M5.update();    // ボタンなどの入力状態を更新(押された/離されたなどを検出可能にする)

  // --- BtnAが押されたときの処理 ---
  if (M5.BtnA.wasPressed()) {
    // 中央のボタンが押されたら
    tone(GPIO_NUM_2, 2000, 1000);  // GPIO2に接続されたブザーから2000Hzの音を1秒間鳴らす
    M5.Log.println("BtnA TONE(2000Hz)");  // 画面に「BtnAが押され、2000Hzが鳴った」と表示
  }

  // --- BtnBが押されたときの処理 ---
  if (M5.BtnB.wasPressed()) {
    // 右のボタンが押されたら
    tone(GPIO_NUM_2, 4000, 1000);  // 4000Hzの音を1秒間鳴らす
    M5.Log.println("BtnB TONE(4000Hz)");  // ログに出力
  }

  // --- 電源ボタン(BtnPWR)が押されたときの処理 ---
  if (M5.BtnPWR.wasPressed()) {
    // 左側の電源ボタンが押されたら
    for (int i = 0; i < 4; i++) {   // 4回繰り返し(オクターブを上げていく)

      // ド(C)の音を鳴らす
      int frequency = 523.251 * pow(2, i);     // 基本音523.251Hzをオクターブごとに倍にする
      tone(GPIO_NUM_2, frequency, 500);        // 500ms鳴らす
      M5.Log.printf("BtnPWR TONE(C%d %dHz)\n", 5 + i, frequency);

      // レ(D)の音を鳴らす
      frequency = 587.330 * pow(2, i);
      tone(GPIO_NUM_2, frequency, 500);
      M5.Log.printf("BtnPWR TONE(D%d %dHz)\n", 5 + i, frequency);

      // ミ(E)の音を鳴らす
      frequency = 659.255 * pow(2, i);
      tone(GPIO_NUM_2, frequency, 500);
      M5.Log.printf("BtnPWR TONE(E%d %dHz)\n", 5 + i, frequency);

      // 休符(無音)を挿入(frequency=0で指定)
      tone(GPIO_NUM_2, 0, 500);
    }
  }
}

ポイント

  • BtnA / BtnB / BtnPWR
    • BtnA:中央の小さいボタン
    • BtnB:右のボタン
    • BtnPWR:左側の電源ボタン(短押しで入力イベントが取れる)
  • ログ表示
    • M5.Log.println() で LCD にスクロールログを出せるので、Adruino IDEのシリアルモニタを見なくても確認できる(地味に便利)。
  • tone()関数とは?
    • tone(GPIO_NUM_2, 周波数, 時間)という形で使います。
      • GPIO_NUM_2
        • 「どのピンから音を出すか」 を指定する部分です。M5StickC Plus2では、内蔵ブザーがGPIO 2というピンに繋がっています。「GPIO_NUM_」というのは、ピン番号を分かりやすくするためのおまじないだと思ってください。
      • 周波数
        • 「どんな高さの音を出すか」 を指定します。単位はHz(ヘルツ)で、数字が大きいほど高い音になります。(例:2000Hz、4000Hz)
      • 時間
        • 「どれくらいの時間、音を鳴らすか」 を指定します。
          単位はミリ秒(1/1000秒)です。(例:1000で1秒間)

5.7 マイクで音量を画面に表示させる

M5StickC Plus2 には 内蔵マイク があります。

以下コードを実行してみましょう。M5StickC Plus2の内蔵マイクで音を取り込み、その音の波形(時間と共に音の大きさがどう変わるか) をリアルタイムで画面に描画し、同時に録音した中で一番大きな音量(MAX) を数字で表示します。

コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ

// --------------------
// 録音パラメータ
// --------------------
static constexpr const size_t record_samplerate = 16000;  // サンプリング周波数 16kHz
static constexpr const size_t record_length = 240;        // 一度に録音するサンプル数
static int16_t *rec_data;                                 // 録音データを格納するバッファへのポインタ

// --------------------
// 初期化処理
// --------------------
void setup(void) {
  auto cfg = M5.config();  // 設定オブジェクトを取得
  M5.begin(cfg);           // デバイス初期化(LCD,ボタン,マイクなど)

  // 画面上にログを表示できるように設定
  M5.setLogDisplayIndex(0);

  // 画面の向きを横に設定
  M5.Display.setRotation(1);

  // 録音用メモリを確保(240サンプル分のint16_t配列)
  rec_data = (typeof(rec_data))heap_caps_malloc(record_length * sizeof(int16_t), MALLOC_CAP_8BIT);
  memset(rec_data, 0, record_length * sizeof(int16_t));  // 0で初期化

  // マイクを開始
  M5.Mic.begin();
}

// --------------------
// 繰り返し処理(メインループ)
// --------------------
void loop(void) {
  M5.delay(1);    // 1ms待機(CPU占有を防ぐ)
  M5.update();    // 入力デバイス更新(ボタンなど)

  // マイクが有効な場合のみ処理
  if (M5.Mic.isEnabled()) {
    int16_t max = 0;  // 最大音量(振幅)を保存する変数

    // 描画の開始(バッチ描画モード:高速化のため)
    M5.Display.startWrite();

    // マイクからデータを録音してrec_dataに格納
    if (M5.Mic.record(rec_data, record_length, record_samplerate)) {
      // 画面の横幅を取得
      int32_t w = M5.Display.width();
      // サンプル数を超えないように制限
      if (w > record_length - 1) {
        w = record_length - 1;
      }

      // 録音データを画面に描画
      for (int32_t x = 0; x < w; ++x) {
        static int16_t prev_y[record_length];  // 前回描画した線のY座標
        static int16_t prev_h[record_length];  // 前回描画した線の高さ

        // 前回描画した線を黒で上書きして消す
        M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], TFT_BLACK);

        // 録音データの振幅を縮小(>> shiftでスケールダウン)
        static constexpr int shift = 6;  // 大きすぎる値を画面に収めるためのシフト値
        int32_t y1 = (rec_data[x] >> shift);       // 現在サンプル
        int32_t y2 = (rec_data[x + 1] >> shift);   // 次のサンプル
        if (y1 > y2) {  // 上下関係を整理
          int32_t tmp = y1;
          y1 = y2;
          y2 = tmp;
        }

        // 画面中央を基準に上下に振幅を描く
        int32_t y = (M5.Display.height() >> 1) + y1;     // 開始位置
        int32_t h = (M5.Display.height() >> 1) + y2 + 1 - y; // 高さ
        prev_y[x] = y;   // 次回消す用に保存
        prev_h[x] = h;   // 次回消す用に保存

        // 白い縦線で波形を描画
        M5.Display.writeFastVLine(x, y, h, TFT_WHITE);

        // 最大音量(絶対値)を記録
        if (max < abs(rec_data[x])) {
          max = abs(rec_data[x]);
        }
      }
    }

    // 最大音量(振幅値)を画面左上に表示
    M5.Display.setCursor(0, 0);
    M5.Log.printf("MAX = %5d\n", max);

    // 描画の終了
    M5.Display.endWrite();
  }
}

動作の仕組み

  1. マイクの設定:音を取り込むスピード(サンプリング周波数を16kHz=1秒間に16,000回チェック)などを決めます。
  2. 音をキャッチ:マイクで音を少しだけ(15ミリ秒分)録音します。
  3. 前の線を消す:画面をちらつかせないように、前回描いた音の波形を黒(背景色)でサッと消します。
  4. 新しい線を描く:新しく録音した音のデータを使って、音の波形を白で画面に描きます。
  5. 音量を計算・表示:録音したデータの中で一番大きな音のデータ(max)を計算し、画面の左上に表示します。
  6. 繰り返し:この処理を高速で何度も繰り返すことで、音の動きがアニメーションのように見えます。

ポイント

準備と設定 (setup の中)

// 録音用メモリを確保
rec_data = (typeof(rec_data))heap_caps_malloc(record_length * sizeof(int16_t), MALLOC_CAP_8BIT);
// ...
// マイクを開始
M5.Mic.begin();
  • メモリの確保: 録音した音のデータ(数字の並び)を一時的に保存しておくための場所(rec_data)を確保しています。
  • マイクの開始: M5.Mic.begin();で内蔵マイクの電源を入れ、音を取り込める状態にします。

繰り返しの処理 (loop の中)

音の録音

if (M5.Mic.record(rec_data, record_length, record_samplerate)) {
    // ... 録音データを使って画面を描画する処理 ...
}
  • M5.Mic.record(...):マイクから指定した長さ(record_length=240サンプル)のデータを一気に録音し、rec_dataという箱に音のデータ(音の大きさの数値)を詰めます。
  • rec_data内のデータは以下のようなイメージ

画像参照元

波形の描画

この部分が波形を描く核となる処理です。

データのスケール調整

static constexpr int shift = 6;
int32_t y1 = (rec_data[x] >> shift);

マイクが拾う音のデータ(rec_data)はとても大きな数字(±32767)になることがあります。そのままでは画面をはみ出してしまうので、>> shift(ビットシフト)という方法で、データを小さく縮小(スケールダウン)して画面のサイズに収まるように調整しています。(ビットやバイトの仕組みは習いましたか?右に1ビットシフトすると2で割ることになります。nビットシフトだと2^nで割ることになります)

※ ちなみになぜビットシフトしているかというと計算が圧倒的に早いからです。
画像参照元

縦線(波形)の描画

M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], TFT_BLACK); // 前の線を消す(更新する)
// ...
M5.Display.writeFastVLine(x, y, h, TFT_WHITE); // 新しい線を白で描く

writeFastVLine()関数を使って、画面の左から右へ順に、録音データに合わせて縦線を高速に描いていきます。音のデータはなので、画面の真ん中を基準に、音が大きいほど線が上下に伸びて描画されます。

こちらは興味ある人向け。長めです。(描画の理解がちょっと難しいかもしれないので、もう少し詳しく説明したものです)

このプログラムでは、音のデータをとても短い時間で「切り取って」 画面に表示しています。

1. 録音するデータの長さ

このプログラムでは一度に扱うデータの長さを意図的に短く設定しています。

定数意味
record_samplerate160001秒間に16,000回、音の大きさをチェックする(録音の速さ)。
record_length2401回の録音でマイクから取り込むデータ数(サンプル数)。

Google スプレッドシートにエクスポート

つまり、rec_dataという箱に入るデータの長さは、常に240個(240サンプル)です。

16000サンプル/秒240サンプル​=0.015秒=15ミリ秒

1回のループで、わずか15ミリ秒(0.015秒) という、とても短い時間の音を録音しています。この短いデータを画面に描画し、すぐに次の15ミリ秒のデータを録音・描画することで、動いている波形に見せているのです。


2. 画面の横幅とデータ長の調整

この部分が、「240個のデータを、どうやって画面にピッタリ収めるか?」という処理です。

      // 画面の横幅を取得
      int32_t w = M5.Display.width();
      // サンプル数を超えないように制限
      if (w > record_length - 1) {
        w = record_length - 1;
      }

役割

この処理は、画面の幅に応じて、描画に使うデータ数を決めることです。

  1. int32_t w = M5.Display.width();
    • まず、M5StickC Plus2の画面の横幅(ピクセル数)を取得します。
    • 画面を横向きに設定しているので、例えば横幅が128ピクセルだとすると、w128になります。
  2. if (w > record_length - 1) { w = record_length - 1; }
    • ここで、w(画面の横幅)と record_length(240)を比較しています。
    • もし、画面の横幅が240より大きかったら、描画に使うデータの数 w240 に制限しています。(正確には240−1=239)
    • なぜこれが必要か?
      • 録音したデータは240個しかありません。
      • もし画面の横幅が500ピクセルだったとして、240個のデータで500ピクセル分すべてを描画することはできませんよね?
      • このM5StickC Plus2では画面横幅(128ピクセル)は240より小さいので、結果としてwは画面横幅の128になります。
      • 結論: この処理により、forループ(次のステップ)で描画する横のピクセル数(w は、「画面の横幅」「録音データの長さ(240)」小さい方に決められます。

結論:描画のイメージ

このプログラムでは、画面の横幅が128ピクセルだとすると、

  • 240個のデータ(rec_data)の中から、最初の128個のデータだけを取り出します。
  • この128個のデータを、画面の左端(x=0)から右端(x=127)まで、ピクセルごとに対応させて描画しています。

つまり、「1ピクセルごとに、1つの音のデータの大きさ」を描いているというわけです。(半分捨ててます)


3. 描画ループ (for ループ) の動作

最後に、実際に画面に描く部分の仕組みです。

      // 録音データを画面に描画
      for (int32_t x = 0; x < w; ++x) {
          // ... 略 ...
          int32_t y1 = (rec_data[x] >> shift);       // 現在サンプル
          int32_t y2 = (rec_data[x + 1] >> shift);   // 次のサンプル
          // ... 略 ...
          // 白い縦線で波形を描画
          M5.Display.writeFastVLine(x, y, h, TFT_WHITE);
      }
  • for (int32_t x = 0; x < w; ++x):
    • このループは、x(横の位置)を0からw(画面横幅、例えば128)まで1ピクセルずつ進めながら実行されます。
    • ループが1回回るごとに、画面の1列に縦線が描かれます。
  • rec_data[x]
    • x=0のとき、録音データ配列の0番目のデータを使います。
    • x=1のとき、1番目のデータを使います。
    • このように、画面の横ピクセル位置(x)と、録音データ配列のインデックス(rec_data[x])が1対1で対応しているため、データが波形として画面に描かれるのです。

この処理が毎秒数十回繰り返されるため、あなたは音の波が動いているように見える、というわけです。

最大音量の表示

        // 最大音量(絶対値)を記録
        if (max < abs(rec_data[x])) {
          max = abs(rec_data[x]);
        }
    // ...
    M5.Log.printf("MAX = %5d\n", max);
  • max変数が、録音したすべてのデータの中で一番大きな音の振幅(絶対値 (abs) ) を記録しています。このmaxの数字を見ることで、今周りにある音がどれくらい大きいか(ボリューム)の目安が分かります。
  • このmaxの値を、画面の左上にMAX = 12345のように表示しています。

5.8 顔を表示させる

M5StickC Plus2 では、標準ライブラリだけでなく、GitHub などで公開されている第三者のライブラリを組み合わせて活用できます。
その一例として m5stack-avatar というライブラリを利用すると、液晶にキャラクター風の「顔」を表示し、ボタン操作やセンサー入力に応じて表情を変えることができます。
m5stack-avatar は、M5Stackに「しゃべる・動くアバター」を簡単に実装できるように作られた便利なライブラリです。顔のパーツ(目、口など)が独立して描画され、表情や視線をプログラムで制御できます。M5StickC Plus2 でも利用可能です。

インストール手順

  1. GitHub から m5stack-avatar をダウンロードします。
    「Code」→「Download ZIP」を選択してください。
    👉 https://github.com/stack-chan/m5stack-avatar
  2. ダウンロードしたZIPファイルは解凍せず、Arduino IDE の
    [Sketch] → [Inclide Library] → [Add .ZIP Library...]
    から選択してください。

コードは以下の通りです。書き込んで実行してみましょう。

コード
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ

#include <Avatar.h>
using namespace m5avatar;

Avatar avatar;

// ---------------------------------------------------------
// M5StickC Plus2(横向き setRotation(1) → 240×135)に合わせるための調整値
// Avatar ライブラリは論理座標を 320×240 固定で使うため、
// 縮小率とオフセットを計算しておく。
// ---------------------------------------------------------
static constexpr float SCALE_H_FIT = 135.0f / 240.0f;  // 高さに合わせて縮小(=0.5625)
static constexpr int   POS_TOP     = -(240 - 135) / 2; // 論理高との差分を上方向にシフト(-52)
static constexpr int   POS_LEFT    = -(320 - 240) / 2; // 論理幅との差分を左方向にシフト(-40)
//  この計算は、ライブラリの内部的な座標系(論理座標)と実際の画面サイズ(物理座標)のズレを調整するためのものです。
//m5stack-avatarは縦長の320x240ピクセルを基準に動くため、横長の240x135ピクセルのM5StickC Plus2の画面に収まるように、縮小と中央寄せのオフセット値を計算しています。

// Avatar にスケールと位置をまとめて適用する関数
void fitAvatar() {
  avatar.setScale(SCALE_H_FIT);
  avatar.setPosition(POS_TOP, POS_LEFT);
}

void setup() {
  auto cfg = M5.config();
  M5.begin(cfg);

  // 横向きに設定(解像度 240×135)
  M5.Display.setRotation(1);
  M5.Display.setBrightness(200);      // 画面明るさ
  M5.Display.fillScreen(TFT_BLACK);   // 画面を黒で初期化

  // Avatar の初期化前にスケール・位置を一度設定しておく
  fitAvatar();

  // Avatar 初期化(内部バッファの準備)
  avatar.init();

  // 念のため初期化後にも再設定(initでリセットされる場合があるため)
  fitAvatar();

  // デバッグ用:Avatarの描画領域を白枠で描く
  int drawW = int(320 * SCALE_H_FIT); // 縮小後の幅(=180)
  int drawH = int(240 * SCALE_H_FIT); // 縮小後の高さ(=135)
  int x = (M5.Display.width()  - drawW) / 2; // 中央寄せX(=30)
  int y = (M5.Display.height() - drawH) / 2; // 中央寄せY(=0)
  M5.Display.drawRect(x, y, drawW, drawH, TFT_WHITE);

  // 初期表情はニュートラル
  avatar.setExpression(Expression::Neutral);
}

void loop() {
  M5.update(); // ボタンや状態の更新

  // Aボタンが押された瞬間 → 笑顔に切り替え
  if (M5.BtnA.wasPressed()) avatar.setExpression(Expression::Happy);

  // Bボタンが押された瞬間 → 眠そうな顔に切り替え
  if (M5.BtnB.wasPressed()) avatar.setExpression(Expression::Sleepy);

  // Aを押しっぱなし+Bを押した瞬間 → 左に1pxずらす
  if (M5.BtnA.isPressed() && M5.BtnB.wasPressed()) {
    static int top = POS_TOP, left = POS_LEFT;
    left -= 1; // 左方向へシフト
    avatar.setPosition(top, left);
  }

  delay(10); // 負荷軽減のための短い待ち
}

補足説明(m5stack-avatar の主要関数)

  • avatar.init()
    アバターの描画を開始する。内部的に 320×240 の論理キャンバスを用いるため、M5StickC Plus2 ではスケール・位置調整が必要。
  • avatar.setScale(float s)
    アバター全体を縮小/拡大する。M5StickC Plus2 の場合は 0.5625 に設定すると高さがちょうど画面に収まる。
  • avatar.setPosition(int top, int left)
    アバターの描画基準位置を指定する。M5StickC Plus2 では (-52, -40) を指定すると画面中央に収まる。
  • avatar.setExpression(Expression::Xxx)
    表情を切り替える。利用可能な表情の例:
    • Expression::Neutral(普通)
    • Expression::Happy(笑顔)
    • Expression::Sleepy(眠い)
    • Expression::Angry(怒り)
    • Expression::Sad(悲しい)
  • M5.BtnA.wasPressed() / M5.BtnB.wasPressed()
    ボタンが押された瞬間を検出。これを使って表情を切り替えられる。

6 課題

基礎課題

学籍番号を画面に表示させる。

5.3〜5.9で触れた機能(表示/回転/日本語フォント/ボタン/LED/ブザー/マイク/Avatar)のうち2つ以上を組み合わせたデモを作ってみてください。

提出内容

  • ソースコードをMoodleに提出してください。
  • 動作中の写真をMoodleに提出してください。

発展課題

5.3〜5.9で触れた機能(表示/回転/日本語フォント/ボタン/LED/ブザー/マイク/Avatar)のうち2つ以上を組み合わせたデモを作ってみてください。

GitHub などで公開されている第三者のライブラリ(Avatar以外)を活用してインタラクティブな体験を作ってください。

提出内容

  • ソースコードをMoodleに提出してください。
  • 動作中の動画をMoodleに提出してください。