Created multiplexing publish / subscribe example.

This commit is contained in:
Zachary Sunforge
2023-06-16 18:23:43 -07:00
parent a143ad0e54
commit ad66d8e030
22 changed files with 419 additions and 67 deletions

View File

@ -11,7 +11,7 @@ members = [
# Peripheral components # Peripheral components
"peripheral-components/ads1256/*", "peripheral-components/ads1256/*",
# Examples # Examples
"examples/playground" "examples/ads1256"
] ]
[workspace.package] [workspace.package]
@ -40,6 +40,7 @@ version = "0.4.*"
# Serialization # Serialization
[workspace.dependencies.parity-scale-codec] [workspace.dependencies.parity-scale-codec]
version = "3.5.*" version = "3.5.*"
default-features = false
# Embedded-HAL # Embedded-HAL
[workspace.dependencies.embedded-hal] [workspace.dependencies.embedded-hal]
version = "1.0.0-alpha.10" version = "1.0.0-alpha.10"

View File

@ -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"

View File

@ -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

View File

@ -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");
}

View File

@ -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<f32::ElectricPotential, NoopRawMutex, 10, 1>,
ai2: RawStatePubInput<f32::ElectricPotential, NoopRawMutex, 10, 1>,
ai3: RawStatePubInput<f32::ElectricPotential, NoopRawMutex, 10, 1>,
}
// Inputs
static ANALOG_INPUTS: StaticCell<Inputs> = StaticCell::new();
static ADS_1256: StaticCell<Ads1256<Ads1256Delay, Output<PF7>, ExtiInput<PF6>>> = StaticCell::new();
static SPI: StaticCell<Spi<SPI3, NoDma, NoDma>> = 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::<volt>(-1000.0)),
PubSubChannel::new(),
),
ai2: RawStatePubInput::new(
Cell::new(f32::ElectricPotential::new::<volt>(-1000.0)),
PubSubChannel::new(),
),
ai3: RawStatePubInput::new(
Cell::new(f32::ElectricPotential::new::<volt>(-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<Ads1256Delay, Output<'static, PF7>, 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::<volt>(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::<volt>();
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::<volt>();
info!("Log task ai1: {}", msg);
let msg = ai2_sub.next_message_pure().await.get::<volt>();
info!("Log task ai2: {}", msg);
let msg = ai3_sub.next_message_pure().await.get::<volt>();
info!("Log task ai3: {}", msg);
}
}

View File

@ -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 = "../.." }

View File

@ -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!");
}

View File

