-
Notifications
You must be signed in to change notification settings - Fork 220
feat(adc): differential sampling support #999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
93cb1d5
9493627
e6498b8
25be268
698c958
a7eb6da
9a4a58c
31901e2
ecb62f5
e45ba54
d005756
607694f
4f5d207
52eb5c1
a13b4a4
b55cf0b
5138ebe
f2e39ac
9f019d3
1047ef0
429d732
28d5cae
72d6d2c
36fbece
e61a562
6e3fdb0
a46c8ce
49efeb2
c3d1d40
89f06da
f58a86e
8da29df
d4a885b
5051226
2ba7322
c60c17a
c0eeb9d
3e1f35b
feb6f77
1e014e7
c81757f
51d3b2f
9d8b0c5
f8991fd
c4334d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| use core::marker::PhantomData; | ||
| use atsamd_hal_macros::hal_cfg; | ||
| use crate::{ | ||
| adc::*, | ||
| pac::adc::inputctrl::{Muxposselect, Muxnegselect}, | ||
| typelevel::Sealed, | ||
| }; | ||
|
|
||
| macro_rules! channel { | ||
| ( | ||
| $( | ||
| $CH:ident: ($($PMUX:path)?, $($NMUX:path)?) $(+ $MARKER:ident)* | ||
| ),+ | ||
| $(,)? | ||
| ) => { | ||
| crate::paste::paste!{ | ||
| $( | ||
| pub struct $CH<I: AdcInstance> { | ||
| adc: PhantomData<I>, | ||
| } | ||
|
|
||
| impl<I: AdcInstance> Sealed for $CH<I> {} | ||
|
|
||
| $( | ||
| impl<I: AdcInstance> PosChannel<I> for $CH<I> { | ||
| const MUXVAL: Muxposselect = $PMUX; | ||
|
|
||
| fn get_channel() -> Self { | ||
| Self { | ||
| adc: PhantomData | ||
| } | ||
| } | ||
| } | ||
| )? | ||
| $( | ||
| impl<I: AdcInstance> NegChannel<I> for $CH<I> { | ||
| const MUXVAL: Muxnegselect = $NMUX; | ||
|
|
||
| fn get_channel() -> Self { | ||
| Self { | ||
| adc: PhantomData | ||
| } | ||
| } | ||
| } | ||
| )? | ||
| $( | ||
| impl<I: AdcInstance> $MARKER<I> for $CH<I> {} | ||
| )* | ||
| )+ | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| #[hal_cfg("adc-d21")] | ||
| channel! { | ||
| AIN0: (Muxposselect::Pin0, Muxnegselect::Pin0), | ||
| AIN1: (Muxposselect::Pin1, Muxnegselect::Pin1), | ||
| AIN2: (Muxposselect::Pin2, Muxnegselect::Pin2), | ||
| AIN3: (Muxposselect::Pin3, Muxnegselect::Pin3), | ||
| AIN4: (Muxposselect::Pin4, Muxnegselect::Pin4), | ||
| AIN5: (Muxposselect::Pin5, Muxnegselect::Pin5), | ||
| AIN6: (Muxposselect::Pin6, Muxnegselect::Pin6), | ||
| AIN7: (Muxposselect::Pin7, Muxnegselect::Pin7), | ||
| AIN8: (Muxposselect::Pin8, ), | ||
| AIN9: (Muxposselect::Pin9, ), | ||
| AIN10: (Muxposselect::Pin10, ), | ||
| AIN11: (Muxposselect::Pin11, ), | ||
| AIN12: (Muxposselect::Pin12, ), | ||
| AIN13: (Muxposselect::Pin13, ), | ||
| AIN14: (Muxposselect::Pin14, ), | ||
| AIN15: (Muxposselect::Pin15, ), | ||
| AIN16: (Muxposselect::Pin15, ), | ||
| AIN17: (Muxposselect::Pin15, ), | ||
| AIN18: (Muxposselect::Pin15, ), | ||
| AIN19: (Muxposselect::Pin15, ), | ||
| TEMP: (Muxposselect::Temp, ), | ||
| SCALEDCOREVCC: (Muxposselect::Scaledcorevcc, ) + CpuVoltageSource, | ||
| SCALEDIOVCC: (Muxposselect::Scalediovcc, ) + CpuVoltageSource, | ||
| BANDGAP: (Muxposselect::Bandgap, ) + CpuVoltageSource, | ||
| DAC: (Muxposselect::Dac, ), | ||
| GND: (, Muxnegselect::Gnd), | ||
| IOGND: (, Muxnegselect::Iognd), | ||
| } | ||
|
|
||
| #[hal_cfg("adc-d11")] | ||
| channel! { | ||
| AIN0: (Muxposselect::Pin0, Muxnegselect::Pin0), | ||
| AIN1: (Muxposselect::Pin1, Muxnegselect::Pin1), | ||
| AIN2: (Muxposselect::Pin2, Muxnegselect::Pin2), | ||
| AIN3: (Muxposselect::Pin3, Muxnegselect::Pin3), | ||
| AIN4: (Muxposselect::Pin4, Muxnegselect::Pin4), | ||
| AIN5: (Muxposselect::Pin5, Muxnegselect::Pin5), | ||
| AIN6: (Muxposselect::Pin6, Muxnegselect::Pin6), | ||
| AIN7: (Muxposselect::Pin7, Muxnegselect::Pin7), | ||
| AIN8: (Muxposselect::Pin8, ), | ||
| AIN9: (Muxposselect::Pin9, ), | ||
| TEMP: (Muxposselect::Temp, ), | ||
| SCALEDCOREVCC: (Muxposselect::Scaledcorevcc, ) + CpuVoltageSource, | ||
| SCALEDIOVCC: (Muxposselect::Scalediovcc, ) + CpuVoltageSource, | ||
| BANDGAP: (Muxposselect::Bandgap, ) + CpuVoltageSource, | ||
| DAC: (Muxposselect::Dac, ), | ||
| GND: (, Muxnegselect::Gnd), | ||
| IOGND: (, Muxnegselect::Iognd), | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,20 @@ | ||
| use super::{ | ||
| ADC_SETTINGS_INTERNAL_READ, ADC_SETTINGS_INTERNAL_READ_D21_TEMP, Accumulation, Adc, | ||
| AdcInstance, AdcSettings, CpuVoltageSource, Error, Flags, PrimaryAdc, SampleCount, | ||
| AdcInstance, AdcSettings, Error, Flags, PrimaryAdc, SampleCount, SampleMode, TEMP, GND, | ||
| CpuVoltageSource, PosChannel, NegChannel, | ||
| }; | ||
|
|
||
| use atsamd_hal_macros::{hal_macro_helper}; | ||
|
|
||
| #[cfg(feature = "async")] | ||
| use super::{FutureAdc, async_api}; | ||
|
|
||
| use crate::{calibration, pac}; | ||
| use pac::Peripherals; | ||
| use pac::Sysctrl; | ||
| use pac::adc::inputctrl::Gainselect; | ||
| use pac::adc::inputctrl::{Gainselect, Muxposselect}; | ||
| pub mod pin; | ||
| pub mod channel; | ||
|
|
||
| /// Wrapper around the ADC instance | ||
| pub struct Adc0 { | ||
|
|
@@ -76,10 +80,9 @@ impl<I: AdcInstance> Adc<I> { | |
| .sampctrl() | ||
| .modify(|_, w| unsafe { w.samplen().bits(cfg.sample_clock_cycles.saturating_sub(1)) }); // sample length | ||
| self.sync(); | ||
| self.adc.inputctrl().modify(|_, w| { | ||
| w.muxneg().gnd(); | ||
| w.gain().variant(Gainselect::Div2) | ||
| }); // No negative input (internal gnd) | ||
| self.adc | ||
| .inputctrl() | ||
| .modify(|_, w| w.gain().variant(Gainselect::Div2)); | ||
| self.sync(); | ||
| let (sample_cnt, adjres) = match cfg.accumulation { | ||
| // 1 sample to be used as is | ||
|
|
@@ -100,6 +103,8 @@ impl<I: AdcInstance> Adc<I> { | |
| self.sync(); | ||
| self.set_reference(cfg.vref); | ||
| self.sync(); | ||
| self.adc.refctrl().modify(|_, w| w.refcomp().bit(cfg.reference_compensation)); | ||
| self.sync(); | ||
| self.adc.ctrla().modify(|_, w| w.enable().set_bit()); | ||
| self.sync(); | ||
| self.cfg = cfg; | ||
|
|
@@ -196,16 +201,62 @@ impl<I: AdcInstance> Adc<I> { | |
| } | ||
|
|
||
| #[inline] | ||
| pub(super) fn mux(&mut self, ch: u8) { | ||
| pub(super) fn mux( | ||
| &mut self, | ||
| pos_ch: pac::adc::inputctrl::Muxposselect, | ||
| neg_ch: pac::adc::inputctrl::Muxnegselect, | ||
| ) { | ||
| self.adc.inputctrl().modify(|r, w| { | ||
| if r.muxpos().bits() != ch { | ||
| if (r.muxpos().bits() != pos_ch.into()) || (r.muxneg().bits() != neg_ch.into()) { | ||
| self.discard = true; | ||
| } | ||
| unsafe { w.muxpos().bits(ch) } | ||
|
|
||
| // Safe as pos_ch and neg_ch are derived from Muxposselect and Muxnegselect PAC enums | ||
| unsafe { | ||
| w.muxpos().bits(pos_ch.into()); | ||
| w.muxpos().bits(neg_ch.into()) | ||
| } | ||
| }); | ||
| self.sync() | ||
| } | ||
|
|
||
|
|
||
| /// Sets the sample mode and various ADC settings based on sample mode & the user supplied | ||
| /// [`AdcSettings`]. | ||
| /// | ||
| /// ## Important | ||
| /// * (For D11) The ADC is automatically powered down before modifying the the enable-protected | ||
| /// CTRLB.DIFFMODE and CTRLB.LEFTADJ bits, then turned back on. | ||
| #[inline] | ||
| #[hal_macro_helper] | ||
| pub(super) fn set_sample_mode(&mut self, sample_mode: SampleMode) { | ||
| // Disable the ADC if chip is SAMD11 family | ||
| // SAMD11 datasheet section 31.8.5 states that the ADC must be disabled to modify | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably document this in the user docs that on D11 the ADC is shut down briefly
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've placed a doc comment on |
||
| // the DIFFMODE and LEFTADJ bits. | ||
| #[hal_cfg("adc-d11")] | ||
| self.power_down(); | ||
|
|
||
| self.adc.ctrlb().modify(|_, w| { | ||
| match sample_mode { | ||
| SampleMode::SingleEnded => w.diffmode().clear_bit(), | ||
| SampleMode::Differential => w.diffmode().set_bit(), | ||
| }; | ||
|
|
||
| if self.cfg.auto_left_adjust { | ||
| match sample_mode { | ||
| SampleMode::SingleEnded => w.leftadj().clear_bit(), | ||
| SampleMode::Differential => w.leftadj().set_bit(), | ||
| }; | ||
| } | ||
| w | ||
| }); | ||
| self.sync(); | ||
|
|
||
| // Re-enable the ADC if chip is SAMD11 family | ||
| #[hal_cfg("adc-d11")] | ||
| self.power_up(); | ||
| } | ||
|
|
||
| #[inline] | ||
| fn cpu_raw_to_temp(&self, reading: u16) -> f32 { | ||
| // Source: | ||
|
|
@@ -252,7 +303,11 @@ impl<I: AdcInstance + PrimaryAdc> Adc<I> { | |
| // Settings | ||
| adc.adc.inputctrl().modify(|_, w| w.gain()._1x()); | ||
| adc.discard = true; | ||
| let res = adc.read_channel(0x18); | ||
| let res = adc.read_channel( | ||
| TEMP::get_channel(), | ||
| GND::get_channel(), | ||
| SampleMode::SingleEnded | ||
| ); | ||
| // Set gain back to normal | ||
| adc.adc.inputctrl().modify(|_, w| w.gain().div2()); | ||
| adc.discard = true; | ||
|
|
@@ -265,11 +320,11 @@ impl<I: AdcInstance + PrimaryAdc> Adc<I> { | |
| } | ||
|
|
||
| #[inline] | ||
| pub fn read_cpu_voltage(&mut self, src: CpuVoltageSource) -> u16 { | ||
| pub fn read_cpu_voltage<C: CpuVoltageSource<I>>(&mut self, src: C) -> u16 { | ||
| let voltage = self.with_specific_settings(ADC_SETTINGS_INTERNAL_READ, |adc| { | ||
| let res = adc.read_channel(src as u8); | ||
| let res = adc.read_channel(src, GND::get_channel(), SampleMode::SingleEnded); | ||
| let mut res_f = adc.reading_to_f32(res) * 3.3; | ||
| if CpuVoltageSource::Bandgap != src { | ||
| if C::MUXVAL != Muxposselect::Bandgap { | ||
| res_f *= 4.0; | ||
| } | ||
| res_f | ||
|
|
@@ -293,7 +348,11 @@ where | |
| self.inner.adc.inputctrl().modify(|_, w| w.gain()._1x()); | ||
| self.inner.discard = true; | ||
|
|
||
| let res = self.read_channel(0x18).await; | ||
| let res = self.read_channel( | ||
| TEMP::get_channel(), | ||
| GND::get_channel(), | ||
| SampleMode::SingleEnded | ||
| ).await; | ||
|
|
||
| self.inner.adc.inputctrl().modify(|_, w| w.gain().div2()); | ||
| self.inner.configure(old_adc_settings); | ||
|
|
@@ -305,13 +364,13 @@ where | |
| } | ||
|
|
||
| /// Reads a CPU voltage source. Value returned is in millivolts (mV) | ||
| pub async fn read_cpu_voltage(&mut self, src: CpuVoltageSource) -> u16 { | ||
| pub async fn read_cpu_voltage<C: CpuVoltageSource<I>>(&mut self, src: C) -> u16 { | ||
| let old_adc_settings = self.inner.cfg; | ||
| self.inner.configure(ADC_SETTINGS_INTERNAL_READ); | ||
|
|
||
| let res = self.read_channel(src as u8).await; | ||
| let res = self.read_channel(src, GND::get_channel(), SampleMode::SingleEnded).await; | ||
| let mut voltage = self.inner.reading_to_f32(res) * 3.3; | ||
| if CpuVoltageSource::Bandgap != src { | ||
| if C::MUXVAL != Muxposselect::Bandgap { | ||
| voltage *= 4.0; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.