Closed15

M5Dialの開発メモ

ここのえここのえ

PlatformIOを使うのは避けた方がいいかも?

最初は PlatformIO (+CLion) を使用していたが、どうやらPlatformIOとespressifの対立があり、現在公式ライブラリとしてはESP32は更新されていないらしい。途中まで使っていたが、やめてVSCode拡張を使うことにした。
以下、情報としては残しておく。

https://zenn.dev/kyjb/articles/6950231b231643


以下、PlatformIOを使っていた際のメモ


PlatformIO + CLionの開発環境構築

VSCodeで使う場合は簡単なのだが、CLionで環境構築するのは少し手間がかかる。

予め PlatformIO Core を導入する必要があり、CLionのためにパスを通しておく。

https://docs.platformio.org/en/latest/core/index.html

CLion向けPlatformIOプラグインはJetbrains公式になってるので安心して使用できる。公式Docを見ればまず迷うことはないはず

https://pleiades.io/help/clion/platformio.html

USBでシリアル通信が受信できない

ビルドフラグで ARDUINO_USB_CDC_ON_BOOT=1 を立てておく必要アリ。
ESP32S3の仕様に起因する問題。

[env:usb_serial]
src_dir = src/examples/usb_serial
build_flags =
	-D PIO_USB_SERIAL
	-D ARDUINO_USB_CDC_ON_BOOT=1

参考

https://zenn.dev/nananauno/articles/6514c05c61e0d4

PlatformIOで複数のエントリポイントを共存させたい

src/main.cpp はメイン開発用、サンプルコードを src/examples/usb_serial.cpp に置きたい…といったときの対処法。

platformio.ini の設定を特に何もしていないと、ビルド時に setup()loop() の定義が複数存在した状態で全体をコンパイルするので、重複定義でコンパイルがコケる。

対策として platform.ini で複数envを切っておき、それぞれのビルドフラグで任意のフラグを立てておく。その後コード側の #ifdef で条件付きコンパイル。

[env] では共通設定を書いておけるので、ここに platformboard 設定を書いておくとよい。

例:

platformio.ini
[platformio]
env_default = main

[env]
platform = espressif32
board = m5stack-stamps3
monitor_speed = 115200
framework = arduino
lib_deps =
	m5stack/M5Dial@^1.0.2
	m5stack/M5Unified@^0.2.4
	m5stack/M5GFX@^0.2.6

[env:main]
build_flags = -D PIO_MAIN
main.cpp
#ifdef PIO_MAIN

void setup()
{
   // ...
}

void loop()
{
    // ...
}

#endif
BluetoothのSerial Port Protocol (SPP) は使えない

Bluetoothでのシリアル通信は、SPP(Serial Port Protocol)というプロトコルがあり、これを使うことで容易に実装できる。M5Stack関連のコードを見るとよく以下を見かける。

#include "BluetoothSerial.h"

PlatformIOの arduino-espressif32 ライブラリは C:\Users\<username>\.platformio\packages\framework-arduinoespressif32\libraries にあるのだが、ここのREADME.mdを読むと以下の記載がある。

BluetoothSerial
Serial to Bluetooth redirection server
Note: This library depends on Bluetooth Classic which is only available for ESP32
(Bluetoothserial is not available for ESP32-S2, ESP32-C3, ESP32-S3).

M5Dial は StampS3 ベースなので、つまり ESP32-S3 が乗っている。よくよく調べてみると、ESP32-S3はそもそもSPPプロファイルがサポートされていないことに起因しているらしい。

ここのえここのえ

esp-idfのVSCode Extension

上記のPlatformIOのもろもろの理由でCLionが使いづらいのと、かといってArduino IDEでは開発環境としてパワー不足なところがあり、Arduino core for esp32 を使った開発は断念。

そもそもArduino環境に慣れていたためなんとなく使い続けていたぐらいの感覚だったため、特に拘りもなかった。本来esp32はesp-idfで開発したほうがいいだろうし、こちらを採用することにした。

幸いM5Stackの関連ライブラリである M5GFXM5Unified はesp-idf対応で、Arduino coreを使っていた時と同様のライブラリが使用可能なので、フレームワークを変えてもそんなに問題にはならなそう。M5Dialにプリインストールされているデモプログラムもesp-idfベースのコードになっている。

esp-idfは公式サポートでVSCodeの拡張機能が提供されているため、こちらを使用。ツールチェインは拡張機能インストール後に画面に沿って行けば自動でセットアップしてくれるので楽。ちなみにCLionは開発ができなくもないが、マニュアルでセットアップする項目が多く面倒そうなので見送り。

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/index.html#ide

ここのえここのえ

C++を使いたい

