'}}
ULSA M5BをArduinoでプログラミング!(2023年12月3日更新)

本記事では、以前よりご要望をいただいていた超音波風速計ULSA M5BのM5Stackプログラミングについて解説します。本記事で例示するスケッチ(ソースコード)を参考に、アプリケーションに応じてM5Stackの動作をカスタマイズしていただくことで、あなただけのスマートな風センシングソリューションを構築することができます!

ULSA M5B 超音波風速計って何?

ULSA(アルサ)はストラトビジョンが気球開発の過程で独自に開発した「超音波で風を高精度に計測できる新しい風速計」です。ULSAにはBASICと呼ばれるシンプルな風速計機能のみのモデルと、M5B(エムファイブ・ビー)と呼ばれるM5Stack(Core2)がそのままスタックできる風速計の2種類があります。

ULSA M5B
(M5Stack Core2をスタック可能)
ULSA BASIC
(最小構成の風速計)

従来の超音波風速計は基本的に業務用のもので筐体も大きく重たく、非常に高価な装置ですが、ULSAシリーズは世界最小・最軽量クラスにもかかわらず、多くの方の手にとっていただける低価格を実現しました。

ULSAの詳しい開発の経緯は以下のスイッチサイエンスマガジンをご覧ください。

ULSAは現在スイッチサイエンス様で販売中です。

一方、M5Stackは中国深センのM5Stack社が開発した拡張性の高い組み込み・IoT開発プラットフォームです。ESP32チップをベースとしていて、5cm角のコンパクトな筐体にタッチ式の液晶画面(Core2)やWi-Fi・Bluetooth機能を備えており、エレクトロニクスやIoTの分野で大変な人気を博しています。

M5Stackは5cm角のモジュール形状で
縦方向にスタックして機能拡張ができる

本記事では、このM5Stack Core2のアプリケーションをArduino言語でプログラミングする過程について紹介していきます!

ULSA M5BとM5Stackを組み合わせてできること

M5StackをULSAと組み合わせることでどんなことができるか一例を挙げてみました。

  1. ULSA M5B内蔵の9軸センサーを使用した風向補正
  2. M5StackのLCD画面にグラフィカルな風向・風速の表示
  3. M5Stackのスピーカー機能で一定の風速を超えると警報音を発する
  4. M5StackのmicroSDカードスロットを利用した長時間の風況記録
  5. M5StackのWi-Fi機能を利用してクラウド上にデータを配信
  6. M5StackのBluetooth機能を利用してスマホに風況を送信

などなど、ULSAに直接M5Stackを搭載できることで非常に多くの便利なアプリケーションを製作できることがわかります。
今回のブログ記事では、基本的な項目として1.と2.で挙げた機能を実現するためのスケッチ・プログラミングについて解説していきます。

なお、スケッチ(ソースコード)の内容には興味ないよ!動く結果だけ欲しいよ!という方は、
M5Stackにコンパイル不要で書き込めるファームウェアを配布していますので以下をご覧ください。

M5Stackと開発環境のチョイスについて

M5Stackは深セン速度の目まぐるしい勢いで新製品が投入されており、M5Stack Coreにもたくさんのシリーズが存在しています。またファームウェアやライブラリの更新も頻繁に行われているので、開発環境が少し異なるだけでも思い通りに動かない!ということがよくあります。そういったトラブルを回避するためにも下記の開発環境をご用意していただくことを強くおすすめします!

【推奨】M5Stack Core2

使用するM5StackはM5Stack Core2を推奨します。ULSA M5BはM5Stack Core2に最適化したハードウェア設計をしており、他のM5Stackシリーズではケースなどが干渉し、そのままではULSAに取り付けることはできません。また、後述するPSRAMの有無や接続用のピン配置が異なることもあるため本記事で紹介するスケッチがそのまま使用できない可能性があります。

M5Stack Basic(ボタン式)
M5Stack Core2 推奨
(タッチパネル式)
M5Stack FIRE
(Basicの強化版)

【推奨】Arduino Web Editor

ESP32ベースのM5Stackは、Arduino言語を使用したアプリケーションの開発が可能です。Arduinoの開発環境は現在大きく分けて3つ存在します。それぞれのツールには一長一短があるのですが、今回はクラウド上で開発ができるArduino Web Editorを使用します。

