Repository navigation
Conversation
AutoDiscoverRTCClock::begin() wrote 35h and 37h with plain register writes, which set only the RAM mirror. With EERD = 0 the RV3028 reloads that mirror from EEPROM every day at midnight, so on a part still holding the factory EEPROM (BSM = 00, TCE = 0) the backup switchover and trickle charger turned off at the first midnight after boot. Writing 0xB4 to 37h also forced bit 7, the LSB of the factory frequency calibration. Follow the manual's procedure (RV-3028-C7 App Manual 4.6): wait for EEbusy, set EERD, Refresh, read-modify-write only CLKOE in 35h and TCE, BSM and TCR in 37h, Update only if something changed, Refresh and read back, then clear EERD. If the store fails, set the RAM mirror as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Melopero's readFromRegister() returns 0xFF on a failed transfer. Fed into the read-modify-write, one glitched read of 37h would have been stored in EEPROM, flipping EEOffset[0] and BSIE. Read and write through TwoWire directly and abort on any error. Set EERD before waiting for EEbusy, in the manual's order. The old 0xB4 also forced FEDE = 1, which the manual says should always be set. Include it in the 37h mask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Writing back the copy taken at the start could re-arm a countdown timer the chip ended by itself in between (TE clears when a single-shot countdown ends). Read Control1 again and clear only EERD, falling back to the copy if that read fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Handle failures from the fallback rv3028Write() operation.
Review effort: Lite
Findings: None
What changed in this PR
Updates RV3028 initialization to persist backup switchover and trickle-charger settings in EEPROM while preserving factory calibration bits.
Changes:
- Adds checked EEPROM refresh/update handling.
- Uses masked read-modify-write configuration.
- Retains a RAM-only fallback when EEPROM configuration fails.
| File | Summary |
|---|---|
src/helpers/AutoDiscoverRTCClock.cpp |
Implements persistent RV3028 configuration and fallback handling. The fallback does not check rv3028Write() failure. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
On the overview note "the fallback does not check
|
Manual 3.15.6: BSM must be 00 or 10 for any EEPROM read or write. Hold BSM = 00 in RAM during the procedure, compare each configuration byte against the EEPROM itself with single-byte reads, write only the bytes that differ, then Refresh, which reloads RAM from the EEPROM and restores the stored switchover mode. This replaces the whole-block Update, so the factory calibration in 36h is never rewritten. If the Refresh does not complete, the saved 37h is written back so the switchover is not left disabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delay() can return up to 1 ms early on the nRF52, ESP32 and STM32 cores, so delay(1) after a single-byte EEPROM read could poll EEbusy, and read EE_DATA, before the read had started. Wait one extra millisecond. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On nRF52, delay() can return more than 1 ms early. State only that it does not guarantee the full time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The EEPROM store parks BSM = 00 in RAM. If the store fails and a bus fault also defeats both the restore of 37h and the RAM fallback, the switchover stays off, until the next boot if EERD was left set. Record that and retry the RAM config from getCurrentTime() until it succeeds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If the store fails at boot but the bus recovers, the RAM fallback holds only until the next refresh. On a part still holding the factory EEPROM that turns the switchover off again at midnight, until the next boot. Retry the whole store from getCurrentTime(), at most once an hour since a failing store blocks while it polls EEbusy. The RAM config is still retried on every read while it is unconfirmed. Also state that rv3028StoreConfig() returns false when EERD could not be cleared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Disposition of the three overview notes on e16bf2e. None of them opened a thread.
|
Writing back the copy taken before the EEPROM procedure could set TE again after a single-shot countdown ended in between, re-arming it. Treat a failed re-read as a failed store instead: EERD stays set, which keeps the RAM config, and the hourly store retry clears it later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A password-locked chip, or a non-RTC device answering at 0x52, never completes the store, so the hourly retry ran forever, each run stalling getCurrentTime() and, on a non-RTC device, writing to it again. Stop after 3 retries per boot. Log a failed store, and a failed RAM fallback, with MESH_DEBUG_PRINTLN. Matches the ZephCore fix's retry cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed status read counted as busy, so on a dead bus each of the 100 polls ran into the I2C transfer timeout: about 5 s per wait on ESP32, whose Wire timeout is 50 ms. Fail the step at once instead; the capped retry tries again later. Found by cross-checking the ZephCore fix, where a 1000 ms transfer timeout makes the same wait last about 100 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by cross-reviewing this fix against the ZephCore one: - Skip the config when a time register shows a bit an RV3028 always reads as 0 (manual 3.2): a 24-series EEPROM answering at 0x52 would otherwise get Control1, EEPROM-command and config writes. - Drop the per-read RAM retry. On such an EEPROM it rewrote a byte on every clock read. Retry the whole configuration instead, at most 3 times per boot, every 10 minutes now that a failed attempt is cheap. - Read the config back after the closing Refresh even when nothing was written, so each boot confirms the switchover came back. - Wait for EEbusy, best effort, before re-enabling the switchover after a failed step, so a still-running EEPROM operation can finish. - Mask the table's values, and note in the header that begin() and getCurrentTime() can block briefly on the RTC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A single corrupted read that showed an always-zero bit set skipped the config of a real RV3028 for the whole boot. Count a register only when two reads agree. The retry comments promised a retry the cap can rule out, and the DSM comment above the impostor check described neither it nor the config call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes the failure-path items #35 listed as open, in the shape the MeshCore fix (ptr727/meshcore-dev-MeshCore#11, 037d493b) settled on, so the two behave alike: - EEbusy wait: a failed status read ends it at once. With this board's 1000 ms I2C transfer timeout, polling through failures could block about 100 s at boot or on the system work queue. - Read-back: runs after every Refresh, not only when a byte was written, so every boot confirms the stored switchover mode came back. - Fallback: when the Refresh did not complete, 37h is written as configured only after a best-effort wait for EEbusy, so an EEPROM operation still running normally finishes first. - Any failed store, including one whose Refresh completed but whose read-back did not match, now sets the config in RAM (masked read-modify-write), so switchover is on until the chip's next daily refresh, by which time a retry has normally stored it. - Retry: a delayed work item re-runs the store every 10 minutes, at most 3 times per boot, instead of only on a save, which a node without GPS, app or CLI syncs never makes. Each failed attempt logs whether the RAM fallback took. - Identity: an RV3028 always-zero bit must show on two reads before a chip at an RV3028 descriptor is refused, so one corrupted read does not cost a real RV3028 its adoption. The header and binding now state the two-read identity check, the RAM fallback and its lifetime, and that the power-on refresh is waited out before switchover is turned off rather than inside that window. Still open, as in MeshCore: a password-locked chip can report success with nothing written (manual 4.18.1), and the switchover-off window is estimated, not measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Upstream: PR meshcore-dev#3545 (from |
From review: "so these check" did not say what checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The impostor check passed a device whose reads failed, so a device at 0x52 that one read had ruled out could still get EEPROM writes if the confirming read failed. The config store now runs only when a read of the time registers shows no bit an RV3028 always reads as 0, with a second read taken when the first rules the device out. This matches the rule adopted for the ZephCore counterpart (liquidraver/ZephCore#98 review). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed identification read skipped the config for the whole boot, with no retry, leaving a factory-fresh part's switchover off where the old code at least set it in RAM. Identification now runs inside each configure attempt: a failed read writes nothing and is retried from getCurrentTime() like a failed store, while two reads that rule the device out end it. Also say what the check proves (the always-zero bits read as 0, not that the part is an RV3028), and drop the stale reason from the retry-cap comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A switchover to VBACKUP means VDD is unstable, and the EEPROM needs VDD (manual 4.6.8). Once the chip reads like an RV3028, BSF is cleared, the time registers are read again, and BSF must still read 0; otherwise the store waits for the retry. BSF is cleared first because a power cut before this boot leaves it set. This matches the ZephCore counterpart (liquidraver/ZephCore#98). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A switchover releases the bus, which reads as 1s, so two reads that rule the device out now end the config only if BSF reads 0 afterwards; otherwise identification is not settled and is retried. The deferral log and comment now name every cause, not only a switchover at boot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Manual 3.15.6 requires BSM = 00 or 10 for any EEPROM read or write, and an operation is still running while EEbusy reads 1. Neither the restore after a failed Refresh nor the RAM-only fallback now writes the switchover back unless EEbusy reads 0 within the bound; otherwise it stays off until the next refresh or retry restores it from the EEPROM. EERD is still cleared. This is the rule agreed for MeshCore, liquidraver/ZephCore#98 and zephyrproject-rtos/zephyr#121252. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A command whose write reported failure may still have been latched by the chip, and EEbusy read within a millisecond of it proves nothing (manual 4.6.7 waits 10 ms after a write). The restore now waits as long before checking. The comments no longer claim a refresh restores the switchover: on a part still holding the factory BSM = 00 only a later attempt does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The restore comment named only a retry's store; the RAM fallback in the same attempt, or any later attempt, can also bring it back, as rv3028Configure()'s comment already says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Iteration branch for #10. Keep it open; never merge it. The upstream PR comes from the clean single-commit branch
fix/rv3028-eeprom-config(f64cfc2c), which is byte-identical to this branch's head037d493b. Upstream takes PRs ondev.References used throughout:
devfad0ffb7, which this branch is based onf64cfc2c1.2.0The defect
AutoDiscoverRTCClock::begin()configured the RV3028 with two plain register writes (L38-L39):Melopero's
writeToRegister()is a plain I2C write, so these set only the RAM mirror of EEPROM-backed configuration registers:0xB4also forces 37h bit 7, which is EEOffset[0], the LSB of the factory frequency calibration (§3.15.6, p. 39).The mode itself is right. §7.3 (p. 105) specifies DSM with the trickle charger for a capacitor backup, and adds: "Power Management settings have to be stored in EEPROM for permanent configuration".
The fix
The config is stored in EEPROM following the manual, including §3.15.6 (p. 39): BSM must be 00 or 10 for any EEPROM read or write. Links are to
f64cfc2c.rv3028Impostor(), called frombegin(), skips the config if a time register (00h-06h) shows a bit an RV3028 always reads as 0 (§3.2, p. 12, and §3.3, p. 14), confirmed by a second read. A device at 0x52 that is not an RV3028, such as a 24-series EEPROM, then gets none of these writes. A failed read does not count.rv3028StoreConfig()sets EERD = 1, then waits for EEbusy = 0 (§4.6.7, p. 56). This also covers the ~66 ms POR refresh (§4.6.1, p. 54).rv3028_config, it reads the EEPROM copy with a single-byte read (EECMD 22h, §4.6.6, p. 55). It writes with a single-byte write (21h, §4.6.5, p. 55) only if the byte differs (rv3028EepromRead/Write()). The bits written:0xB4set it too.Waits and transfers:
rv3028EepromCommand()waits per §4.6.7 (p. 56): 1 ms after a read or Refresh, 10 ms after a write. Each gets an extra 1 ms, because Arduinodelay()can return early on some cores.rv3028EepromIdle()ends the EEbusy wait on the first failed status read, so a dead bus is not polled through each transfer's timeout.TwoWiredirectly (rv3028Read/Write()). Melopero'sreadFromRegister()ignores the I2C results and returns0xFFon a failed transfer, and a failed read must never be written back.Failure handling:
rv3028Configure()runs the store. If it fails, it sets the RAM mirror as before, after a best-effort EEbusy wait, with a checked read-modify-write (rv3028SetRam()). A RAM-only config lasts only until the next refresh, sogetCurrentTime()re-runs the configuration every 10 minutes, at most 3 times per boot (L160-L168). The cap is there because a password-locked chip, or a foreign device the identity check missed, never succeeds, and each attempt writes to it again. Failures are logged withMESH_DEBUG_PRINTLN.An already-configured chip costs two EEPROM byte reads and a Refresh per boot, and no EEPROM write.
Notes for review
drivers/mfd/mfd_rv3028.crun a Refresh and a whole-block Update (§4.6.3, p. 54) with BSM live. That was this branch's first version too,f0af008a. Testing on two boards found no spurious switchovers either way (see Testing), and the maintainer chose §3.15.6 on the manual:0x00also cleared CLKSY, PORIE and FD in RAM. Those now keep the chip's values (§3.15.4, p. 37). With CLKOE = 0 the pin is held low, so CLKSY and FD have no effect. PORIE stays at its factory 0.begin()still adopts anything that ACKs at 0x52 as the clock, reading it as the time and writing it on every sync, and does the same for the other three RTC addresses. That is tracked separately as AutoDiscoverRTCClock adopts any device that ACKs at an RTC address, then reads it as the clock and writes it on every sync #13, fixed in Adopt an RTC only if its time registers read like that chip (iteration branch) #14. Here only the new config writes are gated by the identity check, which can also miss a foreign device whose bytes happen to fit the masks.set24HourMode()still uses Melopero's unchecked read-modify-write on Control2.Testing
Builds:
RAK_4631_repeater(nRF52840),heltec_v4_repeater(ESP32-S3) andRAK_11310_repeater(RP2040), release andMESH_DEBUG=1, with no new warnings.Hardware setup:
C0, 37h =10, EEOffset[0] = 0.test/rv3028-diag(290cfc5b, never merged). Itsrv dumpprints RAM and EEPROM 35h/37h and status 0Eh. Itsrv setwrites the time registers, so the RTC could be stepped to 23:59:50 to pass midnight in seconds. Itsrv factoryrestores EEPROM 35h/37h to their factory values, keeping EEOffset[0].devfad0ffb700/B4: bit 7 forced to 1 against the chip's 0. EEPROM stillC0/10devfad0ffb7C0/10: switchover and trickle charger off, CLKOUT ondevfad0ffb711(PORF = 1)f0af008a(vendor Update sequence)40/34written once. Held past midnight. No write on reboot. Time kept to the second (the RTC advanced 4:47 against 4:47 wall clock), 0Eh =30(BSF = 1, PORF = 0)4f6de86240/34written once, RAM BSM restored. Held past midnight. No write on reboot. Time kept to the second (4:07 against 4:07), 0Eh =304f6de862with the124bdf41wait change6faac3bc037d493b, and so the byte-identicalf64cfc2c, differs from6faac3bconly by the identity check's confirming second read and comment fixes. On a real RV3028 that second read never runs, so it was not flashed separately.Spurious switchovers: status 0Eh was polled every 30 s for 31 minutes on the
f0af008abuild, with DSM and the trickle charger stored, across 3 reboots. BSF never set. Spot reads across the session's serial-DFU flashes showed it set only after the deliberate power cuts.Second implementation, same hardware. ZephCore carries the same §3.15.6 sequence in ptr727/liquidraver-ZephCore#35 (see its Testing section). On a second RAK4631 + RAK12002, with USB only and the power cuts also done by the maintainer:
Refs #10
🤖 Generated with Claude Code