Skip to main content

esp_hal/analog/adc/
p4.rs

1use core::{
2    marker::PhantomData,
3    pin::Pin,
4    task::{Context, Poll},
5};
6
7// One-shot ADC is handled by the LP_ADC peripheral.
8use Interrupt::LP_ADC as InterruptSource;
9// Both units share one interrupt, so the enabled instances have to be counted.
10use portable_atomic::{AtomicU32, Ordering};
11use procmacros::handler;
12
13pub use self::calibration::*;
14use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation};
15use crate::{
16    Async,
17    Blocking,
18    asynch::AtomicWaker,
19    efuse::AdcCalibUnit,
20    interrupt::{InterruptConfigurable, InterruptHandler},
21    peripherals::{Interrupt, LP_ADC, LP_PERI},
22    rtc_cntl::WakeLock,
23    soc::regi2c,
24    system::{GenericPeripheralGuard, Peripheral},
25};
26
27mod calibration;
28
29/// ADC1 covers channels 0..=7, ADC2 covers channels 0..=5. The attenuation
30/// table is indexed by channel, so it has to hold the larger of the two.
31pub(super) const NUM_ATTENS: usize = 8;
32
33const ADC_VAL_MASK: u16 = 0xfff;
34const ADC_CAL_CNT_MAX: u16 = 32;
35const ADC_CAL_CHANNEL: u16 = 15;
36
37/// Power the SAR up by software, instead of leaving it to the FSM.
38const FORCE_XPD_SAR_PU: u8 = 3;
39
40impl<ADCX> AdcConfig<ADCX>
41where
42    ADCX: RegisterAccess,
43{
44    /// Calibrate ADC with specified attenuation and voltage source
45    pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16
46    where
47        ADCX: super::CalibrationAccess,
48    {
49        let mut adc_max: u16 = 0;
50        let mut adc_min: u16 = u16::MAX;
51        let mut adc_sum: u32 = 0;
52
53        ADCX::enable_vdef(true);
54
55        // Start sampling
56        ADCX::set_en_pad(ADCX::ADC_CAL_CHANNEL as u8);
57        ADCX::set_attenuation(ADCX::ADC_CAL_CHANNEL as usize, atten as u8);
58
59        // Connect calibration source
60        ADCX::connect_cal(source, true);
61
62        ADCX::calibration_init();
63        ADCX::set_init_code(0);
64
65        for _ in 0..ADCX::ADC_CAL_CNT_MAX {
66            // Trigger ADC sampling
67            ADCX::start_sample();
68
69            // Wait until ADC sampling is done
70            while !ADCX::is_done() {}
71
72            let adc = ADCX::read_data() & ADCX::ADC_VAL_MASK;
73
74            ADCX::reset();
75
76            adc_sum += adc as u32;
77            adc_max = adc.max(adc_max);
78            adc_min = adc.min(adc_min);
79        }
80
81        let cal_val =
82            (adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADCX::ADC_CAL_CNT_MAX - 2);
83
84        // Disconnect calibration source
85        ADCX::connect_cal(source, false);
86
87        cal_val
88    }
89}
90
91#[doc(hidden)]
92pub trait RegisterAccess {
93    fn set_attenuation(channel: usize, attenuation: u8);
94
95    /// Route the unit to the RTC controller, driven by software.
96    fn set_rtc_controller();
97
98    fn set_en_pad(channel: u8);
99
100    fn clear_start_sample();
101
102    fn start_sample();
103
104    /// Check if sampling is done
105    fn is_done() -> bool;
106
107    /// Read sample data
108    fn read_data() -> u16;
109
110    /// Power the SAR up
111    fn power_up();
112
113    /// Set up ADC hardware for calibration
114    fn calibration_init();
115
116    /// Set calibration parameter to ADC hardware
117    fn set_init_code(data: u16);
118
119    /// Reset flags
120    fn reset();
121}
122
123#[cfg(adc_adc1)]
124impl RegisterAccess for crate::peripherals::ADC1<'_> {
125    fn set_attenuation(channel: usize, attenuation: u8) {
126        LP_ADC::regs().atten1().modify(|r, w| {
127            let new_value = (r.bits() & !(0b11 << (channel * 2)))
128                | (((attenuation & 0b11) as u32) << (channel * 2));
129
130            unsafe { w.sar1_atten().bits(new_value) }
131        });
132    }
133
134    fn set_rtc_controller() {
135        LP_ADC::regs()
136            .meas1_mux()
137            .modify(|_, w| w.sar1_dig_force().clear_bit());
138        LP_ADC::regs().meas1_ctrl2().modify(|_, w| {
139            w.meas1_start_force().set_bit();
140            w.sar1_en_pad_force().set_bit()
141        });
142    }
143
144    fn set_en_pad(channel: u8) {
145        LP_ADC::regs()
146            .meas1_ctrl2()
147            .modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) });
148    }
149
150    fn clear_start_sample() {
151        LP_ADC::regs()
152            .meas1_ctrl2()
153            .modify(|_, w| w.meas1_start_sar().clear_bit());
154    }
155
156    fn start_sample() {
157        LP_ADC::regs()
158            .meas1_ctrl2()
159            .modify(|_, w| w.meas1_start_sar().set_bit());
160    }
161
162    fn is_done() -> bool {
163        LP_ADC::regs()
164            .meas1_ctrl2()
165            .read()
166            .meas1_done_sar()
167            .bit_is_set()
168    }
169
170    fn read_data() -> u16 {
171        LP_ADC::regs().meas1_ctrl2().read().meas1_data_sar().bits()
172    }
173
174    fn power_up() {
175        LP_ADC::regs()
176            .force_wpd_sar()
177            .modify(|_, w| unsafe { w.force_xpd_sar1().bits(FORCE_XPD_SAR_PU) });
178    }
179
180    fn calibration_init() {
181        // https://github.com/espressif/esp-idf/blob/08e0d30a74a/components/esp_hal_ana_conv/esp32p4/include/hal/adc_ll.h#L727
182        regi2c::ADC_SAR1_DREF.write_field(4);
183    }
184
185    fn set_init_code(data: u16) {
186        let [msb, lsb] = data.to_be_bytes();
187
188        regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(msb);
189        regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(lsb);
190    }
191
192    fn reset() {
193        // The conversion-done interrupt latches even when it is masked, so clear it
194        // here to keep a later `into_async` from seeing a stale completion.
195        LP_ADC::regs()
196            .int_clr()
197            .write(|w| w.cocpu_saradc1_int_clr().set_bit());
198
199        LP_ADC::regs()
200            .meas1_ctrl2()
201            .modify(|_, w| w.meas1_start_sar().clear_bit());
202    }
203}
204
205#[cfg(adc_adc1)]
206impl super::CalibrationAccess for crate::peripherals::ADC1<'_> {
207    const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
208    const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
209    const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
210
211    fn enable_vdef(enable: bool) {
212        regi2c::ADC_SAR1_DREF.write_field(enable as u8);
213    }
214
215    fn connect_cal(source: AdcCalSource, enable: bool) {
216        match source {
217            AdcCalSource::Gnd => regi2c::ADC_SAR1_ENCAL_GND.write_field(enable as u8),
218            AdcCalSource::Ref => regi2c::ADC_SAR1_ENCAL_REF.write_field(enable as u8),
219        }
220    }
221}
222
223/// ADC2 channels are wired to SAR pads 2..=7, so the pad index is the channel
224/// number plus two.
225///
226/// See `ADC_LL_UNIT2_CHANNEL_SUBSTRATION` in
227/// `components/esp_hal_ana_conv/esp32p4/include/hal/adc_ll.h`.
228#[cfg(adc_adc2)]
229const ADC2_PAD_OFFSET: u8 = 2;
230
231#[cfg(adc_adc2)]
232impl RegisterAccess for crate::peripherals::ADC2<'_> {
233    fn set_attenuation(channel: usize, attenuation: u8) {
234        let pad = channel + ADC2_PAD_OFFSET as usize;
235        LP_ADC::regs().atten2().modify(|r, w| {
236            let new_value =
237                (r.bits() & !(0b11 << (pad * 2))) | (((attenuation & 0b11) as u32) << (pad * 2));
238
239            unsafe { w.sar2_atten().bits(new_value) }
240        });
241    }
242
243    fn set_rtc_controller() {
244        LP_ADC::regs()
245            .meas2_mux()
246            .modify(|_, w| w.sar2_rtc_force().set_bit());
247        LP_ADC::regs().meas2_ctrl2().modify(|_, w| {
248            w.meas2_start_force().set_bit();
249            w.sar2_en_pad_force().set_bit()
250        });
251    }
252
253    fn set_en_pad(channel: u8) {
254        LP_ADC::regs()
255            .meas2_ctrl2()
256            .modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << (channel + ADC2_PAD_OFFSET)) });
257    }
258
259    fn clear_start_sample() {
260        LP_ADC::regs()
261            .meas2_ctrl2()
262            .modify(|_, w| w.meas2_start_sar().clear_bit());
263    }
264
265    fn start_sample() {
266        LP_ADC::regs()
267            .meas2_ctrl2()
268            .modify(|_, w| w.meas2_start_sar().set_bit());
269    }
270
271    fn is_done() -> bool {
272        LP_ADC::regs()
273            .meas2_ctrl2()
274            .read()
275            .meas2_done_sar()
276            .bit_is_set()
277    }
278
279    fn read_data() -> u16 {
280        LP_ADC::regs().meas2_ctrl2().read().meas2_data_sar().bits()
281    }
282
283    fn power_up() {
284        LP_ADC::regs()
285            .force_wpd_sar()
286            .modify(|_, w| unsafe { w.force_xpd_sar2().bits(FORCE_XPD_SAR_PU) });
287    }
288
289    fn calibration_init() {
290        regi2c::ADC_SAR2_DREF.write_field(4);
291    }
292
293    fn set_init_code(data: u16) {
294        let [msb, lsb] = data.to_be_bytes();
295
296        regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(msb);
297        regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(lsb);
298    }
299
300    fn reset() {
301        // The conversion-done interrupt latches even when it is masked, so clear it
302        // here to keep a later `into_async` from seeing a stale completion.
303        LP_ADC::regs()
304            .int_clr()
305            .write(|w| w.cocpu_saradc2_int_clr().set_bit());
306
307        LP_ADC::regs()
308            .meas2_ctrl2()
309            .modify(|_, w| w.meas2_start_sar().clear_bit());
310    }
311}
312
313#[cfg(adc_adc2)]
314impl super::CalibrationAccess for crate::peripherals::ADC2<'_> {
315    const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
316    const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
317    const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
318
319    fn enable_vdef(enable: bool) {
320        regi2c::ADC_SAR2_DREF.write_field(enable as u8);
321    }
322
323    fn connect_cal(source: AdcCalSource, enable: bool) {
324        match source {
325            AdcCalSource::Gnd => regi2c::ADC_SAR2_ENCAL_GND.write_field(enable as u8),
326            AdcCalSource::Ref => regi2c::ADC_SAR2_ENCAL_REF.write_field(enable as u8),
327        }
328    }
329}
330
331#[cfg(adc_adc1)]
332impl super::AdcCalEfuse for crate::peripherals::ADC1<'_> {
333    fn init_code(atten: Attenuation) -> Option<u16> {
334        crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC1, atten)
335    }
336
337    fn cal_mv(atten: Attenuation) -> u16 {
338        crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC1, atten)
339    }
340
341    fn cal_code(atten: Attenuation) -> Option<u16> {
342        crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC1, atten)
343    }
344}
345
346#[cfg(adc_adc2)]
347impl super::AdcCalEfuse for crate::peripherals::ADC2<'_> {
348    fn init_code(atten: Attenuation) -> Option<u16> {
349        crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC2, atten)
350    }
351
352    fn cal_mv(atten: Attenuation) -> u16 {
353        crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC2, atten)
354    }
355
356    fn cal_code(atten: Attenuation) -> Option<u16> {
357        crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC2, atten)
358    }
359}
360
361/// Analog-to-Digital Converter peripheral driver.
362pub struct Adc<'d, ADC, Dm: crate::DriverMode> {
363    _adc: ADC,
364    active_channel: Option<u8>,
365    last_init_code: u16,
366    _guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
367    _phantom: PhantomData<(Dm, &'d mut ())>,
368}
369
370impl<ADCX, Dm> Adc<'_, ADCX, Dm>
371where
372    ADCX: RegisterAccess,
373    Dm: crate::DriverMode,
374{
375    fn start_sample<PIN, CS>(&mut self, pin: &mut AdcPin<PIN, ADCX, CS>)
376    where
377        PIN: AdcChannel,
378        CS: AdcCalScheme<ADCX>,
379    {
380        // Set ADC unit calibration according used scheme for pin
381        let init_code = pin.cal_scheme.adc_cal();
382        if self.last_init_code != init_code {
383            ADCX::calibration_init();
384            ADCX::set_init_code(init_code);
385            self.last_init_code = init_code;
386        }
387
388        ADCX::set_en_pad(pin.pin.adc_channel());
389
390        ADCX::clear_start_sample();
391        ADCX::start_sample();
392    }
393}
394
395impl<'d, ADCX> Adc<'d, ADCX, Blocking>
396where
397    ADCX: RegisterAccess + 'd,
398{
399    /// Configure a given ADC instance using the provided configuration, and
400    /// initialize the ADC for use
401    pub fn new(adc_instance: ADCX, config: AdcConfig<ADCX>) -> Self {
402        let guard = GenericPeripheralGuard::new();
403
404        // The RTC controller lives in the LP domain and has its own clock gate.
405        LP_PERI::regs()
406            .clk_en()
407            .modify(|_, w| w.ck_en_lp_adc().set_bit());
408
409        for (channel, attenuation) in config.attenuations.iter().enumerate() {
410            if let Some(attenuation) = attenuation {
411                ADCX::set_attenuation(channel, *attenuation as u8);
412            }
413        }
414
415        ADCX::set_rtc_controller();
416        ADCX::power_up();
417
418        Adc {
419            _adc: adc_instance,
420            active_channel: None,
421            last_init_code: 0,
422            _guard: guard,
423            _phantom: PhantomData,
424        }
425    }
426
427    /// Start and wait for a conversion on the specified pin and return the
428    /// result
429    pub fn read_blocking<PIN, CS>(&mut self, pin: &mut AdcPin<PIN, ADCX, CS>) -> u16
430    where
431        PIN: AdcChannel,
432        CS: AdcCalScheme<ADCX>,
433    {
434        self.start_sample(pin);
435
436        // Wait for ADC to finish conversion
437        while !ADCX::is_done() {}
438
439        // Get converted value
440        let converted_value = ADCX::read_data();
441        ADCX::reset();
442
443        // Postprocess converted value according to calibration scheme used for pin
444        pin.cal_scheme.adc_val(converted_value)
445    }
446
447    /// Request that the ADC begin a conversion on the specified pin
448    ///
449    /// This method takes an [AdcPin](super::AdcPin) reference, as it is
450    /// expected that the ADC will be able to sample whatever channel
451    /// underlies the pin.
452    pub fn read_oneshot<PIN, CS>(
453        &mut self,
454        pin: &mut super::AdcPin<PIN, ADCX, CS>,
455    ) -> nb::Result<u16, ()>
456    where
457        PIN: super::AdcChannel,
458        CS: super::AdcCalScheme<ADCX>,
459    {
460        if let Some(active_channel) = self.active_channel {
461            // There is conversion in progress:
462            // - if it's for a different channel try again later
463            // - if it's for the given channel, go ahead and check progress
464            if active_channel != pin.pin.adc_channel() {
465                return Err(nb::Error::WouldBlock);
466            }
467        } else {
468            // If no conversions are in progress, start a new one for given channel
469            self.active_channel = Some(pin.pin.adc_channel());
470
471            self.start_sample(pin);
472        }
473
474        // Wait for ADC to finish conversion
475        let conversion_finished = ADCX::is_done();
476        if !conversion_finished {
477            return Err(nb::Error::WouldBlock);
478        }
479
480        // Get converted value
481        let converted_value = ADCX::read_data();
482        ADCX::reset();
483
484        // Postprocess converted value according to calibration scheme used for pin
485        let converted_value = pin.cal_scheme.adc_val(converted_value);
486
487        // Mark that no conversions are currently in progress
488        self.active_channel = None;
489
490        Ok(converted_value)
491    }
492
493    /// Reconfigures the ADC driver to operate in asynchronous mode.
494    pub fn into_async(mut self) -> Adc<'d, ADCX, Async> {
495        acquire_async_adc();
496        self.set_interrupt_handler(adc_interrupt_handler);
497
498        // Clear a stale done flag so that the first future does not complete early.
499        ADCX::reset();
500
501        Adc {
502            _adc: self._adc,
503            active_channel: self.active_channel,
504            last_init_code: self.last_init_code,
505            _guard: self._guard,
506            _phantom: PhantomData,
507        }
508    }
509}
510
511impl<ADCX> crate::private::Sealed for Adc<'_, ADCX, Blocking> {}
512
513impl<ADCX> InterruptConfigurable for Adc<'_, ADCX, Blocking> {
514    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
515        for core in crate::system::Cpu::other() {
516            crate::interrupt::disable(core, InterruptSource);
517        }
518        crate::interrupt::bind_handler(InterruptSource, handler);
519    }
520}
521
522impl<'d, ADCX> Adc<'d, ADCX, Async>
523where
524    ADCX: RegisterAccess + 'd,
525{
526    /// Create a new instance in [crate::Blocking] mode.
527    pub fn into_blocking(self) -> Adc<'d, ADCX, Blocking> {
528        if release_async_adc() {
529            // Disable the ADC interrupt on all cores once the last async instance goes away.
530            for cpu in crate::system::Cpu::all() {
531                crate::interrupt::disable(cpu, InterruptSource);
532            }
533        }
534        Adc {
535            _adc: self._adc,
536            active_channel: self.active_channel,
537            last_init_code: self.last_init_code,
538            _guard: self._guard,
539            _phantom: PhantomData,
540        }
541    }
542
543    /// Start a conversion on the specified pin and wait for the result.
544    ///
545    /// This method takes an [AdcPin](super::AdcPin) reference, as it is
546    /// expected that the ADC will be able to sample whatever channel
547    /// underlies the pin.
548    pub async fn read_oneshot<PIN, CS>(&mut self, pin: &mut super::AdcPin<PIN, ADCX, CS>) -> u16
549    where
550        ADCX: Instance,
551        PIN: super::AdcChannel,
552        CS: super::AdcCalScheme<ADCX>,
553    {
554        self.start_sample(pin);
555
556        AdcFuture::<ADCX>::new(self).await;
557
558        let converted_value = ADCX::read_data();
559        ADCX::reset();
560
561        // Postprocess converted value according to calibration scheme used for pin
562        pin.cal_scheme.adc_val(converted_value)
563    }
564}
565
566static ASYNC_ADC_COUNT: AtomicU32 = AtomicU32::new(0);
567
568fn acquire_async_adc() {
569    ASYNC_ADC_COUNT.fetch_add(1, Ordering::Relaxed);
570}
571
572fn release_async_adc() -> bool {
573    ASYNC_ADC_COUNT.fetch_sub(1, Ordering::Relaxed) == 1
574}
575
576#[handler]
577pub(crate) fn adc_interrupt_handler() {
578    let interrupt_status = LP_ADC::regs().int_st().read();
579
580    #[cfg(adc_adc1)]
581    if interrupt_status.cocpu_saradc1_int_st().bit_is_set() {
582        unsafe { handle_async(crate::peripherals::ADC1::steal()) }
583    }
584
585    #[cfg(adc_adc2)]
586    if interrupt_status.cocpu_saradc2_int_st().bit_is_set() {
587        unsafe { handle_async(crate::peripherals::ADC2::steal()) }
588    }
589}
590
591fn handle_async<ADCX: Instance>(_instance: ADCX) {
592    ADCX::waker().wake();
593    ADCX::unlisten();
594}
595
596/// Enable asynchronous access.
597pub trait Instance: crate::private::Sealed {
598    /// Enable the ADC interrupt
599    fn listen();
600
601    /// Disable the ADC interrupt
602    fn unlisten();
603
604    /// Clear the ADC interrupt
605    fn clear_interrupt();
606
607    /// Obtain the waker for the ADC interrupt
608    fn waker() -> &'static AtomicWaker;
609}
610
611#[cfg(adc_adc1)]
612impl Instance for crate::peripherals::ADC1<'_> {
613    fn listen() {
614        LP_ADC::regs()
615            .int_ena_w1ts()
616            .write(|w| w.cocpu_saradc1_int_ena_w1ts().set_bit());
617    }
618
619    fn unlisten() {
620        LP_ADC::regs()
621            .int_ena_w1tc()
622            .write(|w| w.cocpu_saradc1_int_ena_w1tc().set_bit());
623    }
624
625    fn clear_interrupt() {
626        LP_ADC::regs()
627            .int_clr()
628            .write(|w| w.cocpu_saradc1_int_clr().set_bit());
629    }
630
631    fn waker() -> &'static AtomicWaker {
632        static WAKER: AtomicWaker = AtomicWaker::new();
633
634        &WAKER
635    }
636}
637
638#[cfg(adc_adc2)]
639impl Instance for crate::peripherals::ADC2<'_> {
640    fn listen() {
641        LP_ADC::regs()
642            .int_ena_w1ts()
643            .write(|w| w.cocpu_saradc2_int_ena_w1ts().set_bit());
644    }
645
646    fn unlisten() {
647        LP_ADC::regs()
648            .int_ena_w1tc()
649            .write(|w| w.cocpu_saradc2_int_ena_w1tc().set_bit());
650    }
651
652    fn clear_interrupt() {
653        LP_ADC::regs()
654            .int_clr()
655            .write(|w| w.cocpu_saradc2_int_clr().set_bit());
656    }
657
658    fn waker() -> &'static AtomicWaker {
659        static WAKER: AtomicWaker = AtomicWaker::new();
660
661        &WAKER
662    }
663}
664
665#[must_use = "futures do nothing unless you `.await` or poll them"]
666pub(crate) struct AdcFuture<ADCX: Instance> {
667    phantom: PhantomData<ADCX>,
668    _wake_lock: WakeLock,
669}
670
671impl<ADCX: Instance> AdcFuture<ADCX> {
672    pub fn new(_self: &super::Adc<'_, ADCX, Async>) -> Self {
673        ADCX::listen();
674        Self {
675            phantom: PhantomData,
676            _wake_lock: WakeLock::new(),
677        }
678    }
679}
680
681impl<ADCX: Instance + super::RegisterAccess> core::future::Future for AdcFuture<ADCX> {
682    type Output = ();
683
684    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
685        ADCX::waker().register(cx.waker());
686        if ADCX::is_done() {
687            ADCX::clear_interrupt();
688            Poll::Ready(())
689        } else {
690            Poll::Pending
691        }
692    }
693}
694
695impl<ADCX: Instance> Drop for AdcFuture<ADCX> {
696    fn drop(&mut self) {
697        ADCX::unlisten();
698    }
699}