Initial proof of concept

This commit is contained in:
Zachary Levy
2025-03-09 12:13:14 -07:00
commit e06e76e46b
55 changed files with 4508 additions and 0 deletions

29
node/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "physical-node"
description = "A node hosts peripherals."
version.workspace = true
edition.workspace = true
repository.workspace = true
readme.workspace = true
license.workspace = true
[features]
comms = []
single-packet-msgs = []
usb = ["embassy-usb"]
stm32 = ["embassy-stm32", "physical/stm32"]
[dependencies.physical]
path = ".."
[dependencies.embedded-hal]
workspace = true
[dependencies.embedded-hal-async]
workspace = true
[dependencies.defmt]
workspace = true
[dependencies.embassy-stm32]
workspace = true
optional = true
[dependencies.embassy-usb]
workspace = true
optional = true

17
node/src/comms.rs Normal file
View File

@ -0,0 +1,17 @@
pub trait Sender {
async fn send(&mut self, msg: &[u8]) -> Result<(), Reset>;
}
pub trait Receiver {
async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Reset>;
}
//TODO: Replace with !
pub struct Never;
/// Communication errors indicates either:
/// Our connection was already disconnected, in which case we should reset and wait for new connection to made.
/// or
/// There was an unexpected, irrecoverable error in communication, in which case we don't want to enter a terminal error
/// safe mode, because there is no indication the actual control is broken, so all we can really do is reset the connection.
pub struct Reset;

12
node/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
#![no_std]
#[cfg(feature = "comms")]
pub mod comms;
pub mod spi;
#[cfg(feature = "stm32")]
pub mod stm32;
pub use physical::CriticalError;
pub const GPIO_ERROR_MSG: &'static str =
"Driver does not support GPIO pins with expected failure states";

52
node/src/spi.rs Normal file
View File

@ -0,0 +1,52 @@
use crate::GPIO_ERROR_MSG;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi;
use embedded_hal::spi::SpiBus;
/// End the SPI operation if the result was an error.
/// This function will attempt to flush the SPI bus if the result being inspected was an error.
/// If the flush fails, the flush error will be ignored and the original error will be returned.
#[inline]
pub fn end_spi_if_err<OkT, SpiT: SpiBus>(
slave_select: &mut impl OutputPin,
spi: &mut SpiT,
result: Result<OkT, <SpiT as spi::ErrorType>::Error>,
) -> Result<OkT, <SpiT as spi::ErrorType>::Error> {
match result {
Ok(_) => result,
Err(_) => {
// Ignore flush error and return the original error
slave_select.set_high().expect(GPIO_ERROR_MSG);
let _ = spi.flush();
result
},
}
}
/// End the series of SPI operations and forward the error of the final operation if there was
/// one. Error handling:
/// * If there was an error in the SPI operation and an error flushing the bus, returns the original
/// SPI error, not the bus error.
/// * If there was not an error in the SPI operation but was an error flushing the bus, returns the
/// bus flush error.
/// * If there was an error in the SPI operation and no error flushing the bus, returns the original
/// SPI error.
#[inline]
pub fn end_spi<OkT, SpiT: SpiBus>(
slave_select: &mut impl OutputPin,
spi: &mut SpiT,
result: Result<OkT, <SpiT as spi::ErrorType>::Error>,
) -> Result<OkT, <SpiT as spi::ErrorType>::Error> {
match result {
Ok(_) => {
slave_select.set_high().expect(GPIO_ERROR_MSG);
spi.flush()?;
result
},
Err(_) => {
slave_select.set_high().expect(GPIO_ERROR_MSG);
let _ = spi.flush();
result
},
}
}

2
node/src/stm32/mod.rs Normal file
View File

@ -0,0 +1,2 @@
#[cfg(feature = "usb")]
pub mod usb;

41
node/src/stm32/usb.rs Normal file
View File

@ -0,0 +1,41 @@
// The library will have build errors when built on its own (due to not having embassy-stm32 feature for a specific microcontroller)
// but it will work fine when used as a dependency for firmware that has the feature for a specific stm32 microcontroller.
use crate::comms;
use embassy_stm32::peripherals::USB_OTG_FS;
use embassy_stm32::usb_otg::{Driver, Endpoint, In, Out};
use embassy_usb::driver::{EndpointIn, EndpointOut};
use embassy_usb::UsbDevice;
pub type TypedUSB = UsbDevice<'static, Driver<'static, USB_OTG_FS>>;
pub type TypedInterIn = Endpoint<'static, USB_OTG_FS, In>;
pub type TypedInterOut = Endpoint<'static, USB_OTG_FS, Out>;
pub struct UsbIO {
/// Send to master
pub interrupt_in: TypedInterIn,
/// Recieve from master
pub interrupt_out: TypedInterOut,
}
impl comms::Sender for TypedInterIn {
async fn send(&mut self, msg: &[u8]) -> Result<(), comms::Reset> {
self.write(msg).await.map_err(|_| comms::Reset)
}
}
impl comms::Receiver for TypedInterOut {
#[cfg(feature = "single-packet-msgs")]
async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), comms::Reset> {
// This is OK when all our messages are smaller than a single packet.
self.read(buffer)
.await
.map(|_| ())
.map_err(|_| comms::Reset)
}
#[cfg(not(feature = "single-packet-msgs"))]
async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), comms::Reset> {
todo!("Decide if we want a general purpose multi-packet message receive")
}
}