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:
17624 files changed
+4564928
-38
No files matched your search
@@ -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
@@ -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
@@ -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 {}
|
||||
@@ -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
@@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+269
@@ -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
@@ -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
@@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+135
@@ -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;
|
||||
}
|
||||
Vendored
+11
@@ -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;
|
||||
Reference in new issue
Block a user