Skip to main content

ws63_hal/
lsadc.rs

1//! Low-Speed ADC (LSADC) driver for WS63 — v154 controller.
2//!
3//! Register map and bit fields are cross-checked against the WS63 C SDK
4//! (`hal_adc_v154_regs_def.h`, `hal_adc_v154_regs_op.{c,h}`, `hal_adc_v154.c`).
5//! The control bank is the contiguous `adc_regs_t` struct at base `0x4400_C000`:
6//!
7//! | Register      | Offset | Purpose                                            |
8//! |---------------|--------|----------------------------------------------------|
9//! | `LSADC_CTRL_0`  | 0x00 | scan config: per-channel enable + sample timing    |
10//! | `LSADC_CTRL_1`  | 0x04 | FIFO status (`rne`/`rff`/`bsy`) + waterline        |
11//! | `LSADC_CTRL_2`  | 0x08 | interrupt mask/status                              |
12//! | `LSADC_CTRL_8`  | 0x1C | scan start/stop                                    |
13//! | `LSADC_CTRL_9`  | 0x20 | FIFO read data (`data[13:0]`, `channel[16:14]`)    |
14//! | `LSADC_CTRL_11` | 0x24 | analog enable (`da_lsadc_en`) + reset (`rstn`@16)  |
15//! | `CFG_DATA_SEL`  | 0xDC | data output select                                 |
16//! | `CFG_OFFSET`    | 0xE0 | offset correction                                  |
17//! | `CFG_GAIN`      | 0xE4 | gain correction                                    |
18//! | `CFG_CIC_FILTER_EN` | 0xE8 | CIC filter enable                              |
19//! | `CFG_CIC_OSR`   | 0xEC | CIC oversampling ratio                             |
20//!
21//! # Status
22//!
23//! Not validated on silicon. The full analog power-up sequence (the SDK's
24//! `hal_adc_simulation_cfg` magic writes to `da_lsadc_en` and the `da_lsadc_rwreg`
25//! registers, plus offset/cap/gain calibration) is **not** implemented here;
26//! [`LsAdc::set_analog_enable`] exposes `da_lsadc_en` for callers that port it.
27
28use crate::peripherals::Lsadc;
29
30/// LSADC channel (0-5).
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum AdcChannel {
33    Channel0 = 0,
34    Channel1 = 1,
35    Channel2 = 2,
36    Channel3 = 3,
37    Channel4 = 4,
38    Channel5 = 5,
39}
40
41impl AdcChannel {
42    /// Convert a channel index (0-5) to an `AdcChannel`.
43    pub fn from_index(idx: u8) -> Option<Self> {
44        match idx {
45            0 => Some(Self::Channel0),
46            1 => Some(Self::Channel1),
47            2 => Some(Self::Channel2),
48            3 => Some(Self::Channel3),
49            4 => Some(Self::Channel4),
50            5 => Some(Self::Channel5),
51            _ => None,
52        }
53    }
54}
55
56/// Averaging mode (`equ_model_sel`, `LSADC_CTRL_0[7:6]`).
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum Averaging {
59    /// 1 sample averaged.
60    One = 0,
61    /// 2 samples averaged.
62    Two = 1,
63    /// 4 samples averaged.
64    Four = 2,
65    /// 8 samples averaged.
66    Eight = 3,
67}
68
69/// Scan-mode sample timing (`LSADC_CTRL_0`). Defaults match the SDK's
70/// `hal_adc_auto_scan_mode_set` (8× averaging, `sample_cnt=8`, `start_cnt=0x18`).
71#[derive(Debug, Clone, Copy)]
72pub struct AdcConfig {
73    /// Averaging mode (`equ_model_sel`, 2-bit).
74    pub averaging: Averaging,
75    /// Sample count (`sample_cnt`, 5-bit).
76    pub sample_count: u8,
77    /// Start count (`start_cnt` / SDK `satrt_cnt`, 8-bit).
78    pub start_count: u8,
79    /// Cast count (`cast_cnt`, 7-bit).
80    pub cast_count: u8,
81}
82
83impl Default for AdcConfig {
84    fn default() -> Self {
85        Self { averaging: Averaging::Eight, sample_count: 0x8, start_count: 0x18, cast_count: 0x0 }
86    }
87}
88
89/// ADC conversion result.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct AdcSample {
92    /// 14-bit conversion code.
93    pub data: u16,
94    /// Channel that produced this sample (0-5).
95    pub channel: u8,
96}
97
98/// Parse a raw `LSADC_CTRL_9` word into a sample: `data` = bits `[13:0]`,
99/// `channel` = bits `[16:14]` (matches `adc_fifo_data_str` in the SDK).
100#[inline]
101const fn parse_sample(raw: u32) -> AdcSample {
102    AdcSample { data: (raw & 0x3FFF) as u16, channel: ((raw >> 14) & 0x07) as u8 }
103}
104
105/// LSADC driver.
106pub struct LsAdc<'d> {
107    _lsadc: Lsadc<'d>,
108}
109
110impl<'d> LsAdc<'d> {
111    /// Create a new LSADC driver from the LSADC peripheral.
112    pub fn new(lsadc: Lsadc<'d>) -> Self {
113        Self { _lsadc: lsadc }
114    }
115
116    fn regs(&self) -> &'static ws63_pac::lsadc::RegisterBlock {
117        // SAFETY: PAC peripheral pointer is a static physical MMIO address, always valid.
118        unsafe { &*Lsadc::ptr() }
119    }
120
121    /// Release the analog reset (`LSADC_CTRL_11.da_lsadc_rstn = 1`, active-low).
122    pub fn enable(&mut self) {
123        self.regs().lsadc_ctrl_11().modify(|_, w| w.da_lsadc_rstn().set_bit());
124    }
125
126    /// Assert the analog reset (`LSADC_CTRL_11.da_lsadc_rstn = 0`).
127    pub fn disable(&mut self) {
128        self.regs().lsadc_ctrl_11().modify(|_, w| w.da_lsadc_rstn().clear_bit());
129    }
130
131    /// Write the 16-bit analog-enable field (`LSADC_CTRL_11.da_lsadc_en`).
132    ///
133    /// The SDK power-up sequence ORs in `0x7000`, `0xE7F`, `0x100`, `0x80`
134    /// across several steps; this exposes the raw field for porting it.
135    pub fn set_analog_enable(&mut self, bits: u16) {
136        self.regs().lsadc_ctrl_11().modify(|_, w| unsafe { w.da_lsadc_en().bits(bits) });
137    }
138
139    /// Enable a channel and program the scan timing (`LSADC_CTRL_0`).
140    ///
141    /// Sets the per-channel enable bit (preserving any already-enabled channels)
142    /// and the averaging/sample/start/cast counts, matching
143    /// `hal_adc_auto_scan_mode_set`.
144    pub fn configure_scan(&mut self, channel: AdcChannel, config: &AdcConfig) {
145        self.regs().lsadc_ctrl_0().modify(|r, w| {
146            let ch = r.channel().bits() | (1 << (channel as u8));
147            unsafe {
148                w.channel().bits(ch);
149                w.equ_model_sel().bits(config.averaging as u8);
150                w.sample_cnt().bits(config.sample_count & 0x1F);
151                w.start_cnt().bits(config.start_count);
152                w.cast_cnt().bits(config.cast_count & 0x7F)
153            }
154        });
155    }
156
157    /// Start an ADC scan (`LSADC_CTRL_8.lsadc_start = 1`).
158    pub fn start_scan(&mut self) {
159        self.regs().lsadc_ctrl_8().write(|w| w.lsadc_start().set_bit());
160    }
161
162    /// Stop an ADC scan (`LSADC_CTRL_8.lsadc_stop = 1`).
163    pub fn stop_scan(&mut self) {
164        self.regs().lsadc_ctrl_8().write(|w| w.lsadc_stop().set_bit());
165    }
166
167    /// Set the RX-FIFO interrupt waterline (`LSADC_CTRL_1.rxintsize`, 3-bit).
168    pub fn set_fifo_waterline(&mut self, level: u8) {
169        self.regs().lsadc_ctrl_1().modify(|_, w| unsafe { w.rxintsize().bits(level & 0x07) });
170    }
171
172    /// True if the RX FIFO holds at least one sample (`LSADC_CTRL_1.rne`).
173    ///
174    /// This is the reliable empty check — read it before [`Self::read_sample`].
175    pub fn data_ready(&self) -> bool {
176        self.regs().lsadc_ctrl_1().read().rne().bit_is_set()
177    }
178
179    /// Read one sample from the FIFO (`LSADC_CTRL_9`).
180    ///
181    /// Returns `None` when the FIFO is empty (checked via `rne`), so a genuine
182    /// 0-code reading is **not** mistaken for "no data".
183    pub fn read_sample(&self) -> Option<AdcSample> {
184        if !self.data_ready() {
185            return None;
186        }
187        Some(parse_sample(self.regs().lsadc_ctrl_9().read().bits()))
188    }
189
190    /// Enable the CIC filter with the given oversampling ratio.
191    pub fn enable_cic_filter(&mut self, oversampling_ratio: u8) {
192        let r = self.regs();
193        unsafe {
194            r.cfg_cic_osr().write(|w| w.cic_osr().bits(oversampling_ratio));
195        }
196        r.cfg_cic_filter_en().write(|w| w.cic_filter_en().set_bit());
197    }
198
199    /// Disable the CIC filter.
200    pub fn disable_cic_filter(&mut self) {
201        self.regs().cfg_cic_filter_en().write(|w| w.cic_filter_en().clear_bit());
202    }
203
204    /// Set the ADC offset correction value (`CFG_OFFSET`).
205    pub fn set_offset(&mut self, offset: u16) {
206        self.regs().cfg_offset().write(|w| unsafe { w.offset().bits(offset) });
207    }
208
209    /// Set the ADC gain correction value (`CFG_GAIN`).
210    pub fn set_gain(&mut self, gain: u16) {
211        self.regs().cfg_gain().write(|w| unsafe { w.gain().bits(gain) });
212    }
213
214    /// Select the data source: `true` = post-processed, `false` = raw ADC code.
215    pub fn set_data_select(&mut self, processed: bool) {
216        self.regs().cfg_data_sel().write(|w| w.data_sel().bit(processed));
217    }
218}
219
220// ── Tests ──────────────────────────────────────────────────────
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_parse_sample_data_and_channel() {
228        // channel 3, code 0x0AAA  ->  raw = (3 << 14) | 0x0AAA
229        let raw: u32 = (3 << 14) | 0x0AAA;
230        let s = parse_sample(raw);
231        assert_eq!(s.data, 0x0AAA);
232        assert_eq!(s.channel, 3);
233    }
234
235    #[test]
236    fn test_parse_sample_max_code() {
237        let s = parse_sample(0x3FFF); // all data bits, channel 0
238        assert_eq!(s.data, 0x3FFF);
239        assert_eq!(s.channel, 0);
240    }
241
242    #[test]
243    fn test_parse_sample_channel_field_is_3_bits() {
244        // bits above [16:14] must not leak into channel
245        let raw: u32 = 0xFFFF_FFFF;
246        let s = parse_sample(raw);
247        assert_eq!(s.channel, 0x07);
248        assert_eq!(s.data, 0x3FFF);
249    }
250
251    #[test]
252    fn test_channel_from_index() {
253        assert_eq!(AdcChannel::from_index(0), Some(AdcChannel::Channel0));
254        assert_eq!(AdcChannel::from_index(5), Some(AdcChannel::Channel5));
255        assert_eq!(AdcChannel::from_index(6), None);
256        assert_eq!(AdcChannel::from_index(255), None);
257    }
258
259    #[test]
260    fn test_default_config_matches_sdk() {
261        let c = AdcConfig::default();
262        assert_eq!(c.averaging as u8, 3); // AVERAGE_OF_EIGHT_SAMPLES
263        assert_eq!(c.sample_count, 0x8);
264        assert_eq!(c.start_count, 0x18);
265        assert_eq!(c.cast_count, 0x0);
266    }
267}
268
269// ── Property-based fuzz tests ──────────────────────────────────
270
271#[cfg(test)]
272mod proptests {
273    use super::*;
274    use proptest::prelude::*;
275
276    proptest! {
277        /// Fuzz: channel field is always within 3 bits regardless of input.
278        #[test]
279        fn parse_channel_always_3_bits(raw in any::<u32>()) {
280            prop_assert!(parse_sample(raw).channel <= 0x07);
281        }
282
283        /// Fuzz: data field is always within 14 bits.
284        #[test]
285        fn parse_data_always_14_bits(raw in any::<u32>()) {
286            prop_assert!(parse_sample(raw).data <= 0x3FFF);
287        }
288
289        /// Fuzz: data and channel are extracted from the correct, disjoint lanes.
290        #[test]
291        fn parse_sample_lanes(raw in any::<u32>()) {
292            let s = parse_sample(raw);
293            prop_assert_eq!(s.data, (raw & 0x3FFF) as u16);
294            prop_assert_eq!(s.channel, ((raw >> 14) & 0x07) as u8);
295        }
296
297        /// Fuzz: AdcChannel::from_index returns Some for 0-5, None otherwise.
298        #[test]
299        fn channel_from_index_coverage(idx in any::<u8>()) {
300            prop_assert_eq!(AdcChannel::from_index(idx).is_some(), idx <= 5);
301        }
302    }
303}
304
305// ── Async LSADC (bespoke; LSADC_INTR = IRQ 72) ──────────────────────────────
306#[cfg(feature = "async")]
307mod asynch_impl {
308    use super::{AdcSample, LsAdc};
309    use crate::asynch::IrqSignal;
310    use crate::interrupt::{self, Interrupt};
311    use core::future::Future;
312    use core::pin::Pin;
313    use core::task::{Context, Poll};
314
315    static LSADC_SIGNAL: IrqSignal = IrqSignal::new();
316
317    /// LSADC trap hook (IRQ 72): wake the awaiting conversion. The sample is
318    /// read-cleared by `read_sample`, so the ISR just signals + clears pending.
319    pub fn on_interrupt() {
320        LSADC_SIGNAL.signal();
321        interrupt::clear_pending(Interrupt::LSADC_INTR);
322    }
323
324    struct ConvFuture;
325    impl Future for ConvFuture {
326        type Output = ();
327        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
328            if LSADC_SIGNAL.take_fired() {
329                Poll::Ready(())
330            } else {
331                LSADC_SIGNAL.register(cx.waker());
332                Poll::Pending
333            }
334        }
335    }
336
337    impl LsAdc<'_> {
338        /// Start a scan and await conversion-complete (IRQ 72), returning a sample.
339        /// On hardware this parks until the IRQ; the WS63 model fills the FIFO
340        /// synchronously, so the fast path returns without parking.
341        pub async fn read_async(&mut self) -> Option<AdcSample> {
342            LSADC_SIGNAL.reset();
343            // SAFETY: enabling a known, fixed WS63 IRQ line.
344            unsafe { interrupt::enable(Interrupt::LSADC_INTR) };
345            self.start_scan();
346            if !self.data_ready() {
347                ConvFuture.await;
348            }
349            self.read_sample()
350        }
351    }
352}
353
354#[cfg(feature = "async")]
355pub use asynch_impl::on_interrupt;