Arduino Web Editorを使えば、スケッチの共有、コンパイル、書き込みがブラウザ上で完結するのでローカルの開発環境で生じる様々な環境依存のトラブルが発生しにくくなります。

Arduino IDE
(ローカル環境)
PlatformIO
(ローカル環境)

Arduino Web Editorを使用する理由は以下の記事で詳しく触れています。ぜひご覧ください。

ULSA M5Bデモアプリケーションのスケッチを公開!(2023年12月3日更新)

2023年11月にM5Stack Core2 V1.1の発売を受けて、スケッチに互換性がなくなったため、新しいスケッチに更新しました。
旧スケッチ(ULSA_M5B_Sample_030)では「M5Core2.h + LovyanGFXを用いていますが、
新スケッチ(ULSA_M5B_Sample_100)では「M5Unified.h」にライブラリが統合されました。

さて、MStackとその開発環境(Arduino Web Editor)が揃いましたので、いきなりスケッチを公開します!Arduino Web Editorを使えばこの様にWeb上に直接スケッチを埋め込むことが可能になります。

※ 紹介するスケッチはお世辞にも整理されているとはいえないべた書きなので、適宜いい感じに書き直してください😅

"OPEN CODE"→"ADD TO MY SKETCHBOOK"であなたのEditorにスケッチが登録されます。

スケッチ部分解説

スケッチの分量が多いので、特に重要な箇所について部分解説をしていきます。

スケッチの構成

Arduinoスケッチの構成は以下のようになっています。
一つのスケッチに書くととても長くなってしまうので、要素ごとにArduinoタブ機能を使ってスケッチを分割して記述しています。

 ULSA_M5B_Sample_030           => Arduinoスケッチフォルダー
  ├── ULSA_M5B_Sample_001.ino  => setup()とloop()を含むmainのArduinoスケッチ
  ├── DrawLCD.ino              => LovyanGFXライブラリを使用した画面描画に関連するスケッチ
  ├── IMU.ino                  => BNO055ライブラリを使用した姿勢取得に関連するスケッチ
  ├── RecvData.ino             => ULSA M5Bのデータ受信(UART)に関連するスケッチ
  └── ReadMe.adoc              => ReadMe

FreeRTOSで並列処理

サンプルのスケッチでは、ざっくりと以下の処理タスクが存在します。

ULSA M5Bの
データ受信(UART)
LCDへの描画
(SPI)
9軸センサーの
データ受信(I2C)

超音波風速計のアプリケーションを効率的に作成するには、場合によって複数のタスクを並列で処理する必要があり、サンプルスケッチではArduinoの標準機能の他に以下の機能を使用しました。

  • ESP32のマルチコア(CPU0 / CPU1)を活用した並列処理
  • 並列処理を扱うためのFreeRTOS(組み込みリアルタイムOS)の利用

これらは、いわゆる8-bit AVRマイコンを搭載したクラシックのArduinoには存在しない機能になります。

サンプルスケッチのsetup()には、クラシックなArduinoでは見かけない、以下のような処理が入っています。

C++
//  起動時に一回だけ実行される
void setup()
{
  ...
  ...
  ...

  //  マルチタスク中の排他制御のためのミューテックスを作成
  xMutex = xSemaphoreCreateMutex();

  //  デュアルコアによる並列処理タスクを追加
  //  FreeRTOSでは優先度の数字が大きいほど優先されます 例)優先度0 < 優先度5
  xTaskCreatePinnedToCore(drawProcess, "drawProcess", 4096, NULL, 1, NULL, 0);  // CPU Core 0(優先度1)でタスク開始
  xTaskCreatePinnedToCore(recvUART, "recvUART", 4096, NULL, 2, NULL, 1);        // CPU Core 1(優先度2)でタスク開始
} //  void setup()

setup()関数に配置されたFreeRTOS(組み込みリアルタイムOS)に関連する処理

並列処理を競合なく動作させるためには、Mutexによる排他制御が必要になります。Mutexによる排他制御がなければペリフェラルや変数などに対して複数のタスクが同時にアクセスできるようになり、タイミングによっては予期しない動作を招く可能性があります。スケッチでは下記のような形でMutexによる排他制御を行っています。

