msp430fr5969-hal 0.2.0

Hardware abstraction layer (embedded-hal) for the TI MSP430FR5969 microcontroller
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
//! RTC_B real-time clock — a battery-/supercap-backed **calendar** in hardware.
//!
//! The RTC_B keeps wall-clock date and time (year, month, day, weekday, hour,
//! minute, second) in dedicated registers that advance once per second entirely
//! on their own — including through LPM3.5 deep sleep, on microamps — so the CPU
//! can be off for hours and still wake knowing the real time. It is the
//! *timekeeping* peer of [`crate::timer`] (which counts elapsed ticks, not
//! calendar time) and [`crate::delay`] (which just burns time).
//!
//! # The 32 768 Hz requirement
//!
//! The calendar advances by counting a **32 768 Hz** clock and dividing it by
//! 32 768 (a chain of prescalers) to get exactly one tick per second. On this
//! part that source is the **LFXT crystal on ACLK** — the datasheet is explicit
//! that "RTC is clocked by XT1". So the RTC only keeps correct time when ACLK is
//! the 32.768 kHz crystal: bring the clock tree up with
//! [`crate::clocks::configure_low_power`], which starts LFXT. If the crystal is
//! absent and ACLK falls back to the imprecise VLO, the seconds would be wrong —
//! so [`Rtc::new`] **refuses to start** unless `clocks.aclk()` is 32 768 Hz,
//! returning [`Error::ClockNot32768`]. (The default [`crate::clocks::configure`]
//! profile parks ACLK on the VLO, so it will *not* drive a correct RTC.)
//!
//! # Binary vs. BCD
//!
//! The calendar registers can hold their fields as **binary** or as packed
//! **BCD** (`RTCBCD`). This driver uses **binary** (`RTCBCD = 0`), so every field
//! is a plain integer: `second` is `0..=59`, `year` is the full number like
//! `2026`. The hour register is 24-hour (`0..=23`).
//!
//! # Reading without tearing
//!
//! The seven calendar registers update together once a second, so a naive read
//! can land mid-increment and return e.g. `01:59:60`. The hardware exposes
//! **`RTCRDY`**: it is 1 while the registers are static and safe to read, and
//! drops for one ACLK period around each one-second update. [`Rtc::now`] reads
//! only while `RTCRDY` stays set across the whole read, retrying otherwise, so it
//! never returns a torn value.
//!
//! # The alarm
//!
//! RTC_B has one hardware alarm: four byte-wide registers (minute, hour,
//! day-of-week, day-of-month), each with a per-field enable, compared against
//! the calendar at every minute increment — all enabled fields matching
//! latches `RTCAIFG` (once). The enabled subset picks the recurrence
//! (minute-only = hourly, minute+hour = daily, …). Program it with
//! [`Rtc::set_alarm`] ([`Alarm`]'s validation/encoding math lives in
//! `rtc_alarm.rs` and is host-tested), poll it with [`alarm_irq_pending`], or
//! get the `RTC` vector via [`Rtc::enable_alarm_interrupt`] ([`read_iv`]
//! returns `0x06`). Because the RTC counts LFXT on ACLK, an alarm wakes LPM3.
//!
//! # No `embedded-hal` trait?
//!
//! `embedded-hal` 1.0 has **no RTC/clock trait** — its modules are `digital`,
//! `i2c`, `spi`, `pwm`, and `delay` only. The ecosystem's de-facto abstraction
//! lives in the separate **`rtcc`** crate (`DateTimeAccess`/`Rtcc`), which is
//! built on `chrono`'s date types. We deliberately don't pull that in: `chrono`
//! is heavyweight for this part's 48 KB FRAM budget (the same reason
//! [`crate::serial`] avoids `core::fmt`). Instead this API mirrors the *shape*
//! `rtcc` would use — a plain [`DateTime`] value object and `set`/`now` — so a
//! `rtcc::Rtcc` impl could be added behind a feature later without changing it.
//!
//! # Example
//!
//! ```ignore
//! let clocks = hal::clocks::configure_low_power(p.cs); // ACLK = LFXT 32.768 kHz
//! hal::gpio::unlock_pins(&p.pmm);
//! let start = DateTime { year: 2026, month: 6, day: 27, weekday: 6,
//!                        hour: 9, minute: 30, second: 0 };
//! let rtc = Rtc::new(p.rtc_b_real_time_clock, &clocks, &start).unwrap();
//! loop {
//!     let t = rtc.now();           // never torn
//!     // print t.hour:t.minute:t.second ...
//! }
//! ```

