diff --git a/esp-hal-common/src/analog/adc/esp32s3.rs b/esp-hal-common/src/analog/adc/esp32s3.rs index 8cf7e7c440d..1d46e609fe2 100644 --- a/esp-hal-common/src/analog/adc/esp32s3.rs +++ b/esp-hal-common/src/analog/adc/esp32s3.rs @@ -1 +1,401 @@ -// Currently we are missing the SENS peripheral from the SVD +use core::marker::PhantomData; + +use embedded_hal::adc::{Channel, OneShot}; + +use crate::{ + analog::{ADC1, ADC2}, + pac::{APB_SARADC, SENS}, +}; + +/// The sampling/readout resolution of the ADC +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum Resolution { + Resolution13Bit, +} + +/// The attenuation of the ADC pin +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum Attenuation { + Attenuation0dB = 0b00, + Attenuation2p5dB = 0b01, + Attenuation6dB = 0b10, + Attenuation11dB = 0b11, +} + +pub struct AdcPin { + pub pin: PIN, + _phantom: PhantomData, +} + +impl, ADCI> Channel for AdcPin { + type ID = u8; + + fn channel() -> Self::ID { + PIN::channel() + } +} + +pub struct AdcConfig { + pub resolution: Resolution, + pub attenuations: [Option; 10], + _phantom: PhantomData, +} + +impl AdcConfig +where + ADCI: RegisterAccess, +{ + pub fn new() -> AdcConfig { + Self::default() + } + + pub fn enable_pin>( + &mut self, + pin: PIN, + attenuation: Attenuation, + ) -> AdcPin { + self.attenuations[PIN::channel() as usize] = Some(attenuation); + + AdcPin { + pin, + _phantom: PhantomData::default(), + } + } +} + +impl Default for AdcConfig { + fn default() -> Self { + AdcConfig { + resolution: Resolution::Resolution13Bit, + attenuations: [None; 10], + _phantom: PhantomData::default(), + } + } +} + +#[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) { + // no-op + } + + fn set_sample_bit(_resolution: u8) { + // no-op + } + + 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 as u8 & 0b11) as u32) << (channel * 2)); + + unsafe { w.sar_sar1_atten().bits(new_value) } + }); + } + + fn clear_dig_force() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas1_mux + .modify(|_, w| w.sar_sar1_dig_force().clear_bit()); + } + + fn set_start_force() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas1_ctrl2 + .modify(|_, w| w.sar_meas1_start_force().set_bit()); + } + + fn set_en_pad_force() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas1_ctrl2 + .modify(|_, w| w.sar_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.sar_sar1_en_pad().bits(1 << channel) }); + } + + fn clear_start_sar() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas1_ctrl2 + .modify(|_, w| w.sar_meas1_start_sar().clear_bit()); + } + + fn set_start_sar() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas1_ctrl2 + .modify(|_, w| w.sar_meas1_start_sar().set_bit()); + } + + fn read_done_sar() -> bool { + let sensors = unsafe { &*SENS::ptr() }; + sensors.sar_meas1_ctrl2.read().sar_meas1_done_sar().bit_is_set() + } + + fn read_data_sar() -> u16 { + let sensors = unsafe { &*SENS::ptr() }; + sensors.sar_meas1_ctrl2.read().sar_meas1_data_sar().bits() as u16 + } +} + +impl RegisterAccess for ADC2 { + fn set_bit_width(_resolution: u8) { + // no-op + } + + fn set_sample_bit(_resolution: u8) { + // no-op + } + + 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 as u8 & 0b11) as u32) << (channel * 2)); + + unsafe { w.sar_sar2_atten().bits(new_value) } + }); + } + + fn clear_dig_force() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas2_mux + .modify(|_, w| w.sar_sar2_rtc_force().set_bit()); + + let sar_apb = unsafe { &*APB_SARADC::ptr() }; + sar_apb + .apb_adc_arb_ctrl + .modify(|_, w| w.adc_arb_rtc_force().set_bit()); + } + + fn set_start_force() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas2_ctrl2 + .modify(|_, w| w.sar_meas2_start_force().set_bit()); + } + + fn set_en_pad_force() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas2_ctrl2 + .modify(|_, w| w.sar_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.sar_sar2_en_pad().bits(1 << channel) }); + } + + fn clear_start_sar() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas2_ctrl2 + .modify(|_, w| w.sar_meas2_start_sar().clear_bit()); + } + + fn set_start_sar() { + let sensors = unsafe { &*SENS::ptr() }; + sensors + .sar_meas2_ctrl2 + .modify(|_, w| w.sar_meas2_start_sar().set_bit()); + } + + fn read_done_sar() -> bool { + let sensors = unsafe { &*SENS::ptr() }; + sensors.sar_meas2_ctrl2.read().sar_meas2_done_sar().bit_is_set() + } + + fn read_data_sar() -> u16 { + let sensors = unsafe { &*SENS::ptr() }; + sensors.sar_meas2_ctrl2.read().sar_meas2_data_sar().bits() as u16 + } +} + +pub struct ADC { + adc: PhantomData, + attenuations: [Option; 10], + active_channel: Option, +} + +impl ADC +where + ADCI: RegisterAccess, +{ + pub fn adc(_adc_instance: ADCI, config: AdcConfig) -> Result { + 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 in 0..attenuations.len() { + if let Some(attenuation) = attenuations[channel] { + 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_hall_ctrl + .modify(|_, w| w.sar_xpd_hall_force().set_bit()); + sensors + .sar_hall_ctrl + .modify(|_, w| w.sar_hall_phase_force().set_bit()); + + // Set power to SW power on + sensors + .sar_peri_clk_gate_conf + .modify(|_, w| w.sar_saradc_clk_en().set_bit()); + + sensors + .sar_power_xpd_sar + .modify(|_, w| w.sar_sarclk_en().set_bit()); + + sensors + .sar_power_xpd_sar + .modify(|_, w| unsafe { w.sar_force_xpd_sar().bits(0b11) }); + + // disable AMP + sensors + .sar_meas1_ctrl1 + .modify(|_, w| unsafe { w.sar_force_xpd_amp().bits(0b11) }); + sensors + .sar_amp_ctrl3 + .modify(|_, w| unsafe { w.sar_amp_rst_fb_fsm().bits(0) }); + sensors + .sar_amp_ctrl3 + .modify(|_, w| unsafe { w.sar_amp_short_ref_fsm().bits(0) }); + sensors + .sar_amp_ctrl3 + .modify(|_, w| unsafe { w.sar_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) }); + + let adc = ADC { + adc: PhantomData, + attenuations: config.attenuations, + active_channel: None, + }; + + Ok(adc) + } +} + +impl OneShot> for ADC +where + WORD: From, + PIN: Channel, + ADCI: RegisterAccess, +{ + type Error = (); + + fn read(&mut self, _pin: &mut AdcPin) -> nb::Result { + if self.attenuations[AdcPin::::channel() as usize] == None { + panic!( + "Channel {} is not configured reading!", + AdcPin::::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 != AdcPin::::channel() { + return Err(nb::Error::WouldBlock); + } + } else { + // If no conversions are in progress, start a new one for given channel + self.active_channel = Some(AdcPin::::channel()); + + ADCI::set_en_pad(AdcPin::::channel() as u8); + + 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.into()) + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! impl_adc_interface { + ($adc:ident [ + $( ($pin:ident, $channel:expr) ,)+ + ]) => { + + $( + impl Channel<$adc> for $pin { + type ID = u8; + + fn channel() -> u8 { $channel } + } + )+ + } +} + +pub use impl_adc_interface; diff --git a/esp-hal-common/src/analog/mod.rs b/esp-hal-common/src/analog/mod.rs index 047b09dee1f..c57f4551288 100644 --- a/esp-hal-common/src/analog/mod.rs +++ b/esp-hal-common/src/analog/mod.rs @@ -8,7 +8,7 @@ pub mod adc; pub mod dac; cfg_if::cfg_if! { - if #[cfg(any(esp32, esp32s2))] { + if #[cfg(any(esp32, esp32s2, esp32s3))] { use core::marker::PhantomData; use crate::pac::SENS; diff --git a/esp32s3-hal/examples/adc.rs b/esp32s3-hal/examples/adc.rs new file mode 100644 index 00000000000..2c14f7199e1 --- /dev/null +++ b/esp32s3-hal/examples/adc.rs @@ -0,0 +1,55 @@ +//! Connect a potentiometer to PIN3 and see the read values change when +//! rotating the shaft. Alternatively you could also connect the PIN to GND or +//! 3V3 to see the maximum and minimum raw values read. + +#![no_std] +#![no_main] + +use esp32s3_hal::{ + adc::{AdcConfig, Attenuation, ADC, ADC1}, + analog::SensExt, + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; +use xtensa_lx_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt = timer_group0.wdt; + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + + // Disable MWDT and RWDT (Watchdog) flash boot protection + wdt.disable(); + rtc.rwdt.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + + // Create ADC instances + let analog = peripherals.SENS.split(); + + let mut adc1_config = AdcConfig::new(); + + let mut pin3 = adc1_config.enable_pin(io.pins.gpio3.into_analog(), Attenuation::Attenuation11dB); + + let mut adc1 = ADC::::adc(analog.adc1, adc1_config).unwrap(); + + let mut delay = Delay::new(&clocks); + + loop { + let pin3_value: u16 = nb::block!(adc1.read(&mut pin3)).unwrap(); + println!("PIN3 ADC reading = {}", pin3_value); + delay.delay_ms(1500u32); + } +} diff --git a/esp32s3-hal/src/adc.rs b/esp32s3-hal/src/adc.rs new file mode 100644 index 00000000000..4c34bb2180b --- /dev/null +++ b/esp32s3-hal/src/adc.rs @@ -0,0 +1,40 @@ +//! Analog to digital (ADC) conversion support. +//! +//! This module provides functions for reading analog values from two +//! analog to digital converters available on the ESP32-S3: `ADC1` and `ADC2`. + +use embedded_hal::adc::Channel; +use esp_hal_common::analog::adc::impl_adc_interface; +pub use esp_hal_common::analog::{adc::*, ADC1, ADC2}; + +use crate::{gpio::*, gpio_types::Analog}; + +impl_adc_interface! { + ADC1 [ + (Gpio1, 0), + (Gpio2, 1), + (Gpio3, 2), + (Gpio4, 3), + (Gpio5, 4), + (Gpio6, 5), + (Gpio7, 6), + (Gpio8, 7), + (Gpio9, 8), + (Gpio10,9), + ] +} + +impl_adc_interface! { + ADC2 [ + (Gpio11, 0), + (Gpio12, 1), + (Gpio13, 2), + (Gpio14, 3), + (Gpio15, 4), + (Gpio16, 5), + (Gpio17, 6), + (Gpio18, 7), + (Gpio19, 8), + (Gpio20, 9), + ] +} diff --git a/esp32s3-hal/src/lib.rs b/esp32s3-hal/src/lib.rs index 9b3b2b2ce81..e4ebb5928e3 100644 --- a/esp32s3-hal/src/lib.rs +++ b/esp32s3-hal/src/lib.rs @@ -33,8 +33,14 @@ pub use esp_hal_common::{ pub use self::gpio::IO; +pub mod adc; pub mod gpio; +/// Common module for analog functions +pub mod analog { + pub use esp_hal_common::analog::{AvailableAnalog, SensExt}; +} + #[no_mangle] extern "C" fn EspDefaultHandler(_level: u32, _interrupt: pac::Interrupt) {}