C++
  // ミューテックスを取得(ロック)
  if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
    
    //  ここに記述された処理は他タスクではアクセスできないようにすることができる
    hogehoge();
  
    // ミューテックスを解放(アンロック)
    xSemaphoreGive(xMutex);
  }

ミューテックスを使うことで他タスクからの同時アクセスを禁止する

以上のようなFreeRTOS(リアルタイムOS)の機能も活用しながらスケッチを構築しています。

ULSA M5BからUART経由でデータを読み取る

風速計アプリケーションを作成するには、まず超音波風速計からデータを読み取らなければいけません。M5StackはULSA M5BからM-BUS(UART)経由で計測データを受け取ります。Arduino上では"Serial2"を指定します。物理的なピンアサインとしてはM5Stackから見たときUART受信がRXD2(G13)、UART送信がTXD2(G14)になります。なお、現時点ではULSA M5Bは一方的なデータ送信のみをサポートしており、M5Stackからコマンド等を受け取る機能はないので片方向通信となっています。

ハードウェアの詳しい仕様に関しては、スタートアップガイド・テクニカルドキュメントもご参照ください。

ULSA M5Bからは風向・風速などのデータが1秒間に最大10回と比較的高頻度で送られており、サンプルコードでは"recvUART"というUART受信専用タスクを用意しCPU1で処理しています。ULSA M5BからはASCIIデータの文字列として計測データが送られてくるので、受信するごとに文字列バッファに溜めていき、改行コードにあたると文字列を終端します。

この文字列(計測データ)に対してstrtok()とatoi(), atof()を使用して計測値として各変数に格納するというトラディショナルなコードになっています。

C++
//  ULSA M5BからのUART送信データをポーリングで受信する
void recvData() {

  //  シリアル2に受信データがあるとき
  if (Serial2.available() > 0)
  {
    //  受信バッファに1文字ずつ格納
    recvPacket[count] = Serial2.read();

    //  バッファが改行文字コードに到達しなおかつ最初の文字がプリアンブル'#'であるとき
    if (recvPacket[count] == '\n' && recvPacket[0] == '#')
    {
      //  文字列を終端
      recvPacket[count] = '\0';
      // memcpy(sendPacket, recvPacket, sizeof(recvPacket));

      //  ULSAの姿勢データを取得して表示
      getAttitude();

      /*
        ===============================================
        ULSA SimpleCSVプロトコル

         #,0,1,359,12.123,24.123,340.123,21.12\r\n
        |A|B|C|-D-|---E--|---F--|---G---|--H--|-I-|

        A: プリアンブル
        B: センサーノードID
        C: 測定有効・無効
        D: 風向(整数) [°]
        E: 風速(浮動小数点数) [m/s]
        F: 機首対気速度(浮動小数点数) [m/s]
        G: 音速(浮動小数点数) [m/s]
        H: 音仮温度(浮動小数点数) [℃]
        I: 改行コード
        ===============================================
      */

      //  文字列バッファを分割して各要素に格納
      preamble = atoi(strtok(recvPacket, ","));
      nodeID = atoi(strtok(NULL, ","));
      isValid = atoi(strtok(NULL, ","));
      windDirection = atoi(strtok(NULL, ","));
      airSpeed = atof(strtok(NULL, ","));
      noseSpeed = atof(strtok(NULL, ","));
      soundSpeed = atof(strtok(NULL, ","));
      virtualTemp = atof(strtok(NULL, ","));

      //  BNO055コンパスデータから補正風向を計算
      corrWind = windCorrection(windDirection, yaw);

      //  バッファインデックスをクリア
      count = 0;
    }
    else
    {
      count++;
    }
  } // if (Serial2.available() > 0)
} //  void recvData()

ULSAから風向・風速データを読み取る。これが風速計アプリケーション作成の最初の取っ掛かりということになります。Arduinoでシリアル通信から文字列を読み取って処理する方法は他にも色々な方法がありますので、好みに応じて実装してみてください。

LCDにグラフィカル表示する

得られた風速データはなにかしら画面に表示したくなるのが常です。今回はLovyanGFX (→ M5Unifiedに統合)という便利なライブラリを活用させていただき、M5StackのLCD画面に計測値をグラフィカルに表示しています。これらの描画処理は"drawProcess"という専用のタスクを用意しCPU0で実行しています。