use crate::clocks::Clocks;
use crate::pac;

// The alarm field-validation/register-encoding math lives in `rtc_alarm.rs`
// (dependency-free, host-tested in `unit_tests/`); re-exported here so
// consumers only ever see `hal::rtc::Alarm`.
pub use crate::rtc_alarm::{alarm_matches, Alarm, AlarmError};

// The prescaler-tick rate↔register math lives in `rtc_tick.rs` (dependency-
// free, host-tested in `unit_tests/`); re-exported here so consumers only
// ever see `hal::rtc::TickRate`.
pub use crate::rtc_tick::TickRate;

/// A wall-clock date and time, all fields **binary** (not BCD).
///
/// Field ranges follow the RTC_B calendar: `weekday` is `0..=6` with whatever
/// convention you assign (the hardware just counts 0→6→0), `hour` is 24-hour.
/// The driver writes these verbatim and does not validate them, so pass legal
/// values.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct DateTime {
    /// Full year, `0..=4095` (e.g. `2026`).
    pub year: u16,
    /// Month, `1..=12`.
    pub month: u8,
    /// Day of month, `1..=31`.
    pub day: u8,
    /// Day of week, `0..=6`.
    pub weekday: u8,
    /// Hour, `0..=23` (24-hour).
    pub hour: u8,
    /// Minute, `0..=59`.
    pub minute: u8,
    /// Second, `0..=59`.
    pub second: u8,
}

/// Why [`Rtc::new`] (or [`Rtc::attach`]) declined to hand over the clock.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Error {
    /// ACLK is not 32 768 Hz, so the calendar would not advance at one Hz.
    /// Configure the clock tree with [`crate::clocks::configure_low_power`] so
    /// ACLK is the LFXT crystal. See the module docs.
    ClockNot32768,
}

/// A periodic time event the RTC can interrupt on (`RTCTEV`).
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Event {
    /// At every change of minute (`00` seconds).
    MinuteChanged,
    /// At every change of hour (`00:00`).
    HourChanged,
    /// At midnight (`00:00:00`).
    Midnight,
    /// At noon (`12:00:00`).
    Noon,
}

/// The RTC_B calendar clock. Owns the PAC peripheral; configured once at
/// construction, then read with [`now`](Rtc::now).
pub struct Rtc {
    rtc: pac::RtcBRealTimeClock,
}

impl Rtc {
    /// Configure the RTC_B in binary calendar mode, set it to `init`, and start
    /// it.
    ///
    /// Returns [`Error::ClockNot32768`] unless ACLK is 32 768 Hz (see the module
    /// docs) — the one external precondition for correct timekeeping. The
    /// calendar is loaded while the counter is **held** (`RTCHOLD = 1`, which is
    /// also the reset state and the only time the registers are writable), then
    /// released so it begins advancing from `init`.
    pub fn new(
        rtc: pac::RtcBRealTimeClock,
        clocks: &Clocks,
        init: &DateTime,
    ) -> Result<Self, Error> {
        if clocks.aclk() != 32_768 {
            return Err(Error::ClockNot32768);
        }

        // Hold the counter and select binary mode while we load the calendar
        // (the time registers are writable only while held).
        rtc.rtcctl01()
            .modify(|_, w| w.rtchold().set_bit().rtcbcd().clear_bit());

        let this = Rtc { rtc };
        this.write_calendar(init);

        // Release: the calendar starts advancing from the loaded value.
        this.rtc.rtcctl01().modify(|_, w| w.rtchold().clear_bit());

        Ok(this)
    }

