Skip to content

Commit

Permalink
Finished up comments & example
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidTheFighter authored and mciantyre committed Nov 17, 2020
1 parent 1f6aa15 commit 88d02cd
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 63 deletions.
216 changes: 155 additions & 61 deletions imxrt-hal/src/adc.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
//! ADC
//!
//! Theoretically maybe might support reading analog values
//! This ADC driver supports `embedded_hal`'s ADC traits
//!
//! # Example
//! ```no_run
//! use imxrt_hal::{self, adc};
//!
//! let mut peripherals = imxrt_hal::Peripherals::take().unwrap();
//! let (adc1_builder, _) = peripherals.adc.clock(&mut peripherals.ccm.handle);
//!
//! let mut adc1 = adc1_builder.build(adc::ClockSelect::default(), adc::ClockDivision::default());
//! let mut a1 = adc::AnalogPin::new(peripherals.iomuxc.ad_b1.p02, adc1);
//!
//! let reading = adc1.read(&mut a1).unwrap();
//!```

use crate::ccm;
use crate::ral;
Expand All @@ -10,12 +22,40 @@ use crate::iomuxc::{consts::Unsigned, adc};
use core::marker::PhantomData;
use embedded_hal::adc::{Channel, OneShot};

/// The clock input for an ADC
#[allow(non_camel_case_types)]
pub enum ClockSelect {
IPG, // IPG clock
IPG_2, // IPG clock / 2
ADACK // Asynchronous clock
}

/// How much to divide the clock input
pub enum ClockDivision {
Div1, // Input clock / 1
Div2, // Input clock / 2
Div4, // Input clock / 4
Div8 // Input clock / 8
}

impl ClockSelect {
pub fn default() -> Self {
ClockSelect::ADACK
}
}

impl ClockDivision {
pub fn default() -> Self {
ClockDivision::Div2
}
}

/// Conversion speeds done by clock cycles
pub enum ConversionSpeed {
Slow, // 25 ADC clock cycles
Medium, // 17 ADC clock cycles
Fast, // 9 ADC clock cycles
VeryFast // 3 ADC clock cycles
Slow, // 25 ADC clock cycles (24 on imxrt102x)
Medium, // 17 ADC clock cycles (16 on imxrt102x)
Fast, // 9 ADC clock cycles (8 on imxrt102x)
VeryFast // 3 ADC clock cycles (2 on imxrt102x)
}

/// Denotes how much hardware averaging to do
Expand All @@ -34,13 +74,14 @@ pub enum ResolutionBits {
Res12
}

pub struct AnalogPin<ADCx, P>
/// A pin representing an analog input for a particular ADC
pub struct AnalogInput<ADCx, P>
{
_module: PhantomData<ADCx>,
pin: P
_pin: P
}

impl<P, ADCx> Channel<ADCx> for AnalogPin<ADCx, P>
impl<P, ADCx> Channel<ADCx> for AnalogInput<ADCx, P>
where
P: Pin<ADCx>,
ADCx: adc::ADC
Expand All @@ -52,16 +93,17 @@ where
}
}

impl<P, ADCx> AnalogPin<ADCx, P>
impl<P, ADCx> AnalogInput<ADCx, P>
where
P: Pin<ADCx>,
ADCx: adc::ADC
{
/// Creates a new analog input pin
pub fn new(mut pin: P, _adc: &ADC<ADCx>) -> Self {
prepare_adc_pin(&mut pin);
Self {
_module: PhantomData,
pin
_pin: pin
}
}
}
Expand All @@ -79,61 +121,65 @@ impl<ADCx> ADC<ADCx> {
};

inst.set_resolution(ResolutionBits::Res10);
inst.set_averaging(AveragingCount::Avg4);
inst.set_conversion_speed(ConversionSpeed::Medium);
inst.set_low_power_mode(false);

// Calibrate w/ slow settings initially
inst.set_averaging(AveragingCount::Avg32);
inst.set_conversion_speed(ConversionSpeed::Slow);
inst.calibrate();

// Set to default of 4 hardware averages & medium conversion speed
inst.set_averaging(AveragingCount::Avg4);
inst.set_conversion_speed(ConversionSpeed::Medium);

