Skip to content

Adc implementation #23

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

Merged
merged 18 commits into from
Jul 26, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
165 changes: 165 additions & 0 deletions avr-hal-generic/src/adc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@

/// The division factor between the system clock frequency and the input clock to the AD converter.
///
/// To get 10bit precision, clock from 50kHz to 200kHz must be supplied. If you need less precision, you can supply higher clock.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClockRateDivision {
Factor2,
Factor4,
Factor8,
Factor16,
Factor32,
Factor64,
Factor128,
}


impl Default for ClockRateDivision {
fn default() -> Self {
Self::Factor128
}
}


/// Select the voltage reference for the ADC peripheral
///
/// The inernal voltage reference options may not be used if an external reference voltage isbeing applied to the AREF pin.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferenceVoltage {
/// Voltage applied to AREF pin.
Aref,
/// Default referece voltage.
AVcc,
/// Internal reference voltage
Internal,
}

impl Default for ReferenceVoltage {
fn default() -> Self {
Self::AVcc
}
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdcSettings{
pub clock_divider: ClockRateDivision,
pub ref_voltage: ReferenceVoltage
}


#[macro_export]
macro_rules! impl_adc {
(
pub struct $Adc:ident {
type ChannelID = $ID:ty;
peripheral: $ADC:ty,
pins: {$($pxi:ident: ($PXi:ident, $ChannelIDExpr:expr, $ChannelIDPat:pat, $name:ident),)+}
}
) => {

use $crate::void::Void;
use $crate::hal::adc::{Channel, OneShot};
use $crate::nb;
use $crate::port::mode::Analog;
pub use avr_hal::adc::*;

pub struct $Adc {
peripheral: $ADC,
$( $pxi: bool,)+
}

impl $Adc {
pub fn new(peripheral: $ADC, settings: AdcSettings) -> $Adc {
let s = Self { peripheral, $( $pxi: false,)+ } ;
s.enable(settings);
s
}

fn enable(&self, settings: AdcSettings) {
self.peripheral.adcsra.write(|w| {
w.aden().set_bit();
match settings.clock_divider {
ClockRateDivision::Factor2 => w.adps().prescaler_2(),
ClockRateDivision::Factor4 => w.adps().prescaler_4(),
ClockRateDivision::Factor8 => w.adps().prescaler_8(),
ClockRateDivision::Factor16 => w.adps().prescaler_16(),
ClockRateDivision::Factor32 => w.adps().prescaler_32(),
ClockRateDivision::Factor64 => w.adps().prescaler_64(),
ClockRateDivision::Factor128 => w.adps().prescaler_128(),
}});
self.peripheral.admux.write(|w| match settings.ref_voltage {
ReferenceVoltage::Aref => w.refs().aref(),
ReferenceVoltage::AVcc => w.refs().avcc(),
ReferenceVoltage::Internal => w.refs().internal(),
});

}

fn disable(&self) {
self.peripheral.adcsra.reset();
}

fn set_reading(&mut self, channel: $ID, state: bool) {
match channel {
$( $ChannelIDPat => self.$pxi = state, )+
_ => unreachable!(),
}
}

fn is_reading(&self, channel: $ID) -> bool {
match channel {
$( $ChannelIDPat => self.$pxi, )+
_ => unreachable!(),
}
}

pub fn release(self) -> $ADC {
self.disable();
self.peripheral
}
}

impl<WORD, PIN> OneShot<$Adc, WORD, PIN> for $Adc
where
WORD: From<u16>,
PIN: Channel<$Adc, ID=$ID>,
{
type Error = ();

fn read(&mut self, _pin: &mut PIN) -> nb::Result<WORD, Self::Error> {
let channel = PIN::channel();

match (self.is_reading(channel), self.peripheral.adcsra.read().adsc().bit_is_set()) {
(true, true) => Err(nb::Error::WouldBlock),
(true, false) => {
self.set_reading(channel, false);
Ok(self.peripheral.adc.read().bits().into())
},
(false, _) => {
self.set_reading(channel, true);
self.peripheral.admux.modify(|_, w| w.mux().variant(channel));
self.peripheral.adcsra.modify(|_, w| w.adsc().set_bit());
Err(nb::Error::WouldBlock)
}
}
}
}

$(
impl Channel<$Adc> for $PXi<Analog> {
type ID = $ID;
fn channel() -> Self::ID {
$ChannelIDExpr
}
}

impl<MODE> $PXi<MODE> {
/// Make this pin a analog input and enable the internal pull-up
pub fn into_analog_input(self, adc: &mut $Adc) -> $PXi<Analog> {
adc.peripheral.didr0.modify(|_, w| w.$name().set_bit());
$PXi { _mode: core::marker::PhantomData }
}
}
)+
}
}
1 change: 1 addition & 0 deletions avr-hal-generic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub mod port;
pub mod serial;
pub mod i2c;
pub mod spi;
pub mod adc;

/// Prelude containing all HAL traits
pub mod prelude {
Expand Down
3 changes: 2 additions & 1 deletion avr-hal-generic/src/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod mode {
}
/// Pin configured as a digital output
pub struct Output;
pub struct Analog;

impl private::Unimplementable for Output {}
impl<M: InputMode> private::Unimplementable for Input<M> {}
Expand Down Expand Up @@ -246,7 +247,7 @@ macro_rules! impl_port {

$(
pub struct $PXi<MODE> {
_mode: marker::PhantomData<MODE>,
pub(crate)_mode: marker::PhantomData<MODE>,
}

// Mode Switch implementations ---------------------------- {{{
Expand Down
46 changes: 46 additions & 0 deletions boards/arduino-uno/examples/uno-adc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#![no_std]
#![no_main]

extern crate panic_halt;
use arduino_uno::prelude::*;

// This example opens a serial connection to the host computer. On most POSIX operating systems (like GNU/Linux or
// OSX), you can interface with the program by running (assuming the device appears as ttyACM0)
//
// $ sudo screen /dev/ttyACM0 9600

#[no_mangle]
pub extern fn main() -> ! {
let dp = arduino_uno::Peripherals::take().unwrap();

let mut pins = arduino_uno::Pins::new(
dp.PORTB,
dp.PORTC,
dp.PORTD,
);

let mut serial = arduino_uno::Serial::new(
dp.USART0,
pins.d0,
pins.d1.into_output(&mut pins.ddr),
9600,
);


// create the Analog Digital Converter
let mut adc = arduino_uno::adc::Adc::new(dp.ADC, arduino_uno::adc::AdcSettings::default());

// Convert pin to Analog input
let mut a0 = pins.a0.into_analog_input(&mut adc);


ufmt::uwriteln!(&mut serial, "Reading Analog Input on PORT a0\r").unwrap();

loop {
// Read the Analog value
let aread: u16 = nb::block!{adc.read(&mut a0)}.unwrap();

// Write it to Serial
ufmt::uwriteln!(&mut serial, "read: {:?}", aread);
}
}
1 change: 1 addition & 0 deletions boards/arduino-uno/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub use atmega328p_hal::atmega328p;
pub use crate::atmega328p::Peripherals;
pub use atmega328p_hal::prelude;
pub use atmega328p_hal::spi;
pub use atmega328p_hal::adc;
pub use crate::pins::*;

pub type Delay = hal::delay::Delay<hal::clock::MHz16>;
Expand Down
20 changes: 20 additions & 0 deletions chips/atmega328p-hal/src/adc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
extern crate avr_hal_generic as avr_hal;

use crate::port::portc::{PC0, PC1, PC2, PC3, PC4, PC5};

use crate::atmega328p::adc::admux::MUX_A;

avr_hal::impl_adc! {
pub struct Adc {
type ChannelID = MUX_A;
peripheral: crate::atmega328p::ADC,
pins: {
pc0: (PC0, MUX_A::ADC0, MUX_A::ADC0, adc0d),
pc1: (PC1, MUX_A::ADC1, MUX_A::ADC1, adc1d),
pc2: (PC2, MUX_A::ADC2, MUX_A::ADC2, adc2d),
pc3: (PC3, MUX_A::ADC3, MUX_A::ADC3, adc3d),
pc4: (PC4, MUX_A::ADC4, MUX_A::ADC4, adc4d),
pc5: (PC5, MUX_A::ADC5, MUX_A::ADC5, adc5d),
}
}
}
2 changes: 2 additions & 0 deletions chips/atmega328p-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub use avr_hal::delay;

pub mod port;

pub mod adc;

pub mod prelude {
pub use crate::avr_hal::prelude::*;
pub use crate::port::PortExt as _;
Expand Down