Defer regulator ADC and flash work during Morse timing

This commit is contained in:
mikecarper committed 2026-10-18 22:38:54 -07:00
1 parent c1e7ce0959
commit 71372f686f
3 files changed
+76 -4

No files matched your search

+7 -2
View File
@@ -38,6 +38,7 @@ void markRegulatorCleanReset() {}
void resumeRegulatorRecovery() {}
}} // namespace mesh::power
#else
#include "MorseLoopPolicy.h"
namespace mesh { namespace power {
namespace {
MainBoard* main_board = nullptr;
@@ -231,14 +232,18 @@ void serviceRegulatorSettings() {
(activeRegulatorMode(true) == RegulatorMode::Dcdc ? RECOVERY_MCU : 0)
| (activeRegulatorMode(false) == RegulatorMode::Dcdc ? RECOVERY_RADIO : 0);
recovery().updateActiveDomains(active_dcdc);
if (uint32_t(now - last_battery) >= 30000) {
// ADC sampling deliberately spaces three readings, and persistence can
// block on flash. Keep both out of keyed Morse TX/RX timing; heartbeats
// remain live without refreshing a deferred sample or retry deadline.
const bool defer_io = mesh::morseTimingActive(main_radio);
if (!defer_io && uint32_t(now - last_battery) >= 30000) {
bool valid = false;
const uint8_t percent = readBatteryPercent(valid);
last_battery = millis();
recovery().updateBattery(last_battery, percent, valid);
}
recovery().heartbeat(millis());
if (!fallback_saved && uint32_t(now - last_save) >= 30000) {
if (!defer_io && !fallback_saved && uint32_t(now - last_save) >= 30000) {
last_save = now;
fallback_saved = save(requested[0], requested[1], recovery().fallbackMask());
}
+53
View File
@@ -269,6 +269,59 @@ int main(int argc,char** argv) {
fresh(board,radio,fs,ResetKind::Cold,true);advance(1000);fresh(board,radio,fs,ResetKind::Crash);counts(1,1);
for(unsigned n=0;n<301;++n)advance(1000);
fresh(board,radio,fs,ResetKind::Crash);counts(1,1);
} else if(test=="morse_battery") {
for(bool receive:{false,true}) {
fs=MockFS();fresh(board,radio,fs,ResetKind::Cold,true);
radio.morse_tx=!receive;radio.morse_rx=receive;
const auto measured=last_battery;
const unsigned reads=board.reads;
advance(30000);
assert(mock_now==measured+30000 && board.reads==reads);
assert(last_battery==measured);
assert(platformRecoveryRecord().heartbeatAt==mock_now);
assert(platformRecoveryRecord().activeDcdcMask==RECOVERY_DOMAINS);
radio.active=RegulatorMode::Ldo;
advance(1000);
assert(platformRecoveryRecord().activeDcdcMask==RECOVERY_MCU);
radio.active=RegulatorMode::Dcdc;
advance(30000);
assert(mock_now==measured+61000 && board.reads==reads);
assert(last_battery==measured);
assert(platformRecoveryRecord().heartbeatAt==mock_now);
assert(platformRecoveryRecord().activeDcdcMask==RECOVERY_DOMAINS);
fresh(board,radio,fs,ResetKind::Crash);counts(0,0);
radio.morse_tx=!receive;radio.morse_rx=receive;
const unsigned resumed_reads=board.reads;
advance(30000);
radio.morse_tx=radio.morse_rx=false;
// An unkeyed carrier does not defer optional work.
radio.carrier_wave=true;
const uint32_t before=mock_now;
advance(1000);
assert(mock_now==before+1010 && board.reads==resumed_reads+3);
assert(last_battery==mock_now);
}
} else if(test=="morse_save") {
for(bool receive:{false,true}) {
fs=MockFS();fresh(board,radio,fs,ResetKind::Cold,true);
for(unsigned n=1;n<=2;++n){advance(1000);fresh(board,radio,fs,ResetKind::Crash);counts(n,n);}
fs.fail_write=true;advance(1000);fresh(board,radio,fs,ResetKind::Crash);
assert(!fallback_saved);
const auto measured=last_battery,save_deadline=last_save;
const unsigned reads=board.reads,writes=fs.writes;
fs.fail_write=false;radio.morse_tx=!receive;radio.morse_rx=receive;
advance(30000);
assert(mock_now==measured+30000 && board.reads==reads && fs.writes==writes);
assert(last_battery==measured && last_save==save_deadline && !fallback_saved);
assert(platformRecoveryRecord().heartbeatAt==mock_now);
radio.morse_tx=radio.morse_rx=false;
advance(1000);
assert(board.reads==reads+3 && fs.writes>writes && fallback_saved);
assert(last_battery==mock_now && last_save==mock_now-10);
fresh(board,radio,fs,ResetKind::Cold,true);
assert(board.active==RegulatorMode::Ldo && radio.active==RegulatorMode::Ldo);
}
} else if(test=="retry") {
fresh(board,radio,fs,ResetKind::Cold,true);
for(unsigned n=1;n<=2;++n){advance(1000);fresh(board,radio,fs,ResetKind::Crash);counts(n,n);}
+16 -2
View File
@@ -86,7 +86,7 @@ struct MainBoard {
uint8_t modes=3;power::RegulatorMode default_mode=power::RegulatorMode::Dcdc;
power::RegulatorMode active=power::RegulatorMode::Default;
unsigned applies=0;bool dcdc_applied=false;uint16_t mv=3600;
std::vector<uint16_t> samples;unsigned sample_index=0;
std::vector<uint16_t> samples;unsigned sample_index=0,reads=0;
uint8_t mcuRegulatorModes() const { return modes; }
power::RegulatorMode defaultMcuRegulatorMode() const { return default_mode; }
power::RegulatorMode activeMcuRegulatorMode() const { return active; }
@@ -96,6 +96,7 @@ struct MainBoard {
dcdc_applied|=active==power::RegulatorMode::Dcdc;return true;
}
uint16_t getBattMilliVolts() {
++reads;
return samples.empty()?mv:samples[sample_index++%samples.size()];
}
void resetHardware() { active=power::RegulatorMode::Default;sample_index=0; }
@@ -104,6 +105,10 @@ struct Radio {
uint8_t modes=3;power::RegulatorMode default_mode=power::RegulatorMode::Dcdc;
power::RegulatorMode active=power::RegulatorMode::Default,boot=power::RegulatorMode::Default;
unsigned configures=0,inits=0;bool locked=false,dcdc_applied=false;
bool morse_tx=false,morse_rx=false,carrier_wave=false;
bool isMorseTxActive() const { return morse_tx; }
bool isMorseRxActive() const { return morse_rx; }
bool isCarrierWaveActive() const { return carrier_wave; }
uint8_t regulatorModes() const { return modes; }
power::RegulatorMode defaultRegulatorMode() const { return default_mode; }
power::RegulatorMode activeRegulatorMode() const { return active; }
@@ -115,7 +120,10 @@ struct Radio {
++inits;locked=true;active=boot==power::RegulatorMode::Default?default_mode:boot;
dcdc_applied|=active==power::RegulatorMode::Dcdc;
}
void resetHardware() { active=boot=power::RegulatorMode::Default;locked=false; }
void resetHardware() {
active=boot=power::RegulatorMode::Default;locked=false;
morse_tx=morse_rx=carrier_wave=false;
}
};
}
'''
@@ -239,6 +247,12 @@ class RegulatorSettingsTests(unittest.TestCase):
def test_failed_fallback_save_retries_and_then_survives_power_loss(self):
self.scenario('retry')
def test_morse_defers_battery_io_without_refreshing_stale_samples_or_stopping_heartbeats(self):
self.scenario('morse_battery')
def test_morse_defers_flash_retry_and_resumes_overdue_io_after_timing_ends(self):
self.scenario('morse_save')
def test_missing_primary_store_rejects_persistence_without_live_mode_changes(self):
self.scenario('no_store')