はじめに
この記事は、ATOM Lite+TF-CARDモジュール+Panel_CVBS+RCAユニットでM5Stack-SD-Updaterを動作させるために改修を行った際のメモです。
なにかの役に立つかもしれないし、役に立たないかもしれませんがコミットさせていただきます。どこかのどなたかのなんらかに貢献できましたら幸いです。
概要
M5Stack-SD-Updaterとは

M5Stack-SD-UpdaterはM5Stack Coreなどの液晶付きデバイス用に作成された、ユーザーがSDカードに保存したbinファイル(実行ファイル)をUIメニューより選択してロードできるbinファイルローダーです。
このアプリケーションのおかげで、PlatfromIOやArduinoIDEなどでビルド&ダウンロード操作を毎回行う必要がなくなりました。事前にビルドしておいたbinファイルをSDカードに保存しておくことで、M5StackはSDカードより複数のアプリケーションを選択して実行できます。
M5GFXのPanel_CVBSについて
M5Stack-SD-UpdaterはM5Stack CoreやBasicなどの液晶付きデバイス用に作成されています。riraosanはATOM LiteとTF-CARDモジュールの組み合わせでこのローダーを使ってみたいと思いました。そこで、UI表示先はM5GFXのPanel_CVBSを使用することとしました。
先日M5GFXのPanel_CVBSがGitHubリポジトリのmasterにマージされました。またRCAユニットもリリースされました。
ATOM Lite+TF-CARDモジュール+Panel_CVBS+RCAユニットでM5Stack-SD-Updaterを動作させるために改修を行った際のメモと参考ソースコードを以下に示します
ソースコードを改修した際におこなったこと
TF-CARDモジュールに対応してみる
元のソースを参照すると、SPIピンアサインがデフォルトであったので、TF-CARDモジュールに対応させるために、_CLK, _MISO, _MOSIをplatformio.iniより設定することとしました。(ソースコード内に記述してもいいかもしれません。)
https://github.com/riraosan/M5Stack-SD-Updater_with_Panel_CVBS/blob/759323470dad4cb9553663e3e12227ce16f0421f/src/M5StackUpdater.h#L210
#if defined (_SD_H_)
log_d(" Checking for SD Support (pin #%d)", cfg->TFCardCsPin );
if( &fs == &SD ) {
SPI.begin(_CLK, _MISO, _MOSI, cfg->TFCardCsPin);
SPI.setDataMode(SPI_MODE3);
if (!SD.begin(cfg->TFCardCsPin, SPI, 80000000)) { // 80MHz(MAX)
msg[0] = String("SD MOUNT FAILED (pin #" + String(cfg->TFCardCsPin) + ")").c_str();
if( report_errors ) _error( msg, 2 );
return false;
} else {
log_d("SD Successfully mounted (pin #%d)", cfg->TFCardCsPin);
}
mounted = true;
}
SDカードの読み込みは最高速の設定としています。(たぶん最高速で読めると思う)
SDカードの読み込みが不安定な場合はこちらの設定を改修してください。
SPI.begin(_CLK, _MISO, _MOSI, cfg->TFCardCsPin);
SPI.setDataMode(SPI_MODE3);
if (!SD.begin(cfg->TFCardCsPin, SPI, 80000000)) { // 80MHz(MAX)
build_flags =
${env.build_flags}
-D ARDUINO_M5STACK_ATOM_AND_TFCARD
-D _MOSI=19
-D _MISO=33
-D _CLK=23
LovyanGFXとESP32-Chimera-Coreライブラリを無効にしてみる
M5GFXのPanel_CVBSライブラリを使う際にLGFX_8BIT_CVBSユーザークラスを使用する目的があるため、lib_ignoreを利用してLovyanGFXとESP32-Chimera-Coreのライブラリの読み込みを無効としました。
lib_ignore =
LovyanGFX
ESP32-Chimera-Core
M5GFXライブラリを有効にしてみる
M5GFXとButton2ライブラリを有効にしました。
Button2ライブラリはATOM LiteのG39を使って、UIメニューを選択する際に利用します。目的はG39を通じてUIの操作を行うため、です。
lib_deps =
${env.lib_deps}
M5GFX
Button2
LGFX_8BIT_CVBSを改修して追加してみる
LovyanGFXのPanel_CVBSを使用するためのサンプルユーザークラスに、M5Stack-SD-Updaterが使用するプログレスバーを追加したものを作成しました。
この改修により、コンポジット信号を出力できるようになり、M5Stack-SD-Menuサンプルプログラムが初回起動直後に表示するプログレスバーを表示できるようになりました。
#pragma once
#include <M5GFX.h>
class LGFX_8BIT_CVBS : public lgfx::LGFX_Device {
public:
lgfx::Panel_CVBS _panel_instance;
LGFX_8BIT_CVBS(void) {
{ // 表示パネル制御の設定を行います。
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
// 出力解像度を設定;
cfg.memory_width = 360; // 出力解像度 幅
cfg.memory_height = 240; // 出力解像度 高さ
// 実際に利用する解像度を設定;
cfg.panel_width = 360 - 8; // 実際に使用する幅 (memory_width と同値か小さい値を設定する)
cfg.panel_height = 240 - 16; // 実際に使用する高さ (memory_heightと同値か小さい値を設定する)
// 表示位置オフセット量を設定;
cfg.offset_x = 4; // 表示位置を右にずらす量 (初期値 0)
cfg.offset_y = 8; // 表示位置を下にずらす量 (初期値 0)
_panel_instance.config(cfg);
// 通常は memory_width と panel_width に同じ値を指定し、 offset_x = 0 で使用します。;
// 画面端の表示が画面外に隠れるのを防止したい場合は、 panel_width の値をmemory_widthより小さくし、offset_x で左右の位置調整をします。;
// 例えば memory_width より panel_width を 32 小さい値に設定した場合、offset_x に 16 を設定することで左右位置が中央寄せになります。;
// 上下方向 (memory_height , panel_height , offset_y ) についても同様に、必要に応じて調整してください。;
}
{
auto cfg = _panel_instance.config_detail();
// 出力信号の種類を設定;
// cfg.signal_type = cfg.signal_type_t::NTSC;
cfg.signal_type = cfg.signal_type_t::NTSC_J;
// cfg.signal_type = cfg.signal_type_t::PAL;
// cfg.signal_type = cfg.signal_type_t::PAL_M;
// cfg.signal_type = cfg.signal_type_t::PAL_N;
// 出力先のGPIO番号を設定;
cfg.pin_dac = 26; // DACを使用するため、 25 または 26 のみが選択できます;
// PSRAMメモリ割当の設定;
cfg.use_psram = 0; // 0=PSRAM不使用 / 1=PSRAMとSRAMを半々使用 / 2=全部PSRAM使用;
// 出力信号の振幅の強さを設定;
cfg.output_level = 128; // 初期値128
// ※ GPIOに保護抵抗が付いている等の理由で信号が減衰する場合は数値を上げる。;
// ※ M5StackCore2 はGPIOに保護抵抗が付いているため 200 を推奨。;
// 彩度信号の振幅の強さを設定;
cfg.chroma_level = 128; // 初期値128
// 数値を下げると彩度が下がり、0で白黒になります。数値を上げると彩度が上がります。;
_panel_instance.config_detail(cfg);
}
setPanel(&_panel_instance);
}
template <typename T>
void clearDisplay(T color = 0) { fillScreen(color); }
void display() {}
void progressBar(int x, int y, int w, int h, uint8_t val, uint16_t color = 0x09F1, uint16_t bgcolor = 0x0000)
{
drawRect(x, y, w, h, color);
if (val > 100)
val = 100;
if (val == 0)
{
fillRect(x + 1, y + 1, w - 2, h - 2, bgcolor);
}
else
{
int fillw = (w * (((float)val) / 100.0)) - 2;
fillRect(x + 1, y + 1, fillw - 2, h - 2, color);
fillRect(x + fillw + 1, y + 1, w - fillw - 2, h - 2, bgcolor);
}
}
const uint32_t &textcolor = _text_style.fore_rgb888;
const uint32_t &textbgcolor = _text_style.back_rgb888;
const textdatum_t &textdatum = _text_style.datum;
const float &textsize = _text_style.size_x;
};
menu.hを改修してみる
menu.hを改修しました。ATOMを使用する際は、core.hをコンパイルしないこととしました。
USE_DISPLAY, LGFX_ONLYを定義してM5GFXのPanel_CVBSを使えるようにしました。
また、TF-CARDモジュールではSDカードのCSピンは使用しないので、TFCARD_CS_PINを-1としました。
#if !defined(ARDUINO_M5STACK_ATOM_AND_TFCARD)
#include "core.h"
#else
#include <SD.h>
#include "LGFX_8BIT_CVBS.h"
#include <Button2.h>
#define USE_DISPLAY
#define LGFX_ONLY
#define TFCARD_CS_PIN -1
static LGFX_8BIT_CVBS tft;
#define LGFX LGFX_8BIT_CVBS
//#define BUTTON_WIDTH 60
#define SDU_APP_NAME "Application Launcher"
#include <M5StackUpdater.h>
static LGFX_Sprite sprite(&tft);
fs::SDFS &M5_FS(SD);
Button2 button;//for G39
#endif
#if !defined(ARDUINO_M5STACK_ATOM_AND_TFCARD)
#include "core.h"
#endif
main.cppを改修してみる
SD Updaterの描画先をPanel_CVBSにする目的でsetSDUGfx()
を記述したあと、Panel_CVBSライブラリをtft.init()
で初期化しました。
void setup() {
#if defined(ARDUINO_M5STACK_ATOM_AND_TFCARD)
setSDUGfx(&tft);
tft.init();
#else
M5.begin(); // bool LCDEnable, bool SDEnable, bool SerialEnable, bool I2CEnable, bool ScreenShotEnable
#endif
....
M5.Lcdをtftに置換してみる
描画系のAPIをLGFX_8BIT_CVBSに更新したので、M5.LcdをLGFX_8BIT_CVBSインスタンスのtftに置換しました。
(例)
controls.hを改修してみる
メニューを選択するため、G39を押下した際のButton2イベントハンドラーをcontrols.hに追加しました。
HIDSignal HIDFeedback( HIDSignal signal, int ms = 50 )
{
if( signal != HID_INERT ) {
HIDFeedback( ms );
}
return signal;
}
static HIDSignal _button;
void handler(Button2 &btn)
{
switch (btn.getType())
{
case clickType::single_click:
Serial.print("single ");
_button = HIDFeedback(HID_DOWN);
break;
case clickType::double_click:
Serial.print("double ");
_button = HIDFeedback(HID_PAGE_DOWN);
break;
case clickType::triple_click:
Serial.print("triple ");
_button = HIDFeedback(HID_PAGE_UP);
break;
case clickType::long_click:
Serial.print("long ");
_button = HIDFeedback(HID_SELECT);
break;
case clickType::empty:
break;
default:
break;
}
Serial.print("click");
Serial.print(" (");
Serial.print(btn.getNumberOfClicks());
Serial.println(")");
}
void HIDInit()
{
#if defined CAN_I_HAZ_M5FACES
Wire.begin(SDA, SCL);
M5FacesEnabled = Faces.canControlFaces();
if( M5FacesEnabled ) {
// set the interrupt
attachInterrupt(5, M5FacesIsr, FALLING); // 5 = KEYBOARD_INT from M5Faces.cpp
Serial.println("M5Faces enabled and listening");
}
#endif
// G39 button
button.setClickHandler(handler);
button.setDoubleClickHandler(handler);
button.setTripleClickHandler(handler);
button.setLongClickHandler(handler);
button.begin(39);
}
....
#if defined ARDUINO_M5Stack_Core_ESP32 || defined ARDUINO_M5STACK_FIRE || defined ARDUINO_M5STACK_Core2
// legacy buttons support
bool a = M5.BtnA.wasPressed();
bool b = M5.BtnB.wasPressed() && !M5.BtnC.isPressed();
bool c = M5.BtnC.wasPressed() && !M5.BtnB.isPressed();
bool d = ( M5.BtnB.wasPressed() && M5.BtnC.isPressed() );
bool e = ( M5.BtnB.isPressed() && M5.BtnC.wasPressed() );
if( d || e ) return HIDFeedback( HID_PAGE_UP ); // multiple push, suggested by https://github.com/mongonta0716
if( b ) return HIDFeedback( HID_PAGE_DOWN );
if( c ) return HIDFeedback( HID_DOWN );
if( a ) return HIDFeedback( HID_SELECT );
#elif defined(ARDUINO_M5STACK_ATOM_AND_TFCARD)
button.loop();
HIDSignal temp = _button;
_button = HID_INERT;
return temp;
#endif
// if( d || e ) return HIDFeedback( HID_PAGE_UP ); // multiple push, suggested by https://github.com/mongonta0716
// if( b ) return HIDFeedback( HID_PAGE_DOWN );
// if( c ) return HIDFeedback( HID_DOWN );
// if( a ) return HIDFeedback( HID_SELECT );
#endif
さいごに
これらの改修を行ったソースコードを次のリポジトリに公開しています。
MIT Licenseです。
実際の動作風景はこちら。
ご参考になれば幸いです。
ヤッター
— riraosan@再起動 (@riraosan_0901) December 31, 2022
一方通行ですが、LGFXのPanel_CVBSとATOMのG39ボタンでbinをロード出来ました。
参考ですが改修方法をQiitaに書きます。
元のメニューに戻る方法を調べてみよう。#M5Stack pic.twitter.com/DR8BhvIBO1
(了)