sifli_hal/
adc.rs

1//! ADC (Analog-to-Digital Converter)
2
3use core::future::poll_fn;
4use core::marker::PhantomData;
5use core::sync::atomic::{compiler_fence, AtomicBool, Ordering};
6use core::task::Poll;
7
8use embassy_hal_internal::Peripheral;
9use embassy_sync::waitqueue::AtomicWaker;
10use sifli_pac::HPSYS_CFG;
11
12use crate::_generated::{FIRST_CHANNEL_PIN, VBAT_CHANNEL_ID, VOL_OFFSET, VOL_RATIO};
13use crate::{blocking_delay_us, interrupt, rcc};
14use crate::mode::{Async, Blocking, Mode};
15use crate::gpio::{self, Analog};
16use crate::interrupt::typelevel::{Binding};
17use crate::interrupt::InterruptExt;
18use crate::pac::gpadc::vals as AdcVals;
19use crate::pac::GPADC;
20use crate::peripherals;
21
22static WAKER: AtomicWaker = AtomicWaker::new();
23static IRQ_DONE: AtomicBool = AtomicBool::new(false);
24
25/// ADC configuration.
26/// f_ADCCLK = f_PCLK / (DATA_SAMP_DLY + CONV_WIDTH + SAMP_WIDTH + 2)
27#[non_exhaustive]
28pub struct Config {
29    /// Sample width in ADCCLK cycles. Affects sample rate.
30    pub sample_width: u32,
31    /// Conversion width in ADCCLK cycles. Affects sample rate.
32    pub conv_width: u8,
33    /// Data sample delay in PCLK cycles. Affects sample rate.
34    pub data_samp_dly: u8,
35}
36
37impl Default for Config {
38    fn default() -> Self {
39        // example\hal\adc\multichannel\src\main.c
40        Self {
41            sample_width: 0x71,
42            conv_width: 75,
43            data_samp_dly: 0x4,
44        }
45    }
46}
47
48/// ADC error.
49#[derive(Debug, Eq, PartialEq, Copy, Clone)]
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51pub enum Error {
52    /// Conversion failed.
53    ConversionFailed,
54}
55
56/// ADC sample.
57/// The ADC returns a 12-bit result for single reads and a 13-bit result for DMA reads.
58/// Both are stored in a u16.
59#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
60#[cfg_attr(feature = "defmt", derive(defmt::Format))]
61#[repr(transparent)]
62pub struct Sample(u16);
63
64impl Sample {
65    /// Get the raw sample value.
66    pub fn value(&self) -> u16 {
67        self.0
68    }
69
70    /// Convert the sample to millivolts.
71    pub fn to_mv(&self) -> u16 {
72        if self.0 <= VOL_OFFSET {
73            0 // Below the offset, return 0 mV
74        } else {
75            // Convert to millivolts using the formula:
76            // (sample - VOL_OFFSET) * VOL_RATIO / 1000(ADC_RATIO_ACCURATE)
77            ((self.0 - VOL_OFFSET) as u32 * VOL_RATIO as u32 / 1000) as u16
78        }
79    }
80
81    /// Convert the sample to volts (float).
82    pub fn to_v_float(&self) -> f32 {
83        if self.0 <= VOL_OFFSET {
84            0.0 // Below the offset, return 0 V
85        } else {
86            // Convert to volts using the formula:
87            // (sample - VOL_OFFSET) * VOL_RATIO / 1000000(ADC_RATIO_ACCURATE)
88            (self.0 - VOL_OFFSET) as f32 * VOL_RATIO as f32 / 1_000_000.0
89        }
90    }
91}
92
93/// An ADC channel, which can be a pin or an internal source.
94pub struct Channel<'p> {
95    pub id: u8,
96    phantom: PhantomData<&'p ()>,
97}
98
99/// A GPIO pin that can be used as an ADC channel.
100///
101/// The user must configure the pin's MUX to the ADC function.
102pub trait AdcPin: gpio::Pin {
103    fn adc_channel_id(&self) -> u8 {
104        self.pin() - FIRST_CHANNEL_PIN
105    }
106}
107
108impl<'p> Channel<'p> {
109    /// Create a new ADC channel from a GPIO pin.
110    pub fn new_pin(pin: impl AdcPin + 'p) -> Self {
111        let id = pin.adc_channel_id();
112        Analog::new(pin);
113        Self {
114            id,
115            phantom: PhantomData,
116        }
117    }
118
119    /// Create a new ADC channel for the internal battery voltage monitor.
120    /// This corresponds to ADC channel 7.
121    /// An ownership token for `ADC_VBAT` is required to ensure exclusive access.
122    pub fn new_vbat(_vbat: impl Peripheral<P = peripherals::ADC_VBAT> + 'p) -> Self {
123        Self {
124            id: VBAT_CHANNEL_ID,
125            phantom: PhantomData,
126        }
127    }
128}
129
130/// ADC driver.
131pub struct Adc<'d, M: Mode> {
132    _phantom: PhantomData<(&'d peripherals::GPADC, M)>,
133}
134
135impl<'d, M: Mode> Adc<'d, M> {
136    /// Common initialization logic for both blocking and async modes.
137    fn new_inner(
138        _inner: impl Peripheral<P = peripherals::GPADC> + 'd,
139        config: Config,
140    ) -> Self {
141        rcc::enable_and_reset::<peripherals::GPADC>();
142        let regs = GPADC;
143
144        // This initialization sequence is based on `HAL_ADC_Init` for
145        // GPADC_CALIB_FLOW_VERSION == 3 (targeting SF32LB52x) and the user manual.
146
147        // 1. Enable shared bandgap from HPSYS_CFG.
148        // The manual suggests this is shared with the temperature sensor and recommends leaving it on.
149        // This driver enables it but does not disable it on Drop, leaving that to the application owner.
150        HPSYS_CFG.anau_cr().modify(|r| r.set_en_bg(true));
151
152        // 2. Set ADC to single-ended mode by default.
153        regs.cfg_reg1().modify(|r| r.set_anau_gpadc_se(true));
154
155        // 3. Configure timing/width parameters from the Config struct.
156        regs.ctrl_reg2().write(|w| {
157            w.set_samp_width(config.sample_width);
158            w.set_conv_width(config.conv_width);
159        });
160        regs.ctrl_reg().modify(|r| {
161            r.set_data_samp_dly(config.data_samp_dly);
162            // Set init time. The C HAL uses a value of 8 for SF32LB52x.
163            r.set_init_time(8);
164            // Disable hardware triggers by default.
165            r.set_timer_trig_en(false);
166        });
167
168        // 4. Set default analog tuning parameters from the C HAL for SF32LB52x.
169        regs.cfg_reg1().modify(|r| {
170            r.set_anau_gpadc_vsp(AdcVals::Vsp::V0_642); // Value '2'
171            r.set_anau_gpadc_cmm(0x10);
172            r.set_anau_gpadc_en_v18(false); // SF32LB52x is 3.3V AVDD
173        });
174
175        // 5. Disable all conversion slots initially.
176        for i in 0..8 {
177            regs.slot(i).modify(|r| r.set_slot_en(false));
178        }
179
180        Self {
181            _phantom: PhantomData,
182        }
183    }
184
185    /// Prepares the ADC for a conversion by powering it up and waiting for stabilization.
186    fn prepare(&mut self, channel: &Channel) {
187        // From manual and `HAL_ADC_Prepare`.
188
189        if channel.id == VBAT_CHANNEL_ID {
190            // Enable battery monitoring path when using channel 7.
191            HPSYS_CFG.anau_cr().modify(|r| r.set_en_vbat_mon(true));
192        }
193
194        // Necessary! Otherwise the data is incorrect (but why?)
195        GPADC.slot(channel.id as _).modify(|r| r.set_slot_en(true));
196
197        // 1. Enable the LDO that provides the reference voltage to the ADC.
198        GPADC.cfg_reg1().modify(|r| r.set_anau_gpadc_ldoref_en(true));
199        // Manual: Wait 200us for LDO to stabilize.
200        blocking_delay_us(200);
201
202        // 2. Unmute ADC inputs to connect them to the external pins.
203        GPADC.cfg_reg1().modify(|r| {r.set_anau_gpadc_mute(false)});
204
205        // 3. Enable the main GPADC core logic.
206        GPADC.ctrl_reg().modify(|r| r.set_frc_en_adc(true));
207        // Manual: Wait 200us for GPADC core to stabilize.
208        blocking_delay_us(200);
209    }
210
211    /// Powers down ADC components after a conversion to save power.
212    fn finish(&mut self, channel: &Channel) {
213        // Reverse of the `prepare` sequence.
214
215        if channel.id == VBAT_CHANNEL_ID {
216            // Disable battery monitoring path to save power.
217            HPSYS_CFG.anau_cr().modify(|r| r.set_en_vbat_mon(false));
218        }
219
220        GPADC.ctrl_reg().modify(|r| r.set_frc_en_adc(false));
221        GPADC.cfg_reg1().modify(|r| {
222            r.set_anau_gpadc_ldoref_en(false);
223            // Mute inputs to disconnect them.
224            r.set_anau_gpadc_mute(true);
225        });
226    }
227
228    /// Perform a single conversion on a channel in blocking mode.
229    pub fn blocking_read(&mut self, ch: &mut Channel) -> Result<Sample, Error> {
230        self.prepare(ch);
231
232        // Use forced channel selection for single-shot conversions.
233        GPADC.ctrl_reg().modify(|r| {
234            r.set_adc_op_mode(false); // Single conversion mode
235            r.set_chnl_sel_frc_en(true); // Enable forced channel selection
236        });
237        GPADC.cfg_reg1().modify(|r| r.set_anau_gpadc_sel_pch(ch.id));
238
239        // Start the conversion.
240        GPADC.ctrl_reg().modify(|r| r.set_adc_start(true));
241
242        // Poll for completion flag (GPADC_IRSR).
243        while !GPADC.gpadc_irq().read().gpadc_irsr() {}
244
245        // Clear the interrupt flag by writing 1 to ICR.
246        GPADC.gpadc_irq().write(|w| w.set_gpadc_icr(true));
247
248        // In single conversion mode, the result is always in the even part of the first data register.
249        let result = GPADC.rdata(0).read().even_slot_rdata();
250
251        self.finish(ch);
252
253        Ok(Sample(result & 0xfff))
254    }
255}
256
257impl<'d, M: Mode> Drop for Adc<'d, M> {
258    fn drop(&mut self) {
259        // Ensure ADC is powered down when the driver is dropped.
260        GPADC.ctrl_reg().modify(|r| r.set_frc_en_adc(false));
261        GPADC.cfg_reg1().modify(|r| r.set_anau_gpadc_ldoref_en(false));
262        // The shared HPSYS bandgap (`EN_BG`) is not disabled here.
263        // The application is responsible for managing it if it's no longer needed by any peripheral.
264    }
265}
266
267impl<'d> Adc<'d, Blocking> {
268    /// Create a new ADC driver in blocking mode.
269    ///
270    /// - `inner`: The ADC peripheral singleton.
271    /// - `hpsys`: The HPSYS_CFG peripheral singleton, required for managing shared analog resources.
272    /// - `config`: ADC timing and operational configuration.
273    pub fn new_blocking(
274        inner: impl Peripheral<P = peripherals::GPADC> + 'd,
275        config: Config,
276    ) -> Self {
277        Self::new_inner(inner, config)
278    }
279}
280
281/// ADC interrupt handler.
282pub struct InterruptHandler;
283
284impl interrupt::typelevel::Handler<interrupt::typelevel::GPADC> for InterruptHandler {
285    unsafe fn on_interrupt() {
286        if GPADC.gpadc_irq().read().gpadc_irsr() {
287            IRQ_DONE.store(true, Ordering::SeqCst);
288        }
289        GPADC.gpadc_irq().modify(|w| w.set_gpadc_icr(true));
290        WAKER.wake();
291    }
292}
293
294impl<'d> Adc<'d, Async> {
295    /// Create a new ADC driver in asynchronous mode.
296    pub fn new(
297        inner: impl Peripheral<P = peripherals::GPADC> + 'd,
298        _irq: impl Binding<interrupt::typelevel::GPADC, InterruptHandler>,
299        config: Config,
300    ) -> Self {
301        let s = Self::new_inner(inner, config);
302
303        let irq = crate::interrupt::GPADC;
304        irq.unpend();
305        unsafe { irq.enable() };
306
307        s
308    }
309
310    /// Waits asynchronously for the current ADC operation to complete.
311    async fn wait_for_completion(&mut self) {
312        let regs = GPADC;
313        poll_fn(move |cx| {
314            WAKER.register(cx.waker());
315            // Re-enable interrupt mask before pending.
316            regs.gpadc_irq().modify(|r| r.set_gpadc_imr(false));
317            compiler_fence(Ordering::SeqCst);
318
319            if IRQ_DONE.load(Ordering::SeqCst) {
320                IRQ_DONE.store(false, Ordering::SeqCst);
321                Poll::Ready(())
322            } else {
323                Poll::Pending
324            }
325        })
326        .await
327    }
328
329    /// Perform a single conversion on a channel asynchronously.
330    pub async fn read(&mut self, ch: &mut Channel<'_>) -> Result<Sample, Error> {
331        self.prepare(ch);
332
333        // Configure for single-shot forced channel conversion.
334        GPADC.ctrl_reg().modify(|r| {
335            r.set_adc_op_mode(false);
336            r.set_chnl_sel_frc_en(true);
337        });
338        GPADC.cfg_reg1().modify(|r| r.set_anau_gpadc_sel_pch(ch.id));
339
340        // Enable interrupt and start conversion.
341        GPADC.gpadc_irq().modify(|r| r.set_gpadc_imr(false));
342
343        // Clear any previous IRQ done state before starting a new conversion.
344        IRQ_DONE.store(false, Ordering::SeqCst);
345        compiler_fence(Ordering::SeqCst);
346
347        GPADC.ctrl_reg().modify(|r| r.set_adc_start(true));
348        self.wait_for_completion().await;
349        
350        let result = GPADC.rdata(0).read().even_slot_rdata();
351
352        self.finish(ch);
353
354        Ok(Sample(result & 0xfff))
355    }
356}
357
358#[allow(private_interfaces)]
359pub(crate) trait SealedInstance: crate::rcc::RccEnableReset + crate::rcc::RccGetFreq {}
360
361#[allow(private_bounds)]
362pub trait Instance: Peripheral<P = Self> + SealedInstance + 'static + Send {
363    /// Interrupt for this peripheral.
364    type Interrupt: interrupt::typelevel::Interrupt;
365}
366impl SealedInstance for peripherals::GPADC {}
367impl Instance for peripherals::GPADC {
368    type Interrupt = crate::interrupt::typelevel::GPADC;
369}
370
371dma_trait!(Dma, Instance);