inst
}

/// Sets the resolution that analog reads return, in bits
pub fn set_resolution(&self, bits: ResolutionBits) {
let mode: u32 = match bits {
ResolutionBits::Res8 => 0b00,
ResolutionBits::Res10 => 0b01,
ResolutionBits::Res12 => 0b10
};

ral::modify_reg!(ral::adc, self.reg, CFG, MODE: mode);
ral::modify_reg!(ral::adc, self.reg, CFG, MODE: match bits {
ResolutionBits::Res8 => MODE_0,
ResolutionBits::Res10 => MODE_1,
ResolutionBits::Res12 => MODE_2
});
}

/// Sets the number of hardware averages taken by the ADC
pub fn set_averaging(&self, avg: AveragingCount) {
let avge: u32 = match avg {
AveragingCount::Avg1 => 0b0,
_ => 0b1
};

let avgs: u32 = match avg {
AveragingCount::Avg32 => 0b11,
AveragingCount::Avg16 => 0b10,
AveragingCount::Avg8 => 0b01,
_ => 0b00
};

ral::modify_reg!(ral::adc, self.reg, GC, AVGE: avge);
ral::modify_reg!(ral::adc, self.reg, CFG, AVGS: avgs);
ral::modify_reg!(ral::adc, self.reg, GC, AVGE: match avg {
AveragingCount::Avg1 => AVGE_0,
_ => AVGE_1
});
ral::modify_reg!(ral::adc, self.reg, CFG, AVGS: match avg {
AveragingCount::Avg32 => AVGS_3,
AveragingCount::Avg16 => AVGS_2,
AveragingCount::Avg8 => AVGS_1,
_ => AVGS_0,
});
}

/// Sets the conversion speed for this ADC, see ConversionSpeed for clock cycle counts.
/// You may also need to recalibrate afterwards
pub fn set_conversion_speed(&self, conversion_speed: ConversionSpeed) {
let (adsts, adlsmp) = match conversion_speed {
ConversionSpeed::Slow => (0b11, 0b1),
ConversionSpeed::Medium => (0b01, 0b1),
ConversionSpeed::Fast => (0b11, 0b0),
ConversionSpeed::VeryFast => (0b00, 0b0)
};

ral::modify_reg!(ral::adc, self.reg, CFG, ADSTS: adsts, ADLSMP: adlsmp);

// TODO Recalibrate?
ral::modify_reg!(ral::adc, self.reg, CFG,
ADSTS: match conversion_speed {
ConversionSpeed::Slow => ADSTS_3,
ConversionSpeed::Medium => ADSTS_1,
ConversionSpeed::Fast => ADSTS_3,
ConversionSpeed::VeryFast => ADSTS_0
},
ADLSMP: match conversion_speed {
ConversionSpeed::Slow => ADLSMP_1,
ConversionSpeed::Medium => ADLSMP_1,
ConversionSpeed::Fast => ADLSMP_0,
ConversionSpeed::VeryFast => ADLSMP_0
}
);
}

/// Enables or disables the low power configuration in the ADC
/// Enables or disables the low power configuration in the ADC. This does limit the
/// ADACK clock frequency (<= 20MHz)
pub fn set_low_power_mode(&self, state: bool) {
ral::modify_reg!(ral::adc, self.reg, CFG, ADLPC: if state { 0b1 } else { 0b0 });
ral::modify_reg!(ral::adc, self.reg, CFG, ADLPC: if state { ADLPC_1 } else { ADLPC_0 });
}

/// Calibrates the ADC, will wait for finish
Expand All @@ -143,42 +189,90 @@ impl<ADCx> ADC<ADCx> {
}
}

