Repository navigation
Expand file tree
/
Copy pathpower_monitor.cpp
More file actions
138 lines (124 loc) · 4.85 KB
/
Copy pathpower_monitor.cpp
File metadata and controls
138 lines (124 loc) · 4.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include "power_monitor.hpp"
#include <cstdlib>
void PowerMonitor::begin() {
// M5Unified の内部 I2C バスを借用する(バスの所有権は M5Unified 側)
const m5pm1_err_t result = this->_pm1.begin(&M5.In_I2C);
this->_available = (result == M5PM1_OK);
if (!this->_available) {
M5_LOGW("M5PM1 begin failed (%d), falling back to M5.Power",
(int)result);
}
this->_lastPollMs = 0;
this->poll();
}
void PowerMonitor::turnOffLed() {
if (!this->_available) {
return;
}
// まず全 LED を黒で塗ってから LED_EN を落とす。
// 逆順(先に LED_EN を落とす)だと、消灯前の色がデータに残ったままになり
// 次に LED_EN が上がった瞬間に一瞬点灯してしまう
const m5pm1_rgb_t black[M5PM1_MAX_LED_COUNT] = {};
this->_pm1.setLeds(black, M5PM1_MAX_LED_COUNT, M5PM1_MAX_LED_COUNT,
/*autoRefresh=*/true);
this->_pm1.setLedEnLevel(false);
}
void PowerMonitor::update() {
const uint32_t now = millis();
if (this->_lastPollMs != 0 &&
(now - this->_lastPollMs) < POLL_INTERVAL_MS) {
return;
}
this->_lastPollMs = now;
this->poll();
}
void PowerMonitor::poll() {
if (this->_available) {
// 外部給電判定は VIN(USB-C 入力)の電圧のみで行う。
//
// 実機確認の経緯:
// - getPowerSource() は enum 定義(0-3)外の値(5)を返し信頼できない
// - 5VINOUT は双方向ポートで、電池駆動時はデバイス自身が昇圧して
// 5V を出力するため常時 5V 付近になり、判定には使えない
// - VIN は USB 接続時 約5000mV / 未接続時 0
// 付近なので確実に判定できる
bool external = this->_externallyPowered;
uint16_t vinMv = 0;
if (this->_pm1.readVin(&vinMv) == M5PM1_OK) {
external = (vinMv > EXTERNAL_POWER_MV);
}
uint16_t vbatMv = 0;
if (this->_pm1.readVbat(&vbatMv) == M5PM1_OK) {
this->_batteryMilliVolts = vbatMv;
}
// 切り分け用: 給電状態が変化したときに生の値をログに残す
if (external != this->_externallyPowered) {
M5_LOGI("Power changed: vin=%umV vbat=%umV ext=%d", (unsigned)vinMv,
(unsigned)vbatMv, (int)external);
}
this->_externallyPowered = external;
} else {
// フォールバック: M5Unified 経由(充電状態が取れない環境では
// is_charging にならない可能性が高いが、無いよりまし)
this->_externallyPowered =
(M5.Power.isCharging() ==
m5::Power_Class::is_charging_t::is_charging);
}
// 残量%の生値: M5Unified の値を優先し、取得不可なら VBAT から近似する
int rawPercent = -1;
const int32_t unifiedLevel = M5.Power.getBatteryLevel();
if (unifiedLevel >= 0 && unifiedLevel <= 100) {
rawPercent = (int)unifiedLevel;
} else if (this->_batteryMilliVolts > 0) {
rawPercent = percentFromMilliVolts(this->_batteryMilliVolts);
}
if (rawPercent < 0) {
this->_percentEma = -1.0f;
this->_batteryPercent = -1;
return;
}
// EMA で平滑化し、表示値はヒステリシス幅を超えたときだけ更新する
// (電圧読み取りの揺らぎによる 96%⇔97% のような表示のふらつきを防ぐ)
if (this->_percentEma < 0.0f) {
this->_percentEma = (float)rawPercent;
} else {
this->_percentEma = this->_percentEma * (1.0f - EMA_ALPHA) +
(float)rawPercent * EMA_ALPHA;
}
const int smoothed = (int)(this->_percentEma + 0.5f);
if (this->_batteryPercent < 0 ||
std::abs(smoothed - this->_batteryPercent) >=
DISPLAY_HYSTERESIS_PERCENT ||
smoothed == 0 || smoothed == 100) {
this->_batteryPercent = smoothed;
}
}
int PowerMonitor::percentFromMilliVolts(uint16_t mv) {
// LiPo 1セルの区分線形近似(開放電圧ベース)
struct Point
{
uint16_t mv;
int percent;
};
static constexpr Point kCurve[] = {
{4150, 100}, {4000, 85}, {3850, 60}, {3700, 30}, {3550, 10}, {3400, 0},
};
constexpr size_t kCount = sizeof(kCurve) / sizeof(kCurve[0]);
if (mv >= kCurve[0].mv) {
return 100;
}
if (mv <= kCurve[kCount - 1].mv) {
return 0;
}
for (size_t i = 1; i < kCount; ++i) {
if (mv >= kCurve[i].mv) {
const Point& upper = kCurve[i - 1];
const Point& lower = kCurve[i];
const int span = (int)upper.mv - (int)lower.mv;
const int offset = (int)mv - (int)lower.mv;
return lower.percent +
(upper.percent - lower.percent) * offset / span;
}
}
return 0;
}