Skip to content

rtc: select 24-hour mode before a time write, and take no time in 12-hour mode - #104

Closed
ptr727 wants to merge 1 commit into
liquidraver:devfrom
ptr727:rtc-24h-mode
Closed

ptr727 wants to merge 1 commit into
liquidraver:devfrom
ptr727:rtc-24h-mode

Conversation

@ptr727

@ptr727 ptr727 commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Separate from #103 (the RV3028 A0h year mark), and based on dev without it; the two touch different functions in ZephyrRTCDiscover.c. The Zephyr and MeshCore counterparts are zephyrproject-rtos/zephyr#121389 and meshcore-dev/MeshCore#3421.

Code links are permalinks: dev at a2e8d5f, this PR's commit 7286e50.

12-hour mode. Each project ensures the chip is in 24-hour mode before it takes or writes a time. How each project does it: Zephyr's driver owns the chip and writes Control 2 = 00h at every boot, which also disables interrupts it does not handle, then takes the converted time. MeshCore clears 12_24 inside its read bracket, keeping the other bits, and takes the time only from a BSF- and mode-checked re-read. ZephCore selects 24-hour mode at each time write, keeping the other bits, with a read-back, and refuses a time read in 12-hour mode until the next sync, because a boot-time write would sit outside its BSF bracket. MeshCore and ZephCore trust a Control 2 read for a write only if its always-zero RESET bit reads 0; Zephyr's interrupt-enable read-modify-writes do not yet check it (open follow-up). A single-byte write is stored whole when acknowledged; a switchover in the middle of a byte is not covered (5.10).

The defect

ZephCore assumed 24-hour mode. If other firmware had left a chip in 12-hour mode, the boot read masked hours with 0x3F and took any BCD 00-23. So 12 AM (12h) was restored as 12:xx, twelve hours ahead, PM 1-3 (21h-23h) as 21:00-23:00, eight hours ahead, and PM 4-12 gave no time. On the RV3028, whose mode bit is in Control 2, a save's 24-hour hours byte also meant a different hour to the chip. On the DS3231/DS1307 the hours byte holds the mode bit, so a save already selected 24-hour mode.

The fix

  • A new optional descriptor property. twelve-hour-bit = [register mask zero] names the bit that reads set in 12-hour mode, and that register's bits that always read 0:
    • RV3028: [10 02 01], Control 2 12_24, with RESET (bit 0) always reading 0 (Application Manual Rev 1.4, p. 24). Set in rtc-i2c.dtsi and the RAK4631 overlay.
    • DS3231: [02 40 00], hours bit 6 (DS3231 19-5170 Rev 10, p. 12). This node also covers the DS1307 (p. 8) and the DS3232.
    • Not set: the PCF8563, RX8130CE and RX8900/YSN8900, which count 24 hours only.
  • Build checks. RTC_H12_CHECK rejects a malformed property: 3 bytes, a nonzero mode bit, zero bits that don't overlap it, an in-block register that is the hours register, and an out-of-block zero byte that includes bit 0. I2C sends the MSB first, so a read cut short ends in 1s and always shows bit 0 set.
  • Read. rtc_12h(): while the bit reads set, or cannot be read cleanly, the boot takes no time and leaves the mode bit alone (12-hour mode — clock will be set on the next GPS/app/CLI sync). The boot log now promises a later sync only for the adopted write-back target.
  • Write. rtc_select_24h() runs before each time write. On the RV3028 it clears 12_24 by read-modify-write, refuses an unclean read, then reads the result back and writes it once more if it differs; at an rv3028-eeprom-config descriptor this sits inside the BSF bracket. The chip converts its Hours register itself (02h, p. 15). On the DS3231/DS1307 the write's own 24-hour hours byte clears bit 6 and re-enters the hours, as the DS1307 sheet requires.

Testing

