Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
93cb1d5
added ability to trigger ADC flush, added call in Adc::read_channel()
NoahLutz Jan 14, 2026
9493627
initial implementation to support differential sampling
NoahLutz Jan 30, 2026
e6498b8
restored CPU voltage measurment
NoahLutz Feb 2, 2026
25be268
updated d11 implementation to support differential sampling
NoahLutz Feb 2, 2026
698c958
removed unused macro
NoahLutz Feb 2, 2026
a7eb6da
added missing DAC0 analog input channel for D5x family chips
NoahLutz Feb 2, 2026
9a4a58c
fixed typo in Muxposselect enum variant name
NoahLutz Feb 2, 2026
31901e2
added method to retrieve ADC sample resolution
NoahLutz Feb 3, 2026
ecb62f5
Merge branch 'adc-retrieve-resolution' into adc-differential-sampling
NoahLutz Feb 3, 2026
e45ba54
fixed enum variant match syntax
NoahLutz Feb 3, 2026
d005756
Merge branch 'adc-retrieve-resolution' into adc-differential-sampling
NoahLutz Feb 3, 2026
607694f
added associated type to AdcInput to handle signedness for ADC result…
NoahLutz Feb 5, 2026
4f5d207
set CTRLB.LEFTADJ when using differential sampling, added appropriate…
NoahLutz Feb 9, 2026
52eb5c1
added support for right-shifting 8-bit, 10-bit and 16-bit results whe…
NoahLutz Feb 10, 2026
a13b4a4
added support for offset and reference compensation
NoahLutz Feb 10, 2026
b55cf0b
added support for automatically handling left-adjusting and enabling …
NoahLutz Feb 11, 2026
5138ebe
added read of RESULT register in check_read_discard() to discard ADC …
NoahLutz Feb 11, 2026
f2e39ac
Merge branch 'adc-fix-read-discard' into adc-differential-sampling
NoahLutz Feb 11, 2026
9f019d3
added handling of enable/disable of offset compensation when auto rai…
NoahLutz Feb 12, 2026
1047ef0
fixed unintentional overwriting of various registers during operation
NoahLutz Feb 12, 2026
429d732
cleaned up the logic for handling left-shifted results
NoahLutz Feb 12, 2026
28d5cae
simplified get_resolution() and adjusted auto left-shit to only adjus…
NoahLutz Feb 13, 2026
72d6d2c
Merge branch 'master' into adc-differential-sampling
NoahLutz Feb 26, 2026
36fbece
removed now unecessary flush() after conversion
NoahLutz Feb 26, 2026
e61a562
added support for d11 implementation
NoahLutz Mar 3, 2026
6e3fdb0
refactored read* functions and elevated CpuSourceVoltage to type-leve…
NoahLutz Mar 5, 2026
a46c8ce
removed conditonally-compiled fields in AdcSettings
NoahLutz Mar 9, 2026
49efeb2
updated API to use pin parameters and inputs as mutable borrows and c…
NoahLutz Mar 10, 2026
c3d1d40
updated impacted board examples
NoahLutz Mar 10, 2026
89f06da
added back initialization of INPUTCTRL.GAIN for D11/D21 family chips
NoahLutz Mar 11, 2026
f58a86e
minor formatting changes
NoahLutz Mar 11, 2026
8da29df
updated doc example
NoahLutz Mar 11, 2026
d4a885b
Merge branch 'master' into adc-differential-sampling
NoahLutz Mar 12, 2026
5051226
rustfmt changes and additional documentation comments
NoahLutz Mar 12, 2026
2ba7322
fixed clippy errors
NoahLutz Mar 12, 2026
c60c17a
fixed same clippy error in d11 implementation
NoahLutz Mar 12, 2026
c0eeb9d
refactored to remove AdcInput wrapper type, separated functionality i…
NoahLutz Mar 16, 2026
3e1f35b
moved remaining traits to mod.rs, removed input.rs
NoahLutz Mar 16, 2026
feb6f77
Revert "updated impacted board examples"
NoahLutz Mar 16, 2026
1e014e7
fix d11 implementation
NoahLutz Mar 16, 2026
c81757f
fix feather_m0 ADC example
NoahLutz Mar 16, 2026
51d3b2f
added feature gates for enable_offset_compensation() and enable_auto_…
NoahLutz Mar 16, 2026
9d8b0c5
updated documentation comments
NoahLutz Mar 16, 2026
f8991fd
Merge branch 'atsamd-rs:master' into adc-differential-sampling
NoahLutz Mar 23, 2026
c4334d1
replaced cast_signed() with cast using as
NoahLutz Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion boards/feather_m0/examples/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ fn main() -> ! {
loop {
let res = adc.read(&mut adc_pin);
#[cfg(feature = "use_semihosting")]
cortex_m_semihosting::hprintln!("ADC value: {}", read).unwrap();
cortex_m_semihosting::hprintln!("ADC value: {}", res).unwrap();
}
}
57 changes: 57 additions & 0 deletions hal/src/peripherals/adc/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ pub struct AdcBuilder {
pub sample_clock_cycles: Option<u8>,
pub accumulation: Accumulation,
pub vref: Option<Reference>,
pub offset_compensation: Option<bool>,
pub reference_compensation: Option<bool>,
pub auto_left_adjust: Option<bool>,
pub auto_rail_to_rail: Option<bool>,
}