    /// Adopt the calendar that survived LPM3.5 and set it running again — the
    /// wake-path counterpart of [`Rtc::new`].
    ///
    /// The RTC_B lives in its own always-powered domain, so it keeps counting
    /// straight through LPM3.5's regulator-off sleep — but the wake is a BOR
    /// reset, so the rebooted program holds no [`Rtc`] value, and [`Rtc::new`]
    /// would destroy exactly what survived (it rewrites the calendar).
    /// `attach` adopts it instead, honoring what the wake actually does to the
    /// module (hardware-observed on this part, 2026-07-04): the **calendar
    /// contents survive frozen** — the wake re-asserts `RTCHOLD`, halting the
    /// count at the wake moment — the wake's interrupt flag (e.g. `RTCTEVIFG`,
    /// see [`event_irq_pending`]) stays latched as evidence, and the interrupt
    /// *enable* bits come back cleared. So `attach` verifies ACLK is 32 768 Hz
    /// again (re-run [`crate::clocks::configure_low_power`] first — the
    /// crystal never stopped, its pins being latched, so it settles at once)
    /// and **releases `RTCHOLD`** so time resumes.
    ///
    /// Two consequences to plan around:
    ///
    /// - The calendar stands still from the wake until this call — attach
    ///   early in boot, or each wake silently loses wall-clock time (up to
    ///   a second even when prompt, since the sub-second prescaler state is
    ///   not recoverable).
    /// - Re-arm any wake interrupt (e.g. [`enable_event_interrupt`]
    ///   (Rtc::enable_event_interrupt)) before the next LPM3.5 entry; the
    ///   enables do not survive the wake.
    ///
    /// Calling this on a genuinely cold RTC (nothing survived) is not
    /// detectable from the registers — `RTCHOLD` reads 1 in both cases — and
    /// would set a reset-value calendar running. Gate the call on the reset
    /// reason instead: only attach when `SYSRSTIV` reported
    /// [`Lpm5WakeUp`](crate::sys::ResetReason::Lpm5WakeUp).
    pub fn attach(rtc: pac::RtcBRealTimeClock, clocks: &Clocks) -> Result<Self, Error> {
        if clocks.aclk() != 32_768 {
            return Err(Error::ClockNot32768);
        }
        // Release the hold the wake re-asserted; the calendar resumes from
        // the preserved (frozen-at-wake) value. Binary/BCD mode survived the
        // wake, so unlike `new` there is nothing else to program.
        rtc.rtcctl01().modify(|_, w| w.rtchold().clear_bit());
        Ok(Rtc { rtc })
    }

    /// Overwrite the calendar with `dt` (e.g. to set the time from an external
    /// source). Holds the counter for the update and restarts it afterward.
    pub fn set(&mut self, dt: &DateTime) {
        self.rtc.rtcctl01().modify(|_, w| w.rtchold().set_bit());
        self.write_calendar(dt);
        self.rtc.rtcctl01().modify(|_, w| w.rtchold().clear_bit());
    }

    /// Read the current date and time, never torn by the one-second update.
    ///
    /// Spins until `RTCRDY` is set, takes one snapshot, and confirms `RTCRDY` is
    /// still set — guaranteeing no one-second increment occurred mid-read (see
    /// the module docs). At 32 768 Hz the unsafe window is ~30 µs per second, so
    /// this almost always reads on the first try.
    pub fn now(&self) -> DateTime {
        loop {
            if self.ready() {
                let snapshot = self.read_calendar();
                if self.ready() {
                    return snapshot;
                }
            }
        }
    }

    /// Whether the calendar registers are currently static and safe to read
    /// (`RTCRDY`).
    pub fn ready(&self) -> bool {
        self.rtc.rtcctl01().read().rtcrdy().bit_is_set()
    }

    /// Enable the **time-event** interrupt for `event` (`RTCTEV` + `RTCTEVIE`).
    ///
    /// Fires the single **`RTC`** interrupt vector (`pac::Interrupt::RTC`) at the
    /// chosen calendar boundary. The application defines that ISR, enables
    /// interrupts globally (GIE), and clears the flag — reading `RTCIV`
    /// ([`read_iv`]) returns the source and auto-clears it, or call
    /// [`clear_event_irq`].
    pub fn enable_event_interrupt(&self, event: Event) {
        self.rtc.rtcctl01().modify(|_, w| {
            match event {
                Event::MinuteChanged => w.rtctev().rtctev_0(),
                Event::HourChanged => w.rtctev().rtctev_1(),
                Event::Midnight => w.rtctev().rtctev_2(),
                Event::Noon => w.rtctev().rtctev_3(),
            };
            w.rtctevie().set_bit()
        });
    }

    /// Enable the **once-per-second** interrupt (`RTCRDYIE`), which fires on the
    /// `RTC` vector each time the calendar finishes an update. Useful for a
    /// 1 Hz tick (clock display, seconds blink).
    pub fn enable_second_interrupt(&self) {
        self.rtc.rtcctl01().modify(|_, w| w.rtcrdyie().set_bit());
    }

