fix(rtc): disable compare interrupt instead of stopping the RTC counter#992
fix(rtc): disable compare interrupt instead of stopping the RTC counter#992QuartzShard wants to merge 2 commits intoatsamd-rs:masterfrom
Conversation
disable_timer()/enable_timer() were calling RtcMode::disable()/enable(), which clears/sets CTRLA.ENABLE — stopping and restarting the entire RTC peripheral. The rtic-time TimerQueue calls disable_timer() whenever the queue is empty, so any period with no pending delays freezes the hardware counter and permanently loses wall-clock time. This violates the TimerQueueBackend trait contract, which states that enabling/disabling must propagate to now() so instants remain valid. Fix: toggle the compare match interrupt (INTENCLR/INTENSET) instead of the peripheral enable bit. The RTC counter keeps running, preserving monotonicity. Only the Compare0 interrupt used for RTIC task wakeups is affected — half-period and overflow interrupts remain enabled.
|
Looks good! Not quite sure what changed that started causing the D2/1x chip pipelines to fail, seems to be nothing from this PR - I'll do some testing on my D21 board to verify its working there as well, though, I don't see any reason why it shouldn't |
|
Ran into this one while trying to do thermal control and wondering why the logged timestamps were slipping behind the real time passed over ~20 minutes, turns out I was losing ~5 seconds every 20-or-so while there was no actively |
Looks like a new lint on nightly, it's not in code I changed as far as I can see? |
|
Ran this branch for a couple days on my E51 and D21 boards (Didn't compare to systick though), just a simple counter. Over 24 hours the time loss appears to be less than 1 second (Not measurable to me), so I'd say this is fine to be merged |
Summary
disable_timer()/enable_timer()in both RTC monotonic backends (__internal_basic_backendand__internal_half_period_counting_backend) callRtcMode::disable()/enable(), which clears/setsCTRLA.ENABLE— stopping and restarting the entire RTC peripheral. Thertic-timeTimerQueuecallsdisable_timer()whenever the queue is empty, so any period with no pending delays freezes the hardware counter and permanently loses wall-clock time.This violates the
TimerQueueBackendtrait contract:The RTC is a low-power peripheral (~1 µA in standby per the datasheet) driven by an already-running 32 kHz crystal, so leaving it counting continuously is negligible. For a higher-power timer driven by a faster oscillator, it might make sense to compensate now() for the time elapsed while disabled — but for the RTC, keeping it running seems to me the straightforward approach.
Fix
Toggle only the compare match interrupt (
INTENCLR/INTENSETfor$rtic_int, i.e. Compare0) instead of the peripheral enable bit. The RTC counter keeps running so thatnow()returns correct values. Half-period and overflow interrupts are unaffected — they are enabled once in_start()and not touched by this change.Reproduction
Minimal reproduction crate with side-by-side RTC vs SysTick comparison:
https://github.com/QuartzShard/rtc_mono_bug_demo
Without fix — RTC reports 0 ms for a 5 s busy-wait (counter frozen):
With fix — RTC tracks correctly:
Testing
Checklist
#[allow]certain lints where reasonable, but ideally justify those with a short comment.