gd32e2-hal 0.26.0

HAL for the GD32E23x series (Cortex-M23), currently the GD32E230
Documentation
//! 12-bit analog-to-digital converter.
//!
//! Single-channel conversions, software triggered: blocking through
//! [`Adc::read`], or started with [`Adc::start`] and collected with
//! [`Adc::result`] on [`Event::Eoc`]. The ADC needs its own clock, which is
//! *not* started by default — call
//! [`ClockConfig::adc_sel`](crate::rcu::ClockConfig::adc_sel) before
//! constructing an [`Adc`].
//!
//! ```ignore
//! let config = ClockConfig::default().adc_sel(AdcSel::Prescaled(AdcPsc::Apb2Div8));
//! let mut rcu = dp.rcu.constrain().freeze(&mut fmc, config);
//! let mut adc = dp.adc.constrain(&mut rcu);
//! let pin = parts.pa0.into_analog();
//! let raw = adc.read(&pin, SampTime::Cycles55_5);
//! ```

use crate::gpio::{Analog, Pin};
use crate::pac;
use crate::rcu::{Clocks, Enable, Rcu, Reset};

const VREFINT_CAL_ADDR: *const u16 = 0x1FFF_F7C0 as *const u16;
/// Erased-flash value: the factory VREFINT calibration is missing on this part.
const VREFINT_CAL_BLANK: u16 = 0xFFFF;
/// Typical VREFINT (datasheet 4.13): ~1.2 V. Used only when the factory
/// calibration is blank, so `VDDA` can still be estimated ratiometrically.
const VREFINT_TYP_MV: i32 = 1200;
const NOMINAL_VDDA_MV: i32 = 3300;
const ADC_MAX_CODE: i32 = 4095;
/// Temperature sensor output at 25 °C.
const V25_MV: i32 = 1450;
/// Temperature sensor slope, 4.3 mV/°C, scaled by 10 to stay integer.
const AVG_SLOPE_X10: i32 = 43;

const TEMP_CHANNEL: u8 = 16;
const VREF_CHANNEL: u8 = 17;

/// Cycles of `CK_ADC` to wait between ADCON and calibration (manual, 11.4.1).
const CALIBRATION_DELAY_CYCLES: u32 = 14;
/// Minimum sampling time for the temperature sensor: 17.1 us (manual, 10.4.11).
const TEMP_MIN_SAMPTIME_US_X10: u64 = 171;
/// The longest available `SampTime` (`Cycles239_5`), scaled by 10 to stay integer.
const MAX_SAMPTIME_CYCLES_X10: u64 = 2395;
const US_PER_S: u64 = 1_000_000;

/// How long the input is sampled before conversion, in `CK_ADC` cycles.
///
/// Longer sampling suits higher-impedance sources; the internal temperature
/// sensor has a minimum requirement expressed in *time*, so how long is long
/// enough depends on the actual `CK_ADC` frequency.
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)]
pub enum SampTime {
    Cycles1_5 = 0b000,
    Cycles7_5 = 0b001,
    Cycles13_5 = 0b010,
    Cycles28_5 = 0b011,
    Cycles41_5 = 0b100,
    Cycles55_5 = 0b101,
    Cycles71_5 = 0b110,
    Cycles239_5 = 0b111,
}

/// Binds a pin to the ADC input number it is wired to.
///
/// Implemented only for pins in [`Analog`] mode, so a pin that hasn't been
/// through [`into_analog`](crate::gpio::Pin::into_analog) can't be measured.
pub trait Channel {
    /// The ADC input number for this pin.
    const CHANNEL: u8;
}