    /// Program the **alarm**: `RTCAIFG` latches when the calendar reaches an
    /// instant where every enabled field of `alarm` matches (see [`Alarm`] —
    /// the enabled subset picks the recurrence: minute-only = hourly,
    /// minute+hour = daily, and so on).
    ///
    /// Follows SLAU367P's reprogramming procedure: the alarm interrupt enable
    /// is cleared first (so no ISR can fire off a half-written alarm), the
    /// four alarm registers are written, and `RTCAIFG` is cleared **last** —
    /// a mix of old and new fields can spuriously match during the update (the
    /// comparison runs at each minute increment), and the trailing clear
    /// scrubs any such latch. Consequence: the alarm is armed for matches
    /// *after* this call returns; poll [`alarm_irq_pending`] or call
    /// [`enable_alarm_interrupt`](Rtc::enable_alarm_interrupt) to be notified.
    /// The calendar keeps running throughout (the alarm registers are not
    /// behind `RTCHOLD`).
    ///
    /// Returns the field that was out of range ([`AlarmError`]) with the
    /// hardware untouched; an alarm with no enabled field is rejected as
    /// [`AlarmError::NoFieldEnabled`] since it could never fire.
    pub fn set_alarm(&mut self, alarm: &Alarm) -> Result<(), AlarmError> {
        let regs = crate::rtc_alarm::encode_alarm(alarm)?;
        self.rtc.rtcctl01().modify(|_, w| w.rtcaie().clear_bit());
        self.rtc.rtcamin().write(|w| unsafe { w.bits(regs.minute) });
        self.rtc.rtcahour().write(|w| unsafe { w.bits(regs.hour) });
        self.rtc.rtcadow().write(|w| unsafe { w.bits(regs.weekday) });
        self.rtc.rtcaday().write(|w| unsafe { w.bits(regs.day) });
        self.rtc.rtcctl01().modify(|_, w| w.rtcaifg().clear_bit());
        Ok(())
    }

    /// Disarm the alarm entirely: clears the interrupt enable, every field's
    /// `AE` bit (so nothing is compared anymore), and a pending `RTCAIFG`.
    pub fn disable_alarm(&mut self) {
        self.rtc.rtcctl01().modify(|_, w| w.rtcaie().clear_bit());
        self.rtc.rtcamin().write(|w| unsafe { w.bits(0) });
        self.rtc.rtcahour().write(|w| unsafe { w.bits(0) });
        self.rtc.rtcadow().write(|w| unsafe { w.bits(0) });
        self.rtc.rtcaday().write(|w| unsafe { w.bits(0) });
        self.rtc.rtcctl01().modify(|_, w| w.rtcaifg().clear_bit());
    }

    /// Enable the **alarm** interrupt (`RTCAIE`): a programmed alarm match
    /// ([`set_alarm`](Rtc::set_alarm)) fires the single `RTC` vector, where
    /// [`read_iv`] returns `0x06` (hardware-observed 2026-07-07 — one slot
    /// below where a casual reading of the generic RTC_B chapter puts it; see
    /// [`read_iv`] for the full table). An already-latched `RTCAIFG` fires the ISR
    /// immediately on enable — call this right after `set_alarm` (which
    /// leaves the flag clean) to only hear about future matches. The RTC
    /// keeps its clock (LFXT on ACLK) in LPM3, so an alarm wakes LPM3 given a
    /// `#[interrupt(wake_cpu)]` handler.
    pub fn enable_alarm_interrupt(&self) {
        self.rtc.rtcctl01().modify(|_, w| w.rtcaie().set_bit());
    }

