gd32e2_hal/watchdog/wwdgt.rs
1//! Window watchdog timer.
2//!
3//! Resets the chip for feeding it too early as well as too late. The early
4//! bound is the point of this watchdog: code that has run away into a loop
5//! still calling `feed` keeps a plain watchdog happy, but breaks the pace this
6//! one checks.
7//!
8//! Clocked from `PCLK1`, so unlike [`Fwdgt`](crate::watchdog::Fwdgt) its timeout
9//! moves with the bus clock and spans tens of milliseconds rather than seconds.
10
11use crate::pac;
12use crate::rcu::{Enable, Rcu, Reset};
13
14/// Largest period, in counter ticks: the timeout is counted by `CNT[5:0]`.
15const TICKS_MAX: u8 = 0x3F;
16/// `CNT[6]`, standing for "the counter is still alive" — the chip resets the
17/// moment it goes low, so it sits above the timeout rather than inside it.
18const ALIVE_BIT: u8 = 1 << 6;
19
20/// Prescaler on the counter clock, named after the `PSC` field it writes.
21///
22/// The names count that field alone: the counter runs at `PCLK1 / 4096 / N`, so
23/// even [`Div1`](Self::Div1) is already `PCLK1` divided by 4096.
24#[derive(Clone, Copy, PartialEq, Eq)]
25#[cfg_attr(feature = "defmt", derive(defmt::Format))]
26#[allow(missing_docs)]
27pub enum WwdgtPsc {
28 Div1 = 0b00,
29 Div2 = 0b01,
30 Div4 = 0b10,
31 Div8 = 0b11,
32}
33
34/// Checks a period and a window and returns them as the register expects them.
35///
36/// # Panics
37///
38/// If either value needs more than 6 bits, or if `win` exceeds `cnt`.
39fn checked_bits(cnt: u8, win: u8) -> (u8, u8) {
40 assert!(cnt <= TICKS_MAX, "WWDGT period must fit in 6 bits");
41 assert!(win <= TICKS_MAX, "WWDGT window must fit in 6 bits");
42 assert!(
43 win <= cnt,
44 "WWDGT window must not outlast the period it opens in"
45 );
46 (cnt | ALIVE_BIT, win | ALIVE_BIT)
47}
48
49/// The watchdog before it is started, the only state it can be configured in.
50pub struct Wwdgt {
51 wwdgt: pac::Wwdgt,
52}
53
54impl Wwdgt {
55 /// Sets the period and the window and starts counting down; there is no way
56 /// back.
57 ///
58 /// Both `cnt` and `win` are counted in ticks of `PCLK1 / 4096 / psc`, from 0
59 /// to 63 — the register's own top bit is added here, so a value that would
60 /// reset the chip on the spot cannot be passed in. The period lasts
61 /// `cnt + 1` ticks, and the window opens `cnt - win` ticks into it: feeding
62 /// before that resets the chip just as failing to feed does.
63 ///
64 /// # Panics
65 ///
66 /// If either value needs more than 6 bits, or if `win` exceeds `cnt`.
67 pub fn start(self, psc: WwdgtPsc, cnt: u8, win: u8) -> WwdgtRunning {
68 let (cnt, win) = checked_bits(cnt, win);
69 self.wwdgt
70 .cfg()
71 .modify(|_, w| w.psc().bits(psc as u8).win().bits(win));
72 self.wwdgt
73 .ctl()
74 .modify(|_, w| w.cnt().bits(cnt).wdgten().enabled());
75 WwdgtRunning {
76 wwdgt: self.wwdgt,
77 cnt,
78 }
79 }
80}
81
82/// The watchdog once it is counting down.
83///
84/// No way out: `WDGTEN` ignores a written zero and only a hardware reset clears
85/// it, so neither the peripheral nor the period comes back.
86#[must_use = "the watchdog keeps running; without this value nothing can feed it"]
87pub struct WwdgtRunning {
88 wwdgt: pac::Wwdgt,
89 cnt: u8,
90}
91
92impl WwdgtRunning {
93 /// Reloads the counter, which must happen inside the window: too early
94 /// resets the chip exactly as too late does.
95 pub fn feed(&mut self) {
96 self.wwdgt.ctl().write(|w| w.cnt().bits(self.cnt));
97 }
98 /// Changes the period and the window, taking effect at the next
99 /// [`feed`](Self::feed).
100 ///
101 /// The new window applies at once, so the next feed is already judged by it.
102 ///
103 /// # Panics
104 ///
105 /// On the same three conditions as [`Wwdgt::start`].
106 pub fn set_period(&mut self, cnt: u8, win: u8) {
107 let (cnt, win) = checked_bits(cnt, win);
108 self.wwdgt.cfg().modify(|_, w| w.win().bits(win));
109 self.cnt = cnt;
110 }
111
112 /// Lets the counter reaching `0x40` raise an interrupt — one tick before
113 /// the reset, the last moment anything can still run.
114 ///
115 /// Irreversible, like the watchdog itself: `EWIE` ignores a written zero, so
116 /// there is no `unlisten`. Unmasking the line in the NVIC is the caller's.
117 pub fn listen(&mut self) {
118 self.wwdgt.cfg().modify(|_, w| w.ewie().enable());
119 }
120 /// Whether the early wakeup interrupt is enabled.
121 pub fn is_listening(&self) -> bool {
122 self.wwdgt.cfg().read().ewie().bit_is_set()
123 }
124 /// Whether the counter has reached `0x40`.
125 ///
126 /// Hardware sets the flag whether or not [`listen`](Self::listen) was
127 /// called, so this doubles as a plain poll for "one tick left".
128 pub fn is_pending(&self) -> bool {
129 self.wwdgt.stat().read().ewif().is_pending()
130 }
131 /// Clears the flag, which a handler must do before returning — the flag is
132 /// the request, and hardware never drops it.
133 ///
134 /// The flag rises again for as long as the counter sits at `0x40`, so a
135 /// handler that only clears it is entered over and over until the reset.
136 /// For exactly one entry, mask the line in the NVIC inside the handler.
137 pub fn clear_interrupt(&mut self) {
138 self.wwdgt.stat().write(|w| w.ewif().finished());
139 }
140}
141
142/// Entry point on the raw peripheral.
143pub trait WwdgtExt {
144 /// Enables the peripheral's clock and resets it.
145 ///
146 /// Nothing is started here — the counter only begins running once
147 /// [`start`](Wwdgt::start) sets `WDGTEN`.
148 fn constrain(self, rcu: &mut Rcu) -> Wwdgt;
149}
150
151impl WwdgtExt for pac::Wwdgt {
152 fn constrain(self, rcu: &mut Rcu) -> Wwdgt {
153 <pac::Wwdgt as Enable>::enable(rcu);
154 <pac::Wwdgt as Reset>::reset(rcu);
155 Wwdgt { wwdgt: self }
156 }
157}