Conversation
auto_power_on not persisting across unexpected power loss: - Add persist_bypass flag in ProcessI2CPendingWrite; REG_AUTO_POWER_ON writes now use RequestFlashSave(bypass=1) for immediate flash commit, bypassing the rate-limit window. Empty voltage corrupted to near-full after I2C failure: - Add EMPTY_LEARN_MIN_MEANINGFUL_DISCHARGE_MV (300 mV) guard in EmptyLearn_CommitTrackedMin; rejects commits where the tracked minimum is within 300 mV of full voltage (genuine discharge required). Empty learning hardening: - Add 3 s load-off debounce (EMPTY_LEARN_LOAD_OFF_HOLD_TICKS) to avoid single-sample current glitches triggering early commit. - Add 30 s brownout debounce (EMPTY_LEARN_BROWNOUT_HOLD_TICKS) to distinguish I2C polling gaps from genuine brownouts; cancelled if validity restores before hold expires. - Exclude FORCED_OFF_WINDOW from empty tracking (calibration VBAT dip). - Add reset_min parameter to EmptyLearn_CommitTrackedMin: load-off and protection paths reset the tracker; brownout path preserves it. Full learning detection refactor: - Add independent taper path for battery_full (OR semantics with plateau path); accepts bidirectional current (+/-I_TAPER_MA). - Add PLATEAU_MEAN_MIN_MV (4150 mV) gate to prevent mid-voltage plateau misclassification. - Remove PLATEAU_MIN_CHANGE_MV threshold; EMA update always applies. - Remove PLATEAU_POWER_STABLE_SEC guard and plateau_last_power_state. - Fix FORCED_OFF_WINDOW handling: charger_connected uses != ABSENT so plateau session survives calibration windows; session reset only on ABSENT -> non-ABSENT transition. Docs: update spec for new power-state transitions, button FSM, plateau/taper OR semantics, and standalone INA219 note.
The reset cause capture implemented in Phase 3 always returned 0x00 because the STM32 bootloader writes RMVF=1 to RCC_CSR before jumping to the application. Factory test selectors 0x08 (this boot's CSR) and 0x09 (last persisted cause) were non-functional from the start. Removed: - boot_reset_csr, last_persisted_reset_cause, last_persisted_reset_seq static variables and RCC_CSR_RESET_FLAGS_* defines from main.c - Factory test cases 0x08/0x09 from StateToRegisterBuffer() - last_reset_cause/last_reset_seq from Flash_FillRecordFromState() and Flash_Load(); replaced with reserved_padding2[4] in the flash struct to preserve layout compatibility with existing flash records The RMVF clear at boot is retained for hygiene (prevents flag accumulation across resets). Spec and debugging guide updated to document that reset cause is not available and why.
After an MCU reset with the I2C peripheral already enabled, the ADDR flag can be set and SCL held low indefinitely if the NVIC interrupt has not yet been re-armed. The main loop had no mechanism to detect or recover from this without a full power cycle. Added: - I2C1_IsAddrFlagSet() in I2C_Slave.c/.h: reads the ADDR hardware flag directly (atomic register read, safe from main-loop context) - I2C_STUCK_ADDR_RECOVERY_SEC (5) constant and i2c_stuck_addr_sec counter in main.c - tick_1s watchdog block: increments counter while ADDR is set, resets on any clear second; on >= 5 consecutive seconds calls MX_I2C1_Slave_Init() to disable the peripheral (releasing SCL), clear all flags, and reinitialise the slave MX_I2C1_Slave_Init() already performs LL_I2C_Disable → ClearAllFlags → reconfigure → LL_I2C_Enable → NVIC_EnableIRQ, which is the correct sequence to release a clock-stretched bus.
After an MCU crash or IWDG reset, battery_percent calculated as 0% because last_true_vbat_mv was lost and UpdateBatteryPercentage() had nothing to work with while the charger was influencing VBAT. This prevented CheckPowerOnConditions() from ever transitioning to LOAD_ON_DELAY in a crash-reset loop, leaving the RPi permanently unpowered even with a charged battery and charger connected. Persist last_true_vbat_mv and last_true_vbat_age_sec in the flash record (version 2 → 3). On load, restore them into authoritative state so that IsTrueVbatUsableForDecision() — which now replaces the raw IsTrueVbatSampleFresh() call in CheckPowerOnConditions(), UpdateBatteryPercentage(), and the button short-press handler — can accept a persisted sample within TRUE_VBAT_MAX_AGE_SEC (10 min) as valid. Minimum saved age is clamped to 1 s to distinguish a fresh sample from the sentinel value 0 = "no data". Age is accumulated correctly across multiple save/load cycles via cumulative_runtime_sec_at_flash_load.
After I2C1_ExitMasterWindow() restores slave config (CR1 write sets PE=1), the peripheral immediately begins address-matching. If the RPi master begins a transaction before NVIC_EnableIRQ(), ADDR is latched and SCL is held low by clock-stretching with no ISR to release it. Fix: add I2C1_BusIdleBrief() which samples SCL (PA9) and SDA (PA10) twice with a 1ms gap. If either line is low, I2C1_BusRecovery() is called to release the bus before the NVIC is re-enabled. The 1ms window also catches a mid-transaction ADDR latch that set PE=1 triggered: SCL will read low on the second sample, forcing recovery. I2C1_BusRecovery() already calls LL_I2C_Disable() internally, so no redundant pre-call is needed. LL_mDelay() is already in the binary via I2C1_BusRecovery(), adding only a call site.
CheckPowerOnConditions() was only called from the main loop when auto_power_on != 0. The PROTECTION_LATCHED exit logic lived inside that function after the !auto_power_on early return, so it was never reached when auto_power_on = 0 — the state machine stalled permanently after a protection event. Fix: move the PROTECTION_LATCHED block to the top of CheckPowerOnConditions(), before the early return. Remove the call-site guard so the function runs unconditionally each loop. When latched and recovered, the device now always clears to RPI_OFF (or LOAD_ON_DELAY if auto_power_on = 1). The !auto_power_on early return continues to block normal auto power-on logic.
Extend the short-press outer guard to include POWER_STATE_PROTECTION_LATCHED so a button press can initiate power-on when the battery has recovered above the protection voltage. Remove the dead inner PROTECTION_LATCHED check (unreachable behind the RPI_OFF-only outer guard) and the redundant re-enable block. The shared charger and battery-voltage checks produce the correct allow/deny result for both states without state-specific logic, reducing flash consumption.
… threshold When battery_ok fails during a LOAD_ON_DELAY countdown, the cancellation now transitions to PROTECTION_LATCHED (instead of RPI_OFF) if the voltage is also below the protection threshold. This prevents an immediate re-entry into LOAD_ON_DELAY on the next CheckPowerOnConditions() call if the voltage transiently reads above protection again.
Writing to REG_LOAD_ON_DELAY_L (0x2C–0x2D) only updated load_on_delay_config_sec. If a LOAD_ON_DELAY countdown was already in progress, the running countdown was unaffected and a register readback would return the old remaining value, not the newly written one. Split the validation block so that the config update (with its persist flag) is guarded on the value changing, while a separate check applies the new value to load_on_delay_remaining_sec whenever power_state == POWER_STATE_LOAD_ON_DELAY. A write now takes effect on the active countdown immediately.
I2C1_GuardWindowReady() used a 16-bit 1 MHz timer to guard against
MCU master-window collisions with an active RPi. With no RPi present,
the timestamps never updated and the 16-bit counter wrapped every
~65 ms, making the comparisons unreliable and permanently blocking
INA219 reads. This caused output_current_valid and
battery_current_valid to age out, stalling taper detection, empty-
voltage learning, and battery-percent updates during standalone
(no-RPi) operation.
Fix: track last I2C master activity at tick resolution (1 s). If no
STOP or ADDR event has been observed since boot, or none for more than
5 s (I2C_MASTER_IDLE_ALLOW_READ_TICKS), the guard bypasses the 50 ms
µs check and allows INA219 reads after confirming the bus lines are
high and stable. The 50 ms µs guard is retained for the window when
a master has been recently active, where it remains effective.
The first_call tracking variable was removed: initialising
last_seen_{stop,addr}_us to 0 is sufficient — if the hardware
timestamps are also 0 at boot (no events yet), last_i2c_activity_tick
stays 0 and the idle path opens immediately; if they differ, a
conservative 5 s delay before idle reads is harmless given the INA
probe timer is gated ~2 minutes out.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Battery Full and Empty where not functioning as expected. Fix this along with other issues discovered while testing Full and Empty values and conditions.