    /// Enable a **periodic prescaler tick** interrupt at `rate` — a
    /// sub-second, crystal-accurate heartbeat from the RTC's own divider
    /// chain, at sixteen power-of-two rates from 16.384 kHz to 0.5 Hz (see
    /// [`TickRate`]). Fires the single `RTC` vector; [`read_iv`] returns
    /// `0x08` for an RT0PS rate (the first eight) or `0x0A` for an RT1PS
    /// rate (the last eight). The prescalers ride ACLK's LFXT like the
    /// calendar itself, so a tick **wakes LPM3** given a
    /// `#[interrupt(wake_cpu)]` handler — the classic clocked-sleep system
    /// tick without spending a Timer_A block.
    ///
    /// The two prescalers are independent: one rate from each bank can run
    /// concurrently (a second call in the *same* bank replaces that bank's
    /// rate). Programming order per SLAU367's arming discipline: the
    /// interval is selected and the stale flag cleared in the same register
    /// write that sets the enable, so a pending old-rate tick cannot fire
    /// the new-rate ISR.
    ///
    /// **Fast rates vs. slow MCLK**: at 16.384 kHz a tick arrives every
    /// 61 µs — an ISR slower than that starves `main` forever (the capture
    /// module's ACLK lesson). One-shot wake handlers must disarm *inside
    /// the ISR* via [`isr_disable_tick_interrupts`].
    pub fn enable_tick_interrupt(&self, rate: TickRate) {
        if rate.uses_rt1ps() {
            self.rtc.rtcps1ctl().modify(|_, w| {
                w.rt1ip().set(rate.ip_code());
                w.rt1psifg().clear_bit();
                w.rt1psie().set_bit()
            });
        } else {
            self.rtc.rtcps0ctl().modify(|_, w| {
                w.rt0ip().set(rate.ip_code());
                w.rt0psifg().clear_bit();
                w.rt0psie().set_bit()
            });
        }
    }

    /// Disable the prescaler-tick interrupt serving `rate`'s **bank** (any
    /// RT0PS rate disables the RT0PS tick, any RT1PS rate the RT1PS one)
    /// and clear its pending flag. The other bank is untouched.
    pub fn disable_tick_interrupt(&self, rate: TickRate) {
        if rate.uses_rt1ps() {
            self.rtc
                .rtcps1ctl()
                .modify(|_, w| w.rt1psie().clear_bit().rt1psifg().clear_bit());
        } else {
            self.rtc
                .rtcps0ctl()
                .modify(|_, w| w.rt0psie().clear_bit().rt0psifg().clear_bit());
        }
    }

    /// Release the underlying peripheral. The calendar is left running.
    pub fn free(self) -> pac::RtcBRealTimeClock {
        self.rtc
    }

    // -- internals --

    /// Load all seven calendar registers. Caller must hold the counter
    /// (`RTCHOLD = 1`). Binary mode: every field is written verbatim; the year
    /// register is 16-bit, the rest 8-bit.
    fn write_calendar(&self, dt: &DateTime) {
        // SAFETY: `bits` on these calendar registers is an unsafe writer (the
        // upper bits are reserved); the values are plain binary fields.
        self.rtc.rtcsec().write(|w| unsafe { w.bits(dt.second) });
        self.rtc.rtcmin().write(|w| unsafe { w.bits(dt.minute) });
        self.rtc.rtchour().write(|w| unsafe { w.bits(dt.hour) });
        self.rtc.rtcdow().write(|w| unsafe { w.bits(dt.weekday) });
        self.rtc.rtcday().write(|w| unsafe { w.bits(dt.day) });
        self.rtc.rtcmon().write(|w| unsafe { w.bits(dt.month) });
        self.rtc.rtcyear().write(|w| unsafe { w.bits(dt.year) });
    }

    /// One raw snapshot of the calendar registers (may be torn — callers go
    /// through [`now`](Rtc::now), which guards it with `RTCRDY`).
    fn read_calendar(&self) -> DateTime {
        DateTime {
            second: self.rtc.rtcsec().read().bits(),
            minute: self.rtc.rtcmin().read().bits(),
            hour: self.rtc.rtchour().read().bits(),
            weekday: self.rtc.rtcdow().read().bits(),
            day: self.rtc.rtcday().read().bits(),
            month: self.rtc.rtcmon().read().bits(),
            year: self.rtc.rtcyear().read().bits(),
        }
    }
}

/// Read the RTC interrupt vector register `RTCIV` from inside the `RTC` ISR.
///
/// The RTC's several sources share one vector; reading `RTCIV` returns a small
/// number identifying the highest-priority pending source and **auto-clears that
/// flag**. Provided as a free function because the ISR does not own the [`Rtc`].
/// Values (TI's `msp430fr5969.h` `RTCIV_*` constants; the alarm slot
/// hardware-observed 2026-07-07): `0x02` = `RTCRDYIFG` (per-second), `0x04` =
/// `RTCTEVIFG` (time event), `0x06` = `RTCAIFG` (alarm), `0x08`/`0x0A` =
/// prescaler 0/1, `0x0C` = oscillator fault (the *lowest*-priority slot, not
/// the highest).
pub fn read_iv() -> u16 {
    // SAFETY: a stolen handle reading the self-clearing RTCIV — the architected
    // way to service the shared RTC vector; touches no state the Rtc owner writes.
    let rtc = unsafe { pac::RtcBRealTimeClock::steal() };
    rtc.rtciv().read().bits()
}