esp-idfのサンプルコードを見ると拡張子が .c になっている。ただC++はサポートされているので使用可能。一部制限がある。

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/cplusplus.html

特に esp-idf のエントリポイントになる app_main 関数の定義には注意が必要で、 .cpp ファイル内にある場合は extern "C" としてCリンケージで定義されている必要がある。

main.cpp
extern "C" void app_main()
{
}
ここのえここのえ

起動時に毎回CMakeのKit選択を要求される

プロジェクトを開くたびにVSCodeのCMake Extensionがキットの選択を要求してくる。

esp-idfの拡張機能ではCMake generatorを使っていないので、これは無視できるらしい[1]。esp-idfのツールチェインのコンパイラを指定する方法もある[2]ようだが、generator周りでエラーを吐いてうまく解決できなかった。ワークスペース単位で無効化しておいた方が無難。

https://blog.logical.co.jp/entry/2022/07/25/130000

脚注
  1. https://github.com/espressif/vscode-esp-idf-extension/issues/664 ↩︎

  2. http://forum.esp32.com/viewtopic.php?t=26006 ↩︎

ここのえここのえ

外部 Component の読み込み

プロジェクトルートに components フォルダを作成し、その中に入れていけばよい。
例えば git submodule add で追加していくなら以下の通り。

cd components
git submodule add https://github.com/M5Stack/M5GFX
git submodule add https://github.com/m5stack/M5Unified

次にmainの CMakeLists.txt に依存関係を定義。複数読み込むなら横並びにすればよい。これで main.cpp 内で参照できるようになる。

CMakeLists.txt
idf_component_register(SRCS "main.cpp"
                    PRIV_REQUIRES M5GFX M5Unified
                    INCLUDE_DIRS "")

参考
https://zenn.dev/mai/articles/72519e289973c6#fn-d9a8-2

ここのえここのえ

M5GFXがM5DialをM5PaperS3として誤認識してしまう?

M5GFXがM5PaperS3として認識されてしまい、正常に動かない場合がある。この時ログをモニタリングしていると、以下のようなログが出ている。

I (302) M5GFX: [Autodetect] board_M5PaperS3

ところが、M5DialのWakeボタンを押しながら起動すると、正常なデバイス判定となり表示もうまくいく。回路図からWakeボタンのGPIOの番号は 42らしい 。
https://community.m5stack.com/topic/6324/what-s-the-pin-for-m5dial-btna/7?_=1740037487643

M5GFX::autodetect のM5PaperS3の判定に関する箇所を覗いてみると、確かにG42ピンのチェックを行っている様子。ただ自分はそこまでM5Stackシリーズに詳しくないので、ここが直接的な原因なのかどうかは断言できません…。

https://github.com/m5stack/M5GFX/blob/master/src/M5GFX.cpp#L1352-L1383

不思議な点としてArduino coreの環境では同様の問題が発生しなかった。Arduino IDEで下記のサンプルコードをbuild & uploadし、 Core Debug Level: Verbose にして検証。

#include <M5GFX.h>
#include <M5Unified.h>

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

  M5GFX* gfx = &M5.Display;

  int fontSize = gfx->height() / 50;
  if (fontSize == 0) fontSize = 1;

    gfx->setTextSize(fontSize);
    
    int x = gfx->width() / 2;
    int y = gfx->height() / 2;
    gfx->setCursor(x, y);
    gfx->print("hello world");
}

void loop() {
  
}

すると以下のログを確認。

[  1000][I][M5GFX.cpp:732] init_impl(): [M5GFX] [Autodetect] load from NVS : board:12
[  1003][D][M5GFX.cpp:694] _read_panel_id(): [M5GFX] [Autodetect] read cmd:04 = ff019a00
[  1005][I][M5GFX.cpp:1603] autodetect(): [M5GFX] [Autodetect] board_M5Dia

Arduino環境ではNVSを見てデバイスの特定を行っている様子。暫定的に直接NVSにM5Dialのボード番号 12 を書き込めばひとまず解決できそう。 以下のような関数を用意し、 M5.config() より前に呼び出すようにした。

結果は成功。

I (313) M5GFX: [Autodetect] load from NVS : board:12
I (323) M5GFX: [Autodetect] board_M5Dial

esp-idfはバリバリ初心者なので、そもそもビルドに関する設定が誤ってこのような現象が発生してる可能性もありそう。ライブラリ側ではなく自分の問題な気がしないでもないので、もう少し調べてみる…

WIP

ここのえここのえ

再起動後にESP-IDFのFrameworkのインストール場所を見失う

インストールの翌日、VSCodeがESP-IDF frameworkを見つけられなくなり、 No ESP-IDF frameworks found のエラーになった。