2023/12/03 - M5Stack Core2のライブラリをM5Unifiedに統合しました。

LovyanGFXは現在のコードでは使用していません。

サンプルスケッチでは、LovyanGFXのスプライト(バッファ)機能を使用しており、画面描画はバッファに溜め込んだあとに、バッファの内容を一気にLCDに転送するという処理を行っています。こうすることで画面描画のタイミングを制御することが可能になり、描画も高速になるので画面書き換えの際のちらつきも抑えることができます。

しかし、スプライトは通常のスケッチで使用するメモリとは比較にならない大量のメモリを消費します。通常M5Stackでは全画面分(320x240)のスプライトバッファをSRAM領域(520kB)に確保することはできませんが、このスケッチではM5Stack Core2にSPIで外部接続されたPSRAM(疑似SRAM:8MB)領域を使用することで大容量のスプライトバッファの使用を可能にしています。下記の様に、setPsram(true)によりPSRAM領域にバッファを確保しています。

C++
//  スプライトの初期化をする
void initGUI() {
  ...
  ...
  ...
  FlowGUI.setPsram(true);
  FlowGUI.createSprite(320, 240);
  ...
  ...
  ...
} //void initGUI()

スプライトの初期化

C++
//  画面上に描画する
void drawSequence() {

  //  風速ゲージの構成
  drawGauge();
  ...
  ...
  ...
  //  構成したゲージ表示をLCDに出力
  FlowGUI.pushSprite(&lcd, (lcd.width() - FlowGUI.width()) / 2.0f,
                     (lcd.height() - FlowGUI.height()) / 2.0f, TFT_GREEN);
} //  void drawSequence()

風速ゲージの内容をスプライトバッファに描画"drawGauge()"した後に
pushSprite()で一気にスプライトバッファのデータをLCDに出力する

なおM5Stack BasicのようにPSRAMを持たない場合はSRAM領域にメモリ使用量を抑える努力をする必要があります。本記事のスケッチではPSRAMが利用できるのをいいことに富豪的にメモリを使用しているので、もっと巧みな実装ができる方はぜひチャレンジしてみてください😅

図らずも「メモリリソースの限られる組み込み機器におけるグラフィック実装の難しさ」の一端を垣間見ることができましたが、この分野には深い歴史とノウハウがあり、ここで全貌を解説するのは難しいので気になった方はLovyanGFX (→ M5Unified)ライブラリや、Lang-shipさんがわかりやすくまとめられている記事をご覧ください!

BNO055で風向補正

ULSA M5Bのデモアプリケーション機能の一つに風向補正機能があります。

ULSA M5B自体は図のように筐体基準の風向値を常に出力しています。これは常に筐体のマーク(△マークおよび縦スリット)から時計回りに何度の方向から風が吹き込んでいるかを示しています。この風向値をM5Stack上でBNO055の地磁気方位角の値を使用して補正することで、実際の北側(磁北)に対してどの方向から風が吹いたのかを知ることができるようになります。

これにより例えば移動や回転を伴うドローン上にULSAを設置しても、どこから風が吹いたのか実際の方位基準で検証することが可能になります。

ここで使用する9軸センサーとは、内部に加速度・ジャイロ・地磁気各3軸、計9軸を搭載したセンサーのことで、各々のセンサー出力結果を使用することで姿勢推定ができるようになっています。6軸センサーもよく使われますが、3軸の地磁気センサーをあわせて9軸とすることで、Yaw軸の出力を磁北基準に一致させる、つまりコンパスの様に使うことが可能になります。

マイコンに接続できる9軸センサーはいくつかあり、一般的には各軸のRAWデータ(生の値)をセンサーフュージョンするアルゴリズムを自身で実装する必要があるものが多いのですが、BNO055はセンサーだけではなくCortex M0+ MCUを内蔵しており、センサーフュージョンした結果をスタンドアローン出力することができます。

お手軽に姿勢角が取得できるので私は好んで使ってきました。
しかし、BNO055は便利な代わりに機能や設定項目も多いので、スケッチではAdafruit BNO055ライブラリを活用しています。

BNO055の接続ピンアサインはUARTの項で紹介した図にも掲載していますが、M5StackのPA_SDA(G32)/ PA_SCL(G33)のI2Cポートを使います。

