↑ 前回記事の関連です。
1. はじめに
↓ 上部中央付近に、バッテリーに関する情報が出力されていますが、バッテリーパックのバッテリー残量の表示がありません。
今回のカスタマイズは、この付近に バッテリー残量の表示を追加する 内容です。
- バッテリーで動作中は、バッテリーが放電している 電圧(V) と 電流(A) が表示されます
- 外部電源で動作中は、電流(A)は マイナス値となって(電流の流れる向きが反転して)、バッテリー充電中の 電圧 と 電流 が表示されます
- バッテリーパックを装着していない場合は(電流は流れないため)、0(-0.00A)が表示されます
2. バッテリー残量の求め方
Arduino環境の場合、M5.Battery.getBatteryLevel()で得ることができます。
#include <M5Unified.h>
void setup() {
M5.begin();
Serial.begin(115200);
}
void loop() {
int bat_level = M5.Power.getBatteryLevel();
Serial.printf("Battery Level: %d\n", bat_level);
delay(1000);
}
- バッテリー充電中は、少しずつ増えていくことが確認できます。
Battery Level: 79 Battery Level: 80 Battery Level: 81 : :
- バッテリーで動作中は、少しずつ減っていくことが確認できます。
Battery Level: 70 Battery Level: 69 Battery Level: 68 : :
そこで、M5Unifiedのソースコードから、M5.Battery.getBatteryLevel()の実装を確認しました。
2.1 M5.Battery.getBatteryLevel()実装確認
M5Unified のsrc/utility/Power_Class.cppの1266行目からのPower_Class::getBatteryLevelの処理を解析し、M5Tab5/esp32-p4 の処理を抜き出すと、おおよそ 次の処理であることが分かります。
std::int32_t Power_Class::getBatteryLevel(void)
{
float mv = 0.0f;
switch (_pmic)
{
case pmic_t::pmic_aw32001:
(略)
default:
switch (M5.getBoard()) {
case board_t::board_M5Tab5:
// 2S Li-Po ( * 1000 / 2 == * 500)
mv = Ina226.getBusVoltage() * 500;
break;
(略)
}
}
int level = (mv - 3300) * 100 / (float)(4150 - 3350);
return (level < 0) ? 0
: (level >= 100) ? 100
: level;
}
M5Tab5は _pmic == M5.Battery.getType() == pmic_unknownのため、switch文のdefault句に行き、ボードタイプのswitch文でboard_M5Tab5に行き、Ina226.getBusVoltage()で得た値が使われていることが分かります。
その後の計算も含め、M5Stack User Demoでは、次のコードで求めることができます。これを表示に追加します。
float mv = GetHAL()->powerMonitorData.busVoltage * 500;
int level = (mv - 3300) * 100 / (float)(4150 - 3350);
if (level < 0) level = 0;
if (level > 100) level = 100;
`GetHAL()->powerMonitorData.busVoltage`は、`ina226.readBusVoltage()`の値です。
void HalEsp32::updatePowerMonitorData()
{
powerMonitorData.busVoltage = ina226.readBusVoltage();
powerMonitorData.shuntVoltage = ina226.readShuntVoltage();
powerMonitorData.busPower = ina226.readBusPower();
powerMonitorData.shuntCurrent = ina226.readShuntCurrent();
}
計算式の考察
- mv:INA226センサーが測定した、Li-Poバッテリーの電圧の半分(mV)
- mv - 3300:バッテリーの最低電圧(0%残量)を3300mVとする
- 4150 - 3350: バッテリーの満充電時電圧の目安4150mVと、ほぼ空の電圧の目安3350mVの差分、つまり使用可能な電圧範囲の総量を表す
- * 100: 百分率に変換
つまり、電圧範囲(3.35V〜4.15V)内でバッテリー残量がリニアに変化すると仮定して、現在の電圧値を0%〜100%のスケールにマッピングしている式である。
2.2 カスタマイズ(BatteryLevelを表示)
次の2ファイルを変更します。
- app/apps/app_launcher/view/view.h
- app/apps/app_launcher/view/panel_power_monitor.cpp
(1) view.h
view.hの97行目あたりに、次の1行を追加します。
std::unique_ptr<smooth_ui_toolkit::lvgl_cpp::Image> _img_chg_arrow_down;
+ std::unique_ptr<smooth_ui_toolkit::lvgl_cpp::Label> _label_bat_level; //battery level
(2) panel_power_monitor.cpp
-
panel_power_monitor.cppの55行目あたりに、次の処理を追加します。
_img_chg_arrow_down = std::make_unique<Image>(lv_screen_active());
_img_chg_arrow_down->align(LV_ALIGN_CENTER, 216, -264);
_img_chg_arrow_down->setSrc(&chg_arrow_down);
+ _label_bat_level = std::make_unique<Label>(lv_screen_active());
+ _label_bat_level->align(LV_ALIGN_RIGHT_MID, _label_voltage_pos_x + 92, _label_voltage_pos_y);
+ _label_bat_level->setText("..");
+ _label_bat_level->setTextColor(lv_color_hex(_label_color));
+ _label_bat_level->setTextFont(&lv_font_montserrat_22);
+ _label_bat_level->setBgColor(lv_color_white());
+ _label_bat_level->setBgOpa(LV_OPA_COVER);
}
2. panel_power_monitor.cppの72行目あたりに、次の処理を追加します。
_label_voltage->setText(fmt::format("{:.2f}V", GetHAL()->powerMonitorData.busVoltage));
_label_current->setText(fmt::format("{:.2f}A", GetHAL()->powerMonitorData.shuntCurrent));
+ if (abs(GetHAL()->powerMonitorData.shuntCurrent) > 0.001) {
+ _label_bat_level->setText(fmt::format("L: {:d}%", []()->int {
+ // see M5Unified/src/utility/Power_Class.cpp#1309
+ float mv = GetHAL()->powerMonitorData.busVoltage * 500;
+ int level = (mv - 3300) * 100 / (float)(4150 - 3350);
+ return (level < 0) ? 0 : (level > 100) ? 100 : level;
+ }()));
+ } else {
+ _label_bat_level->setText("L: - ");
+ }
3. ビルド & 書き込み
環境準備、ビルド および カスタマイズしたファームウェアの書き込みについては、前回記事を参照してください。
なお、今回のカスタマイズ内容は、前回のカスタマイズ内容とは独立しています。よって、前回のカスタマイズをスキップして 今回のカスタマイズを適用することが可能です。
4. 結果確認
電圧(V)の右側、INA226 の上部に バッテリー残量を追加しました。
下記スクショのように、期待した通りに表示されています。
| 外部電源で動作中 (バッテリー充電中) |
![]() 矢印が赤色 |
|---|---|
| 外部電源で動作中 (バッテリーパック無し) |
![]() なぜか矢印が赤色 |
| バッテリーで動作中 |
![]() 矢印がグレー |
バッテリーパック無しの場合に、矢印が赤い原因を調べました。
- if (GetHAL()->powerMonitorData.shuntCurrent < 0) {
+ if (GetHAL()->powerMonitorData.shuntCurrent < -0.001) {
浮動小数点の計算誤差なのか、バッテリーパック無しの場合の電流値が 「真に 0 ではない」 ことが原因です。
この閾値を調整することで、バッテリーパック無しの場合に 矢印がグレーになります。
5. バッテリーパックの充放電時間
使用した NP-F550互換バッテリーパックは こちら のもの。
(電圧:7.2V、容量:2600mAh)
LCDの明るさを 20%に設定。
5.1 バッテリーパックの充電時間
バッテリー残量 0%で動作を停止した状態から、外部電源に切り替えて、バッテリーパックの充電状況(バッテリー残量)を観察した。
ちょうど1時間ほどで100%となったが、充電電流は流れたまま・・・。
タイムラプス(4秒間隔)撮影
その後、しばらく放置していたら 電流値が 0.00A となっていた。
5.2 バッテリーパックの放電時間
公式資料に次の説明があります。
標準使用環境(画面輝度 50%、Wi‑Fi 常時接続、バックグラウンドタスク稼働等)において、Tab5 内蔵バッテリー電圧が満充電(8.23 V)からシャットダウン閾値(6.0 V)まで放電し、約 6 時間連続稼働可能。
満充電したバッテリーで動作させて、バッテリーの残量を観察した。
6時間を経過しても40%ほど残っていた。
その後も放置していたら、気付いた時には落ちていました。
5.3 シリアルポートを使った監視
バッテリーレベルと電流値をシリアルポートに出力する処理を加えてモニターした結果をグラフ化しました。
- USBシリアルから電源を供給させないため、USBシリアルとM5Tab5は、TX / RX / GND のみ接続
- 画面の明るさは、起動直後に20%に設定
- 外部電源は、Raspberry Pi 5向けの5.1V 5.0A出力 の電源アダプタを使用
5.3.1 充電
外部電源で動作させて、バッテリーの充電状況をモニター
- 0%から充電を開始し 57分後にバッテリーレベルは100%となったが、充電電流は流れたまま。充電電流が止まったのは、充電を開始して2時間24分後であった。充電電流値はほぼ一定で、平均値は 0.971A であった。
5.3.2 放電
バッテリーパックで動作させて、バッテリーの放電状況をモニター
- 満充電した100%から放電を開始し 7時間11分後にシステム停止。停止直前のバッテリーレベルは51%であった。電流値は多少の振れはあるが、平均値は 0.237A であった。
「M5Stack User Demo」の内容だと、それほど能動的に動作する処理が少ないのか、バッテリーパックの容量が 2600mAh と多めなのか、公称値の「約6時間連続稼働」より1時間以上 長く動作した。
以上