<project root>/.vscode/settings.json でframeworkのフォルダが指定されており、これを参照している模様[1]。しかし正常な値が入っていた。念のため IDF_PATHIDF_TOOLS_PATH も手動で設定したが、これも効果なし。

GithubのReleaseページを見てみると、ちょうど14時間前に拡張機能が 1.9.1 にアップデートされていた[2]。これが問題になってコンフィグ回りがおかしくなっていた可能性が高い。

案の定VSCodeのクイックバーから > ESP-IDF: COnfigure ESP-IDF Extension で再設定 したところ、上記の現象は収まった。はっきりと検証したわけではないが、bugfixesの中に関連しそうなトピックが幾つか見当たるので、たぶん大丈夫だと思いたい。

脚注
  1. https://www.esp32.com/viewtopic.php?t=40174 ↩︎

  2. https://github.com/espressif/vscode-esp-idf-extension/releases/tag/v1.9.1 ↩︎

ここのえここのえ

ESP-IDF環境で、JTAGでシリアル通信

ESP-IDFでシリアル通信をするのが結構難関で、Arduinoじゃないので当然 Serial.println() みたいなものは存在しない。いろいろ試してみたが、たぶんJTAGを使うのが一番楽。

#include <driver/usb_serial_jtag.h>

// ...

void read_serial(void *pvParameters) {
    ESP_LOGI("READ_SERIAL", "Starting read_serial Task...");

    // USB Serial JTAG
    usb_serial_jtag_driver_config_t jtag_config = {
        .tx_buffer_size = SERIAL_BUFFER_SIZE,
        .rx_buffer_size = SERIAL_BUFFER_SIZE,
    };

    ESP_ERROR_CHECK(usb_serial_jtag_driver_install(&jtag_config));
    ESP_LOGI("JTAG", "USB_SERIAL_JTAG init done");

    uint8_t* data = (uint8_t *) malloc(SERIAL_BUFFER_SIZE);
    if (data == NULL) {
        ESP_LOGE("JTAG", "out of memory");
        vTaskDelete(NULL);
    }

    while(1) {
        int len = usb_serial_jtag_read_bytes(data, (SERIAL_BUFFER_SIZE - 1), 20 / portTICK_PERIOD_MS);

        if (len) {
            usb_serial_jtag_write_bytes((const char *) data, len, 20 / portTICK_PERIOD_MS);
            data[len] = '\0';
        }

    }
}

ちなみにUARTでできないこともないのだが、猛烈にコードが異様にデカくなったり、line_endings を正しく設定しても fgets() 後に \r\n 送出後に謎の空行が出てきたりと頭を抱えることが多かったためやめました。

このexampleが参考になります。というかほとんどそのまんま。ただ下記コードではコンフィグの構造体定義だけ out-of-order initializers are nonstandard in C++ になるのと、Taskはreturnしてはならない[1]ので vTaskDelete(NULL) してます。

https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb_serial_jtag/usb_serial_jtag_echo

脚注
  1. https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/freertos_idf.html#execution ↩︎

ここのえここのえ

シリアル通信をcloseするとESP32-S3が落ちる?

モニターソフトウェアのせいです

VSCodeのシリアルモニタもそうですが、デフォルトでクローズ時に DTRRTS を自動的に飛ばします。ESP32では DTR = high RTS = lowになると電源が落ちるようで、これが悪さをします。PuTTYも同じことが起きるらしい。

VSCodeのシリアルモニタを使っている場合は、歯車マークをクリックして詳細設定(Data bitsとかStop bitsの設定が出せるところ)を押して、DTRとRTSのチェックを外す と回避できます。

参考
https://www.esp32.com/viewtopic.php?t=33641

ここのえここのえ

フォルダを切るとIntellisenseが ESP_LOGI の行でエラーになる

例えば <project root>/main/common/hoge.cpp のように、mainフォルダ内にディレクトリを切ってその中でコーディングを行っていると、 ESP_LOGI 等がエラーになってしまう。これはあくまでIntellisenseが勝手にエラーを出しているだけで、ビルドは問題なく通る。

identifier "CONFIG_LOG_MAXIMUM_LEVEL" is undefined

CONFIG_LOG_MAXIMUM_LEVELbuild/config/sdkconfig.h で定義されており、Intellisenseがこれを読みにいかなくなる。この現象は main直下のファイル main/main.cpp では発生しない。