RAK4631 + RAK12002, repeater debug builds. This PR's code is byte-identical to the fork iteration head (c18e1f0, on dev 81d98ab), where it was tested; rebased onto a2e8d5f since, with a clean build.

  • Acceptance case H (the shared matrix all three projects ran): a throwaway image set 21:30, then forced 12_24 (Hours 21h -> 29h). The exact c18e1f0 build then booted with 12-hour mode and took no time. A CLI time sync wrote the time with 24-hour mode selected, and the next boot logged RTC rtc-rv3028@52: restored 2026-10-07 00:24:37 UTC.
  • Earlier runs: two refused boots in a row before a sync; a save made in 12-hour mode cleared the bit (10h=00) and stored 24-hour hours.
  • Build checks: each of [10 02 00], [10 02 03], [10 02 80], [10 00 01] and [04 10 00] fails the build.
  • Other boards: a thinknode_m1 companion (DS3231 node, generic path) builds.
  • Not run on hardware: a DS3231 or DS1307 in 12-hour mode (no such board here).

Known point: the binding's rv3028-eeprom-config text says a write cut by a power loss reads as "time not yet set". Here, a cut after the 24-hour clear and before the year-00h byte leaves the chip's previous time, which it converted to 24-hour itself, so the next boot takes it. That time is correct, so the behaviour is benign. #103 rewrites that sentence.

Fork issue: ptr727#51. Iteration history and review: ptr727#52.

🤖 Generated with Claude Code

…hour mode

ZephCore assumed 24-hour mode. If other firmware had left a chip in
12-hour mode, the boot read masked hours with 0x3F and took any BCD
00-23: 12 AM (12h) was restored as 12:xx, twelve hours ahead, PM 1-3
(21h-23h) as 21:00-23:00, eight hours ahead, and PM 4-12 gave no time.
On the RV3028, whose mode bit is in Control 2, a save's 24-hour hours
byte also meant a different hour to the chip; on the DS3231/DS1307 the
hours byte holds the mode bit, so a save already selected 24-hour mode.

A new optional twelve-hour-bit descriptor property, [register mask
zero], names the bit that reads set in 12-hour mode and that register's
bits that always read 0: [10 02 01] on the RV3028 (Control 2 12_24;
RESET, bit 0, always reads 0; Application Manual Rev 1.4, p. 24) and
[02 40 00] on the DS3231 node, hours bit 6 (DS3231 19-5170 Rev 10,
p. 12; DS1307 p. 8). The PCF8563, RX8130CE and RX8900 count 24 hours
only. Build checks reject a malformed property: 3 bytes, a nonzero mode
bit, zero bits that don't overlap it, an in-block register that is the
hours register, and an out-of-block zero byte that includes bit 0,
since I2C sends the MSB first and a read cut short always shows bit 0.

While the bit reads set, or cannot be read cleanly, the boot takes no
time and leaves the mode bit alone. Each time write selects 24-hour mode first: on
the RV3028 a read-modify-write clears 12_24, refuses an unclean read,
and is read back and written once more if it differs, inside the BSF
bracket at an rv3028-eeprom-config descriptor; the chip converts Hours
itself (p. 15). On the DS3231/DS1307 the write's own 24-hour hours byte
clears bit 6. The boot log promises a later sync only for the adopted
write-back target.

Zephyr and MeshCore each ensure 24-hour mode before taking or writing a
time, each by its own mechanism; the three are kept consistent.

Link: zephyrproject-rtos/zephyr#121389
Link: meshcore-dev/MeshCore#3421

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings October 7, 2026 00:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Enforce one-hot validation for the configured mode-bit mask before approval.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds RTC 12-hour-mode detection and ensures time writes select 24-hour mode.

Changes:

  • Adds and validates the twelve-hour-bit devicetree property.
  • Configures RV3028 and DS3231-family RTC metadata.
  • Rejects 12-hour restores and verifies 24-hour selection before writes.
File Summary
zephcore/​dts/​bindings/​rtc/​zephcore,rtc-i2c.yaml Documents the new RTC property.
zephcore/​boards/​nrf52840/​rak4631/​board.overlay Configures RV3028 mode metadata.
zephcore/​boards/​common/​rtc-i2c.dtsi Adds RTC mode descriptors.
zephcore/​adapters/​clock/​ZephyrRTCDiscover.h Updates RTC API contracts.
zephcore/​adapters/​clock/​ZephyrRTCDiscover.c Implements mode detection and selection; the mode mask requires one-hot validation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +69 to +70
BUILD_ASSERT(DT_PROP_BY_IDX(node, twelve_hour_bit, 1) != 0, \
"twelve-hour-bit needs a mode bit"); \
@ptr727

ptr727 commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Replaced by #106, reworked as asked in #103 (comment): a one-time 12_24 clear at boot, as MeshCore does. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants