Skip to content

Commit

Permalink
Add CPU internal sensors reading, and setting VREF for adc
Browse files Browse the repository at this point in the history
  • Loading branch information
rnd-ash committed Jan 23, 2025
1 parent 627d9a9 commit d0e5602
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 18 deletions.
22 changes: 18 additions & 4 deletions hal/src/peripherals/adc/adc_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::pac::adc0;

pub use adc0::avgctrl::Samplenumselect as AdcSampleCount;
pub use adc0::ctrlb::Resselselect as AdcResolution;
pub use adc0::refctrl::Refselselect;

/// Result accumulation strategy for the ADC
#[derive(Copy, Clone)]
Expand All @@ -28,7 +29,10 @@ pub enum AdcDivider {
Div256 = 256,
}

/// # ADC sampling rate settings
#[derive(Copy, Clone)]
pub enum VrefSource {}

/// # ADC sampling settings
///
/// Multiple factors can affect the ADCs overall sampling rate, and this
/// structure allows for the configuring of the majority of factors that affect
Expand Down Expand Up @@ -64,22 +68,27 @@ pub struct AdcSettingsBuilder {
pub sample_clock_cycles: u8,
pub bit_width: AdcResolution,
pub accumulation: AdcAccumulation,
pub vref: Refselselect,
}