不思議なことに、 .vscode/c_cpp_properties.jsonincludePath${workspaceFolder}/**で再帰になっているので問題がなさそうに見える。

"includePath": [
    "${config:idf.espIdfPath}/components/**",
    "${config:idf.espIdfPathWin}/components/**",
    "${workspaceFolder}/**"
],

解決法はこの下に直指定で ${workspaceFolder}/build/config を指定すること。なぜかこれで解決する…

"includePath": [
    "${config:idf.espIdfPath}/components/**",
    "${config:idf.espIdfPathWin}/components/**",
    "${workspaceFolder}/**",
    "${workspaceFolder}/build/config"
],
ここのえここのえ

M5Dialのダイヤルを使う

esp-bspの M5Dialのページ を見ると、 espressif/knob コンポーネントが使えることが記載されている。これを使うと楽。

https://components.espressif.com/components/espressif/knob

エンコーダのAがGPIO_41、BがGPIO_40なので[1]それをもとにコードを書く。

M5Dial.h
#ifndef LVS_M5_DIAL
#define LVS_M5_DIAL

#include "iot_knob.h"
#include "esp_log.h"

#define TAG_M5DIAL "M5DIAL"
#define M5DIAL_KNOB_A 41
#define M5DIAL_KNOB_B 40

class M5DialUtils {
    private:
    knob_config_t knob_config;
    knob_handle_t knob_handler;
    static void knob_left_callback(void* arg, void* data);
    static void knob_right_callback(void* arg, void* data);

    public:
    void init();
};

extern M5DialUtils M5Dial;

#endif
M5Dial.cpp
#include "M5Dial.h"

void M5DialUtils::init() {
    knob_config = {
        .default_direction = 1, // 1にしないと逆回転になるので注意
        .gpio_encoder_a = M5DIAL_KNOB_A,
        .gpio_encoder_b = M5DIAL_KNOB_B
    };
    knob_handler = iot_knob_create(&knob_config);

    iot_knob_register_cb(knob_handler, KNOB_LEFT, knob_left_callback, NULL);
    iot_knob_register_cb(knob_handler, KNOB_RIGHT, knob_right_callback, NULL);
}

void M5DialUtils::knob_left_callback(void* arg, void* data) {
    ESP_LOGI(TAG_M5DIAL, "LEFT");
}

void M5DialUtils::knob_right_callback(void* arg, void* data) {
    ESP_LOGI(TAG_M5DIAL, "RIGHT");
}

M5DialUtils M5Dial;
脚注
  1. https://docs.m5stack.com/ja/core/M5Dial ↩︎

ここのえここのえ

CLion + ESP-IDFのセットアップ

JetbrainsヘビーユーザーなのでやはりVSCodeだと補完周りが心もとなく、CLionを使うことにした…

VSCode拡張からインストールしたツールや、Windows向けインストーラでセットアップしてしまうと .espressif\python_env 内に activate.bat が入っていなかったりなど、微妙に構成が違ってそのまま使えない。Githubのリポジトリから落としてきて全部手動でやった方がよい。

cmd.exe
git clone https://github.com/espressif/esp-idf
cd esp-idf
install.bat
export.bat

CLionのドキュメントを参考に、CMake用ツールチェイン設定のカスタムスクリプトを用意。
https://pleiades.io/help/clion/esp-idf.html#env-vars-windows

以上で終了。動画やドキュメントを読んでも旧verの情報だったりしてちょっと苦労しました。

ここのえここのえ

CLionでESP-IDFプロジェクトを開いたときの注意点

幾つか気になる点があったためメモ。随時更新。


TerminalのStart Directoryがおかしい

Tools > Terminal > Project Settings > Start directoryが ESP-IDFのインストールパス になることがある。プロジェクトルートに変更推奨。


Terminalで idf.py を使えるようにしたい

コマンドプロンプトを使っている場合は、前述のCMakeツールチェイン設定で作った .bat ファイルが使えるのですが、powershellだとそうもいきません。新しく作ります。パスは自分のインストール場所で置き換えてください。

espidf_source.ps1
D:\Install\.espressif\python_env\idf5.5_py3.12_env\Scripts\Activate.ps1
D:\Install\esp-idf\export.ps1

起動時引数でこれを読み込むように設定しておきます。


Jetbrains IDEのTerminalと相性が悪そうです。こればかりは諦めて、Windowsターミナルで動かした方が賢明だと思います。一応動く組み合わせはあるので紹介しておきます。

  • 標準Terminalの場合
    cmd, powershell ともに行間に隙間ができて見づらいですが、動きはする。

  • New Terminalの場合
    cmdは行間に隙間ができて見づらいですが、動きはする。
    powershell は表示が乱れ、Escが反応せず厳しい。

ここのえここのえ

作ってる最中のプログラムが完成したので、ひとまずクローズ。
esp-idf絡みというより、freertosやC++周りが分かっていない点が多く詰まることが多かった…
ごちゃごちゃしてるのでリファクタ後に記事にちゃんとまとめる予定。

このスクラップは2025/03/05にクローズされました