impl<P, ADCx> OneShot<ADCx, u16, AnalogPin<ADCx, P>> for ADC<ADCx>
/// Implement embedded-hal traits
impl<ADCx, WORD, P> OneShot<ADCx, WORD, AnalogInput<ADCx, P>> for ADC<ADCx>
where
P: Pin<ADCx>,
ADCx: adc::ADC
ADCx: adc::ADC,
WORD: From<u16>,
P: Pin<ADCx>
{
type Error = u32;
type Error = ();

fn read(&mut self, _pin: &mut AnalogPin<ADCx, P>) -> nb::Result<u16, Self::Error> {
/// Read an ADC value from an AnalogInput. This should always return a good result
fn read(&mut self, _pin: &mut AnalogInput<ADCx, P>) -> nb::Result<WORD, Self::Error> {
let channel = <P as Pin<ADCx>>::Input::U32;
ral::modify_reg!(ral::adc, self.reg, HC0, |_| channel);
while (ral::read_reg!(ral::adc, self.reg, HS, COCO0) == 0) {}

Ok(ral::read_reg!(ral::adc, self.reg, R0) as u16)
Ok((ral::read_reg!(ral::adc, self.reg, R0) as u16).into())
}
}

/// Unclocked ADC modules
///
/// The `Unclocked` struct represents both unconfigured ADC peripherals.
/// Once clocked, you'll have the ability to either the ADC1 or ADC2
/// peripherals.
pub struct Unclocked {
pub(crate) adc1: ral::adc::Instance,
pub(crate) adc2: ral::adc::Instance
}

impl Unclocked {
pub fn clock(self, handle: &mut ccm::Handle) -> (ADC<ADC1>, ADC<ADC2>) {
pub fn clock(self, handle: &mut ccm::Handle) -> (Builder<ADC1>, Builder<ADC2>) {
let (ccm, _) = handle.raw();
ral::modify_reg!(ral::ccm, ccm, CCGR1, CG8: 0b11); // adc1_clk_enable
ral::modify_reg!(ral::ccm, ccm, CCGR1, CG4: 0b11); // adc2_clk_enable

// Set to asynchronous clock
ral::modify_reg!(ral::adc, self.adc1, GC, ADACKEN: 0b1);
ral::modify_reg!(ral::adc, self.adc2, GC, ADACKEN: 0b1);
ral::modify_reg!(ral::adc, self.adc1, CFG, ADICLK: 0b11, ADIV: 0b01);
ral::modify_reg!(ral::adc, self.adc2, CFG, ADICLK: 0b11, ADIV: 0b01);

(
ADC::new(self.adc1),
ADC::new(self.adc2)
Builder::new(self.adc1),
Builder::new(self.adc2)
)
}
}

/// An ADC builder than can build an ADC1 or ADC2 module
pub struct Builder<ADCx> {
_module: PhantomData<ADCx>,
reg: ral::adc::Instance
}

impl<ADCx> Builder<ADCx>
where
ADCx: adc::ADC
{
fn new(reg: ral::adc::Instance) -> Self {
Self {
_module: PhantomData,
reg
}
}

/// Builds an ADC peripheral with a certain clock selection. The ADC starts at
/// a 10-bit resolution w/ 4 hardware averaging samples
pub fn build(self, clock: ClockSelect, division: ClockDivision) -> ADC<ADCx> {
// Enable asynchronous clock if applicable
ral::modify_reg!(ral::adc, self.reg, GC, ADACKEN: match clock {
ClockSelect::ADACK => ADACKEN_1,
_ => ADACKEN_0
});

// Select the clock selection, division, and enable ADHSC if applicable
ral::modify_reg!(ral::adc, self.reg, CFG,
ADICLK: match clock {
ClockSelect::IPG => ADICLK_0,
ClockSelect::IPG_2 => ADICLK_1,
ClockSelect::ADACK => ADICLK_3
},
ADIV: match division {
ClockDivision::Div1 => ADIV_0,
ClockDivision::Div2 => ADIV_1,
ClockDivision::Div4 => ADIV_2,
ClockDivision::Div8 => ADIV_3
},
ADHSC: ADHSC_1
);

ADC::new(self.reg)
}
}
2 changes: 0 additions & 2 deletions imxrt-iomuxc/src/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,4 @@ pub fn prepare_adc_pin<ADCx: ADC, P: Pin<ADCx>>(pin: &mut P) {
pin,
super::Config::modify().set_pull_keep(super::PullKeep::Disabled),
);
// TODO any other necessary configurations. Haven't searched enough
// to know if we need anything else.
}

0 comments on commit 88d02cd

Please sign in to comment.