1. はじめに
初期ファームウェア とは、M5Stack Tab5 に出荷時にインストールされている「M5Stack User Demo」のことです。
今年になり、RTCの日付設定カレンダーに 2026 が無いことに気付きました。RTCは 2026年と正しいのですが、カレンダーの年ドロップダウンリストに 2026年が無いため、1901年が表示されていると思われます。

LVGLのカレンダー部品(デフォルトの年リスト)が、1901〜2025となっていることが直接要因
そこで、「M5Stack User Demo」に年リストの設定処理を追加します。
もう一点、WiFi が APモードになっているので、これを STAモード(ステーションモード)に変更して自宅のWiFiルータに接続するように変更します。

ちなみに、初期ファームウェア に戻す方法は、次のURLで案内されています。
Tab5 ファームウェアの初期化:
https://docs.m5stack.com/ja/guide/restore_factory/m5tab5
Windows向け:Easyloader: Tab5 Factory Firmware
https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1132/C145_Tab5_User_Demo.exe
M5Stack Tab5 初期ファームウェア:
https://github.com/m5stack/M5Tab5-UserDemo/releases/download/V0.2/C145-Tab5-UserDemo-V0.2-20250909_0x0.bin
2. 環境準備
「ESP-IDF v5.4.2」の環境構築 と 「M5Stack User Demo」のビルドを行います。次のURLで説明されているので、参考にしてください。
- Tab5 ESP-IDF 工場出荷時ファームウェアのコンパイル:
https://docs.m5stack.com/ja/esp_idf/m5tab5/userdemo
以下のコマンドを順に実行します。
# esp-idf v5.4.2 クローン
$ cd ~
$ git clone -b v5.4.2 --recursive https://github.com/espressif/esp-idf.git
# esp-idf インストール
$ cd esp-idf
$ ./install.sh
$ . ./export.sh
# M5Stack User Demo クローン
$ cd ~
$ git clone https://github.com/m5stack/M5Tab5-UserDemo.git
$ cd M5Tab5-UserDemo
$ python ./fetch_repos.py
# M5Stack User Demo ビルド
$ cd platforms/tab5
$ idf.py build
コンパイルエラーが無ければ、次のメッセージが出力されます。
Successfully created esp32p4 image.
Generated /home/USERNAME/M5Tab5-UserDemo/platforms/tab5/build/m5stack_tab5.bin
(以下省略)
多くのワーニングが出力されますが無視してOKです。
3.カスタマイズ
先にも書きましたが、次の2点を変更します。
- カレンダーに 独自の
年リストを追加します - WiFi を STAモードに変更し、自IPアドレスを表示するように変更します
(以降の変更箇所は、コピペしてタイプミスを防止することをお勧めします)
なお、2つのカスタマイズ内容は、互いに独立しています。よって、どちらか一方のカスタマイズだけを適用することも可能です。
3.1 年リストの追加
次の2ファイルを変更します。
dependencies/smooth_ui_toolkit/src/lvgl/lvgl_cpp/calendar.happ/apps/app_launcher/view/panel_rtc.cpp
(1) calendar.h
calendar.hの56行目あたりに、次のheaderDropdownSetYearList関数を追加します。
lv_obj_t* headerDropdownCreate()
{
return lv_calendar_header_dropdown_create(this->raw_ptr());
}
+ void headerDropdownSetYearList(const char* year_list)
+ {
+ lv_calendar_header_dropdown_set_year_list(this->raw_ptr(), year_list);
+ }
(2) panel_rtc.cpp
panel_rtc.cppの61行目あたりに、次の2行を追加します。
_calendar->headerDropdownCreate();
+ const char *year_list = "2029\n2028\n2027\n2026\n2025\n2024\n2023\n2022\n2021\n2020";
+ _calendar->headerDropdownSetYearList(year_list);
_calendar->setTodayDate(local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday);
今回は年リストを、2020〜2029 としましたが、範囲はご自由に設定してください。
static const char * year_list = {
"2025\n2024\n2023\n2022\n2021\n"
"2020\n2019\n2018\n2017\n2016\n2015\n2014\n2013\n2012\n2011\n2010\n2009\n2008\n2007\n2006\n2005\n2004\n2003\n2002\n2001\n"
"2000\n1999\n1998\n1997\n1996\n1995\n1994\n1993\n1992\n1991\n1990\n1989\n1988\n1987\n1986\n1985\n1984\n1983\n1982\n1981\n"
"1980\n1979\n1978\n1977\n1976\n1975\n1974\n1973\n1972\n1971\n1970\n1969\n1968\n1967\n1966\n1965\n1964\n1963\n1962\n1961\n"
"1960\n1959\n1958\n1957\n1956\n1955\n1954\n1953\n1952\n1951\n1950\n1949\n1948\n1947\n1946\n1945\n1944\n1943\n1942\n1941\n"
"1940\n1939\n1938\n1937\n1936\n1935\n1934\n1933\n1932\n1931\n1930\n1929\n1928\n1927\n1926\n1925\n1924\n1923\n1922\n1921\n"
"1920\n1919\n1918\n1917\n1916\n1915\n1914\n1913\n1912\n1911\n1910\n1909\n1908\n1907\n1906\n1905\n1904\n1903\n1902\n1901"
};
いずれ、2026が追加されるものと思います。
3.2 WiFi STAモードへの変更
次の4ファイルを変更します。
app/hal/hal.hplatforms/tab5/main/hal/hal_esp32.hplatforms/tab5/main/hal/components/hal_wifi.cppapp/apps/app_launcher/view/panel_switches.cpp
(1) hal.h
hal.hの260行目あたりに、次の2行を追加します。
virtual void startWifiAp()
{
}
+ virtual std::string getWifiSSID(){ return ""; }
+ virtual std::string getWifiIPv4(){ return ""; }
(2) hal_esp32.h
hal_esp32.hの77行目あたりに、次の2行を追加します。
void startWifiAp() override;
+ std::string getWifiSSID() override; // get SSID
+ std::string getWifiIPv4() override; // get My IP Address (v4)
(3) hal_wifi.cpp
-
hal_wifi.cppの24行目あたりから、次のように変更します
- #define WIFI_SSID "M5Tab5-UserDemo-WiFi"
- #define WIFI_PASS ""
+ #define WIFI_SSID "YOUR_WIFI_SSID"
+ #define WIFI_PASS "YOUR_WIFI_PASS"
(自分のWi-Fi環境に合わせて、SSIDとパスワードを変更)
2. hal_wifi.cppの94行目あたりから、次のように追加・変更します
void wifi_init_softap()
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
- esp_netif_create_default_wifi_ap();
+ esp_netif_create_default_wifi_sta(); //STAモード
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {};
- std::strncpy(reinterpret_cast<char*>(wifi_config.ap.ssid), WIFI_SSID, sizeof(wifi_config.ap.ssid));
- std::strncpy(reinterpret_cast<char*>(wifi_config.ap.password), WIFI_PASS, sizeof(wifi_config.ap.password));
- wifi_config.ap.ssid_len = std::strlen(WIFI_SSID);
- wifi_config.ap.max_connection = MAX_STA_CONN;
- wifi_config.ap.authmode = WIFI_AUTH_OPEN;
+ std::strncpy(reinterpret_cast<char*>(wifi_config.sta.ssid), WIFI_SSID, sizeof(wifi_config.sta.ssid));
+ std::strncpy(reinterpret_cast<char*>(wifi_config.sta.password), WIFI_PASS, sizeof(wifi_config.sta.password));
+ wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
+ wifi_config.sta.pmf_cfg.capable = true;
+ wifi_config.sta.pmf_cfg.required = false;
- ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
- ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); //STAモード
+ ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
+ ESP_ERROR_CHECK(esp_wifi_connect()); //WiFi Connecting...
- ESP_LOGI(TAG, "Wi-Fi AP started. SSID:%s password:%s", WIFI_SSID, WIFI_PASS);
+ ESP_LOGI(TAG, "Wi-Fi Station started. SSID:%s password:%s", WIFI_SSID, WIFI_PASS);
}
esp_wifi_connect()は 非同期処理のため、本来はイベント処理を追加して接続完了を監視すべきですが、(SSIDとパスワードに誤りが無ければ)いずれ接続されるため、今回は良しとしました。
3. hal_wifi.cppの165行目あたりに、次の2つの関数を追加します
void HalEsp32::startWifiAp()
{
wifi_init();
}
+ std::string HalEsp32::getWifiSSID() {
+ return std::string(WIFI_SSID);
+ }
+ std::string HalEsp32::getWifiIPv4() {
+ char ip[64];
+ esp_netif_ip_info_t ip_info;
+ esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); // STAモードのインターフェースを取得
+ if (netif != NULL) {
+ esp_netif_get_ip_info(netif, &ip_info);
+ return std::string(esp_ip4addr_ntoa(&ip_info.ip, ip, sizeof(ip))); // IPアドレスv4
+ }
+ return "NO IP Address";
+ }
(4) panel_switches.cpp
-
panel_switches.cppの75行目あたりに、次のように変更(削除)します
_panel_ssid = std::make_unique<Container>(_window->get());
_panel_ssid->align(LV_ALIGN_CENTER, 87, -80);
_panel_ssid->setSize(379, 51);
_panel_ssid->setRadius(22);
_panel_ssid->setBorderWidth(0);
_panel_ssid->setBgColor(lv_color_hex(0x725151));
- _label_ssid = std::make_unique<Label>(_panel_ssid->get());
- _label_ssid->align(LV_ALIGN_CENTER, 0, 0);
- _label_ssid->setTextFont(&lv_font_montserrat_24);
- _label_ssid->setTextColor(lv_color_hex(0xFFFFFF));
- _label_ssid->setText("M5Tab5-UserDemo-WiFi");
_panel_url = std::make_unique<Container>(_window->get());
_panel_url->align(LV_ALIGN_CENTER, 105, 0);
_panel_url->setSize(336, 51);
_panel_url->setRadius(22);
_panel_url->setBorderWidth(0);
_panel_url->setBgColor(lv_color_hex(0x725151));
- _label_url = std::make_unique<Label>(_panel_url->get());
- _label_url->align(LV_ALIGN_CENTER, 0, 0);
- _label_url->setTextFont(&lv_font_montserrat_24);
- _label_url->setTextColor(lv_color_hex(0xFFFFFF));
- _label_url->setText("http://192.168.4.1");
(//をつけてコメント化を推奨)
2. panel_switches.cppの169行目あたりに、次のように追加します
} else {
_label_msg_a = std::make_unique<Label>(_panel_msg->get());
_label_msg_a->align(LV_ALIGN_CENTER, 0, 0);
_label_msg_a->setTextFont(&lv_font_montserrat_22);
_label_msg_a->setTextColor(lv_color_hex(0xFFFFFF));
_label_msg_a->setText("Using internal antenna.");
_label_msg_b.reset();
}
+ _label_ssid = std::make_unique<Label>(_panel_ssid->get());
+ _label_ssid->align(LV_ALIGN_CENTER, 0, 0);
+ _label_ssid->setTextFont(&lv_font_montserrat_24);
+ _label_ssid->setTextColor(lv_color_hex(0xFFFFFF));
+ _label_ssid->setText(GetHAL()->getWifiSSID());
+
+ _label_url = std::make_unique<Label>(_panel_url->get());
+ _label_url->align(LV_ALIGN_CENTER, 0, 0);
+ _label_url->setTextFont(&lv_font_montserrat_24);
+ _label_url->setTextColor(lv_color_hex(0xFFFFFF));
+ _label_url->setText("http://" + GetHAL()->getWifiIPv4());
}
以上で、ソースコードの変更は終わりです。
各ファイルを保存してビルドします。
4. ビルド
次のコマンドにて ビルドします。変更ソースに関係するファイルだけ再コンパイルされるため、環境準備のときのビルドより短時間で済むはずです。
# esp-idf 環境設定(念のため)
$ cd ~/esp-idf
$ . ./export.sh
$ cd ~/M5Tab5-UserDemo/platforms/tab5
$ idf.py build
コンパイルエラーが無ければ、次のメッセージが出力されます。
Successfully created esp32p4 image.
Generated /home/USERNAME/M5Tab5-UserDemo/platforms/tab5/build/m5stack_tab5.bin
(以下省略)
コンパイルエラーになった場合は、変更したソースファイルを見直してください。
5. カスタマイズした「M5Stack User Demo」ファームウェアを書き込む
M5Stack Tab5 ダウンロードモードにします(デバイスのリセットボタンを長押し(約2秒)、内部の緑色LED が点滅したらボタンを離す)。
$ idf.py flash
or
$ idf.py -p PORT flash
or
$ python -m esptool --chip esp32p4 -b 460800 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 16MB --flash_freq 80m 0x2000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/m5stack_tab5.bin
or
$ python -m esptool --chip esp32p4 -b 460800 --before default_reset --after hard_reset write_flash "@flash_args"
しかし、自分のESP-IDF環境はDockerで構築しているので、直接PCのシリアルデバイスに書き込むことができません。そのため、次の3ステップにて書き込みます。
- 3本の
.binを統合
$ cd ~/M5Tab5-UserDemo/platforms/tab5
$ esptool.py --chip esp32p4 merge_bin --output ~/m5-merged.bin \
0x2000 build/bootloader/bootloader.bin \
0x8000 build/partition_table/partition-table.bin \
0x10000 build/m5stack_tab5.bin
2. 統合したファームウェアをホストPCにコピー
$ docker cp <コンテナID>:/home/USERNAME/m5-merged.bin .
3. コピーしたファームウェアを書き込む
$ esptool --chip auto --port <シリアルポート> --baud 1500000 \
--after hard_reset write-flash 0 ./m5-merged.bin
リモートシリアルポートを使えば、docker環境から直接書き込むこともできるのですが、時間がかかるのでコピーしています。
docker コンテナからリモートシリアルポートを使って ホストに接続した シリアルデバイスにアクセスする:
https://qiita.com/nak435/items/76cbf08b25b614e3c80b
4. 結果確認
下記スクショのように、期待した通りに変更されました。
WiFi接続完了前(もしくは、DHCPアドレス取得前)の状態の場合は、http://0.0.0.0と表示されます。その場合は、一旦閉じて、再度開くと表示されると思います。
PCのブラウザから http://192.168.0.9 にアクセスできました。
5. 終わりに
ソースコードの解析と、変更コードの作成・検証に半日ほどかかりましたが、無事に目的を達成できました。
何かの参考になれば幸いです。
以上