diff --git a/Cargo.toml b/Cargo.toml index dde9b18..83426da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ # Peripheral components "peripheral-components/ads1256/*", # Examples - "examples/playground" + "examples/ads1256" ] [workspace.package] @@ -40,6 +40,7 @@ version = "0.4.*" # Serialization [workspace.dependencies.parity-scale-codec] version = "3.5.*" +default-features = false # Embedded-HAL [workspace.dependencies.embedded-hal] version = "1.0.0-alpha.10" diff --git a/examples/ads1256/.cargo/config.toml b/examples/ads1256/.cargo/config.toml new file mode 100644 index 0000000..dbcc285 --- /dev/null +++ b/examples/ads1256/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` +runner = "probe-rs-cli run --chip STM32F429ZITx" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" \ No newline at end of file diff --git a/examples/ads1256/Cargo.toml b/examples/ads1256/Cargo.toml new file mode 100644 index 0000000..194ac18 --- /dev/null +++ b/examples/ads1256/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "ads1256-examples" +description = "Examples using the ads1256." +version.workspace = true +edition.workspace = true +repository.workspace = true +readme.workspace = true +license.workspace = true + +[dependencies.physical-node] +path = "../../node" +features = ["embassy-sync"] +[dependencies.physical-ads1256] +path = "../../peripheral-components/ads1256/node" +features = ["standard-input"] +[dependencies.ads1256] +workspace = true +[dependencies.uom] +workspace = true +[dependencies.defmt] +workspace = true +[dependencies.defmt-rtt] +workspace = true +[dependencies.cortex-m] +workspace = true +features = ["critical-section-single-core"] +[dependencies.cortex-m-rt] +workspace = true +[dependencies.embassy-stm32] +workspace = true +features = ["stm32f429zi", "memory-x", "time", "exti", "time-driver-any"] +[dependencies.embassy-executor] +workspace = true +[dependencies.embassy-futures] +workspace = true +[dependencies.embassy-time] +workspace = true +features = ["tick-hz-16_000_000"] +[dependencies.embassy-sync] +workspace = true +[dependencies.panic-probe] +workspace = true diff --git a/examples/ads1256/build.rs b/examples/ads1256/build.rs new file mode 100644 index 0000000..56127fd --- /dev/null +++ b/examples/ads1256/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} \ No newline at end of file diff --git a/examples/ads1256/src/bin/multiplex.rs b/examples/ads1256/src/bin/multiplex.rs new file mode 100644 index 0000000..d35a011 --- /dev/null +++ b/examples/ads1256/src/bin/multiplex.rs @@ -0,0 +1,181 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait, async_fn_in_trait)] + +use core::cell::Cell; +use cortex_m::prelude::{ + _embedded_hal_blocking_delay_DelayMs, _embedded_hal_blocking_delay_DelayUs, +}; +use {defmt_rtt as _, panic_probe as _}; + +use {embassy_executor as executor, embassy_stm32 as stm32}; + +use ads1256::{ + AdControl, Ads1256, AutoCal, BitOrder, BlockingDelay, Buffer, ClockOut, Config, DState, + DataRate, DigitalIo, DigitalIoDirection, DigitalIoState, DioDirection, Gain, Multiplexer, + MuxInput, OutputPin, Sdcs, SpiBus, Status, Wait, +}; +use embassy_time::{Delay, Duration, Timer}; +use executor::Spawner; +use stm32::dma::NoDma; +use stm32::exti::ExtiInput; +use stm32::gpio::{Input, Level, Output, Pull, Speed}; +use stm32::spi::Spi; +use stm32::time::Hertz; +use stm32::{pac, spi}; + +use uom::si::electric_potential::volt; +use uom::si::f32; + +use defmt::{debug, error, info, trace, unwrap}; +use embassy_executor::_export::StaticCell; +use embassy_stm32::peripherals::{EXTI6, PF6, PF7, SPI3}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pubsub::PubSubChannel; +use physical_ads1256::{standard_input, SingleEnded}; +use physical_node::transducer::input::RawStatePubInput; +use physical_node::transducer::Publisher; + +const AUTOCAL_CONF: Config = Config { + status: Status::setting(Buffer::Enabled, AutoCal::Enabled, BitOrder::MostSigFirst), + multiplexer: Multiplexer::setting(MuxInput::AIn0, MuxInput::Common), + ad_control: AdControl::setting(Gain::X2, Sdcs::Off, ClockOut::Off), + data_rate: DataRate::Sps2_5, + digital_io: DigitalIo::setting(DigitalIoState::default(), DigitalIoDirection::default()), +}; + +struct Ads1256Delay; + +impl ads1256::BlockingDelay for Ads1256Delay { + #[inline] + fn t6_delay(&mut self) { + Delay.delay_us(1u32); + } + + fn t11_1_delay(&mut self) {} + + fn t11_2_delay(&mut self) {} +} + +struct Inputs { + ai1: RawStatePubInput, + ai2: RawStatePubInput, + ai3: RawStatePubInput, +} + +// Inputs +static ANALOG_INPUTS: StaticCell = StaticCell::new(); +static ADS_1256: StaticCell, ExtiInput>> = StaticCell::new(); +static SPI: StaticCell> = StaticCell::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + unsafe { + pac::FLASH.acr().modify(|v| { + v.set_prften(true); + v.set_icen(true); + v.set_dcen(true); + }); + } + + let p = embassy_stm32::init(Default::default()); + + let mut spi_conf = spi::Config::default(); + spi_conf.mode = spi::MODE_1; + spi_conf.bit_order = spi::BitOrder::MsbFirst; + + let ads1256_data_ready = ExtiInput::new(Input::new(p.PF6, Pull::Up), p.EXTI6); + let select_ads1256 = Output::new(p.PF7, Level::High, Speed::VeryHigh); + + let spi = SPI.init(Spi::new( + p.SPI3, + p.PC10, + p.PC12, + p.PC11, + NoDma, + NoDma, + Hertz(ads1256::defaults::SPI_CLK_HZ), + spi_conf, + )); + + let ads_1256 = ADS_1256.init(Ads1256::new(Ads1256Delay, select_ads1256, ads1256_data_ready)); + + let inputs = &*ANALOG_INPUTS.init(Inputs { + ai1: RawStatePubInput::new( + Cell::new(f32::ElectricPotential::new::(-1000.0)), + PubSubChannel::new(), + ), + ai2: RawStatePubInput::new( + Cell::new(f32::ElectricPotential::new::(-1000.0)), + PubSubChannel::new(), + ), + ai3: RawStatePubInput::new( + Cell::new(f32::ElectricPotential::new::(-1000.0)), + PubSubChannel::new(), + ), + }); + + spawner.spawn(log_task(inputs)).unwrap(); + spawner + .spawn(drive_inputs_task(ads_1256, spi, inputs)) + .unwrap(); +} + +#[embassy_executor::task] +async fn drive_inputs_task( + ads_1256: &'static mut Ads1256, ExtiInput<'static, PF6>>, + spi: &'static mut Spi<'static, SPI3, NoDma, NoDma>, + inputs: &'static Inputs, +) { + let Inputs { ai1, ai2, ai3 } = inputs; + + loop { + let mut accumulator = f32::ElectricPotential::new::(0.0); + + let voltage = ads_1256 + .autocal_convert(spi, SingleEnded::AIn0.into(), None, None, None, false) + .await + .unwrap() + .to_voltage(AUTOCAL_CONF.ad_control.gain()); + ai1.update(voltage); + accumulator += voltage; + + let voltage = ads_1256 + .autocal_convert(spi, SingleEnded::AIn1.into(), None, None, None, false) + .await + .unwrap() + .to_voltage(AUTOCAL_CONF.ad_control.gain()); + ai2.update(voltage); + accumulator += voltage; + + let voltage = ads_1256 + .autocal_convert(spi, SingleEnded::AIn2.into(), None, None, None, false) + .await + .unwrap() + .to_voltage(AUTOCAL_CONF.ad_control.gain()); + ai3.update(voltage); + accumulator += voltage; + + let accum_volts = accumulator.get::(); + info!("Immediate loop iteration result, combined volts: {}", accum_volts); + } +} + +#[embassy_executor::task] +async fn log_task(inputs: &'static Inputs) { + let Inputs { ai1, ai2, ai3 } = inputs; + let mut ai1_sub = ai1.subscribe().unwrap(); + let mut ai2_sub = ai2.subscribe().unwrap(); + let mut ai3_sub = ai3.subscribe().unwrap(); + + loop { + let msg = ai1_sub.next_message_pure().await.get::(); + info!("Log task ai1: {}", msg); + + let msg = ai2_sub.next_message_pure().await.get::(); + info!("Log task ai2: {}", msg); + + let msg = ai3_sub.next_message_pure().await.get::(); + info!("Log task ai3: {}", msg); + } +} diff --git a/examples/playground/Cargo.toml b/examples/playground/Cargo.toml deleted file mode 100644 index 33f173c..0000000 --- a/examples/playground/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "playground" -description = """The playground example is a special example meant for unstructured experimentation which can potentially - be turned into an actual example at some point or deleted. It should always be reset to unchanged hello world before - the working branch where the experimentation is happening is merged.""" -version.workspace = true -edition.workspace = true -repository.workspace = true -readme.workspace = true -license.workspace = true - -[dependencies] -physical = { path = "../.." } \ No newline at end of file diff --git a/examples/playground/src/main.rs b/examples/playground/src/main.rs deleted file mode 100644 index 964a26c..0000000 --- a/examples/playground/src/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! The playground example is a special example meant for unstructured experimentation which can potentially be turned -//! into an actual example at some point or deleted. It should always be reset to unchanged hello world before the working -//! branch where the experimentation is happening is merged. - -fn main() { - println!("Hello, world!"); -} diff --git a/node/src/lib.rs b/node/src/lib.rs index a55204f..e712a34 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] #![feature(async_fn_in_trait)] -mod transducer; +pub mod transducer; pub mod cell { pub use physical::cell::*; diff --git a/node/src/transducer/input.rs b/node/src/transducer/input.rs index a57dfa9..43f0885 100644 --- a/node/src/transducer/input.rs +++ b/node/src/transducer/input.rs @@ -1,30 +1,105 @@ use crate::cell::CellView; +use crate::transducer::Publisher; +use core::cell::Cell; #[cfg(feature = "embassy-sync")] use embassy_sync::blocking_mutex::raw::RawMutex; #[cfg(feature = "embassy-sync")] use embassy_sync::pubsub::PubSubChannel; +use embassy_sync::pubsub::{Error, PubSubBehavior, Subscriber}; pub use physical::transducer::input::*; +use physical::transducer::Stateful; #[cfg(feature = "embassy-sync")] -pub struct PublishInput< +pub struct RawPublishInput +{ + pub channel: PubSubChannel, +} + +impl + RawPublishInput +{ + #[inline(always)] + pub fn new(channel: PubSubChannel) -> Self { + Self { channel } + } + + #[inline(always)] + pub fn update(&self, value: T) { + self.channel.publish_immediate(value); + } +} + +impl + Publisher for RawPublishInput +{ + type Value = T; + type Mutex = MutexT; + + #[inline(always)] + fn subscribe( + &self, + ) -> Result, Error> { + self.channel.subscriber() + } +} + +#[cfg(feature = "embassy-sync")] +pub struct RawStatePubInput< T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize, - const NUM_PUBS: usize, > { - channel: PubSubChannel, + pub state_cell: Cell, + pub channel: PubSubChannel, } -#[cfg(feature = "embassy-sync")] -pub struct StatefulPublishInput< - 'a, - T: Copy, - MutexT: RawMutex, - const CAPACITY: usize, - const NUM_SUBS: usize, - const NUM_PUBS: usize, -> { - pub state_cell: CellView<'a, T>, - channel: PubSubChannel, +impl + RawStatePubInput +{ + #[inline(always)] + pub fn new( + state_cell: Cell, + channel: PubSubChannel, + ) -> Self { + Self { + state_cell, + channel, + } + } + + #[inline] + pub fn update(&self, value: T) { + self.state_cell.set(value); + self.channel.publish_immediate(value); + } +} + +impl Stateful + for RawStatePubInput +{ + type Value = T; + + #[inline(always)] + fn state_cell(&self) -> CellView { + (&self.state_cell).into() + } + + #[inline(always)] + fn state(&self) -> Self::Value { + self.state_cell.get() + } +} + +impl + Publisher for RawStatePubInput +{ + type Value = T; + type Mutex = MutexT; + + fn subscribe( + &self, + ) -> Result, Error> { + self.channel.subscriber() + } } diff --git a/node/src/transducer/mod.rs b/node/src/transducer/mod.rs index 5a37c76..c698b30 100644 --- a/node/src/transducer/mod.rs +++ b/node/src/transducer/mod.rs @@ -1,5 +1,5 @@ -mod input; -mod output; +pub mod input; +pub mod output; pub use physical::transducer::*; @@ -11,17 +11,11 @@ use embassy_sync::pubsub; use embassy_sync::pubsub::Subscriber; #[cfg(feature = "embassy-sync")] -pub trait Publisher { +pub trait Publisher { type Value: Copy; type Mutex: RawMutex; - const CAPACITY: usize; - const NUM_SUBS: usize; - const NUM_PUBS: usize; fn subscribe( &self, - ) -> Result< - Subscriber, - pubsub::Error, - >; + ) -> Result, pubsub::Error>; } diff --git a/peripheral-components/ads1256/node/Cargo.toml b/peripheral-components/ads1256/node/Cargo.toml index b4aa150..dfe59d3 100644 --- a/peripheral-components/ads1256/node/Cargo.toml +++ b/peripheral-components/ads1256/node/Cargo.toml @@ -8,12 +8,15 @@ readme.workspace = true license.workspace = true [features] -standard-multiplexer = [] +config = ["physical-ads1256-types/config"] +standard-input = ["physical-ads1256-types/standard-input"] +standard-multiplexer = ["standard-input"] [dependencies.physical-node] path = "../../../node" [dependencies.physical-ads1256-types] path = "../types" +features = ["defmt"] [dependencies.ads1256] workspace = true [dependencies.embedded-hal] diff --git a/peripheral-components/ads1256/node/src/analog_input.rs b/peripheral-components/ads1256/node/src/analog_input.rs deleted file mode 100644 index bf02d65..0000000 --- a/peripheral-components/ads1256/node/src/analog_input.rs +++ /dev/null @@ -1,10 +0,0 @@ - -struct AnalogInput {} - -// AnalogInputS -// AnalogInputI -// AnalogInputC -// AnalogInputSI -// AnalogInputSC -// AnalogInputIC -// AnalogInputSIC diff --git a/peripheral-components/ads1256/node/src/lib.rs b/peripheral-components/ads1256/node/src/lib.rs index 54dc809..23a18d5 100644 --- a/peripheral-components/ads1256/node/src/lib.rs +++ b/peripheral-components/ads1256/node/src/lib.rs @@ -1,4 +1,6 @@ #![no_std] #[cfg(feature = "standard-multiplexer")] -mod analog_input; \ No newline at end of file +mod standard_multiplexer; + +pub use physical_ads1256_types::*; \ No newline at end of file diff --git a/peripheral-components/ads1256/node/src/standard_multiplexer.rs b/peripheral-components/ads1256/node/src/standard_multiplexer.rs new file mode 100644 index 0000000..e69de29 diff --git a/peripheral-components/ads1256/types/Cargo.toml b/peripheral-components/ads1256/types/Cargo.toml index 29dbf41..065bf12 100644 --- a/peripheral-components/ads1256/types/Cargo.toml +++ b/peripheral-components/ads1256/types/Cargo.toml @@ -8,7 +8,8 @@ readme.workspace = true license.workspace = true [features] -standard-multiplexer = [] +config = [] +standard-input = [] [dependencies.ads1256-types] workspace = true diff --git a/peripheral-components/ads1256/types/src/config.rs b/peripheral-components/ads1256/types/src/config.rs new file mode 100644 index 0000000..d162cab --- /dev/null +++ b/peripheral-components/ads1256/types/src/config.rs @@ -0,0 +1,2 @@ +use ads1256_types::{Buffer, Config, DataRate, Gain}; + diff --git a/peripheral-components/ads1256/types/src/lib.rs b/peripheral-components/ads1256/types/src/lib.rs index 0c9ac1a..ec78e3c 100644 --- a/peripheral-components/ads1256/types/src/lib.rs +++ b/peripheral-components/ads1256/types/src/lib.rs @@ -1 +1,11 @@ #![no_std] + +#[cfg(feature = "standard-input")] +pub mod standard_input; +#[cfg(feature = "config")] +mod config; + +#[cfg(feature = "config")] +pub use config::*; +#[cfg(feature = "standard-input")] +pub use standard_input::*; diff --git a/peripheral-components/ads1256/types/src/standard_input.rs b/peripheral-components/ads1256/types/src/standard_input.rs new file mode 100644 index 0000000..b4fa825 --- /dev/null +++ b/peripheral-components/ads1256/types/src/standard_input.rs @@ -0,0 +1,39 @@ +use ads1256_types::{Buffer, Config, DataRate, Gain, Multiplexer, MuxInput}; + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[repr(u8)] +pub enum Differential { + AIn0 = Multiplexer::setting(MuxInput::AIn0, MuxInput::AIn1).0, + AIn1 = Multiplexer::setting(MuxInput::AIn2, MuxInput::AIn3).0, + AIn2 = Multiplexer::setting(MuxInput::AIn4, MuxInput::AIn5).0, + AIn3 = Multiplexer::setting(MuxInput::AIn6, MuxInput::AIn7).0, +} + +impl Into for Differential { + #[inline(always)] + fn into(self) -> Multiplexer { + Multiplexer(self as u8) + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[repr(u8)] +pub enum SingleEnded { + AIn0 = Multiplexer::setting(MuxInput::AIn0, MuxInput::Common).0, + AIn1 = Multiplexer::setting(MuxInput::AIn1, MuxInput::Common).0, + AIn2 = Multiplexer::setting(MuxInput::AIn2, MuxInput::Common).0, + AIn3 = Multiplexer::setting(MuxInput::AIn3, MuxInput::Common).0, + AIn4 = Multiplexer::setting(MuxInput::AIn4, MuxInput::Common).0, + AIn5 = Multiplexer::setting(MuxInput::AIn5, MuxInput::Common).0, + AIn6 = Multiplexer::setting(MuxInput::AIn6, MuxInput::Common).0, + AIn7 = Multiplexer::setting(MuxInput::AIn7, MuxInput::Common).0, +} + +impl Into for SingleEnded { + #[inline(always)] + fn into(self) -> Multiplexer { + Multiplexer(self as u8) + } +} diff --git a/src/cell.rs b/src/cell.rs index 9526551..16ee00b 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -6,13 +6,14 @@ use core::cell::Cell; pub struct CellView<'a, T: Copy>(&'a Cell); impl CellView<'_, T> { - #[inline] + #[inline(always)] pub fn get(self) -> T { self.0.get() } } impl<'a, T: Copy> From<&'a Cell> for CellView<'a, T> { + #[inline(always)] fn from(value: &'a Cell) -> Self { CellView(value) } diff --git a/src/transducer/conversion/mod.rs b/src/transducer/conversion/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/transducer/input.rs b/src/transducer/input.rs index 7b40489..12540ba 100644 --- a/src/transducer/input.rs +++ b/src/transducer/input.rs @@ -1,3 +1,4 @@ +use core::cell::Cell; use crate::cell::CellView; use crate::transducer::Stateful; @@ -5,19 +6,31 @@ pub trait Poll { async fn poll() -> T; } -pub struct StatefulInput<'a, T: Copy> { - pub state_cell: CellView<'a, T>, +pub struct RawStatefulInput { + pub state_cell: Cell, } -impl<'a, T: Copy> Stateful for StatefulInput<'a, T> { +impl RawStatefulInput { + #[inline(always)] + pub fn new(state_cell: Cell) -> Self { + Self { state_cell } + } + + #[inline(always)] + pub fn raw_update(&self, value: T) { + self.state_cell.set(value); + } +} + +impl Stateful for RawStatefulInput { type Value = T; #[inline(always)] - fn state_cell(&self) -> CellView<'a, Self::Value> { - self.state_cell + fn state_cell(&self) -> CellView { + (&self.state_cell).into() } - #[inline] + #[inline(always)] fn state(&self) -> Self::Value { self.state_cell.get() } diff --git a/src/transducer/mod.rs b/src/transducer/mod.rs index 2d69172..eada613 100644 --- a/src/transducer/mod.rs +++ b/src/transducer/mod.rs @@ -2,6 +2,7 @@ use crate::cell::CellView; pub mod input; pub mod output; +mod conversion; // Initialisation will always be async and won't complete until a state is available for all // stateful transducers. @@ -10,5 +11,8 @@ pub trait Stateful { fn state_cell(&self) -> CellView; - fn state(&self) -> Self::Value; + #[inline(always)] + fn state(&self) -> Self::Value { + self.state_cell().get() + } }