diff --git a/src/ad7172/adc.rs b/src/ad7172/adc.rs new file mode 100644 index 0000000..24438b5 --- /dev/null +++ b/src/ad7172/adc.rs @@ -0,0 +1,212 @@ +use core::fmt; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal::blocking::spi::Transfer; +use log::{info, warn}; +use super::checksum::{ChecksumMode, Checksum}; +use super::AdcError; +use super::{ + regs, regs::RegisterData, + Input, RefSource, PostFilter, DigitalFilterOrder, +}; + +/// AD7172-2 implementation +/// +/// [Manual](https://www.analog.com/media/en/technical-documentation/data-sheets/AD7172-2.pdf) +pub struct Adc, NSS: OutputPin> { + spi: SPI, + nss: NSS, + checksum_mode: ChecksumMode, +} + +impl, NSS: OutputPin, E: fmt::Debug> Adc { + pub fn new(spi: SPI, mut nss: NSS) -> Result { + let _ = nss.set_high(); + let mut adc = Adc { + spi, nss, + checksum_mode: ChecksumMode::Off, + }; + adc.reset()?; + + match adc.identify() { + Err(e) => + warn!("Cannot identify ADC: {:?}", e), + Ok(id) if id & 0xFFF0 == 0x00D0 => + info!("ADC id: {:04X}", id), + Ok(id) => + info!("ADC id: {:04X} (corrupt)", id), + } + + Ok(adc) + } + + /// `0x00DX` for AD7172-2 + pub fn identify(&mut self) -> Result> { + self.read_reg(®s::Id) + .map(|id| id.id()) + } + + pub fn set_checksum_mode(&mut self, mode: ChecksumMode) -> Result<(), AdcError> { + // Cannot use update_reg() here because checksum_mode is + // updated between read_reg() and write_reg(). + let mut ifmode = self.read_reg(®s::IfMode)?; + ifmode.set_crc(mode); + self.checksum_mode = mode; + self.write_reg(®s::IfMode, &mut ifmode)?; + Ok(()) + } + + pub fn set_sync_enable(&mut self, enable: bool) -> Result<(), AdcError> { + self.update_reg(®s::GpioCon, |data| { + data.set_sync_en(enable); + }) + } + + pub fn setup_channel( + &mut self, index: u8, in_pos: Input, in_neg: Input + ) -> Result<(), AdcError> { + self.update_reg(®s::SetupCon { index }, |data| { + data.set_bipolar(false); + data.set_refbuf_pos(true); + data.set_refbuf_neg(true); + data.set_ainbuf_pos(true); + data.set_ainbuf_neg(true); + data.set_ref_sel(RefSource::External); + })?; + self.update_reg(®s::FiltCon { index }, |data| { + data.set_enh_filt_en(true); + data.set_enh_filt(PostFilter::F16SPS); + data.set_order(DigitalFilterOrder::Sinc5Sinc1); + })?; + // let mut offset = ::Data::empty(); + // offset.set_offset(0); + // self.write_reg(®s::Offset { index }, &mut offset); + self.update_reg(®s::Channel { index }, |data| { + data.set_setup(index); + data.set_enabled(true); + data.set_a_in_pos(in_pos); + data.set_a_in_neg(in_neg); + })?; + Ok(()) + } + + pub fn get_postfilter(&mut self, index: u8) -> Result, AdcError> { + self.read_reg(®s::FiltCon { index }) + .map(|data| { + if data.enh_filt_en() { + Some(data.enh_filt()) + } else { + None + } + }) + } + + pub fn set_postfilter(&mut self, index: u8, filter: Option) -> Result<(), AdcError> { + self.update_reg(®s::FiltCon { index }, |data| { + match filter { + None => data.set_enh_filt_en(false), + Some(filter) => { + data.set_enh_filt_en(true); + data.set_enh_filt(filter); + } + } + }) + } + + /// Returns the channel the data is from + pub fn data_ready(&mut self) -> Result, AdcError> { + self.read_reg(®s::Status) + .map(|status| { + if status.ready() { + Some(status.channel()) + } else { + None + } + }) + } + + /// Get data + pub fn read_data(&mut self) -> Result> { + self.read_reg(®s::Data) + .map(|data| data.data()) + } + + fn read_reg(&mut self, reg: &R) -> Result> { + let mut reg_data = R::Data::empty(); + let address = 0x40 | reg.address(); + let mut checksum = Checksum::new(self.checksum_mode); + checksum.feed(address); + let checksum_out = checksum.result(); + let checksum_in = self.transfer(address, reg_data.as_mut(), checksum_out)?; + for &mut b in reg_data.as_mut() { + checksum.feed(b); + } + let checksum_expected = checksum.result(); + if checksum_expected != checksum_in { + return Err(AdcError::ChecksumMismatch(checksum_expected, checksum_in)); + } + Ok(reg_data) + } + + fn write_reg(&mut self, reg: &R, reg_data: &mut R::Data) -> Result<(), AdcError> { + let address = reg.address(); + let mut checksum = Checksum::new(match self.checksum_mode { + ChecksumMode::Off => ChecksumMode::Off, + // write checksums are always crc + ChecksumMode::Xor => ChecksumMode::Crc, + ChecksumMode::Crc => ChecksumMode::Crc, + }); + checksum.feed(address); + for &mut b in reg_data.as_mut() { + checksum.feed(b); + } + let checksum_out = checksum.result(); + self.transfer(address, reg_data.as_mut(), checksum_out)?; + Ok(()) + } + + fn update_reg(&mut self, reg: &R, f: F) -> Result> + where + R: regs::Register, + F: FnOnce(&mut R::Data) -> A, + { + let mut reg_data = self.read_reg(reg)?; + let result = f(&mut reg_data); + self.write_reg(reg, &mut reg_data)?; + Ok(result) + } + + pub fn reset(&mut self) -> Result<(), SPI::Error> { + let mut buf = [0xFFu8; 8]; + let _ = self.nss.set_low(); + let result = self.spi.transfer(&mut buf); + let _ = self.nss.set_high(); + result?; + Ok(()) + } + + fn transfer<'w>(&mut self, addr: u8, reg_data: &'w mut [u8], checksum: Option) -> Result, SPI::Error> { + let mut addr_buf = [addr]; + + let _ = self.nss.set_low(); + let result = match self.spi.transfer(&mut addr_buf) { + Ok(_) => self.spi.transfer(reg_data), + Err(e) => Err(e), + }; + let result = match (result, checksum) { + (Ok(_),None) => + Ok(None), + (Ok(_), Some(checksum_out)) => { + let mut checksum_buf = [checksum_out; 1]; + match self.spi.transfer(&mut checksum_buf) { + Ok(_) => Ok(Some(checksum_buf[0])), + Err(e) => Err(e), + } + } + (Err(e), _) => + Err(e), + }; + let _ = self.nss.set_high(); + + result + } +} diff --git a/src/ad7172/checksum.rs b/src/ad7172/checksum.rs new file mode 100644 index 0000000..3c93171 --- /dev/null +++ b/src/ad7172/checksum.rs @@ -0,0 +1,54 @@ +#[derive(Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum ChecksumMode { + Off = 0b00, + /// Seems much less reliable than `Crc` + Xor = 0b01, + Crc = 0b10, +} + +impl From for ChecksumMode { + fn from(x: u8) -> Self { + match x { + 0 => ChecksumMode::Off, + 1 => ChecksumMode::Xor, + _ => ChecksumMode::Crc, + } + } +} + +pub struct Checksum { + mode: ChecksumMode, + state: u8, +} + +impl Checksum { + pub fn new(mode: ChecksumMode) -> Self { + Checksum { mode, state: 0 } + } + + pub fn feed(&mut self, input: u8) { + match self.mode { + ChecksumMode::Off => {}, + ChecksumMode::Xor => self.state ^= input, + ChecksumMode::Crc => { + for i in 0..8 { + let input_mask = 0x80 >> i; + self.state = (self.state << 1) ^ + if ((self.state & 0x80) != 0) != ((input & input_mask) != 0) { + 0x07 /* x8 + x2 + x + 1 */ + } else { + 0 + }; + } + } + } + } + + pub fn result(&self) -> Option { + match self.mode { + ChecksumMode::Off => None, + _ => Some(self.state) + } + } +} diff --git a/src/ad7172/mod.rs b/src/ad7172/mod.rs new file mode 100644 index 0000000..067ec0e --- /dev/null +++ b/src/ad7172/mod.rs @@ -0,0 +1,210 @@ +use core::fmt; +use stm32f4xx_hal::{ + time::{MegaHertz, U32Ext}, + spi, +}; + +pub mod regs; +mod checksum; +pub use checksum::ChecksumMode; +mod adc; +pub use adc::*; + +/// SPI Mode 3 +pub const SPI_MODE: spi::Mode = spi::Mode { + polarity: spi::Polarity::IdleHigh, + phase: spi::Phase::CaptureOnSecondTransition, +}; +/// 2 MHz +pub const SPI_CLOCK: MegaHertz = MegaHertz(2); + +#[derive(Clone, Debug, PartialEq)] +pub enum AdcError { + SPI(SPI), + ChecksumMismatch(Option, Option), +} + +impl From for AdcError { + fn from(e: SPI) -> Self { + AdcError::SPI(e) + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(u8)] +pub enum Input { + Ain0 = 0, + Ain1 = 1, + Ain2 = 2, + Ain3 = 3, + Ain4 = 4, + TemperaturePos = 17, + TemperatureNeg = 18, + AnalogSupplyPos = 19, + AnalogSupplyNeg = 20, + RefPos = 21, + RefNeg = 22, + Invalid = 0b11111, +} + +impl From for Input { + fn from(x: u8) -> Self { + match x { + 0 => Input::Ain0, + 1 => Input::Ain1, + 2 => Input::Ain2, + 3 => Input::Ain3, + 4 => Input::Ain4, + 17 => Input::TemperaturePos, + 18 => Input::TemperatureNeg, + 19 => Input::AnalogSupplyPos, + 20 => Input::AnalogSupplyNeg, + 21 => Input::RefPos, + 22 => Input::RefNeg, + _ => Input::Invalid, + } + } +} + +impl fmt::Display for Input { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use Input::*; + + match self { + Ain0 => "ain0", + Ain1 => "ain1", + Ain2 => "ain2", + Ain3 => "ain3", + Ain4 => "ain4", + TemperaturePos => "temperature+", + TemperatureNeg => "temperature-", + AnalogSupplyPos => "analogsupply+", + AnalogSupplyNeg => "analogsupply-", + RefPos => "ref+", + RefNeg => "ref-", + _ => "", + }.fmt(fmt) + } +} + +/// Reference source for ADC conversion +#[repr(u8)] +pub enum RefSource { + /// External reference + External = 0b00, + /// Internal 2.5V reference + Internal = 0b10, + /// AVDD1 − AVSS + Avdd1MinusAvss = 0b11, + Invalid = 0b01, +} + +impl From for RefSource { + fn from(x: u8) -> Self { + match x { + 0 => RefSource::External, + 1 => RefSource::Internal, + 2 => RefSource::Avdd1MinusAvss, + _ => RefSource::Invalid, + } + } +} + +impl fmt::Display for RefSource { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use RefSource::*; + + match self { + External => "external", + Internal => "internal", + Avdd1MinusAvss => "avdd1-avss", + _ => "", + }.fmt(fmt) + } +} + +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum PostFilter { + /// 27 SPS, 47 dB rejection, 36.7 ms settling + F27SPS = 0b010, + /// 21.25 SPS, 62 dB rejection, 40 ms settling + F21SPS = 0b011, + /// 20 SPS, 86 dB rejection, 50 ms settling + F20SPS = 0b101, + /// 16.67 SPS, 92 dB rejection, 60 ms settling + F16SPS = 0b110, + Invalid = 0b111, +} + +impl PostFilter { + pub const VALID_VALUES: &'static [Self] = &[ + PostFilter::F27SPS, + PostFilter::F21SPS, + PostFilter::F20SPS, + PostFilter::F16SPS, + ]; + + pub fn closest(rate: f32) -> Option { + /// (x - y).abs() + fn d(x: f32, y: f32) -> f32 { + if x >= y { + x - y + } else { + y - x + } + } + + let mut best: Option<(f32, Self)> = None; + for value in Self::VALID_VALUES { + let error = d(rate, value.output_rate().unwrap()); + let better = best + .map(|(best_error, _)| error < best_error) + .unwrap_or(true); + if better { + best = Some((error, *value)); + } + } + best.map(|(_, best)| best) + } + + /// Samples per Second + pub fn output_rate(&self) -> Option { + match self { + PostFilter::F27SPS => Some(27.0), + PostFilter::F21SPS => Some(21.25), + PostFilter::F20SPS => Some(20.0), + PostFilter::F16SPS => Some(16.67), + PostFilter::Invalid => None, + } + } +} + +impl From for PostFilter { + fn from(x: u8) -> Self { + match x { + 0b010 => PostFilter::F27SPS, + 0b011 => PostFilter::F21SPS, + 0b101 => PostFilter::F20SPS, + 0b110 => PostFilter::F16SPS, + _ => PostFilter::Invalid, + } + } +} + +#[repr(u8)] +pub enum DigitalFilterOrder { + Sinc5Sinc1 = 0b00, + Sinc3 = 0b11, + Invalid = 0b10, +} + +impl From for DigitalFilterOrder { + fn from(x: u8) -> Self { + match x { + 0b00 => DigitalFilterOrder::Sinc5Sinc1, + 0b11 => DigitalFilterOrder::Sinc3, + _ => DigitalFilterOrder::Invalid, + } + } +} diff --git a/src/ad7172/regs.rs b/src/ad7172/regs.rs new file mode 100644 index 0000000..e96a066 --- /dev/null +++ b/src/ad7172/regs.rs @@ -0,0 +1,260 @@ +use byteorder::{BigEndian, ByteOrder}; +use bit_field::BitField; + +use super::*; + +pub trait Register { + type Data: RegisterData; + fn address(&self) -> u8; +} +pub trait RegisterData { + fn empty() -> Self; + fn as_mut(&mut self) -> &mut [u8]; +} + +macro_rules! def_reg { + ($Reg: ident, $reg: ident, $addr: expr, $size: expr) => { + /// AD7172 register + pub struct $Reg; + impl Register for $Reg { + /// Register contents + type Data = $reg::Data; + /// Register address + fn address(&self) -> u8 { + $addr + } + } + mod $reg { + /// Register contents + pub struct Data(pub [u8; $size]); + impl super::RegisterData for Data { + /// Generate zeroed register contents + fn empty() -> Self { + Data([0; $size]) + } + /// Borrow for SPI transfer + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + } + }; + ($Reg: ident, u8, $reg: ident, $addr: expr, $size: expr) => { + pub struct $Reg { pub index: u8, } + impl Register for $Reg { + type Data = $reg::Data; + fn address(&self) -> u8 { + $addr + self.index + } + } + mod $reg { + pub struct Data(pub [u8; $size]); + impl super::RegisterData for Data { + fn empty() -> Self { + Data([0; $size]) + } + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } + } + } + } +} + +macro_rules! reg_bit { + ($getter: ident, $byte: expr, $bit: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> bool { + self.0[$byte].get_bit($bit) + } + }; + ($getter: ident, $setter: ident, $byte: expr, $bit: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> bool { + self.0[$byte].get_bit($bit) + } + #[allow(unused)] + #[doc = $doc] + pub fn $setter(&mut self, value: bool) { + self.0[$byte].set_bit($bit, value); + } + }; +} + +macro_rules! reg_bits { + ($getter: ident, $byte: expr, $bits: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> u8 { + self.0[$byte].get_bits($bits) + } + }; + ($getter: ident, $setter: ident, $byte: expr, $bits: expr, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> u8 { + self.0[$byte].get_bits($bits) + } + #[allow(unused)] + #[doc = $doc] + pub fn $setter(&mut self, value: u8) { + self.0[$byte].set_bits($bits, value); + } + }; + ($getter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> $ty { + self.0[$byte].get_bits($bits) as $ty + } + }; + ($getter: ident, $setter: ident, $byte: expr, $bits: expr, $ty: ty, $doc: expr) => { + #[allow(unused)] + #[doc = $doc] + pub fn $getter(&self) -> $ty { + self.0[$byte].get_bits($bits).into() + } + #[allow(unused)] + #[doc = $doc] + pub fn $setter(&mut self, value: $ty) { + self.0[$byte].set_bits($bits, value as u8); + } + }; +} + +def_reg!(Status, status, 0x00, 1); +impl status::Data { + /// Is there new data to read? + pub fn ready(&self) -> bool { + ! self.not_ready() + } + + reg_bit!(not_ready, 0, 7, "No data ready indicator"); + reg_bits!(channel, 0, 0..=1, "Channel for which data is ready"); + reg_bit!(adc_error, 0, 6, "ADC error"); + reg_bit!(crc_error, 0, 5, "SPI CRC error"); + reg_bit!(reg_error, 0,4, "Register error"); +} + +def_reg!(IfMode, if_mode, 0x02, 2); +impl if_mode::Data { + reg_bits!(crc, set_crc, 1, 2..=3, ChecksumMode, "SPI checksum mode"); +} + +def_reg!(Data, data, 0x04, 3); +impl data::Data { + pub fn data(&self) -> i32 { + let raw = + (u32::from(self.0[0]) << 16) | + (u32::from(self.0[1]) << 8) | + u32::from(self.0[2]); + if raw & 0x80_0000 != 0 { + ((raw & 0x7F_FFFF) | 0x8000_0000) as i32 + } else { + raw as i32 + } + } +} + +def_reg!(GpioCon, gpio_con, 0x06, 2); +impl gpio_con::Data { + reg_bit!(sync_en, set_sync_en, 0, 3, "Enables the SYNC/ERROR pin as a sync input"); +} + +def_reg!(Id, id, 0x07, 2); +impl id::Data { + pub fn id(&self) -> u16 { + BigEndian::read_u16(&self.0) + } +} + +def_reg!(Channel, u8, channel, 0x10, 2); +impl channel::Data { + reg_bit!(enabled, set_enabled, 0, 7, "Channel enabled"); + reg_bits!(setup, set_setup, 0, 4..=5, "Setup number"); + + /// Which input is connected to positive input of this channel + #[allow(unused)] + pub fn a_in_pos(&self) -> Input { + ((self.0[0].get_bits(0..=1) << 3) | + self.0[1].get_bits(5..=7)).into() + } + /// Set which input is connected to positive input of this channel + #[allow(unused)] + pub fn set_a_in_pos(&mut self, value: Input) { + let value = value as u8; + self.0[0].set_bits(0..=1, value >> 3); + self.0[1].set_bits(5..=7, value & 0x7); + } + reg_bits!(a_in_neg, set_a_in_neg, 1, 0..=4, Input, + "Which input is connected to negative input of this channel"); + + // const PROPS: &'static [Property] = &[ + // Property::named("enable") + // .readable(&|self_: &Self| self_.enabled().into()) + // .writebale(&|self_: &mut Self, value| self_.set_enabled(value != 0)), + // Property::named("setup") + // .readable(&|self_: &Self| self_.0[0].get_bits(4..=5).into()) + // .writeable(&|self_: &mut Self, value| { + // self_.0[0].set_bits(4..=5, value as u8); + // }), + // ]; + + // pub fn props() -> &'static [Property] { + // Self::PROPS + // } +} + +def_reg!(SetupCon, u8, setup_con, 0x20, 2); +impl setup_con::Data { + reg_bit!(bipolar, set_bipolar, 0, 4, "Unipolar (`false`) or bipolar (`true`) coded output"); + reg_bit!(refbuf_pos, set_refbuf_pos, 0, 3, "Enable REF+ input buffer"); + reg_bit!(refbuf_neg, set_refbuf_neg, 0, 2, "Enable REF- input buffer"); + reg_bit!(ainbuf_pos, set_ainbuf_pos, 0, 1, "Enable AIN+ input buffer"); + reg_bit!(ainbuf_neg, set_ainbuf_neg, 0, 0, "Enable AIN- input buffer"); + reg_bit!(burnout_en, 1, 7, "enables a 10 µA current source on the positive analog input selected and a 10 µA current sink on the negative analog input selected"); + reg_bits!(ref_sel, set_ref_sel, 1, 4..=5, RefSource, "Select reference source for conversion"); +} + +def_reg!(FiltCon, u8, filt_con, 0x28, 2); +impl filt_con::Data { + reg_bit!(sinc3_map, 0, 7, "If set, mapping of filter register changes to directly program the decimation rate of the sinc3 filter"); + reg_bit!(enh_filt_en, set_enh_filt_en, 0, 3, "Enable postfilters for enhanced 50Hz and 60Hz rejection"); + reg_bits!(enh_filt, set_enh_filt, 0, 0..=2, PostFilter, "Select postfilters for enhanced 50Hz and 60Hz rejection"); + reg_bits!(order, set_order, 1, 5..=6, DigitalFilterOrder, "order of the digital filter that processes the modulator data"); + reg_bits!(odr, set_odr, 1, 0..=4, "Output data rate"); +} + +def_reg!(Offset, u8, offset, 0x30, 3); +impl offset::Data { + #[allow(unused)] + pub fn offset(&self) -> u32 { + (u32::from(self.0[0]) << 16) | + (u32::from(self.0[1]) << 8) | + u32::from(self.0[2]) + } + #[allow(unused)] + pub fn set_offset(&mut self, value: u32) { + self.0[0] = (value >> 16) as u8; + self.0[1] = (value >> 8) as u8; + self.0[2] = value as u8; + } +} + +def_reg!(Gain, u8, gain, 0x38, 3); +impl gain::Data { + #[allow(unused)] + pub fn gain(&self) -> u32 { + (u32::from(self.0[0]) << 16) | + (u32::from(self.0[1]) << 8) | + u32::from(self.0[2]) + } + #[allow(unused)] + pub fn set_gain(&mut self, value: u32) { + self.0[0] = (value >> 16) as u8; + self.0[1] = (value >> 8) as u8; + self.0[2] = value as u8; + } +} diff --git a/src/adc_input.rs b/src/adc_input.rs deleted file mode 100644 index aebf953..0000000 --- a/src/adc_input.rs +++ /dev/null @@ -1,46 +0,0 @@ -use stm32f4xx_hal::{ - adc::{ - Adc, - config::*, - }, - gpio::{Analog, gpioa::PA3 as Pin}, - stm32::ADC1 as ADC, -}; - -/// ADC Input -pub struct AdcInput { - /// unused but consumed - _pin: Pin, - adc: Adc, -} - -impl AdcInput { - /// Configure pin into analog mode - pub fn new(adc: ADC, pin: Pin) -> Self { - let pin = pin.into_analog(); - let adc_config = AdcConfig::default() - .scan(Scan::Enabled) - .continuous(Continuous::Single) - .clock(Clock::Pclk2_div_2); - let mut adc = Adc::adc1(adc, true, adc_config); - - adc.configure_channel(&pin, Sequence::One, SampleTime::Cycles_480); - - AdcInput { _pin: pin, adc } - } - - /// Enable the ADC, - /// run a conversion - /// disable the ADC - pub fn read(&mut self) -> u16 { - let adc = &mut self.adc; - adc.enable(); - adc.clear_end_of_conversion_flag(); - adc.start_conversion(); - let sample = adc.current_sample(); - let result = adc.sample_to_millivolts(sample); - adc.wait_for_conversion_sequence(); - adc.disable(); - result - } -} diff --git a/src/main.rs b/src/main.rs index 4631bab..795e610 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,6 @@ use cortex_m_rt::entry; use embedded_hal::watchdog::{WatchdogEnable, Watchdog}; use stm32f4xx_hal::{ rcc::RccExt, - gpio::GpioExt, watchdog::IndependentWatchdog, time::U32Ext, stm32::{CorePeripherals, Peripherals}, @@ -26,8 +25,8 @@ use smoltcp::{ }; mod pins; -mod adc_input; -use adc_input::AdcInput; +use pins::Pins; +mod ad7172; mod net; mod server; use server::Server; @@ -86,20 +85,11 @@ fn main() -> ! { wd.start(1000u32.ms()); wd.feed(); - let gpioa = dp.GPIOA.split(); - let gpiob = dp.GPIOB.split(); - let gpioc = dp.GPIOC.split(); - let gpiog = dp.GPIOG.split(); + let pins = Pins::setup(clocks, dp.GPIOA, dp.GPIOB, dp.GPIOC, dp.GPIOG, dp.SPI2); info!("ADC init"); - let mut adc_input = AdcInput::new(dp.ADC1, gpioa.pa3); - - info!("Eth setup"); - pins::setup_ethernet( - gpioa.pa1, gpioa.pa2, gpioc.pc1, gpioa.pa7, - gpioc.pc4, gpioc.pc5, gpiob.pb11, gpiog.pg13, - gpiob.pb13 - ); + let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap(); + adc.set_checksum_mode(ad7172::ChecksumMode::Crc).unwrap(); info!("Timer setup"); timer::setup(cp.SYST, clocks); @@ -128,8 +118,8 @@ fn main() -> ! { let now = timer::now().0; if now - last_output >= OUTPUT_INTERVAL { - let adc_value = adc_input.read(); - writeln!(server, "t={},pa3={}\r", now, adc_value).unwrap(); + // let adc_value = adc_input.read(); + writeln!(server, "t={},pa3={}\r", now, 0.0 /*adc_value*/).unwrap(); last_output = now; } diff --git a/src/pins.rs b/src/pins.rs index 5c6ebc3..aa8b6c1 100644 --- a/src/pins.rs +++ b/src/pins.rs @@ -1,32 +1,93 @@ -use stm32f4xx_hal::gpio::{ - gpioa::{PA1, PA2, PA7}, - gpiob::{PB11, PB13}, - gpioc::{PC1, PC4, PC5}, - gpiog::{PG13}, - Speed::VeryHigh, +use stm32f4xx_hal::{ + gpio::{ + AF5, Alternate, + gpioa::{PA1, PA2, PA7}, + gpiob::{PB10, PB11, PB12, PB13, PB14, PB15}, + gpioc::{PC1, PC4, PC5}, + gpiog::{PG13}, + GpioExt, + Output, PushPull, + Speed::VeryHigh, + }, + rcc::Clocks, + spi::Spi, + stm32::{GPIOA, GPIOB, GPIOC, GPIOG, SPI2}, }; -pub fn setup_ethernet( - pa1: PA1, pa2: PA2, pc1: PC1, pa7: PA7, - pc4: PC4, pc5: PC5, pb11: PB11, pg13: PG13, - pb13: PB13 -) { - // PA1 RMII Reference Clock - SB13 ON - pa1.into_alternate_af11().set_speed(VeryHigh); - // PA2 RMII MDIO - SB160 ON - pa2.into_alternate_af11().set_speed(VeryHigh); - // PC1 RMII MDC - SB164 ON - pc1.into_alternate_af11().set_speed(VeryHigh); - // PA7 RMII RX Data Valid D11 JP6 ON - pa7.into_alternate_af11().set_speed(VeryHigh); - // PC4 RMII RXD0 - SB178 ON - pc4.into_alternate_af11().set_speed(VeryHigh); - // PC5 RMII RXD1 - SB181 ON - pc5.into_alternate_af11().set_speed(VeryHigh); - // PB11 RMII TX Enable - SB183 ON - pb11.into_alternate_af11().set_speed(VeryHigh); - // PG13 RXII TXD0 - SB182 ON - pg13.into_alternate_af11().set_speed(VeryHigh); - // PB13 RMII TXD1 I2S_A_CK JP7 ON - pb13.into_alternate_af11().set_speed(VeryHigh); + +/// SPI peripheral used for communication with the ADC +type AdcSpi = Spi>, PB14>, PB15>)>; + +pub struct Pins { + pub adc_spi: AdcSpi, + pub adc_nss: PB12>, +} + +impl Pins { + /// Setup GPIO pins and configure MCU peripherals + pub fn setup(clocks: Clocks, gpioa: GPIOA, gpiob: GPIOB, gpioc: GPIOC, gpiog: GPIOG, spi2: SPI2) -> Self { + let gpioa = gpioa.split(); + let gpiob = gpiob.split(); + let gpioc = gpioc.split(); + let gpiog = gpiog.split(); + + Self::setup_ethernet( + gpioa.pa1, gpioa.pa2, gpioc.pc1, gpioa.pa7, + gpioc.pc4, gpioc.pc5, gpiob.pb11, gpiog.pg13, + gpiob.pb13 + ); + let adc_spi = Self::setup_spi_adc(clocks, spi2, gpiob.pb10, gpiob.pb14, gpiob.pb15); + let adc_nss = gpiob.pb12.into_push_pull_output(); + Pins { + adc_spi, + adc_nss, + } + } + + /// Configure the GPIO pins for SPI operation, and initialize SPI + fn setup_spi_adc( + clocks: Clocks, + spi2: SPI2, + sck: PB10, + miso: PB14, + mosi: PB15, + ) -> AdcSpi + { + let sck = sck.into_alternate_af5(); + let miso = miso.into_alternate_af5(); + let mosi = mosi.into_alternate_af5(); + Spi::spi2( + spi2, + (sck, miso, mosi), + crate::ad7172::SPI_MODE, + crate::ad7172::SPI_CLOCK.into(), + clocks + ) + } + + /// Configure the GPIO pins for Ethernet operation + fn setup_ethernet( + pa1: PA1, pa2: PA2, pc1: PC1, pa7: PA7, + pc4: PC4, pc5: PC5, pb11: PB11, pg13: PG13, + pb13: PB13 + ) { + // PA1 RMII Reference Clock - SB13 ON + pa1.into_alternate_af11().set_speed(VeryHigh); + // PA2 RMII MDIO - SB160 ON + pa2.into_alternate_af11().set_speed(VeryHigh); + // PC1 RMII MDC - SB164 ON + pc1.into_alternate_af11().set_speed(VeryHigh); + // PA7 RMII RX Data Valid D11 JP6 ON + pa7.into_alternate_af11().set_speed(VeryHigh); + // PC4 RMII RXD0 - SB178 ON + pc4.into_alternate_af11().set_speed(VeryHigh); + // PC5 RMII RXD1 - SB181 ON + pc5.into_alternate_af11().set_speed(VeryHigh); + // PB11 RMII TX Enable - SB183 ON + pb11.into_alternate_af11().set_speed(VeryHigh); + // PG13 RXII TXD0 - SB182 ON + pg13.into_alternate_af11().set_speed(VeryHigh); + // PB13 RMII TXD1 I2S_A_CK JP7 ON + pb13.into_alternate_af11().set_speed(VeryHigh); + } }