/// Version of [AdcBuilder] without any optional settings.
Expand All @@ -162,6 +166,10 @@ pub(crate) struct AdcSettings {
pub sample_clock_cycles: u8,
pub accumulation: Accumulation,
pub vref: Reference,
pub offset_compensation: bool,
pub reference_compensation: bool,
pub auto_left_adjust: bool,
pub auto_rail_to_rail: bool,
}

impl AdcBuilder {
Expand All @@ -172,6 +180,10 @@ impl AdcBuilder {
sample_clock_cycles: None,
accumulation: accumulation_method,
vref: None,
offset_compensation: None,
reference_compensation: None,
auto_left_adjust: None,
auto_rail_to_rail: None,
}
}

Expand All @@ -190,6 +202,10 @@ impl AdcBuilder {
sample_clock_cycles: self.sample_clock_cycles.unwrap(),
accumulation: self.accumulation,
vref: self.vref.unwrap(),
offset_compensation: self.offset_compensation.unwrap_or(false),
reference_compensation: self.reference_compensation.unwrap_or(false),
auto_left_adjust: self.auto_left_adjust.unwrap_or(false),
auto_rail_to_rail: self.auto_rail_to_rail.unwrap_or(false),
})
}

Expand Down Expand Up @@ -244,6 +260,47 @@ impl AdcBuilder {
Ok(adc_clk_freq / clocks_per_sample)
}

/// Configure the ADC offset compensation
///
/// ## Important
/// * Enabling offset compesation forces the clock cycles per sample to be 4
/// GCLK cycles, any change to the cycles per sample via
/// [`Self::with_clock_cycles_per_sample()`] will be ignored.
#[hal_cfg("adc-d5x")]
pub fn enable_offset_compensation(mut self, enable: bool) -> Self {
Comment thread
rnd-ash marked this conversation as resolved.
self.offset_compensation = Some(enable);
self
}

/// Configure the ADC reference compensation
pub fn enable_reference_compensation(mut self, enable: bool) -> Self {
self.reference_compensation = Some(enable);
self
}

/// Enables automatic left-adjustment when measuring differential inputs.
/// This allows use of the ADC summation or averaging hardware with
/// negative result values. Results are automatically right-shifted back
/// appropriately.
pub fn enable_auto_left_adjust(mut self, enable: bool) -> Self {
self.auto_left_adjust = Some(enable);
self
}

/// Automatically enables rail-to-rail operation when measuring a
/// differential input. This relaxes common-mode input requirements on
/// differential inputs and allows measurments closer to supply rails.
///
/// ## Important
/// * Enabling auto rail-to-rail incurs a slight runtime performance hit as
/// the CTRLA.R2R bit is enable-protected, meaning the ADC must be shut
/// down and re-enabled to enable/disable rail-to-rail mode.
#[hal_cfg("adc-d5x")]
pub fn enable_auto_rail_to_rail(mut self, enable: bool) -> Self {
Comment thread
rnd-ash marked this conversation as resolved.
self.auto_rail_to_rail = Some(enable);
self
}

/// Turn the builder into an ADC
#[hal_cfg("adc-d5x")]
#[inline]
Expand Down
104 changes: 104 additions & 0 deletions hal/src/peripherals/adc/d11/channel.rs
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),
}
93 changes: 76 additions & 17 deletions hal/src/peripherals/adc/d11/mod.rs
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 {
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've placed a doc comment on set_sample_mode() however, I realize that since this is not part of the public API that users will not be able to view this info in the documentation. Is there a better place to stick this info other than on the Adc::read*() methods?

// 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:
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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;
}

Expand Down
Loading
Loading