impl AdcSettingsBuilder {
///
/// Configure the ADC to sample at 250_000 SPS (Assuming the clock source is
/// 48_000_000) using the following settings:
/// 48MHz) using the following settings:
/// * clock divider factor of 32
/// * 5 clock cycles per sample
/// * 6 clock cycles per sample
/// * 12bit sampling
/// * Single accumulation (No averaging or summing)
///
/// ## Additional reading settings by default
/// * Use VDDANA as reference voltage for a full 0.0-3.3V reading
pub fn new() -> Self {
Self {
clk_divider: AdcDivider::Div32,
sample_clock_cycles: 5,
sample_clock_cycles: 6,
bit_width: AdcResolution::_12bit,
accumulation: AdcAccumulation::Single,
vref: Refselselect::Intvcc1,
}
}

Expand All @@ -100,6 +109,11 @@ impl AdcSettingsBuilder {
self
}

pub fn with_vref(mut self, reference: Refselselect) -> Self {
self.vref = reference;
self
}

/// Sets how the ADC will accumulate values before actually returning a
/// value.
///
Expand Down
81 changes: 67 additions & 14 deletions hal/src/peripherals/adc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ pub enum Error {
TemperatureSensorNotEnabled,
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum CpuVoltageSource {
/// Core voltage
Core,
/// VBAT supply voltage
Vbat,
/// IO supply voltage
Io,
}

bitflags::bitflags! {
#[derive(Clone, Copy)]
pub struct Flags: u8 {
Expand Down Expand Up @@ -356,30 +366,43 @@ impl<I: AdcInstance> Adc<I> {
}

self.sync();
self.set_reference(settings.vref);
}

/// Converts our ADC Reading (0-n) to the range 0.0-1.0, where 1.0 = 2^(reading_bitwidth)
fn reading_to_f32(&self, raw: u16) -> f32 {
let max = match self.cfg.bit_width {
AdcResolution::_16bit => 65536,
AdcResolution::_12bit => 4096,
AdcResolution::_10bit => 1024,
AdcResolution::_8bit => 256,
};
raw as f32 / max as f32
}

#[inline]
fn set_reference(&mut self, reference: Reference) {
self.adc
.refctrl()
.modify(|_, w| w.refsel().variant(Reference::Intref));
.modify(|_, w| w.refsel().variant(reference));
self.sync();
}

#[inline]
pub fn read_blocking(&mut self, ch: u8) -> u16 {
self.disable_freerunning();
// Clear overrun errors that might've occured before we try to read anything
let _ = self.check_and_clear_flags(self.read_flags());

self.disable_interrupts(Flags::all());
self.mux(ch);
self.power_up();
self.start_conversion();

self.clear_flags(Flags::RESRDY);
let _discard = self.conversion_result();
while !self.read_flags().contains(Flags::RESRDY) {
core::hint::spin_loop();
}

let res = self.conversion_result();
self.power_down();
let res = self.conversion_result();
res
}

Expand All @@ -397,13 +420,11 @@ impl<I: AdcInstance> Adc<I> {
while !self.read_flags().contains(Flags::RESRDY) {
core::hint::spin_loop();
}

*result = self.conversion_result();
self.check_and_clear_flags(self.read_flags())?;
}

self.power_down();

self.disable_freerunning();
Ok(())
}

Expand Down Expand Up @@ -431,6 +452,35 @@ impl<I: AdcInstance> Adc<I> {
Ok(self.tp_tc_to_temp(tp, tc))
}

/// Read one of the CPU internal voltage supply, and return the value in
/// millivolts (Volts/1000)
pub fn read_internal_voltage(&mut self, src: CpuVoltageSource) -> u16 {
let chan = match src {
CpuVoltageSource::Core => 0x18,
CpuVoltageSource::Vbat => 0x19,
CpuVoltageSource::Io => 0x1A
};
// Before reading, we have to select VDDANA as our reference voltage
// so we get the full 3v3 range
if self.cfg.vref != Reference::Intvcc1 {
// Modify it
self.set_reference(Reference::Intvcc1);
}

let mut adc_val = self.read_blocking(chan);
if let AdcAccumulation::Summed(sum) = self.cfg.accumulation {
let div: u16 = 2u16.pow(sum as u32);
adc_val /= div;
}
let mut res = self.reading_to_f32(adc_val) * 3.3 * 4.0;

// Restore our settings
if Reference::Intvcc1 != self.cfg.vref {
self.set_reference(self.cfg.vref);
}
(res * 1000.0) as u16
}

/// Return the underlying ADC PAC object
///
/// You must also return all channels to the ADC to free its resources.
Expand Down Expand Up @@ -533,8 +583,13 @@ impl<I: AdcInstance, T> Adc<I, T> {

#[inline]
fn start_conversion(&mut self) {
// The double trigger here is in case the VREF value changed between
// reads, this discards the conversion made just after the VREF changed,
// which the data sheet tells us to do in order to not get a faulty reading
// right after changing VREF value
self.adc.swtrig().modify(|_, w| w.start().set_bit());
self.sync();
self.adc.swtrig().modify(|_, w| w.start().set_bit());
}

#[inline]
Expand Down Expand Up @@ -583,37 +638,35 @@ where
{
#[inline]
pub async fn read(&mut self, ch: u8) -> u16 {
self.disable_freerunning();
// Clear overrun errors that might've occured before we try to read anything
let _ = self.check_and_clear_flags(self.read_flags());

self.mux(ch);
self.power_up();
let _ = self.check_and_clear_flags(self.read_flags());
self.start_conversion();
// Here we explicitly ignore the result, because we know that
// overrun errors are impossible since the ADC is configured in one-shot mode.
let _ = self.wait_flags(Flags::RESRDY).await;
let result = self.conversion_result();

self.power_down();
result
}

#[inline]
pub async fn read_buffer(&mut self, ch: u8, dst: &mut [u16]) -> Result<(), Error> {
// Clear overrun errors that might've occured before we try to read anything
let _ = self.check_and_clear_flags(self.read_flags());
self.enable_freerunning();

self.mux(ch);
self.power_up();
let _ = self.check_and_clear_flags(self.read_flags());
self.start_conversion();
for result in dst.iter_mut() {
self.wait_flags(Flags::RESRDY).await?;
*result = self.conversion_result();
}

self.power_down();
self.disable_freerunning();
Ok(())
}

Expand Down

0 comments on commit d0e5602

Please sign in to comment.