/// Is the time-event interrupt flag (`RTCTEVIFG`) latched?
///
/// The direct-flag sibling of [`read_iv`], for the one situation where the IV
/// register cannot answer: after an **LPM3.5 wake**, the event flag that woke
/// the part is still latched but its *enable* bit was cleared by the wake —
/// and `RTCIV` only reports enabled sources, so it reads 0 (hardware-observed
/// on this part). Non-destructive: reading the flag does not clear it (unlike
/// a `read_iv` consume), so it can be checked before deciding how to handle
/// the wake; clear it with [`clear_event_irq`] when done.
pub fn event_irq_pending() -> bool {
    // SAFETY: a stolen handle reading one interrupt flag — no state written.
    let rtc = unsafe { pac::RtcBRealTimeClock::steal() };
    rtc.rtcctl01().read().rtctevifg().bit_is_set()
}

/// Clear the time-event interrupt flag (`RTCTEVIFG`) from the `RTC` ISR, for
/// handlers that do not read [`read_iv`].
pub fn clear_event_irq() {
    // SAFETY: a stolen handle clearing only RTCTEVIFG via read-modify-write —
    // disjoint from the calendar/control bits the Rtc owner manages.
    let rtc = unsafe { pac::RtcBRealTimeClock::steal() };
    rtc.rtcctl01().modify(|_, w| w.rtctevifg().clear_bit());
}

/// Is the alarm interrupt flag (`RTCAIFG`) latched?
///
/// The alarm sibling of [`event_irq_pending`], serving the same two callers:
/// polled use (an alarm armed by [`Rtc::set_alarm`] with the interrupt left
/// off — the flag latches regardless of `RTCAIE`), and the post-LPM3.5-wake
/// check, where the flag that woke the part is still latched but its enable
/// was cleared by the wake so `RTCIV` reads 0. Non-destructive; clear with
/// [`clear_alarm_irq`].
pub fn alarm_irq_pending() -> bool {
    // SAFETY: a stolen handle reading one interrupt flag — no state written.
    let rtc = unsafe { pac::RtcBRealTimeClock::steal() };
    rtc.rtcctl01().read().rtcaifg().bit_is_set()
}

/// Clear the alarm interrupt flag (`RTCAIFG`) — for polled alarm consumers
/// and for `RTC` ISR handlers that do not read [`read_iv`]. The alarm stays
/// armed (the `AE` bits are untouched) and will latch again at the next
/// matching minute increment.
pub fn clear_alarm_irq() {
    // SAFETY: a stolen handle clearing only RTCAIFG via read-modify-write —
    // disjoint from the calendar/control bits the Rtc owner manages.
    let rtc = unsafe { pac::RtcBRealTimeClock::steal() };
    rtc.rtcctl01().modify(|_, w| w.rtcaifg().clear_bit());
}

/// ISR-side: disarm **both** prescaler-tick interrupts (`RT0PSIE` and
/// `RT1PSIE`, pending flags cleared with them).
///
/// **Required inside any tick handler meant to fire once** — the prescaler
/// re-latches its flag every period, and at the fast RT0PS rates (61 µs at
/// 16.384 kHz) a keep-armed handler re-enters faster than a 1 MHz MCLK can
/// leave it, starving `main` forever (the capture module's ACLK lesson).
/// Sound from the ISR because the tick enables live in the prescaler
/// control registers, which the owning [`Rtc`] only touches through
/// [`enable_tick_interrupt`](Rtc::enable_tick_interrupt) /
/// [`disable_tick_interrupt`](Rtc::disable_tick_interrupt) from thread mode
/// — don't call those while a tick ISR that also disarms may be in flight.
pub fn isr_disable_tick_interrupts() {
    // SAFETY: a stolen handle clearing enable/flag bits in the two
    // prescaler control registers — disjoint from the calendar/control bits
    // the Rtc owner manages between these calls.
    let rtc = unsafe { pac::RtcBRealTimeClock::steal() };
    rtc.rtcps0ctl()
        .modify(|_, w| w.rt0psie().clear_bit().rt0psifg().clear_bit());
    rtc.rtcps1ctl()
        .modify(|_, w| w.rt1psie().clear_bit().rt1psifg().clear_bit());
}