macro_rules! channel {
    ($($(#[$cfg:meta])? $port:literal $pin:literal => $channel:literal),+ $(,)?) => {
        $($(#[$cfg])? impl Channel for Pin<$port, $pin, Analog> { const CHANNEL: u8 = $channel; })+
    };
}

channel!(
    'A' 0 => 0,
    'A' 1 => 1,
    'A' 2 => 2,
    'A' 3 => 3,
    'A' 4 => 4,
    'A' 5 => 5,
    'A' 6 => 6,
    'A' 7 => 7,
    #[cfg(pads_ge_24)] 'B' 0 => 8,
    'B' 1 => 9,
);

/// An ADC event that can raise an interrupt.
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Event {
    /// A conversion finished; `RDATA` holds the result. `EOC` clears on
    /// reading it, the same read a handler makes to fetch the value.
    Eoc,
}

/// A calibrated ADC, ready to convert.
pub struct Adc {
    adc: pac::Adc,
    clocks: Clocks,
}

impl Adc {
    /// Returns the peripheral, powered down, with its interrupt off and its
    /// clock stopped. A conversion still running is abandoned.
    pub fn release(mut self, rcu: &mut Rcu) -> pac::Adc {
        self.unlisten(Event::Eoc);
        self.adc.ctl1().modify(|_, w| w.adcon().disabled());
        <pac::Adc as Enable>::disable(rcu);
        self.adc
    }

    fn set_channel(&mut self, channel: u8) {
        self.adc.rsq0().modify(|_, w| w.rl().bits(0b0));
        // SAFETY: `channel` is an input number — 0–9 from a `Channel` impl, or
        // `TEMP_CHANNEL`/`VREF_CHANNEL` — each one the field can select.
        self.adc
            .rsq2()
            .modify(|_, w| unsafe { w.rsq0().bits(channel) });
    }
    fn set_sample_time(&mut self, channel: u8, time: SampTime) {
        self.adc.sampt1().modify(|_, w| match channel {
            0 => w.spt0().bits(time as u8),
            1 => w.spt1().bits(time as u8),
            2 => w.spt2().bits(time as u8),
            3 => w.spt3().bits(time as u8),
            4 => w.spt4().bits(time as u8),
            5 => w.spt5().bits(time as u8),
            6 => w.spt6().bits(time as u8),
            7 => w.spt7().bits(time as u8),
            8 => w.spt8().bits(time as u8),
            9 => w.spt9().bits(time as u8),
            _ => unreachable!(),
        });
    }
    fn set_internal_channel(&mut self, channel: u8) {
        self.set_channel(channel);
    }
    fn sample_time_sufficient(&self) -> bool {
        TEMP_MIN_SAMPTIME_US_X10 * self.clocks.ck_adc().to_Hz() as u64
            <= MAX_SAMPTIME_CYCLES_X10 * US_PER_S
    }
    fn set_internal_sample_time(&mut self, channel: u8, time: SampTime) {
        self.adc.sampt0().modify(|_, w| match channel {
            TEMP_CHANNEL => w.spt16().bits(time as u8),
            VREF_CHANNEL => w.spt17().bits(time as u8),
            _ => unreachable!(),
        });
    }
    fn convert(&mut self) -> u16 {
        self.start_conversion();
        while self.adc.stat().read().eoc().is_not_complete() {}
        self.read_result()
    }
    fn start_conversion(&mut self) {
        self.adc.ctl1().modify(|_, w| w.swrcst().start());
        while self.adc.ctl1().read().swrcst().is_not_started() {}
    }
    fn read_result(&mut self) -> u16 {
        self.adc.rdata().read().rdata().bits()
    }
    fn with_internal<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
        let was_enabled = self.adc.ctl1().read().tsvren().is_enabled();
        if !was_enabled {
            self.adc.ctl1().modify(|_, w| w.tsvren().enabled());
        }
        let result = f(self);
        if !was_enabled {
            self.adc.ctl1().modify(|_, w| w.tsvren().disabled());
        }
        result
    }

    /// Triggers a conversion, blocking only until the trigger is acknowledged —
    /// not until it finishes. Pairs with [`listen`](Self::listen) and
    /// [`result`](Self::result) for the interrupt-driven path.
    pub fn start<PIN: Channel>(&mut self, _pin: &PIN, time: SampTime) {
        self.set_channel(PIN::CHANNEL);
        self.set_sample_time(PIN::CHANNEL, time);
        self.start_conversion();
    }
    /// Reads the conversion result out of `RDATA`, clearing `EOC`.
    ///
    /// Does not check `EOC` itself — call this once notified of it (an `Eoc`
    /// handler after [`listen`](Self::listen)), not on a guess.
    pub fn result(&mut self) -> u16 {
        self.read_result()
    }

    /// Lets `event` raise an interrupt. Unmasking the line in the NVIC is the
    /// caller's.
    pub fn listen(&mut self, event: Event) {
        match event {
            Event::Eoc => self.adc.ctl0().modify(|_, w| w.eocie().enabled()),
        }
    }
    /// Stops `event` from raising an interrupt.
    pub fn unlisten(&mut self, event: Event) {
        match event {
            Event::Eoc => self.adc.ctl0().modify(|_, w| w.eocie().disabled()),
        }
    }

    /// Converts one channel and returns the raw 12-bit code (0..=4095).
    ///
    /// The pin is borrowed only to identify the channel — nothing is read from
    /// the value itself. Blocks until the conversion finishes.
    pub fn read<PIN: Channel>(&mut self, _pin: &PIN, time: SampTime) -> u16 {
        self.set_channel(PIN::CHANNEL);
        self.set_sample_time(PIN::CHANNEL, time);
        self.convert()
    }
    /// Reads the internal temperature sensor, in tenths of a degree Celsius.
    ///
    /// Scaled against the real supply from [`read_vref`](Self::read_vref).
    ///
    /// Returns `None` when `CK_ADC` runs too fast for the sensor's minimum
    /// sampling time: [`SampTime`] is counted in cycles, so above roughly 14
    /// MHz even the longest no longer spans the required 17.1 µs.
    pub fn read_temperature(&mut self) -> Option<i32> {
        if !self.sample_time_sufficient() {
            return None;
        }
        let vdda_mv = self.read_vref();
        let raw = self.with_internal(|s| {
            s.set_internal_channel(TEMP_CHANNEL);
            s.set_internal_sample_time(TEMP_CHANNEL, SampTime::Cycles239_5);
            s.convert()
        });
        let v_temperature_mv = raw as i32 * vdda_mv / ADC_MAX_CODE;
        let temperature_x10 = 100 * (V25_MV - v_temperature_mv) / AVG_SLOPE_X10 + 250;
        Some(temperature_x10)
    }
    /// Measures the actual analog supply voltage `VDDA`, in millivolts.
    ///
    /// The internal reference is fixed, so its raw code moves only because `VDDA`
    /// — the ADC's own reference — moved; comparing it against the factory
    /// calibration in flash gives the real supply.
    ///
    /// If that calibration is blank (`0xFFFF`, seen on some parts), falls back to
    /// the typical VREFINT of ~1.2 V: less accurate, but not wildly wrong.
    pub fn read_vref(&mut self) -> i32 {
        // SAFETY: `VREFINT_CAL_ADDR` is an aligned half-word of system memory,
        // always readable and never written.
        let vrefint_cal = unsafe { core::ptr::read_volatile(VREFINT_CAL_ADDR) };
        let raw = self.with_internal(|s| {
            s.set_internal_channel(VREF_CHANNEL);
            s.set_internal_sample_time(VREF_CHANNEL, SampTime::Cycles239_5);
            s.convert()
        }) as i32;
        if vrefint_cal == VREFINT_CAL_BLANK {
            VREFINT_TYP_MV * ADC_MAX_CODE / raw
        } else {
            NOMINAL_VDDA_MV * vrefint_cal as i32 / raw
        }
    }
}

/// Entry point on the raw peripheral.
pub trait AdcExt {
    /// Enables the peripheral, powers it up and runs the calibration sequence.
    ///
    /// Blocks until calibration completes.
    ///
    /// # Panics
    ///
    /// If the ADC clock was never selected —
    /// [`Clocks::ck_adc`](crate::rcu::Clocks::ck_adc) is then zero and the
    /// calibration delay divides by it. Configure the clock with
    /// [`ClockConfig::adc_sel`](crate::rcu::ClockConfig::adc_sel) first.
    fn constrain(self, rcu: &mut Rcu) -> Adc;
}

impl AdcExt for pac::Adc {
    fn constrain(self, rcu: &mut Rcu) -> Adc {
        let clocks = rcu.clocks();
        <pac::Adc as Enable>::enable(rcu);
        <pac::Adc as Reset>::reset(rcu);
        self.ctl1().modify(|_, w| w.adcon().enabled());
        cortex_m::asm::delay(
            (CALIBRATION_DELAY_CYCLES * clocks.hclk().to_Hz()).div_ceil(clocks.ck_adc().to_Hz()),
        );
        self.ctl1().modify(|_, w| w.rstclb().start());
        self.ctl1().modify(|_, w| w.clb().start());
        while self.ctl1().read().clb().is_not_complete() {}
        self.ctl1()
            .modify(|_, w| w.eterc().enabled().etsrc().swrcst());
        Adc { adc: self, clocks }
    }
}