feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
use core::marker::PhantomData;
use crate::analog::adc::{
AdcCalEfuse,
AdcCalScheme,
AdcCalSource,
AdcConfig,
Attenuation,
CalibrationAccess,
};
/// Basic ADC calibration scheme
///
/// Basic calibration sets the initial ADC bias value so that a zero voltage
/// gives a reading of zero. The correct bias value is usually stored in efuse,
/// but can also be measured at runtime by connecting the ADC input to ground
/// internally.
///
/// Failing to apply basic calibration can substantially reduce the ADC's output
/// range because bias correction is done *before* the ADC's output is truncated
/// to 12 bits.
#[derive(Clone, Copy)]
pub struct AdcCalBasic<ADCI> {
/// Calibration value to set to ADC unit
cal_val: u16,
_phantom: PhantomData<ADCI>,
}
impl<ADCI> crate::private::Sealed for AdcCalBasic<ADCI> {}
impl<ADCI> AdcCalScheme<ADCI> for AdcCalBasic<ADCI>
where
ADCI: AdcCalEfuse + CalibrationAccess,
{
fn new_cal(atten: Attenuation) -> Self {
// Try to get init code (Dout0) from efuse
// Dout0 means mean raw ADC value when zero voltage applied to input.
let cal_val = ADCI::init_code(atten).unwrap_or_else(|| {
// As a fallback try to calibrate via connecting input to ground internally.
AdcConfig::<ADCI>::adc_calibrate(atten, AdcCalSource::Gnd)
});
Self {
cal_val,
_phantom: PhantomData,
}
}
fn adc_cal(&self) -> u16 {
self.cal_val
}
}
+252
View File
@@ -0,0 +1,252 @@
use core::marker::PhantomData;
use crate::analog::adc::{
AdcCalEfuse,
AdcCalLine,
AdcCalScheme,
AdcHasLineCal,
Attenuation,
CalibrationAccess,
};
const COEFF_MUL: i64 = 1 << 52;
/// Integer type for the error polynomial's coefficients. Despite
/// the type, this is a fixed-point number with 52 fractional bits.
type CurveCoeff = i64;
/// Polynomial coefficients for specified attenuation.
pub struct CurveCoeffs {
/// Attenuation
atten: Attenuation,
/// Polynomial coefficients
coeff: &'static [CurveCoeff],
}
type CurvesCoeffs = &'static [CurveCoeffs];
/// Marker trait for ADC which support curve fitting
///
/// See also [`AdcCalCurve`].
pub trait AdcHasCurveCal {
/// Coefficients for calculating the reading voltage error.
///
/// A sets of coefficients for each attenuation.
const CURVES_COEFFS: CurvesCoeffs;
}
/// Curve fitting ADC calibration scheme
///
/// This scheme implements polynomial error correction using predefined
/// coefficient sets for each attenuation. It returns readings in mV.
///
/// This scheme also includes basic calibration ([`super::AdcCalBasic`]) and
/// line fitting ([`AdcCalLine`]).
#[derive(Clone, Copy)]
pub struct AdcCalCurve<ADCI> {
line: AdcCalLine<ADCI>,
/// Coefficients of the error estimation polynomial.
///
/// The constant coefficient comes first; the error polynomial is
/// `coeff[0] + coeff[1] * x + ... + coeff[n] * x^n`.
///
/// This calibration works by first applying linear calibration. Then
/// the error polynomial is applied to the output of linear calibration.
/// The output of the polynomial is our estimate of the error; it gets
/// subtracted from linear calibration's output to get the final reading.
coeff: &'static [CurveCoeff],
_phantom: PhantomData<ADCI>,
}
impl<ADCI> crate::private::Sealed for AdcCalCurve<ADCI> {}
impl<ADCI> AdcCalScheme<ADCI> for AdcCalCurve<ADCI>
where
ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + CalibrationAccess,
{
fn new_cal(atten: Attenuation) -> Self {
let line = AdcCalLine::<ADCI>::new_cal(atten);
let coeff = ADCI::CURVES_COEFFS
.iter()
.find(|item| item.atten == atten)
.expect("No curve coefficients for given attenuation")
.coeff;
Self {
line,
coeff,
_phantom: PhantomData,
}
}
fn adc_cal(&self) -> u16 {
self.line.adc_cal()
}
fn adc_val(&self, val: u16) -> u16 {
let val = self.line.adc_val(val);
let err = if val == 0 {
0
} else {
// err = coeff[0] + coeff[1] * val + coeff[2] * val^2 + ... + coeff[n] * val^n
let mut var = 1i64;
let mut err = (var * self.coeff[0] / COEFF_MUL) as i32;
for coeff in &self.coeff[1..] {
var *= val as i64;
err += (var * *coeff / COEFF_MUL) as i32;
}
err
};
(val as i32 - err) as u16
}
}
macro_rules! coeff_tables {
($($(#[$($meta:meta)*])* $name:ident [ $($att:ident => [ $($val:literal,)* ],)* ];)*) => {
$(
$(#[$($meta)*])*
const $name: CurvesCoeffs = &[
$(CurveCoeffs {
atten: Attenuation::$att,
coeff: &[
$(($val as f64 * COEFF_MUL as f64) as CurveCoeff,)*
],
},)*
];
)*
};
}
#[cfg(any(esp32c3, esp32c6, esp32s3))]
mod impls {
use super::*;
impl AdcHasCurveCal for crate::peripherals::ADC1 {
const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1;
}
#[cfg(esp32c3)]
impl AdcHasCurveCal for crate::peripherals::ADC2 {
const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1;
}
#[cfg(esp32s3)]
impl AdcHasCurveCal for crate::peripherals::ADC2 {
const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS2;
}
coeff_tables! {
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c3/curve_fitting_coefficients.c>
#[cfg(esp32c3)]
CURVES_COEFFS1 [
Attenuation0dB => [
-0.225966470500043,
-0.0007265418501948,
0.0000109410402681,
],
Attenuation2p5dB => [
0.4229623392600516,
-0.0000731527490903,
0.0000088166562521,
],
Attenuation6dB => [
-1.017859239236435,
-0.0097159265299153,
0.0000149794028038,
],
Attenuation11dB => [
-1.4912262772850453,
-0.0228549975564099,
0.0000356391935717,
-0.0000000179964582,
0.0000000000042046,
],
];
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c6/curve_fitting_coefficients.c>
#[cfg(esp32c6)]
CURVES_COEFFS1 [
Attenuation0dB => [
-0.0487166399931449,
0.0006436483033201,
0.0000030410131806,
],
Attenuation2p5dB => [
-0.8665498165817785,
0.0015239070452946,
0.0000013818878844,
],
Attenuation6dB => [
-1.2277821756674387,
0.0022275554717885,
0.0000005924302667,
],
Attenuation11dB => [
-0.3801417550380255,
-0.0006020352420772,
0.0000012442478488,
],
];
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
#[cfg(esp32s3)]
CURVES_COEFFS1 [
Attenuation0dB => [
-2.7856531419538344,
-0.0050871540569528,
0.0000097982495890,
],
Attenuation2p5dB => [
-2.9831022915028695,
-0.0049393185868806,
0.0000101379430548,
],
Attenuation6dB => [
-2.3285545746296417,
-0.0147640181047414,
0.0000208385525314,
],
Attenuation11dB => [
-0.644403418269478,
-0.0644334888647536,
0.0001297891447611,
-0.0000000707697180,
0.0000000000135150,
],
];
/// Error curve coefficients derived from <https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c>
#[cfg(esp32s3)]
CURVES_COEFFS2 [
Attenuation0dB => [
-2.5668651654328927,
0.0001353548869615,
0.0000036615265189,
],
Attenuation2p5dB => [
-2.3690184690298404,
-0.0066319894226185,
0.0000118964995959,
],
Attenuation6dB => [
-0.9452499397020617,
-0.0200996773954387,
0.00000259011467956,
],
Attenuation11dB => [
1.2247719764336924,
-0.0755717904943462,
0.0001478791187119,
-0.0000000796725280,
0.0000000000150380,
],
];
}
}
+104
View File
@@ -0,0 +1,104 @@
use core::marker::PhantomData;
use crate::analog::adc::{
AdcCalBasic,
AdcCalEfuse,
AdcCalScheme,
AdcCalSource,
AdcConfig,
Attenuation,
CalibrationAccess,
};
/// Marker trait for ADC units which support line fitting
///
/// Usually it means that reference points are stored in efuse.
/// See also [`AdcCalLine`].
pub trait AdcHasLineCal {}
/// We store the gain as a u32, but it's really a fixed-point number.
const GAIN_SCALE: u32 = 1 << 16;
/// Line fitting ADC calibration scheme
///
/// This scheme implements gain correction based on reference points, and
/// returns readings in mV.
///
/// A reference point is a pair of a reference voltage and the corresponding
/// mean raw digital ADC value. Such values are usually stored in efuse bit
/// fields for each supported attenuation.
///
/// Also it can be measured in runtime by connecting ADC to reference voltage
/// internally but this method is not so good because actual reference voltage
/// may varies in range 1.0..=1.2 V. Currently this method is used as a fallback
/// (with 1.1 V by default) when calibration data is missing.
///
/// This scheme also includes basic calibration ([`AdcCalBasic`]).
#[derive(Clone, Copy)]
pub struct AdcCalLine<ADCI> {
basic: AdcCalBasic<ADCI>,
/// ADC gain.
///
/// After being de-biased by the basic calibration, the reading is
/// multiplied by this value. Despite the type, it is a fixed-point
/// number with 16 fractional bits.
gain: u32,
_phantom: PhantomData<ADCI>,
}
impl<ADCI> crate::private::Sealed for AdcCalLine<ADCI> {}
impl<ADCI> AdcCalScheme<ADCI> for AdcCalLine<ADCI>
where
ADCI: AdcCalEfuse + AdcHasLineCal + CalibrationAccess,
{
fn new_cal(atten: Attenuation) -> Self {
let basic = AdcCalBasic::<ADCI>::new_cal(atten);
// Try get the reference point (Dout, Vin) from efuse
// Dout means mean raw ADC value when specified Vin applied to input.
let (code, mv) = ADCI::cal_code(atten)
.map(|code| (code, ADCI::cal_mv(atten)))
.unwrap_or_else(|| {
// As a fallback try to calibrate using reference voltage source.
// This method is not too good because actual reference voltage may varies
// in range 1000..=1200 mV and this value currently cannot be read from efuse.
(
AdcConfig::<ADCI>::adc_calibrate(atten, AdcCalSource::Ref),
1100, // use 1100 mV as a middle of typical reference voltage range
)
});
// Estimate the (assumed) linear relationship between the measured raw value and
// the voltage with the previously done measurement when the chip was
// manufactured.
//
// Note that the constant term is zero because the basic calibration takes care
// of it already.
let gain = mv as u32 * GAIN_SCALE / code as u32;
Self {
basic,
gain,
_phantom: PhantomData,
}
}
fn adc_cal(&self) -> u16 {
self.basic.adc_cal()
}
fn adc_val(&self, val: u16) -> u16 {
let val = self.basic.adc_val(val);
(val as u32 * self.gain / GAIN_SCALE) as u16
}
}
#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))]
impl AdcHasLineCal for crate::peripherals::ADC1 {}
#[cfg(any(esp32c3, esp32s3))]
impl AdcHasLineCal for crate::peripherals::ADC2 {}
+14
View File
@@ -0,0 +1,14 @@
#[cfg(any(esp32c3, esp32c6, esp32s3))]
pub use self::curve::{AdcCalCurve, AdcHasCurveCal};
#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))]
pub use self::{
basic::AdcCalBasic,
line::{AdcCalLine, AdcHasLineCal},
};
#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))]
mod basic;
#[cfg(any(esp32c3, esp32c6, esp32s3))]
mod curve;
#[cfg(any(esp32c2, esp32c3, esp32c6, esp32s3))]
mod line;
+387
View File
@@ -0,0 +1,387 @@
use super::{AdcConfig, Attenuation};
use crate::{
peripheral::PeripheralRef,
peripherals::{ADC1, ADC2, RTC_IO, SENS},
};
pub(super) const NUM_ATTENS: usize = 10;
/// The sampling/readout resolution of the ADC.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Resolution {
/// 9-bit resolution
Resolution9Bit = 0b00,
/// 10-bit resolution
Resolution10Bit = 0b01,
/// 11-bit resolution
Resolution11Bit = 0b10,
/// 12-bit resolution
#[default]
Resolution12Bit = 0b11,
}
#[doc(hidden)]
pub trait RegisterAccess {
fn set_bit_width(resolution: u8);
fn set_sample_bit(resolution: u8);
fn set_attenuation(channel: usize, attenuation: u8);
fn clear_dig_force();
fn set_start_force();
fn set_en_pad_force();
fn set_en_pad(channel: u8);
fn clear_start_sar();
fn set_start_sar();
fn read_done_sar() -> bool;
fn read_data_sar() -> u16;
}
impl RegisterAccess for ADC1 {
fn set_bit_width(resolution: u8) {
unsafe { &*SENS::ptr() }
.sar_start_force()
.modify(|_, w| unsafe { w.sar1_bit_width().bits(resolution) });
}
fn set_sample_bit(resolution: u8) {
unsafe { &*SENS::ptr() }
.sar_read_ctrl()
.modify(|_, w| unsafe { w.sar1_sample_bit().bits(resolution) });
}
fn set_attenuation(channel: usize, attenuation: u8) {
unsafe { &*SENS::ptr() }.sar_atten1().modify(|r, w| {
let new_value = (r.bits() & !(0b11 << (channel * 2)))
| (((attenuation & 0b11) as u32) << (channel * 2));
unsafe { w.sar1_atten().bits(new_value) }
});
}
fn clear_dig_force() {
unsafe { &*SENS::ptr() }
.sar_read_ctrl()
.modify(|_, w| w.sar1_dig_force().clear_bit());
}
fn set_start_force() {
unsafe { &*SENS::ptr() }
.sar_meas_start1()
.modify(|_, w| w.meas1_start_force().set_bit());
}
fn set_en_pad_force() {
unsafe { &*SENS::ptr() }
.sar_meas_start1()
.modify(|_, w| w.sar1_en_pad_force().set_bit());
}
fn set_en_pad(channel: u8) {
unsafe { &*SENS::ptr() }
.sar_meas_start1()
.modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) });
}
fn clear_start_sar() {
unsafe { &*SENS::ptr() }
.sar_meas_start1()
.modify(|_, w| w.meas1_start_sar().clear_bit());
}
fn set_start_sar() {
unsafe { &*SENS::ptr() }
.sar_meas_start1()
.modify(|_, w| w.meas1_start_sar().set_bit());
}
fn read_done_sar() -> bool {
unsafe { &*SENS::ptr() }
.sar_meas_start1()
.read()
.meas1_done_sar()
.bit_is_set()
}
fn read_data_sar() -> u16 {
unsafe { &*SENS::ptr() }
.sar_meas_start1()
.read()
.meas1_data_sar()
.bits()
}
}
impl RegisterAccess for ADC2 {
fn set_bit_width(resolution: u8) {
unsafe { &*SENS::ptr() }
.sar_start_force()
.modify(|_, w| unsafe { w.sar2_bit_width().bits(resolution) });
}
fn set_sample_bit(resolution: u8) {
unsafe { &*SENS::ptr() }
.sar_read_ctrl2()
.modify(|_, w| unsafe { w.sar2_sample_bit().bits(resolution) });
}
fn set_attenuation(channel: usize, attenuation: u8) {
unsafe { &*SENS::ptr() }.sar_atten2().modify(|r, w| {
let new_value = (r.bits() & !(0b11 << (channel * 2)))
| (((attenuation & 0b11) as u32) << (channel * 2));
unsafe { w.sar2_atten().bits(new_value) }
});
}
fn clear_dig_force() {
unsafe { &*SENS::ptr() }
.sar_read_ctrl2()
.modify(|_, w| w.sar2_dig_force().clear_bit());
}
fn set_start_force() {
unsafe { &*SENS::ptr() }
.sar_meas_start2()
.modify(|_, w| w.meas2_start_force().set_bit());
}
fn set_en_pad_force() {
unsafe { &*SENS::ptr() }
.sar_meas_start2()
.modify(|_, w| w.sar2_en_pad_force().set_bit());
}
fn set_en_pad(channel: u8) {
unsafe { &*SENS::ptr() }
.sar_meas_start2()
.modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << channel) });
}
fn clear_start_sar() {
unsafe { &*SENS::ptr() }
.sar_meas_start2()
.modify(|_, w| w.meas2_start_sar().clear_bit());
}
fn set_start_sar() {
unsafe { &*SENS::ptr() }
.sar_meas_start2()
.modify(|_, w| w.meas2_start_sar().set_bit());
}
fn read_done_sar() -> bool {
unsafe { &*SENS::ptr() }
.sar_meas_start2()
.read()
.meas2_done_sar()
.bit_is_set()
}
fn read_data_sar() -> u16 {
unsafe { &*SENS::ptr() }
.sar_meas_start2()
.read()
.meas2_data_sar()
.bits()
}
}
/// Analog-to-Digital Converter peripheral driver.
pub struct Adc<'d, ADC> {
_adc: PeripheralRef<'d, ADC>,
attenuations: [Option<Attenuation>; NUM_ATTENS],
active_channel: Option<u8>,
}
impl<'d, ADCI> Adc<'d, ADCI>
where
ADCI: RegisterAccess,
{
/// Configure a given ADC instance using the provided configuration, and
/// initialize the ADC for use
pub fn new(
adc_instance: impl crate::peripheral::Peripheral<P = ADCI> + 'd,
config: AdcConfig<ADCI>,
) -> Self {
let sensors = unsafe { &*SENS::ptr() };
// Set reading and sampling resolution
let resolution: u8 = config.resolution as u8;
ADCI::set_bit_width(resolution);
ADCI::set_sample_bit(resolution);
// Set attenuation for pins
let attenuations = config.attenuations;
for (channel, attentuation) in attenuations.iter().enumerate() {
if let Some(attenuation) = attentuation {
ADC1::set_attenuation(channel, *attenuation as u8);
}
}
// Set controller to RTC
ADCI::clear_dig_force();
ADCI::set_start_force();
ADCI::set_en_pad_force();
sensors
.sar_touch_ctrl1()
.modify(|_, w| w.xpd_hall_force().set_bit());
sensors
.sar_touch_ctrl1()
.modify(|_, w| w.hall_phase_force().set_bit());
// Set power to SW power on
sensors
.sar_meas_wait2()
.modify(|_, w| unsafe { w.force_xpd_sar().bits(0b11) });
// disable AMP
sensors
.sar_meas_wait2()
.modify(|_, w| unsafe { w.force_xpd_amp().bits(0b10) });
sensors
.sar_meas_ctrl()
.modify(|_, w| unsafe { w.amp_rst_fb_fsm().bits(0) });
sensors
.sar_meas_ctrl()
.modify(|_, w| unsafe { w.amp_short_ref_fsm().bits(0) });
sensors
.sar_meas_ctrl()
.modify(|_, w| unsafe { w.amp_short_ref_gnd_fsm().bits(0) });
sensors
.sar_meas_wait1()
.modify(|_, w| unsafe { w.sar_amp_wait1().bits(1) });
sensors
.sar_meas_wait1()
.modify(|_, w| unsafe { w.sar_amp_wait2().bits(1) });
sensors
.sar_meas_wait2()
.modify(|_, w| unsafe { w.sar_amp_wait3().bits(1) });
// Do *not* invert the output
// NOTE: This seems backwards, but was verified experimentally.
sensors
.sar_read_ctrl2()
.modify(|_, w| w.sar2_data_inv().set_bit());
Adc {
_adc: adc_instance.into_ref(),
attenuations: config.attenuations,
active_channel: None,
}
}
/// Request that the ADC begin a conversion on the specified pin
///
/// This method takes an [AdcPin](super::AdcPin) reference, as it is
/// expected that the ADC will be able to sample whatever channel
/// underlies the pin.
pub fn read_oneshot<PIN>(&mut self, _pin: &mut super::AdcPin<PIN, ADCI>) -> nb::Result<u16, ()>
where
PIN: super::AdcChannel,
{
if self.attenuations[PIN::CHANNEL as usize].is_none() {
panic!("Channel {} is not configured reading!", PIN::CHANNEL);
}
if let Some(active_channel) = self.active_channel {
// There is conversion in progress:
// - if it's for a different channel try again later
// - if it's for the given channel, go ahead and check progress
if active_channel != PIN::CHANNEL {
return Err(nb::Error::WouldBlock);
}
} else {
// If no conversions are in progress, start a new one for given channel
self.active_channel = Some(PIN::CHANNEL);
ADCI::set_en_pad(PIN::CHANNEL);
ADCI::clear_start_sar();
ADCI::set_start_sar();
}
// Wait for ADC to finish conversion
let conversion_finished = ADCI::read_done_sar();
if !conversion_finished {
return Err(nb::Error::WouldBlock);
}
// Get converted value
let converted_value = ADCI::read_data_sar();
// Mark that no conversions are currently in progress
self.active_channel = None;
Ok(converted_value)
}
}
impl<'d, ADC1> Adc<'d, ADC1> {
/// Enable the Hall sensor
pub fn enable_hall_sensor() {
unsafe { &*RTC_IO::ptr() }
.hall_sens()
.modify(|_, w| w.xpd_hall().set_bit());
}
/// Disable the Hall sensor
pub fn disable_hall_sensor() {
unsafe { &*RTC_IO::ptr() }
.hall_sens()
.modify(|_, w| w.xpd_hall().clear_bit());
}
}
impl<'d, ADCI, PIN> embedded_hal_02::adc::OneShot<ADCI, u16, super::AdcPin<PIN, ADCI>>
for Adc<'d, ADCI>
where
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8> + super::AdcChannel,
ADCI: RegisterAccess,
{
type Error = ();
fn read(&mut self, pin: &mut super::AdcPin<PIN, ADCI>) -> nb::Result<u16, Self::Error> {
self.read_oneshot(pin)
}
}
mod adc_implementation {
crate::analog::adc::impl_adc_interface! {
ADC1 [
(GpioPin<36>, 0), // Alt. name: SENSOR_VP
(GpioPin<37>, 1), // Alt. name: SENSOR_CAPP
(GpioPin<38>, 2), // Alt. name: SENSOR_CAPN
(GpioPin<39>, 3), // Alt. name: SENSOR_VN
(GpioPin<33>, 4), // Alt. name: 32K_XP
(GpioPin<32>, 5), // Alt. name: 32K_XN
(GpioPin<34>, 6), // Alt. name: VDET_1
(GpioPin<35>, 7), // Alt. name: VDET_2
]
}
crate::analog::adc::impl_adc_interface! {
ADC2 [
(GpioPin<4>, 0),
(GpioPin<0>, 1),
(GpioPin<2>, 2),
(GpioPin<15>, 3), // Alt. name: MTDO
(GpioPin<13>, 4), // Alt. name: MTCK
(GpioPin<12>, 5), // Alt. name: MTDI
(GpioPin<14>, 6), // Alt. name: MTMS
(GpioPin<27>, 7),
(GpioPin<25>, 8),
(GpioPin<26>, 9),
]
}
}
+269
View File
@@ -0,0 +1,269 @@
//! # Analog to Digital Converter (ADC)
//!
//! ## Overview
//!
//! The ADC is integrated on the chip, and is capable of measuring analog
//! signals from specific analog I/O pins. One or more ADC units are available,
//! depending on the device being used.
//!
//! ## Configuration
//!
//! The ADC can be configured to measure analog signals from specific pins. The
//! configuration includes the resolution of the ADC, the attenuation of the
//! input signal, and the pins to be measured.
//!
//! Some targets also support ADC calibration via different schemes like
//! basic calibration, curve fitting or linear interpolation. The calibration
//! schemes can be used to improve the accuracy of the ADC readings.
//!
//! ## Usage
//!
//! The ADC driver implements the `embedded-hal@0.2.x` ADC traits.
//!
//! ## Example
//!
//! ### Read an analog signal from a pin
//!
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! # use esp_hal::analog::adc::AdcConfig;
//! # use esp_hal::peripherals::ADC1;
//! # use esp_hal::analog::adc::Attenuation;
//! # use esp_hal::analog::adc::Adc;
//! # use esp_hal::delay::Delay;
#![cfg_attr(esp32, doc = "let analog_pin = peripherals.GPIO32;")]
#![cfg_attr(any(esp32s2, esp32s3), doc = "let analog_pin = peripherals.GPIO3;")]
#![cfg_attr(
not(any(esp32, esp32s2, esp32s3)),
doc = "let analog_pin = peripherals.GPIO2;"
)]
//! let mut adc1_config = AdcConfig::new();
//! let mut pin = adc1_config.enable_pin(
//! analog_pin,
//! Attenuation::Attenuation11dB,
//! );
//! let mut adc1 = Adc::new(peripherals.ADC1, adc1_config);
//!
//! let mut delay = Delay::new();
//!
//! loop {
//! let pin_value: u16 = nb::block!(adc1.read_oneshot(&mut pin)).unwrap();
//!
//! delay.delay_millis(1500);
//! }
//! # }
//! ```
//!
//! ## Implementation State
//!
//! - [ADC calibration is not implemented for all targets].
//!
//! [ADC calibration is not implemented for all targets]: https://github.com/esp-rs/esp-hal/issues/326
use core::marker::PhantomData;
pub use self::implementation::*;
use crate::gpio::AnalogPin;
#[cfg_attr(esp32, path = "esp32.rs")]
#[cfg_attr(riscv, path = "riscv.rs")]
#[cfg_attr(any(esp32s2, esp32s3), path = "xtensa.rs")]
mod implementation;
/// The attenuation of the ADC pin.
///
/// The effective measurement range for a given attenuation is dependent on the
/// device being targeted. Please refer to "ADC Characteristics" section of your
/// device's datasheet for more information.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Attenuation {
/// 0dB attenuation
Attenuation0dB = 0b00,
/// 2.5dB attenuation
#[cfg(not(esp32c2))]
Attenuation2p5dB = 0b01,
/// 6dB attenuation
#[cfg(not(esp32c2))]
Attenuation6dB = 0b10,
/// 11dB attenuation
Attenuation11dB = 0b11,
}
/// Calibration source of the ADC.
#[cfg(not(esp32))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdcCalSource {
/// Use Ground as the calibration source
Gnd,
/// Use Vref as the calibration source
Ref,
}
/// An I/O pin which can be read using the ADC.
pub struct AdcPin<PIN, ADCI, CS = ()> {
/// The underlying GPIO pin
pub pin: PIN,
/// Calibration scheme used for the configured ADC pin
#[cfg_attr(esp32, allow(unused))]
pub cal_scheme: CS,
_phantom: PhantomData<ADCI>,
}
impl<PIN, ADCI, CS> embedded_hal_02::adc::Channel<ADCI> for AdcPin<PIN, ADCI, CS>
where
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8>,
{
type ID = u8;
fn channel() -> Self::ID {
PIN::channel()
}
}
/// Configuration for the ADC.
pub struct AdcConfig<ADCI> {
#[cfg_attr(not(esp32), allow(unused))]
resolution: Resolution,
attenuations: [Option<Attenuation>; NUM_ATTENS],
_phantom: PhantomData<ADCI>,
}
impl<ADCI> AdcConfig<ADCI> {
/// Create a new configuration struct with its default values
pub fn new() -> Self {
Self::default()
}
/// Enable the specified pin with the given attenuation
pub fn enable_pin<PIN>(&mut self, pin: PIN, attenuation: Attenuation) -> AdcPin<PIN, ADCI>
where
PIN: AdcChannel + AnalogPin,
{
// TODO revert this on drop
pin.set_analog(crate::private::Internal);
self.attenuations[PIN::CHANNEL as usize] = Some(attenuation);
AdcPin {
pin,
cal_scheme: AdcCalScheme::<()>::new_cal(attenuation),
_phantom: PhantomData,
}
}
/// Enable the specified pin with the given attenuation and calibration
/// scheme
#[cfg(not(esp32))]
pub fn enable_pin_with_cal<PIN, CS>(
&mut self,
pin: PIN,
attenuation: Attenuation,
) -> AdcPin<PIN, ADCI, CS>
where
ADCI: CalibrationAccess,
PIN: AdcChannel + AnalogPin,
CS: AdcCalScheme<ADCI>,
{
// TODO revert this on drop
pin.set_analog(crate::private::Internal);
self.attenuations[PIN::CHANNEL as usize] = Some(attenuation);
AdcPin {
pin,
cal_scheme: CS::new_cal(attenuation),
_phantom: PhantomData,
}
}
}
impl<ADCI> Default for AdcConfig<ADCI> {
fn default() -> Self {
Self {
resolution: Resolution::default(),
attenuations: [None; NUM_ATTENS],
_phantom: PhantomData,
}
}
}
#[cfg(not(esp32))]
#[doc(hidden)]
pub trait CalibrationAccess: RegisterAccess {
const ADC_CAL_CNT_MAX: u16;
const ADC_CAL_CHANNEL: u16;
const ADC_VAL_MASK: u16;
fn enable_vdef(enable: bool);
/// Enable internal calibration voltage source
fn connect_cal(source: AdcCalSource, enable: bool);
}
/// A helper trait to get the ADC channel of a compatible GPIO pin.
pub trait AdcChannel {
/// Channel number used by the ADC
const CHANNEL: u8;
}
/// A trait abstracting over calibration methods.
///
/// The methods in this trait are mostly for internal use. To get
/// calibrated ADC reads, all you need to do is call `enable_pin_with_cal`
/// and specify some implementor of this trait.
pub trait AdcCalScheme<ADCI>: Sized + crate::private::Sealed {
/// Create a new calibration scheme for the given attenuation.
fn new_cal(atten: Attenuation) -> Self;
/// Return the basic ADC bias value.
fn adc_cal(&self) -> u16 {
0
}
/// Convert ADC value.
fn adc_val(&self, val: u16) -> u16 {
val
}
}
impl crate::private::Sealed for () {}
impl<ADCI> AdcCalScheme<ADCI> for () {
fn new_cal(_atten: Attenuation) -> Self {}
}
/// A helper trait to get access to ADC calibration efuses.
#[cfg(not(any(esp32, esp32s2, esp32h2)))]
trait AdcCalEfuse {
/// Get ADC calibration init code
///
/// Returns digital value for zero voltage for a given attenuation
fn init_code(atten: Attenuation) -> Option<u16>;
/// Get ADC calibration reference point voltage
///
/// Returns reference voltage (millivolts) for a given attenuation
fn cal_mv(atten: Attenuation) -> u16;
/// Get ADC calibration reference point digital value
///
/// Returns digital value for reference voltage for a given attenuation
fn cal_code(atten: Attenuation) -> Option<u16>;
}
macro_rules! impl_adc_interface {
($adc:ident [
$( (GpioPin<$pin:literal>, $channel:expr) ,)+
]) => {
$(
impl $crate::analog::adc::AdcChannel for crate::gpio::GpioPin<$pin> {
const CHANNEL: u8 = $channel;
}
impl embedded_hal_02::adc::Channel<crate::peripherals::$adc> for crate::gpio::GpioPin<$pin> {
type ID = u8;
fn channel() -> u8 { $channel }
}
)+
}
}
pub(crate) use impl_adc_interface;
+605
View File
@@ -0,0 +1,605 @@
#[cfg(not(esp32h2))]
pub use self::calibration::*;
use super::{AdcCalSource, AdcConfig, Attenuation};
#[cfg(any(esp32c6, esp32h2))]
use crate::clock::clocks_ll::regi2c_write_mask;
#[cfg(any(esp32c2, esp32c3, esp32c6))]
use crate::efuse::Efuse;
use crate::{
peripheral::PeripheralRef,
peripherals::APB_SARADC,
system::{Peripheral, PeripheralClockControl},
};
mod calibration;
// polyfill for c2 and c3
#[cfg(any(esp32c2, esp32c3))]
#[inline(always)]
fn regi2c_write_mask(block: u8, host_id: u8, reg_add: u8, msb: u8, lsb: u8, data: u8) {
unsafe {
crate::rom::rom_i2c_writeReg_Mask(
block as _,
host_id as _,
reg_add as _,
msb as _,
lsb as _,
data as _,
);
}
}
// Constants taken from:
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c2/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c3/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c6/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h2/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h4/include/soc/regi2c_saradc.h
cfg_if::cfg_if! {
if #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] {
const I2C_SAR_ADC: u8 = 0x69;
const I2C_SAR_ADC_HOSTID: u8 = 0;
const ADC_VAL_MASK: u16 = 0xfff;
const ADC_CAL_CNT_MAX: u16 = 32;
const ADC_CAL_CHANNEL: u16 = 15;
const ADC_SAR1_ENCAL_GND_ADDR: u8 = 0x7;
const ADC_SAR1_ENCAL_GND_ADDR_MSB: u8 = 5;
const ADC_SAR1_ENCAL_GND_ADDR_LSB: u8 = 5;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR: u8 = 0x1;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR: u8 = 0x0;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0;
const ADC_SAR1_DREF_ADDR: u8 = 0x2;
const ADC_SAR1_DREF_ADDR_MSB: u8 = 0x6;
const ADC_SAR1_DREF_ADDR_LSB: u8 = 0x4;
const ADC_SARADC1_ENCAL_REF_ADDR: u8 = 0x7;
const ADC_SARADC1_ENCAL_REF_ADDR_MSB: u8 = 4;
const ADC_SARADC1_ENCAL_REF_ADDR_LSB: u8 = 4;
}
}
cfg_if::cfg_if! {
if #[cfg(esp32c3)] {
const ADC_SAR2_ENCAL_GND_ADDR: u8 = 0x7;
const ADC_SAR2_ENCAL_GND_ADDR_MSB: u8 = 7;
const ADC_SAR2_ENCAL_GND_ADDR_LSB: u8 = 7;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR: u8 = 0x4;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR: u8 = 0x3;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0;
const ADC_SAR2_DREF_ADDR: u8 = 0x5;
const ADC_SAR2_DREF_ADDR_MSB: u8 = 0x6;
const ADC_SAR2_DREF_ADDR_LSB: u8 = 0x4;
const ADC_SARADC2_ENCAL_REF_ADDR: u8 = 0x7;
const ADC_SARADC2_ENCAL_REF_ADDR_MSB: u8 = 6;
const ADC_SARADC2_ENCAL_REF_ADDR_LSB: u8 = 6;
}
}
// The number of analog IO pins, and in turn the number of attentuations,
// depends on which chip is being used
cfg_if::cfg_if! {
if #[cfg(esp32c6)] {
pub(super) const NUM_ATTENS: usize = 7;
} else {
pub(super) const NUM_ATTENS: usize = 5;
}
}
/// The sampling/readout resolution of the ADC.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Resolution {
/// 12-bit resolution
#[default]
Resolution12Bit,
}
impl<ADCI> AdcConfig<ADCI>
where
ADCI: RegisterAccess,
{
/// Calibrate ADC with specified attenuation and voltage source
pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16
where
ADCI: super::CalibrationAccess,
{
let mut adc_max: u16 = 0;
let mut adc_min: u16 = u16::MAX;
let mut adc_sum: u32 = 0;
ADCI::enable_vdef(true);
// Start sampling
ADCI::config_onetime_sample(ADC_CAL_CHANNEL as u8, atten as u8);
// Connect calibration source
ADCI::connect_cal(source, true);
for _ in 0..ADC_CAL_CNT_MAX {
ADCI::set_init_code(0);
// Trigger ADC sampling
ADCI::start_onetime_sample();
// Wait until ADC1 sampling is done
while !ADCI::is_done() {}
let adc = ADCI::read_data() & ADC_VAL_MASK;
ADCI::reset();
adc_sum += adc as u32;
adc_max = adc.max(adc_max);
adc_min = adc.min(adc_min);
}
let cal_val = (adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADC_CAL_CNT_MAX - 2);
// Disconnect calibration source
ADCI::connect_cal(source, false);
cal_val
}
}
#[doc(hidden)]
pub trait RegisterAccess {
/// Configure onetime sampling parameters
fn config_onetime_sample(channel: u8, attenuation: u8);
/// Start onetime sampling
fn start_onetime_sample();
/// Check if sampling is done
fn is_done() -> bool;
/// Read sample data
fn read_data() -> u16;
/// Reset flags
fn reset();
/// Set calibration parameter to ADC hardware
fn set_init_code(data: u16);
}
impl RegisterAccess for crate::peripherals::ADC1 {
fn config_onetime_sample(channel: u8, attenuation: u8) {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc.onetime_sample().modify(|_, w| unsafe {
w.saradc1_onetime_sample().set_bit();
w.onetime_channel().bits(channel);
w.onetime_atten().bits(attenuation)
});
}
fn start_onetime_sample() {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc
.onetime_sample()
.modify(|_, w| w.onetime_start().set_bit());
}
fn is_done() -> bool {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc.int_raw().read().adc1_done().bit()
}
fn read_data() -> u16 {
let sar_adc = unsafe { &*APB_SARADC::PTR };
(sar_adc.sar1data_status().read().saradc1_data().bits() as u16) & 0xfff
}
fn reset() {
let sar_adc = unsafe { &*APB_SARADC::PTR };
// Clear ADC1 sampling done interrupt bit
sar_adc
.int_clr()
.write(|w| w.adc1_done().clear_bit_by_one());
// Disable ADC sampling
sar_adc
.onetime_sample()
.modify(|_, w| w.onetime_start().clear_bit());
}
fn set_init_code(data: u16) {
let [msb, lsb] = data.to_be_bytes();
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_INITIAL_CODE_HIGH_ADDR,
ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB,
ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB,
msb as _,
);
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_INITIAL_CODE_LOW_ADDR,
ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB,
ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB,
lsb as _,
);
}
}
impl super::CalibrationAccess for crate::peripherals::ADC1 {
const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
fn enable_vdef(enable: bool) {
let value = enable as _;
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_DREF_ADDR,
ADC_SAR1_DREF_ADDR_MSB,
ADC_SAR1_DREF_ADDR_LSB,
value,
);
}
fn connect_cal(source: AdcCalSource, enable: bool) {
let value = enable as _;
match source {
AdcCalSource::Gnd => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_ENCAL_GND_ADDR,
ADC_SAR1_ENCAL_GND_ADDR_MSB,
ADC_SAR1_ENCAL_GND_ADDR_LSB,
value,
),
AdcCalSource::Ref => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SARADC1_ENCAL_REF_ADDR,
ADC_SARADC1_ENCAL_REF_ADDR_MSB,
ADC_SARADC1_ENCAL_REF_ADDR_LSB,
value,
),
}
}
}
#[cfg(esp32c3)]
impl RegisterAccess for crate::peripherals::ADC2 {
fn config_onetime_sample(channel: u8, attenuation: u8) {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc.onetime_sample().modify(|_, w| unsafe {
w.saradc2_onetime_sample().set_bit();
w.onetime_channel().bits(channel);
w.onetime_atten().bits(attenuation)
});
}
fn start_onetime_sample() {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc
.onetime_sample()
.modify(|_, w| w.onetime_start().set_bit());
}
fn is_done() -> bool {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc.int_raw().read().adc2_done().bit()
}
fn read_data() -> u16 {
let sar_adc = unsafe { &*APB_SARADC::PTR };
(sar_adc.sar2data_status().read().saradc2_data().bits() as u16) & 0xfff
}
fn reset() {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc
.int_clr()
.write(|w| w.adc2_done().clear_bit_by_one());
sar_adc
.onetime_sample()
.modify(|_, w| w.onetime_start().clear_bit());
}
fn set_init_code(data: u16) {
let [msb, lsb] = data.to_be_bytes();
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_INITIAL_CODE_HIGH_ADDR,
ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB,
ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB,
msb as _,
);
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_INITIAL_CODE_LOW_ADDR,
ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB,
ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB,
lsb as _,
);
}
}
#[cfg(esp32c3)]
impl super::CalibrationAccess for crate::peripherals::ADC2 {
const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
fn enable_vdef(enable: bool) {
let value = enable as _;
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_DREF_ADDR,
ADC_SAR2_DREF_ADDR_MSB,
ADC_SAR2_DREF_ADDR_LSB,
value,
);
}
fn connect_cal(source: AdcCalSource, enable: bool) {
let value = enable as _;
match source {
AdcCalSource::Gnd => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_ENCAL_GND_ADDR,
ADC_SAR2_ENCAL_GND_ADDR_MSB,
ADC_SAR2_ENCAL_GND_ADDR_LSB,
value,
),
AdcCalSource::Ref => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SARADC2_ENCAL_REF_ADDR,
ADC_SARADC2_ENCAL_REF_ADDR_MSB,
ADC_SARADC2_ENCAL_REF_ADDR_LSB,
value,
),
}
}
}
/// Analog-to-Digital Converter peripheral driver.
pub struct Adc<'d, ADCI> {
_adc: PeripheralRef<'d, ADCI>,
attenuations: [Option<Attenuation>; NUM_ATTENS],
active_channel: Option<u8>,
}
impl<'d, ADCI> Adc<'d, ADCI>
where
ADCI: RegisterAccess + 'd,
{
/// Configure a given ADC instance using the provided configuration, and
/// initialize the ADC for use
pub fn new(
adc_instance: impl crate::peripheral::Peripheral<P = ADCI> + 'd,
config: AdcConfig<ADCI>,
) -> Self {
PeripheralClockControl::reset(Peripheral::ApbSarAdc);
PeripheralClockControl::enable(Peripheral::ApbSarAdc);
unsafe { &*APB_SARADC::PTR }.ctrl().modify(|_, w| unsafe {
w.start_force().set_bit();
w.start().set_bit();
w.sar_clk_gated().set_bit();
w.xpd_sar_force().bits(0b11)
});
Adc {
_adc: adc_instance.into_ref(),
attenuations: config.attenuations,
active_channel: None,
}
}
/// Request that the ADC begin a conversion on the specified pin
///
/// This method takes an [AdcPin](super::AdcPin) reference, as it is
/// expected that the ADC will be able to sample whatever channel
/// underlies the pin.
pub fn read_oneshot<PIN, CS>(
&mut self,
pin: &mut super::AdcPin<PIN, ADCI, CS>,
) -> nb::Result<u16, ()>
where
PIN: super::AdcChannel,
CS: super::AdcCalScheme<ADCI>,
{
if self.attenuations[PIN::CHANNEL as usize].is_none() {
panic!("Channel {} is not configured reading!", PIN::CHANNEL);
}
if let Some(active_channel) = self.active_channel {
// There is conversion in progress:
// - if it's for a different channel try again later
// - if it's for the given channel, go ahead and check progress
if active_channel != PIN::CHANNEL {
return Err(nb::Error::WouldBlock);
}
} else {
// If no conversions are in progress, start a new one for given channel
self.active_channel = Some(PIN::CHANNEL);
// Set ADC unit calibration according used scheme for pin
ADCI::set_init_code(pin.cal_scheme.adc_cal());
let channel = self.active_channel.unwrap();
let attenuation = self.attenuations[channel as usize].unwrap() as u8;
ADCI::config_onetime_sample(channel, attenuation);
ADCI::start_onetime_sample();
// see https://github.com/espressif/esp-idf/blob/b4268c874a4cf8fcf7c0c4153cffb76ad2ddda4e/components/hal/adc_oneshot_hal.c#L105-L107
// the delay might be a bit generous but longer delay seem to not cause problems
#[cfg(esp32c6)]
{
crate::rom::ets_delay_us(40);
ADCI::start_onetime_sample();
}
}
// Wait for ADC to finish conversion
let conversion_finished = ADCI::is_done();
if !conversion_finished {
return Err(nb::Error::WouldBlock);
}
// Get converted value
let converted_value = ADCI::read_data();
ADCI::reset();
// Postprocess converted value according to calibration scheme used for pin
let converted_value = pin.cal_scheme.adc_val(converted_value);
// There is a hardware limitation. If the APB clock frequency is high, the step
// of this reg signal: ``onetime_start`` may not be captured by the
// ADC digital controller (when its clock frequency is too slow). A rough
// estimate for this step should be at least 3 ADC digital controller
// clock cycle.
//
// This limitation will be removed in hardware future versions.
// We reset ``onetime_start`` in `reset` and assume enough time has passed until
// the next sample is requested.
// Mark that no conversions are currently in progress
self.active_channel = None;
Ok(converted_value)
}
}
#[cfg(any(esp32c2, esp32c3, esp32c6))]
impl super::AdcCalEfuse for crate::peripherals::ADC1 {
fn init_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_init_code(1, atten)
}
fn cal_mv(atten: Attenuation) -> u16 {
Efuse::rtc_calib_cal_mv(1, atten)
}
fn cal_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_cal_code(1, atten)
}
}
#[cfg(esp32c3)]
impl super::AdcCalEfuse for crate::peripherals::ADC2 {
fn init_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_init_code(2, atten)
}
fn cal_mv(atten: Attenuation) -> u16 {
Efuse::rtc_calib_cal_mv(2, atten)
}
fn cal_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_cal_code(2, atten)
}
}
impl<ADCI, PIN, CS> embedded_hal_02::adc::OneShot<ADCI, u16, super::AdcPin<PIN, ADCI, CS>>
for Adc<'_, ADCI>
where
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8> + super::AdcChannel,
ADCI: RegisterAccess,
CS: super::AdcCalScheme<ADCI>,
{
type Error = ();
fn read(&mut self, pin: &mut super::AdcPin<PIN, ADCI, CS>) -> nb::Result<u16, Self::Error> {
self.read_oneshot(pin)
}
}
#[cfg(esp32c2)]
mod adc_implementation {
crate::analog::adc::impl_adc_interface! {
ADC1 [
(GpioPin<0>, 0),
(GpioPin<1>, 1),
(GpioPin<2>, 2),
(GpioPin<3>, 3),
(GpioPin<4>, 4),
]
}
}
#[cfg(esp32c3)]
mod adc_implementation {
crate::analog::adc::impl_adc_interface! {
ADC1 [
(GpioPin<0>, 0),
(GpioPin<1>, 1),
(GpioPin<2>, 2),
(GpioPin<3>, 3),
(GpioPin<4>, 4),
]
}
crate::analog::adc::impl_adc_interface! {
ADC2 [
(GpioPin<5>, 0),
]
}
}
#[cfg(esp32c6)]
mod adc_implementation {
crate::analog::adc::impl_adc_interface! {
ADC1 [
(GpioPin<0>, 0),
(GpioPin<1>, 1),
(GpioPin<2>, 2),
(GpioPin<3>, 3),
(GpioPin<4>, 4),
(GpioPin<5>, 5),
(GpioPin<6>, 6),
]
}
}
#[cfg(esp32h2)]
mod adc_implementation {
crate::analog::adc::impl_adc_interface! {
ADC1 [
(GpioPin<1>, 0),
(GpioPin<2>, 1),
(GpioPin<3>, 2),
(GpioPin<4>, 3),
(GpioPin<5>, 4),
]
}
}
+637
View File
@@ -0,0 +1,637 @@
#[cfg(esp32s3)]
pub use self::calibration::*;
use super::{AdcCalScheme, AdcCalSource, AdcChannel, AdcConfig, AdcPin, Attenuation};
#[cfg(esp32s3)]
use crate::efuse::Efuse;
use crate::{
peripheral::PeripheralRef,
peripherals::{APB_SARADC, SENS},
system::{Peripheral, PeripheralClockControl},
};
mod calibration;
pub(super) const NUM_ATTENS: usize = 10;
// Constants taken from:
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32s2/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32s3/include/soc/regi2c_saradc.h
cfg_if::cfg_if! {
if #[cfg(any(esp32s2, esp32s3))] {
const I2C_SAR_ADC: u8 = 0x69;
const I2C_SAR_ADC_HOSTID: u8 = 1;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR: u8 = 0x1;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR: u8 = 0x0;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR: u8 = 0x4;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR: u8 = 0x3;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0;
}
}
cfg_if::cfg_if! {
if #[cfg(esp32s3)] {
const ADC_VAL_MASK: u16 = 0xfff;
const ADC_CAL_CNT_MAX: u16 = 32;
const ADC_CAL_CHANNEL: u16 = 15;
const ADC_SAR1_ENCAL_GND_ADDR: u8 = 0x7;
const ADC_SAR1_ENCAL_GND_ADDR_MSB: u8 = 5;
const ADC_SAR1_ENCAL_GND_ADDR_LSB: u8 = 5;
const ADC_SAR1_DREF_ADDR: u8 = 0x2;
const ADC_SAR1_DREF_ADDR_MSB: u8 = 0x6;
const ADC_SAR1_DREF_ADDR_LSB: u8 = 0x4;
const ADC_SARADC1_ENCAL_REF_ADDR: u8 = 0x7;
const ADC_SARADC1_ENCAL_REF_ADDR_MSB: u8 = 4;
const ADC_SARADC1_ENCAL_REF_ADDR_LSB: u8 = 4;
const ADC_SAR2_ENCAL_GND_ADDR: u8 = 0x7;
const ADC_SAR2_ENCAL_GND_ADDR_MSB: u8 = 5;
const ADC_SAR2_ENCAL_GND_ADDR_LSB: u8 = 5;
const ADC_SAR2_DREF_ADDR: u8 = 0x5;
const ADC_SAR2_DREF_ADDR_MSB: u8 = 0x6;
const ADC_SAR2_DREF_ADDR_LSB: u8 = 0x4;
const ADC_SARADC2_ENCAL_REF_ADDR: u8 = 0x7;
const ADC_SARADC2_ENCAL_REF_ADDR_MSB: u8 = 4;
const ADC_SARADC2_ENCAL_REF_ADDR_LSB: u8 = 4;
}
}
/// The sampling/readout resolution of the ADC.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Resolution {
/// 13-bit resolution
#[default]
Resolution13Bit,
}
impl<ADCI> AdcConfig<ADCI>
where
ADCI: RegisterAccess,
{
/// Calibrate ADC with specified attenuation and voltage source
pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16
where
ADCI: super::CalibrationAccess,
{
let mut adc_max: u16 = 0;
let mut adc_min: u16 = u16::MAX;
let mut adc_sum: u32 = 0;
ADCI::enable_vdef(true);
// Start sampling
ADCI::set_en_pad(ADCI::ADC_CAL_CHANNEL as u8);
ADCI::set_attenuation(ADCI::ADC_CAL_CHANNEL as usize, atten as u8);
// Connect calibration source
ADCI::connect_cal(source, true);
ADCI::set_init_code(0);
for _ in 0..ADCI::ADC_CAL_CNT_MAX {
// Trigger ADC sampling
ADCI::start_sample();
// Wait until ADC1 sampling is done
while !ADCI::is_done() {}
let adc = ADCI::read_data() & ADCI::ADC_VAL_MASK;
ADCI::reset();
adc_sum += adc as u32;
adc_max = adc.max(adc_max);
adc_min = adc.min(adc_min);
}
let cal_val =
(adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADCI::ADC_CAL_CNT_MAX - 2);
// Disconnect calibration source
ADCI::connect_cal(source, false);
cal_val
}
}
#[doc(hidden)]
pub trait RegisterAccess {
fn set_attenuation(channel: usize, attenuation: u8);
fn clear_dig_force();
fn set_start_force();
fn set_en_pad_force();
fn set_en_pad(channel: u8);
fn clear_start_sample();
fn start_sample();
/// Check if sampling is done
fn is_done() -> bool;
/// Read sample data
fn read_data() -> u16;
/// Set calibration parameter to ADC hardware
fn set_init_code(data: u16);
/// Reset flags
fn reset();
}
impl RegisterAccess for crate::peripherals::ADC1 {
fn set_attenuation(channel: usize, attenuation: u8) {
let sensors = unsafe { &*SENS::ptr() };
sensors.sar_atten1().modify(|r, w| {
let new_value = (r.bits() & !(0b11 << (channel * 2)))
| (((attenuation & 0b11) as u32) << (channel * 2));
unsafe { w.sar1_atten().bits(new_value) }
});
}
fn clear_dig_force() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas1_mux()
.modify(|_, w| w.sar1_dig_force().clear_bit());
}
fn set_start_force() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas1_ctrl2()
.modify(|_, w| w.meas1_start_force().set_bit());
}
fn set_en_pad_force() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas1_ctrl2()
.modify(|_, w| w.sar1_en_pad_force().set_bit());
}
fn set_en_pad(channel: u8) {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas1_ctrl2()
.modify(|_, w| unsafe { w.sar1_en_pad().bits(1 << channel) });
}
fn clear_start_sample() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas1_ctrl2()
.modify(|_, w| w.meas1_start_sar().clear_bit());
}
fn start_sample() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas1_ctrl2()
.modify(|_, w| w.meas1_start_sar().set_bit());
}
fn is_done() -> bool {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas1_ctrl2()
.read()
.meas1_done_sar()
.bit_is_set()
}
fn read_data() -> u16 {
unsafe { &*SENS::ptr() }
.sar_meas1_ctrl2()
.read()
.meas1_data_sar()
.bits()
}
fn set_init_code(data: u16) {
let [msb, lsb] = data.to_be_bytes();
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_INITIAL_CODE_HIGH_ADDR, msb as u32);
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_INITIAL_CODE_LOW_ADDR, lsb as u32);
}
fn reset() {
let adc = unsafe { &*APB_SARADC::ptr() };
let sensors = unsafe { &*SENS::ptr() };
adc.int_clr().write(|w| w.adc1_done().clear_bit_by_one());
sensors
.sar_meas1_ctrl2()
.modify(|_, w| w.meas1_start_sar().clear_bit());
}
}
#[cfg(esp32s3)]
impl super::CalibrationAccess for crate::peripherals::ADC1 {
const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
fn enable_vdef(enable: bool) {
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_DREF_ADDR, enable as u8);
}
fn connect_cal(source: AdcCalSource, enable: bool) {
match source {
AdcCalSource::Gnd => {
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR1_ENCAL_GND_ADDR, enable as u8);
}
AdcCalSource::Ref => {
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SARADC1_ENCAL_REF_ADDR, enable as u8);
}
}
}
}
impl RegisterAccess for crate::peripherals::ADC2 {
fn set_attenuation(channel: usize, attenuation: u8) {
let sensors = unsafe { &*SENS::ptr() };
sensors.sar_atten2().modify(|r, w| {
let new_value = (r.bits() & !(0b11 << (channel * 2)))
| (((attenuation & 0b11) as u32) << (channel * 2));
unsafe { w.sar2_atten().bits(new_value) }
});
}
fn clear_dig_force() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas2_mux()
.modify(|_, w| w.sar2_rtc_force().set_bit());
let sar_apb = unsafe { &*APB_SARADC::ptr() };
sar_apb.arb_ctrl().modify(|_, w| w.rtc_force().set_bit());
}
fn set_start_force() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas2_ctrl2()
.modify(|_, w| w.meas2_start_force().set_bit());
}
fn set_en_pad_force() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas2_ctrl2()
.modify(|_, w| w.sar2_en_pad_force().set_bit());
}
fn set_en_pad(channel: u8) {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas2_ctrl2()
.modify(|_, w| unsafe { w.sar2_en_pad().bits(1 << channel) });
}
fn clear_start_sample() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas2_ctrl2()
.modify(|_, w| w.meas2_start_sar().clear_bit());
}
fn start_sample() {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas2_ctrl2()
.modify(|_, w| w.meas2_start_sar().set_bit());
}
fn is_done() -> bool {
let sensors = unsafe { &*SENS::ptr() };
sensors
.sar_meas2_ctrl2()
.read()
.meas2_done_sar()
.bit_is_set()
}
fn read_data() -> u16 {
unsafe { &*SENS::ptr() }
.sar_meas2_ctrl2()
.read()
.meas2_data_sar()
.bits()
}
fn set_init_code(data: u16) {
let [msb, lsb] = data.to_be_bytes();
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_INITIAL_CODE_HIGH_ADDR, msb as u32);
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_INITIAL_CODE_LOW_ADDR, lsb as u32);
}
fn reset() {
let adc = unsafe { &*APB_SARADC::ptr() };
let sensors = unsafe { &*SENS::ptr() };
adc.int_clr().write(|w| w.adc2_done().clear_bit_by_one());
sensors
.sar_meas2_ctrl2()
.modify(|_, w| w.meas2_start_sar().clear_bit());
}
}
#[cfg(esp32s3)]
impl super::CalibrationAccess for crate::peripherals::ADC2 {
const ADC_CAL_CNT_MAX: u16 = ADC_CAL_CNT_MAX;
const ADC_CAL_CHANNEL: u16 = ADC_CAL_CHANNEL;
const ADC_VAL_MASK: u16 = ADC_VAL_MASK;
fn enable_vdef(enable: bool) {
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_DREF_ADDR, enable as u8);
}
fn connect_cal(source: AdcCalSource, enable: bool) {
match source {
AdcCalSource::Gnd => {
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SAR2_ENCAL_GND_ADDR, enable as u8);
}
AdcCalSource::Ref => {
crate::regi2c_write_mask!(I2C_SAR_ADC, ADC_SARADC2_ENCAL_REF_ADDR, enable as u8);
}
}
}
}
/// Analog-to-Digital Converter peripheral driver.
pub struct Adc<'d, ADC> {
_adc: PeripheralRef<'d, ADC>,
active_channel: Option<u8>,
last_init_code: u16,
}
impl<'d, ADCI> Adc<'d, ADCI>
where
ADCI: RegisterAccess,
{
/// Configure a given ADC instance using the provided configuration, and
/// initialize the ADC for use
pub fn new(
adc_instance: impl crate::peripheral::Peripheral<P = ADCI> + 'd,
config: AdcConfig<ADCI>,
) -> Self {
PeripheralClockControl::reset(Peripheral::ApbSarAdc);
PeripheralClockControl::enable(Peripheral::ApbSarAdc);
let sensors = unsafe { &*SENS::ptr() };
// Set attenuation for pins
let attenuations = config.attenuations;
for (channel, attenuation) in attenuations.iter().enumerate() {
if let Some(attenuation) = attenuation {
ADCI::set_attenuation(channel, *attenuation as u8);
}
}
// Set controller to RTC
ADCI::clear_dig_force();
ADCI::set_start_force();
ADCI::set_en_pad_force();
sensors
.sar_hall_ctrl()
.modify(|_, w| w.xpd_hall_force().set_bit());
sensors
.sar_hall_ctrl()
.modify(|_, w| w.hall_phase_force().set_bit());
// Set power to SW power on
#[cfg(esp32s2)]
sensors
.sar_meas1_ctrl1()
.modify(|_, w| w.rtc_saradc_clkgate_en().set_bit());
#[cfg(esp32s3)]
sensors
.sar_peri_clk_gate_conf()
.modify(|_, w| w.saradc_clk_en().set_bit());
sensors
.sar_power_xpd_sar()
.modify(|_, w| w.sarclk_en().set_bit());
sensors
.sar_power_xpd_sar()
.modify(|_, w| unsafe { w.force_xpd_sar().bits(0b11) });
// disable AMP
sensors
.sar_meas1_ctrl1()
.modify(|_, w| unsafe { w.force_xpd_amp().bits(0b11) });
sensors
.sar_amp_ctrl3()
.modify(|_, w| unsafe { w.amp_rst_fb_fsm().bits(0) });
sensors
.sar_amp_ctrl3()
.modify(|_, w| unsafe { w.amp_short_ref_fsm().bits(0) });
sensors
.sar_amp_ctrl3()
.modify(|_, w| unsafe { w.amp_short_ref_gnd_fsm().bits(0) });
sensors
.sar_amp_ctrl1()
.modify(|_, w| unsafe { w.sar_amp_wait1().bits(1) });
sensors
.sar_amp_ctrl1()
.modify(|_, w| unsafe { w.sar_amp_wait2().bits(1) });
sensors
.sar_amp_ctrl2()
.modify(|_, w| unsafe { w.sar_amp_wait3().bits(1) });
Adc {
_adc: adc_instance.into_ref(),
active_channel: None,
last_init_code: 0,
}
}
/// Start and wait for a conversion on the specified pin and return the
/// result
pub fn read_blocking<PIN, CS>(&mut self, pin: &mut AdcPin<PIN, ADCI, CS>) -> u16
where
PIN: AdcChannel,
CS: AdcCalScheme<ADCI>,
{
self.start_sample(pin);
// Wait for ADC to finish conversion
while !ADCI::is_done() {}
// Get converted value
let converted_value = ADCI::read_data();
ADCI::reset();
// Postprocess converted value according to calibration scheme used for pin
pin.cal_scheme.adc_val(converted_value)
}
/// Request that the ADC begin a conversion on the specified pin
///
/// This method takes an [AdcPin](super::AdcPin) reference, as it is
/// expected that the ADC will be able to sample whatever channel
/// underlies the pin.
pub fn read_oneshot<PIN, CS>(
&mut self,
pin: &mut super::AdcPin<PIN, ADCI, CS>,
) -> nb::Result<u16, ()>
where
PIN: super::AdcChannel,
CS: super::AdcCalScheme<ADCI>,
{
if let Some(active_channel) = self.active_channel {
// There is conversion in progress:
// - if it's for a different channel try again later
// - if it's for the given channel, go ahead and check progress
if active_channel != PIN::CHANNEL {
return Err(nb::Error::WouldBlock);
}
} else {
// If no conversions are in progress, start a new one for given channel
self.active_channel = Some(PIN::CHANNEL);
self.start_sample(pin);
}
// Wait for ADC to finish conversion
let conversion_finished = ADCI::is_done();
if !conversion_finished {
return Err(nb::Error::WouldBlock);
}
// Get converted value
let converted_value = ADCI::read_data();
ADCI::reset();
// Postprocess converted value according to calibration scheme used for pin
let converted_value = pin.cal_scheme.adc_val(converted_value);
// Mark that no conversions are currently in progress
self.active_channel = None;
Ok(converted_value)
}
fn start_sample<PIN, CS>(&mut self, pin: &mut AdcPin<PIN, ADCI, CS>)
where
PIN: AdcChannel,
CS: AdcCalScheme<ADCI>,
{
// Set ADC unit calibration according used scheme for pin
let init_code = pin.cal_scheme.adc_cal();
if self.last_init_code != init_code {
ADCI::set_init_code(init_code);
self.last_init_code = init_code;
}
ADCI::set_en_pad(PIN::CHANNEL);
ADCI::clear_start_sample();
ADCI::start_sample();
}
}
#[cfg(esp32s3)]
impl super::AdcCalEfuse for crate::peripherals::ADC1 {
fn init_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_init_code(1, atten)
}
fn cal_mv(atten: Attenuation) -> u16 {
Efuse::rtc_calib_cal_mv(1, atten)
}
fn cal_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_cal_code(1, atten)
}
}
#[cfg(esp32s3)]
impl super::AdcCalEfuse for crate::peripherals::ADC2 {
fn init_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_init_code(2, atten)
}
fn cal_mv(atten: Attenuation) -> u16 {
Efuse::rtc_calib_cal_mv(2, atten)
}
fn cal_code(atten: Attenuation) -> Option<u16> {
Efuse::rtc_calib_cal_code(2, atten)
}
}
impl<'d, ADCI, PIN, CS> embedded_hal_02::adc::OneShot<ADCI, u16, AdcPin<PIN, ADCI, CS>>
for Adc<'d, ADCI>
where
PIN: embedded_hal_02::adc::Channel<ADCI, ID = u8> + AdcChannel,
ADCI: RegisterAccess,
CS: AdcCalScheme<ADCI>,
{
type Error = ();
fn read(&mut self, pin: &mut AdcPin<PIN, ADCI, CS>) -> nb::Result<u16, Self::Error> {
self.read_oneshot(pin)
}
}
mod adc_implementation {
crate::analog::adc::impl_adc_interface! {
ADC1 [
(GpioPin<1>, 0),
(GpioPin<2>, 1),
(GpioPin<3>, 2),
(GpioPin<4>, 3),
(GpioPin<5>, 4),
(GpioPin<6>, 5),
(GpioPin<7>, 6),
(GpioPin<8>, 7),
(GpioPin<9>, 8),
(GpioPin<10>, 9),
]
}
crate::analog::adc::impl_adc_interface! {
ADC2 [
(GpioPin<11>, 0),
(GpioPin<12>, 1),
(GpioPin<13>, 2),
(GpioPin<14>, 3),
(GpioPin<15>, 4),
(GpioPin<16>, 5),
(GpioPin<17>, 6),
(GpioPin<18>, 7),
(GpioPin<19>, 8),
(GpioPin<20>, 9),
]
}
}
+135
View File
@@ -0,0 +1,135 @@
//! # Digital to Analog Converter (DAC)
//!
//! ## Overview
//! Espressif devices usually have multiple DAC channels. Each DAC channel can
//! convert the digital value 0~255 to the analog voltage 0~Vref (The reference
//! voltage 'Vref' here is input from an input pin)
//!
//! The DAC peripheral supports outputting analog signal in the multiple ways.
//!
//! Two 8-bit DAC channels are available.
//!
//! ## Configuration
//! Developers can choose the DAC channel they want to use based on the GPIO
//! pin assignments for each channel.
//!
//! ## Examples
//! ### Write a value to a DAC channel
//! ```rust, no_run
#![doc = crate::before_snippet!()]
//! # use esp_hal::analog::dac::Dac;
//! # use esp_hal::delay::Delay;
//! # use embedded_hal::delay::DelayNs;
#![cfg_attr(esp32, doc = "let dac1_pin = peripherals.GPIO25;")]
#![cfg_attr(esp32s2, doc = "let dac1_pin = peripherals.GPIO17;")]
//! let mut dac1 = Dac::new(peripherals.DAC1, dac1_pin);
//!
//! let mut delay = Delay::new();
//!
//! let mut voltage_dac1 = 200u8;
//!
//! // Change voltage on the pins using write function:
//! loop {
//! voltage_dac1 = voltage_dac1.wrapping_add(1);
//! dac1.write(voltage_dac1);
//!
//! delay.delay_ms(50u32);
//! }
//! # }
//! ```
use crate::{
gpio::{self, AnalogPin},
peripheral::{Peripheral, PeripheralRef},
};
// Only specific pins can be used with each DAC peripheral, and of course
// these pins are different depending on which chip you are using; for this
// reason, we will type alias the pins for ease of use later in this module:
cfg_if::cfg_if! {
if #[cfg(esp32)] {
type Dac1Gpio = gpio::GpioPin<25>;
type Dac2Gpio = gpio::GpioPin<26>;
} else if #[cfg(esp32s2)] {
type Dac1Gpio = gpio::GpioPin<17>;
type Dac2Gpio = gpio::GpioPin<18>;
}
}
/// Digital-to-Analog Converter (DAC) Channel
pub struct Dac<'d, T>
where
T: Instance,
T::Pin: AnalogPin,
{
_inner: PeripheralRef<'d, T>,
}
impl<'d, T> Dac<'d, T>
where
T: Instance,
T::Pin: AnalogPin,
{
/// Construct a new instance of [`Dac`].
pub fn new(dac: impl Peripheral<P = T> + 'd, pin: T::Pin) -> Self {
crate::into_ref!(dac);
// TODO: Revert on drop.
pin.set_analog(crate::private::Internal);
#[cfg(esp32s2)]
unsafe { &*crate::peripherals::SENS::PTR }
.sar_dac_ctrl1()
.modify(|_, w| w.dac_clkgate_en().set_bit());
T::enable_xpd();
Self { _inner: dac }
}
/// Writes the given value.
///
/// For each DAC channel, the output analog voltage can be calculated as
/// follows: DACn_OUT = VDD3P3_RTC * PDACn_DAC/256
pub fn write(&mut self, value: u8) {
T::set_pad_source();
T::write_byte(value);
}
}
#[doc(hidden)]
pub trait Instance: crate::private::Sealed {
const INDEX: usize;
type Pin;
fn enable_xpd() {
unsafe { &*crate::peripherals::RTC_IO::PTR }
.pad_dac(Self::INDEX)
.modify(|_, w| w.dac_xpd_force().set_bit().xpd_dac().set_bit());
}
fn set_pad_source() {
unsafe { &*crate::peripherals::SENS::PTR }
.sar_dac_ctrl2()
.modify(|_, w| w.dac_cw_en(Self::INDEX as u8).clear_bit());
}
fn write_byte(value: u8) {
unsafe { &*crate::peripherals::RTC_IO::PTR }
.pad_dac(Self::INDEX)
.modify(|_, w| unsafe { w.dac().bits(value) });
}
}
impl Instance for crate::peripherals::DAC1 {
const INDEX: usize = 0;
type Pin = Dac1Gpio;
}
impl Instance for crate::peripherals::DAC2 {
const INDEX: usize = 1;
type Pin = Dac2Gpio;
}
+11
View File
@@ -0,0 +1,11 @@
//! # Analog Peripherals
//!
//! ## Overview
//! The `analog` module provides drivers for the various analog peripherals
//! available on the device. For more information about a peripheral driver,
//! please refer to the relevant module documentation.
#[cfg(adc)]
pub mod adc;
#[cfg(dac)]
pub mod dac;