また、BNO055は実装状態に応じて軸方向と軸極性をオーバーライドできる機能があります。この指定をしないと適切な姿勢角となりませんので注意してください。ULSA M5BではP4の向きで実装されており、スケッチ上でもP4を指定しています。

BNO055の実装状態の指定
C++
//  風向補正に使用する9軸IMUセンサーBNO055を初期化します
void initIMU() {
  ...
  ...
  ...
  //  BNO055の動作モードをNDOF(9軸センサーヒュージョン)に指定
  //  1.6.1
  bno.setMode(OPERATION_MODE_NDOF);
  //  BNO055の基板配置方向をP7モード(データシート参照)に指定
  bno.setAxisRemap(Adafruit_BNO055::REMAP_CONFIG_P4);
  //  BNO055の軸極性をP7モードに指定
  bno.setAxisSign(Adafruit_BNO055::REMAP_SIGN_P4);
  //  BNO055の外部水晶振動子(32.768kHz)を有効化
  bno.setExtCrystalUse(true);
  ...
  ...
  ...
}

スケッチ上でのBNO055の初期設定


なお、この軸設定に関してライブラリバージョンにより異なる設定が適用されることが分かっているので注意してください。詳細は下記の記事をご覧ください。

それでは、補正方法について見ていきます。

C++
//  IMUから得られる磁気方位角(ヘディング)を使用してULSAの風向を絶対風向に補正します
int windCorrection(int windDirection, float magneticDirection)
{
  int corrWindDir = (roundf(magneticDirection)) + windDirection;

  if (corrWindDir > 359)
    corrWindDir -= 360;

  return corrWindDir;
}

補正方法は単純で、ULSA M5Bから得られる筐体基準方向の風向値(時計回り)に地磁気方位角(時計回り)が足し算されるだけです。ただし、風向のとる範囲は0-359°になりますので、足した結果が360°を超える場合には-360°を引く必要があります。こうして現在地の磁北方向を基準とした風向を得ることができます。

なお、地磁気センサーで正しい方位を得るには地磁気センサーをキャリブレーションが必要です。キャリブレーションを行うには起動後にULSAを手で持って手首を8の字を描くように回転させる必要があります。以下のようにIMUインジケータが塗りつぶし表示になっているときは地磁気センサーのキャリブレーションが完了した状態です。

IMUステータスの表示

だだし、周囲の磁気環境は常に変動しているので、状況によってキャリブレーションが外れることがあります。BNO055による風向補正機能を使用するにはキャリブレーション状態に注意して使用してください。

以上で、風速計を構成する基本的な要素について解説しました。

おわりに

本記事では、M5Stack (Core2)をArduinoで開発し、超音波風速計ULSA M5B用のアプリケーションを作成する方法を解説しました。今回のサンプルスケッチはULSAからデータを受け取って画面に表示するシンプルな内容ですが、M5Stackには数多くの便利なハードウェア・ソフトウェアにより多様なシステムを構築することが可能になっています。M5Stackを自由に活用してあなただけの風センシングソリューションを構築してみましょう!


注意・免責事項

  • 使用される開発環境やハードウェアのバージョン等の様々な要因により、意図した通りに動作しない場合があります。
    特にM5Stackの開発環境はバージョンアップの速度が早く、ファームウェアの組み合わせによって動作が異なることがよくあります。記事でご紹介している内容・開発環境に沿って開発いただくことで動作トラブルを減らすことができます。
  • 本記事で紹介するスケッチ(ソースコード)はあくまで参考であり、動作保証はできませんのでご了承ください。またスケッチを利用したことに関連して生じたいかなるトラブル・損害・損失に対してストラトビジョンは一切の責任を負いません。
  • 本製品によって生ずるいかなるトラブル・損害・損失に対してストラトビジョンは一切の責任を負いません。
  • ULSAで取得したデータを「気象観測値」として外部に公開すると気象業務法に抵触するおそれがあります。気象観測業務ではない用途や私的な用途に限ったデータの活用をお願いします。

コメントする

あなたのメールアドレスは公開されません。 必須フィールドは * でマークされています

' skin='skin1'}}

© All Copyrights 2023 STRATOVISION

Index