Skip to main content

esp_hal/analog/adc/
riscv.rs

1use core::marker::PhantomData;
2
3cfg_select! {
4    any(esp32c6, esp32c61) => {
5        use Interrupt::APB_SARADC as InterruptSource;
6    }
7    _ => {
8        use Interrupt::APB_ADC as InterruptSource;
9    }
10}
11
12use core::{
13    pin::Pin,
14    task::{Context, Poll},
15};
16
17// We only have to count on devices that have multiple ADCs sharing the same interrupt
18#[cfg(all(adc_adc1, adc_adc2))]
19use portable_atomic::{AtomicU32, Ordering};
20use procmacros::handler;
21
22pub use self::calibration::*;
23use super::{AdcCalSource, AdcConfig, Attenuation};
24#[cfg(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2))]
25use crate::efuse::AdcCalibUnit;
26use crate::{
27    Async,
28    Blocking,
29    asynch::AtomicWaker,
30    interrupt::{InterruptConfigurable, InterruptHandler},
31    peripherals::{APB_SARADC, Interrupt},
32    rtc_cntl::WakeLock,
33    soc::regi2c,
34    system::{GenericPeripheralGuard, Peripheral},
35};
36
37mod calibration;
38
39// Constants taken from:
40// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c2/include/soc/regi2c_saradc.h
41// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c3/include/soc/regi2c_saradc.h
42// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c6/include/soc/regi2c_saradc.h
43// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h2/include/soc/regi2c_saradc.h
44cfg_select! {
45    adc_adc1 => {
46        const ADC_VAL_MASK: u16 = 0xfff;
47        const ADC_CAL_CNT_MAX: u16 = 32;
48        const ADC_CAL_CHANNEL: u16 = 15;
49    }
50    _ => {}
51}
52
53// The number of analog IO pins, and in turn the number of attentuations,
54// depends on which chip is being used
55cfg_select! {
56    esp32c6 => {
57        pub(super) const NUM_ATTENS: usize = 7;
58    }
59    esp32c5 => {
60        pub(super) const NUM_ATTENS: usize = 6;
61    }
62    esp32c61 => {
63        pub(super) const NUM_ATTENS: usize = 4;
64    }
65    _ => {
66        pub(super) const NUM_ATTENS: usize = 5;
67    }
68}
69
70impl<ADCX> AdcConfig<ADCX>
71where
72    ADCX: RegisterAccess,
73{
74    /// Calibrate ADC with specified attenuation and voltage source
75    pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16
76    where
77        ADCX: super::CalibrationAccess,
78    {
79        let mut adc_max: u16 = 0;
80        let mut adc_min: u16 = u16::MAX;
81        let mut adc_sum: u32 = 0;
82
83        ADCX::enable_vdef(true);
84
85        // Start sampling
86        ADCX::config_onetime_sample(ADC_CAL_CHANNEL as u8, atten as u8);
87
88        // Connect calibration source
89        ADCX::connect_cal(source, true);
90
91        ADCX::calibration_init();
92        for _ in 0..ADC_CAL_CNT_MAX {
93            ADCX::set_init_code(0);
94
95            // Trigger ADC sampling
96            ADCX::start_onetime_sample();
97
98            // Wait until ADC sampling is done
99            while !ADCX::is_done() {}
100
101            let adc = ADCX::read_data() & ADC_VAL_MASK;
102
103            ADCX::reset();
104
105            adc_sum += adc as u32;
106            adc_max = adc.max(adc_max);
107            adc_min = adc.min(adc_min);
108        }
109
110        let cal_val = (adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADC_CAL_CNT_MAX - 2);
111
112        // Disconnect calibration source
113        ADCX::connect_cal(source, false);
114
115        cal_val
116    }
117}
118
119#[doc(hidden)]
120pub trait RegisterAccess {
121    /// Configure onetime sampling parameters
122    fn config_onetime_sample(channel: u8, attenuation: u8);
123
124    /// Start onetime sampling
125    fn start_onetime_sample();
126
127    /// Check if sampling is done
128    fn is_done() -> bool;
129
130    /// Read sample data
131    fn read_data() -> u16;
132
133    /// Reset flags
134    fn reset();
135
136    /// Set up ADC hardware for calibration
137    fn calibration_init();
138
139    /// Set calibration parameter to ADC hardware
140    fn set_init_code(data: u16);
141}
142
143#[cfg(adc_adc1)]
144impl RegisterAccess for crate::peripherals::ADC1<'_> {
145    fn config_onetime_sample(channel: u8, attenuation: u8) {
146        APB_SARADC::regs().onetime_sample().modify(|_, w| unsafe {
147            w.saradc1_onetime_sample().set_bit();
148            w.onetime_channel().bits(channel);
149            w.onetime_atten().bits(attenuation)
150        });
151    }
152
153    fn start_onetime_sample() {
154        APB_SARADC::regs()
155            .onetime_sample()
156            .modify(|_, w| w.onetime_start().set_bit());
157    }
158
159    fn is_done() -> bool {
160        APB_SARADC::regs().int_raw().read().adc1_done().bit()
161    }
162
163    fn read_data() -> u16 {
164        APB_SARADC::regs()
165            .sar1data_status()
166            .read()
167            .saradc1_data()
168            .bits() as u16
169            & 0xfff
170    }
171
172    fn reset() {
173        // Clear ADC1 sampling done interrupt bit
174        APB_SARADC::regs()
175            .int_clr()
176            .write(|w| w.adc1_done().clear_bit_by_one());
177
178        // Disable ADC sampling
179        APB_SARADC::regs()
180            .onetime_sample()
181            .modify(|_, w| w.onetime_start().clear_bit());
182    }
183
184    fn calibration_init() {
185        // e.g.
186        // https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/hal/esp32c3/include/hal/adc_ll.h#L702
187        regi2c::ADC_SAR1_DREF.write_field(1);
188    }
189
190    fn set_init_code(data: u16) {
191        let [msb, lsb] = data.to_be_bytes();
192
193        regi2c::ADC_SAR1_INITIAL_CODE_HIGH.write_field(msb);
194        regi2c::ADC_SAR1_INITIAL_CODE_LOW.write_field(lsb);
195    }
196}
197
198#[cfg(adc_adc1)]
199impl super::CalibrationAccess for crate::peripherals::ADC1<'_> {
200    const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
201    const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
202    const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
203
204    fn enable_vdef(enable: bool) {
205        regi2c::ADC_SAR1_DREF.write_field(enable as _);
206    }
207
208    fn connect_cal(source: AdcCalSource, enable: bool) {
209        match source {
210            AdcCalSource::Gnd => regi2c::ADC_SAR1_ENCAL_GND.write_field(enable as _),
211            #[cfg(not(esp32h2))]
212            AdcCalSource::Ref => regi2c::ADC_SAR1_ENCAL_REF.write_field(enable as _),
213            // For the ESP32-H2 ground and internal reference voltage are mutually exclusive and
214            // you can toggle between them.
215            //
216            // See: <https://github.com/espressif/esp-idf/blob/5c51472e82a58098dda8d40a1c4f250c374fc900/components/hal/esp32h2/include/hal/adc_ll.h#L645>
217            #[cfg(esp32h2)]
218            AdcCalSource::Ref => regi2c::ADC_SAR1_ENCAL_GND.write_field(!enable as _),
219        }
220    }
221}
222
223#[cfg(adc_adc2)]
224impl RegisterAccess for crate::peripherals::ADC2<'_> {
225    fn config_onetime_sample(channel: u8, attenuation: u8) {
226        APB_SARADC::regs().onetime_sample().modify(|_, w| unsafe {
227            w.saradc2_onetime_sample().set_bit();
228            w.onetime_channel().bits(channel);
229            w.onetime_atten().bits(attenuation)
230        });
231    }
232
233    fn start_onetime_sample() {
234        APB_SARADC::regs()
235            .onetime_sample()
236            .modify(|_, w| w.onetime_start().set_bit());
237    }
238
239    fn is_done() -> bool {
240        APB_SARADC::regs().int_raw().read().adc2_done().bit()
241    }
242
243    fn read_data() -> u16 {
244        APB_SARADC::regs()
245            .sar2data_status()
246            .read()
247            .saradc2_data()
248            .bits() as u16
249            & 0xfff
250    }
251
252    fn reset() {
253        APB_SARADC::regs()
254            .int_clr()
255            .write(|w| w.adc2_done().clear_bit_by_one());
256
257        APB_SARADC::regs()
258            .onetime_sample()
259            .modify(|_, w| w.onetime_start().clear_bit());
260    }
261
262    fn calibration_init() {
263        regi2c::ADC_SAR2_DREF.write_field(1);
264    }
265
266    fn set_init_code(data: u16) {
267        let [msb, lsb] = data.to_be_bytes();
268
269        regi2c::ADC_SAR2_INITIAL_CODE_HIGH.write_field(msb as _);
270        regi2c::ADC_SAR2_INITIAL_CODE_LOW.write_field(lsb as _);
271    }
272}
273
274#[cfg(adc_adc2)]
275impl super::CalibrationAccess for crate::peripherals::ADC2<'_> {
276    const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
277    const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
278    const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
279
280    fn enable_vdef(enable: bool) {
281        regi2c::ADC_SAR2_DREF.write_field(enable as _);
282    }
283
284    fn connect_cal(source: AdcCalSource, enable: bool) {
285        match source {
286            AdcCalSource::Gnd => regi2c::ADC_SAR2_ENCAL_GND.write_field(enable as _),
287            AdcCalSource::Ref => regi2c::ADC_SAR2_ENCAL_REF.write_field(enable as _),
288        }
289    }
290}
291
292/// Analog-to-Digital Converter peripheral driver.
293pub struct Adc<'d, ADCX, Dm: crate::DriverMode> {
294    _adc: ADCX,
295    attenuations: [Option<Attenuation>; NUM_ATTENS],
296    active_channel: Option<u8>,
297    _guard: GenericPeripheralGuard<{ Peripheral::ApbSarAdc as u8 }>,
298    _phantom: PhantomData<(Dm, &'d mut ())>,
299}
300
301impl<'d, ADCX> Adc<'d, ADCX, Blocking>
302where
303    ADCX: RegisterAccess + 'd,
304{
305    /// Configure a given ADC instance using the provided configuration, and
306    /// initialize the ADC for use
307    pub fn new(adc_instance: ADCX, config: AdcConfig<ADCX>) -> Self {
308        let guard = GenericPeripheralGuard::new();
309
310        APB_SARADC::regs().ctrl().modify(|_, w| unsafe {
311            w.start_force().set_bit();
312            w.start().set_bit();
313            w.sar_clk_gated().set_bit();
314            w.xpd_sar_force().bits(0b11)
315        });
316
317        Adc {
318            _adc: adc_instance,
319            attenuations: config.attenuations,
320            active_channel: None,
321            _guard: guard,
322            _phantom: PhantomData,
323        }
324    }
325
326    /// Reconfigures the ADC driver to operate in asynchronous mode.
327    pub fn into_async(mut self) -> Adc<'d, ADCX, Async> {
328        acquire_async_adc();
329        self.set_interrupt_handler(adc_interrupt_handler);
330
331        // Reset interrupt flags and disable oneshot reading to normalize state before
332        // entering async mode, otherwise there can be '0' readings, happening initially
333        // using ADC2
334        ADCX::reset();
335
336        Adc {
337            _adc: self._adc,
338            attenuations: self.attenuations,
339            active_channel: self.active_channel,
340            _guard: self._guard,
341            _phantom: PhantomData,
342        }
343    }
344
345    /// Request that the ADC begin a conversion on the specified pin
346    ///
347    /// This method takes an [AdcPin](super::AdcPin) reference, as it is
348    /// expected that the ADC will be able to sample whatever channel
349    /// underlies the pin.
350    pub fn read_oneshot<PIN, CS>(
351        &mut self,
352        pin: &mut super::AdcPin<PIN, ADCX, CS>,
353    ) -> nb::Result<u16, ()>
354    where
355        PIN: super::AdcChannel,
356        CS: super::AdcCalScheme<ADCX>,
357    {
358        if self.attenuations[pin.pin.adc_channel() as usize].is_none() {
359            panic!(
360                "Channel {} is not configured reading!",
361                pin.pin.adc_channel()
362            );
363        }
364
365        if let Some(active_channel) = self.active_channel {
366            // There is conversion in progress:
367            // - if it's for a different channel try again later
368            // - if it's for the given channel, go ahead and check progress
369            if active_channel != pin.pin.adc_channel() {
370                return Err(nb::Error::WouldBlock);
371            }
372        } else {
373            // If no conversions are in progress, start a new one for given channel
374            self.active_channel = Some(pin.pin.adc_channel());
375
376            // Set ADC unit calibration according used scheme for pin
377            ADCX::calibration_init();
378            ADCX::set_init_code(pin.cal_scheme.adc_cal());
379
380            let channel = self.active_channel.unwrap();
381            let attenuation = self.attenuations[channel as usize].unwrap() as u8;
382            ADCX::config_onetime_sample(channel, attenuation);
383            ADCX::start_onetime_sample();
384
385            // see https://github.com/espressif/esp-idf/blob/b4268c874a4cf8fcf7c0c4153cffb76ad2ddda4e/components/hal/adc_oneshot_hal.c#L105-L107
386            // the delay might be a bit generous but longer delay seem to not cause problems
387            #[cfg(esp32c6)]
388            {
389                crate::rom::ets_delay_us(40);
390                ADCX::start_onetime_sample();
391            }
392        }
393
394        // Wait for ADC to finish conversion
395        let conversion_finished = ADCX::is_done();
396        if !conversion_finished {
397            return Err(nb::Error::WouldBlock);
398        }
399
400        // Get converted value
401        let converted_value = ADCX::read_data();
402        ADCX::reset();
403
404        // Postprocess converted value according to calibration scheme used for pin
405        let converted_value = pin.cal_scheme.adc_val(converted_value);
406
407        // There is a hardware limitation. If the APB clock frequency is high, the step
408        // of this reg signal: ``onetime_start`` may not be captured by the
409        // ADC digital controller (when its clock frequency is too slow). A rough
410        // estimate for this step should be at least 3 ADC digital controller
411        // clock cycle.
412        //
413        // This limitation will be removed in hardware future versions.
414        // We reset ``onetime_start`` in `reset` and assume enough time has passed until
415        // the next sample is requested.
416
417        // Mark that no conversions are currently in progress
418        self.active_channel = None;
419
420        Ok(converted_value)
421    }
422}
423
424impl<ADCX> crate::private::Sealed for Adc<'_, ADCX, Blocking> {}
425
426impl<ADCX> InterruptConfigurable for Adc<'_, ADCX, Blocking> {
427    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
428        for core in crate::system::Cpu::other() {
429            crate::interrupt::disable(core, InterruptSource);
430        }
431        crate::interrupt::bind_handler(InterruptSource, handler);
432    }
433}
434
435#[cfg(adc_adc1)]
436impl super::AdcCalEfuse for crate::peripherals::ADC1<'_> {
437    fn init_code(atten: Attenuation) -> Option<u16> {
438        crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC1, atten)
439    }
440
441    fn cal_mv(atten: Attenuation) -> u16 {
442        crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC1, atten)
443    }
444
445    fn cal_code(atten: Attenuation) -> Option<u16> {
446        crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC1, atten)
447    }
448
449    #[cfg(any(esp32c5, esp32c6, esp32c61, esp32h2))]
450    fn cal_chan_compens(atten: Attenuation, channel: u8) -> Option<i32> {
451        crate::efuse::rtc_calib_get_chan_compens(AdcCalibUnit::ADC1, channel, atten)
452    }
453}
454
455#[cfg(adc_adc2)]
456impl super::AdcCalEfuse for crate::peripherals::ADC2<'_> {
457    fn init_code(atten: Attenuation) -> Option<u16> {
458        crate::efuse::rtc_calib_init_code(AdcCalibUnit::ADC2, atten)
459    }
460
461    fn cal_mv(atten: Attenuation) -> u16 {
462        crate::efuse::rtc_calib_cal_mv(AdcCalibUnit::ADC2, atten)
463    }
464
465    fn cal_code(atten: Attenuation) -> Option<u16> {
466        crate::efuse::rtc_calib_cal_code(AdcCalibUnit::ADC2, atten)
467    }
468}
469
470impl<'d, ADCX> Adc<'d, ADCX, Async>
471where
472    ADCX: RegisterAccess + 'd,
473{
474    /// Create a new instance in [crate::Blocking] mode.
475    pub fn into_blocking(self) -> Adc<'d, ADCX, Blocking> {
476        if release_async_adc() {
477            // Disable ADC interrupt on all cores if the last async ADC instance is disabled
478            for cpu in crate::system::Cpu::all() {
479                crate::interrupt::disable(cpu, InterruptSource);
480            }
481        }
482        Adc {
483            _adc: self._adc,
484            attenuations: self.attenuations,
485            active_channel: self.active_channel,
486            _guard: self._guard,
487            _phantom: PhantomData,
488        }
489    }
490
491    /// Request that the ADC begin a conversion on the specified pin
492    ///
493    /// This method takes an [AdcPin](super::AdcPin) reference, as it is
494    /// expected that the ADC will be able to sample whatever channel
495    /// underlies the pin.
496    pub async fn read_oneshot<PIN, CS>(&mut self, pin: &mut super::AdcPin<PIN, ADCX, CS>) -> u16
497    where
498        ADCX: Instance,
499        PIN: super::AdcChannel,
500        CS: super::AdcCalScheme<ADCX>,
501    {
502        let channel = pin.pin.adc_channel();
503        if self.attenuations[channel as usize].is_none() {
504            panic!("Channel {} is not configured reading!", channel);
505        }
506
507        // Set ADC unit calibration according used scheme for pin
508        ADCX::calibration_init();
509        ADCX::set_init_code(pin.cal_scheme.adc_cal());
510
511        let attenuation = self.attenuations[channel as usize].unwrap() as u8;
512        ADCX::config_onetime_sample(channel, attenuation);
513        ADCX::start_onetime_sample();
514
515        // Wait for ADC to finish conversion and get value
516        let adc_ready_future = AdcFuture::new(self);
517        adc_ready_future.await;
518        let converted_value = ADCX::read_data();
519
520        // There is a hardware limitation. If the APB clock frequency is high, the step
521        // of this reg signal: ``onetime_start`` may not be captured by the
522        // ADC digital controller (when its clock frequency is too slow). A rough
523        // estimate for this step should be at least 3 ADC digital controller
524        // clock cycle.
525        //
526        // This limitation will be removed in hardware future versions.
527        // We reset ``onetime_start`` in `reset` and assume enough time has passed until
528        // the next sample is requested.
529
530        ADCX::reset();
531
532        // Postprocess converted value according to calibration scheme used for pin
533        pin.cal_scheme.adc_val(converted_value)
534    }
535}
536
537#[cfg(all(adc_adc1, adc_adc2))]
538static ASYNC_ADC_COUNT: AtomicU32 = AtomicU32::new(0);
539
540pub(super) fn acquire_async_adc() {
541    #[cfg(all(adc_adc1, adc_adc2))]
542    ASYNC_ADC_COUNT.fetch_add(1, Ordering::Relaxed);
543}
544
545pub(super) fn release_async_adc() -> bool {
546    cfg_select! {
547        all(adc_adc1, adc_adc2) => ASYNC_ADC_COUNT.fetch_sub(1, Ordering::Relaxed) == 1,
548        _ => true,
549    }
550}
551
552#[handler]
553pub(crate) fn adc_interrupt_handler() {
554    let saradc = APB_SARADC::regs();
555    let interrupt_status = saradc.int_st().read();
556
557    #[cfg(adc_adc1)]
558    if interrupt_status.adc1_done().bit_is_set() {
559        unsafe { handle_async(crate::peripherals::ADC1::steal()) }
560    }
561
562    #[cfg(adc_adc2)]
563    if interrupt_status.adc2_done().bit_is_set() {
564        unsafe { handle_async(crate::peripherals::ADC2::steal()) }
565    }
566}
567
568fn handle_async<ADCX: Instance>(_instance: ADCX) {
569    ADCX::waker().wake();
570    ADCX::unlisten();
571}
572
573/// Enable asynchronous access.
574pub trait Instance: crate::private::Sealed {
575    /// Enable the ADC interrupt
576    fn listen();
577
578    /// Disable the ADC interrupt
579    fn unlisten();
580
581    /// Clear the ADC interrupt
582    fn clear_interrupt();
583
584    /// Obtain the waker for the ADC interrupt
585    fn waker() -> &'static AtomicWaker;
586}
587
588#[cfg(adc_adc1)]
589impl Instance for crate::peripherals::ADC1<'_> {
590    fn listen() {
591        APB_SARADC::regs()
592            .int_ena()
593            .modify(|_, w| w.adc1_done().set_bit());
594    }
595
596    fn unlisten() {
597        APB_SARADC::regs()
598            .int_ena()
599            .modify(|_, w| w.adc1_done().clear_bit());
600    }
601
602    fn clear_interrupt() {
603        APB_SARADC::regs()
604            .int_clr()
605            .write(|w| w.adc1_done().clear_bit_by_one());
606    }
607
608    fn waker() -> &'static AtomicWaker {
609        static WAKER: AtomicWaker = AtomicWaker::new();
610
611        &WAKER
612    }
613}
614
615#[cfg(adc_adc2)]
616impl Instance for crate::peripherals::ADC2<'_> {
617    fn listen() {
618        APB_SARADC::regs()
619            .int_ena()
620            .modify(|_, w| w.adc2_done().set_bit());
621    }
622
623    fn unlisten() {
624        APB_SARADC::regs()
625            .int_ena()
626            .modify(|_, w| w.adc2_done().clear_bit());
627    }
628
629    fn clear_interrupt() {
630        APB_SARADC::regs()
631            .int_clr()
632            .write(|w| w.adc2_done().clear_bit_by_one());
633    }
634
635    fn waker() -> &'static AtomicWaker {
636        static WAKER: AtomicWaker = AtomicWaker::new();
637
638        &WAKER
639    }
640}
641
642#[must_use = "futures do nothing unless you `.await` or poll them"]
643pub(crate) struct AdcFuture<ADCX: Instance> {
644    phantom: PhantomData<ADCX>,
645    _wake_lock: WakeLock,
646}
647
648impl<ADCX: Instance> AdcFuture<ADCX> {
649    pub fn new(_self: &super::Adc<'_, ADCX, Async>) -> Self {
650        Self {
651            phantom: PhantomData,
652            _wake_lock: WakeLock::new(),
653        }
654    }
655}
656
657impl<ADCX: Instance + super::RegisterAccess> core::future::Future for AdcFuture<ADCX> {
658    type Output = ();
659
660    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
661        if ADCX::is_done() {
662            ADCX::clear_interrupt();
663            Poll::Ready(())
664        } else {
665            ADCX::waker().register(cx.waker());
666            ADCX::listen();
667            Poll::Pending
668        }
669    }
670}
671
672impl<ADCX: Instance> Drop for AdcFuture<ADCX> {
673    fn drop(&mut self) {
674        ADCX::unlisten();
675    }
676}