@ -1,7 +1,7 @@
#![no_std] #![no_std]
#![feature(async_fn_in_trait)] #![feature(async_fn_in_trait)]
mod transducer; pub mod transducer;
pub mod cell { pub mod cell {
pub use physical::cell::*; pub use physical::cell::*;

View File

@ -1,30 +1,105 @@
use crate::cell::CellView; use crate::cell::CellView;
use crate::transducer::Publisher;
use core::cell::Cell;
#[cfg(feature = "embassy-sync")] #[cfg(feature = "embassy-sync")]
use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::blocking_mutex::raw::RawMutex;
#[cfg(feature = "embassy-sync")] #[cfg(feature = "embassy-sync")]
use embassy_sync::pubsub::PubSubChannel; use embassy_sync::pubsub::PubSubChannel;
use embassy_sync::pubsub::{Error, PubSubBehavior, Subscriber};
pub use physical::transducer::input::*; pub use physical::transducer::input::*;
use physical::transducer::Stateful;
#[cfg(feature = "embassy-sync")] #[cfg(feature = "embassy-sync")]
pub struct PublishInput< pub struct RawPublishInput<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
{
pub channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>,
}
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
RawPublishInput<T, MutexT, CAPACITY, NUM_SUBS>
{
#[inline(always)]
pub fn new(channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>) -> Self {
Self { channel }
}
#[inline(always)]
pub fn update(&self, value: T) {
self.channel.publish_immediate(value);
}
}
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
Publisher<CAPACITY, NUM_SUBS> for RawPublishInput<T, MutexT, CAPACITY, NUM_SUBS>
{
type Value = T;
type Mutex = MutexT;
#[inline(always)]
fn subscribe(
&self,
) -> Result<Subscriber<Self::Mutex, Self::Value, CAPACITY, NUM_SUBS, 0>, Error> {
self.channel.subscriber()
}
}
#[cfg(feature = "embassy-sync")]
pub struct RawStatePubInput<
T: Copy, T: Copy,
MutexT: RawMutex, MutexT: RawMutex,
const CAPACITY: usize, const CAPACITY: usize,
const NUM_SUBS: usize, const NUM_SUBS: usize,
const NUM_PUBS: usize,
> { > {
channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, NUM_PUBS>, pub state_cell: Cell<T>,
pub channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>,
} }
#[cfg(feature = "embassy-sync")] impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
pub struct StatefulPublishInput< RawStatePubInput<T, MutexT, CAPACITY, NUM_SUBS>
'a, {
T: Copy, #[inline(always)]
MutexT: RawMutex, pub fn new(
const CAPACITY: usize, state_cell: Cell<T>,
const NUM_SUBS: usize, channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, 0>,
const NUM_PUBS: usize, ) -> Self {
> { Self {
pub state_cell: CellView<'a, T>, state_cell,
channel: PubSubChannel<MutexT, T, CAPACITY, NUM_SUBS, NUM_PUBS>, channel,
}
}
#[inline]
pub fn update(&self, value: T) {
self.state_cell.set(value);
self.channel.publish_immediate(value);
}
}
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize> Stateful
for RawStatePubInput<T, MutexT, CAPACITY, NUM_SUBS>
{
type Value = T;
#[inline(always)]
fn state_cell(&self) -> CellView<Self::Value> {
(&self.state_cell).into()
}
#[inline(always)]
fn state(&self) -> Self::Value {
self.state_cell.get()
}
}
impl<T: Copy, MutexT: RawMutex, const CAPACITY: usize, const NUM_SUBS: usize>
Publisher<CAPACITY, NUM_SUBS> for RawStatePubInput<T, MutexT, CAPACITY, NUM_SUBS>
{
type Value = T;
type Mutex = MutexT;
fn subscribe(
&self,
) -> Result<Subscriber<Self::Mutex, Self::Value, CAPACITY, NUM_SUBS, 0>, Error> {
self.channel.subscriber()
}
} }

View File

@ -1,5 +1,5 @@
mod input; pub mod input;
mod output; pub mod output;
pub use physical::transducer::*; pub use physical::transducer::*;
@ -11,17 +11,11 @@ use embassy_sync::pubsub;
use embassy_sync::pubsub::Subscriber; use embassy_sync::pubsub::Subscriber;
#[cfg(feature = "embassy-sync")] #[cfg(feature = "embassy-sync")]
pub trait Publisher { pub trait Publisher<const CAPACITY: usize, const NUM_SUBS: usize> {
type Value: Copy; type Value: Copy;
type Mutex: RawMutex; type Mutex: RawMutex;
const CAPACITY: usize;
const NUM_SUBS: usize;
const NUM_PUBS: usize;
fn subscribe( fn subscribe(
&self, &self,
) -> Result< ) -> Result<Subscriber<Self::Mutex, Self::Value, CAPACITY, NUM_SUBS, 0>, pubsub::Error>;
Subscriber<Self::Mutex, Self::Value, Self::CAPACITY, Self::NUM_SUBS, Self::NUM_PUBS>,
pubsub::Error,
>;
} }

View File

@ -8,12 +8,15 @@ readme.workspace = true
license.workspace = true license.workspace = true
[features] [features]
standard-multiplexer = [] config = ["physical-ads1256-types/config"]
standard-input = ["physical-ads1256-types/standard-input"]
standard-multiplexer = ["standard-input"]
[dependencies.physical-node] [dependencies.physical-node]
path = "../../../node" path = "../../../node"
[dependencies.physical-ads1256-types] [dependencies.physical-ads1256-types]
path = "../types" path = "../types"
features = ["defmt"]
[dependencies.ads1256] [dependencies.ads1256]
workspace = true workspace = true
[dependencies.embedded-hal] [dependencies.embedded-hal]

View File

@ -1,10 +0,0 @@
struct AnalogInput {}
// AnalogInputS
// AnalogInputI
// AnalogInputC
// AnalogInputSI
// AnalogInputSC
// AnalogInputIC
// AnalogInputSIC

View File

@ -1,4 +1,6 @@
#![no_std] #![no_std]
#[cfg(feature = "standard-multiplexer")] #[cfg(feature = "standard-multiplexer")]
mod analog_input; mod standard_multiplexer;
pub use physical_ads1256_types::*;

View File

@ -8,7 +8,8 @@ readme.workspace = true
license.workspace = true license.workspace = true
[features] [features]
standard-multiplexer = [] config = []
standard-input = []
[dependencies.ads1256-types] [dependencies.ads1256-types]
workspace = true workspace = true

View File

@ -0,0 +1,2 @@
use ads1256_types::{Buffer, Config, DataRate, Gain};

View File

@ -1 +1,11 @@
#![no_std] #![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::*;

View File

@ -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<Multiplexer> 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<Multiplexer> for SingleEnded {
#[inline(always)]
fn into(self) -> Multiplexer {
Multiplexer(self as u8)
}
}

View File

@ -6,13 +6,14 @@ use core::cell::Cell;
pub struct CellView<'a, T: Copy>(&'a Cell<T>); pub struct CellView<'a, T: Copy>(&'a Cell<T>);
impl<T: Copy> CellView<'_, T> { impl<T: Copy> CellView<'_, T> {
#[inline] #[inline(always)]
pub fn get(self) -> T { pub fn get(self) -> T {
self.0.get() self.0.get()
} }
} }
impl<'a, T: Copy> From<&'a Cell<T>> for CellView<'a, T> { impl<'a, T: Copy> From<&'a Cell<T>> for CellView<'a, T> {
#[inline(always)]
fn from(value: &'a Cell<T>) -> Self { fn from(value: &'a Cell<T>) -> Self {
CellView(value) CellView(value)
} }

View File

View File

@ -1,3 +1,4 @@
use core::cell::Cell;
use crate::cell::CellView; use crate::cell::CellView;
use crate::transducer::Stateful; use crate::transducer::Stateful;
@ -5,19 +6,31 @@ pub trait Poll<T: Copy> {
async fn poll() -> T; async fn poll() -> T;
} }
pub struct StatefulInput<'a, T: Copy> { pub struct RawStatefulInput<T: Copy> {
pub state_cell: CellView<'a, T>, pub state_cell: Cell<T>,
} }
impl<'a, T: Copy> Stateful for StatefulInput<'a, T> { impl<T: Copy> RawStatefulInput<T> {
#[inline(always)]
pub fn new(state_cell: Cell<T>) -> Self {
Self { state_cell }
}
#[inline(always)]
pub fn raw_update(&self, value: T) {
self.state_cell.set(value);
}
}
impl<T: Copy> Stateful for RawStatefulInput<T> {
type Value = T; type Value = T;
#[inline(always)] #[inline(always)]
fn state_cell(&self) -> CellView<'a, Self::Value> { fn state_cell(&self) -> CellView<Self::Value> {
self.state_cell (&self.state_cell).into()
} }
#[inline] #[inline(always)]
fn state(&self) -> Self::Value { fn state(&self) -> Self::Value {
self.state_cell.get() self.state_cell.get()
} }

View File

@ -2,6 +2,7 @@ use crate::cell::CellView;
pub mod input; pub mod input;
pub mod output; pub mod output;
mod conversion;
// Initialisation will always be async and won't complete until a state is available for all // Initialisation will always be async and won't complete until a state is available for all
// stateful transducers. // stateful transducers.
@ -10,5 +11,8 @@ pub trait Stateful {
fn state_cell(&self) -> CellView<Self::Value>; fn state_cell(&self) -> CellView<Self::Value>;
fn state(&self) -> Self::Value; #[inline(always)]
fn state(&self) -> Self::Value {
self.state_cell().get()
}
} }