From 25bf8a54129b759f40c4c7862e1f19f3e58f28b3 Mon Sep 17 00:00:00 2001 From: Joao Pedro Bastos <86593844+jpfbastos@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:54:51 +0000 Subject: [PATCH] SNS - CAN IO Trait (#72) Co-authored-by: David Beechey --- Cargo.lock | 10 ++ boards/stm32f767zi/src/io.rs | 1 - lib/io/hyped_can/Cargo.toml | 9 ++ lib/io/hyped_can/src/lib.rs | 135 +++++++++++++++++++ lib/io/hyped_i2c/hyped_i2c_derive/Cargo.lock | 2 +- lib/io/hyped_i2c/hyped_i2c_derive/src/lib.rs | 89 ++++++------ lib/io/hyped_i2c/src/lib.rs | 5 + 7 files changed, 202 insertions(+), 49 deletions(-) create mode 100644 lib/io/hyped_can/Cargo.toml create mode 100644 lib/io/hyped_can/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index cbe581bb..40f143f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,6 +147,7 @@ source = "git+https://github.com/embassy-rs/embassy?rev=1c466b81e6af6b34b1f70631 dependencies = [ "cfg-if", "critical-section", + "defmt", "document-features", "embassy-time-driver", "embassy-time-queue-driver", @@ -302,6 +303,15 @@ dependencies = [ "heapless", ] +[[package]] +name = "hyped_can" +version = "0.1.0" +dependencies = [ + "embassy-sync 0.6.0", + "embassy-time", + "heapless", +] + [[package]] name = "hyped_config" version = "0.1.0" diff --git a/boards/stm32f767zi/src/io.rs b/boards/stm32f767zi/src/io.rs index e7d410f4..530e6b05 100644 --- a/boards/stm32f767zi/src/io.rs +++ b/boards/stm32f767zi/src/io.rs @@ -3,7 +3,6 @@ use embassy_stm32::adc::{Adc, AnyAdcChannel, Instance}; use embassy_stm32::gpio::{Input, Output}; use embassy_stm32::{i2c::I2c, mode::Blocking}; use embassy_sync::blocking_mutex::{raw::NoopRawMutex, Mutex}; - use hyped_adc::HypedAdc; use hyped_adc_derive::HypedAdc; use hyped_gpio::{HypedGpioInputPin, HypedGpioOutputPin}; diff --git a/lib/io/hyped_can/Cargo.toml b/lib/io/hyped_can/Cargo.toml new file mode 100644 index 00000000..8a040fd9 --- /dev/null +++ b/lib/io/hyped_can/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hyped_can" +version = "0.1.0" +edition = "2021" + +[dependencies] +heapless = "0.8.0" +embassy-sync = { version = "0.6.0", features = ["defmt"], git = "https://github.com/embassy-rs/embassy", rev = "1c466b81e6af6b34b1f706318cc0870a459550b7"} +embassy-time = { version = "0.3.1", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"], git = "https://github.com/embassy-rs/embassy", rev = "1c466b81e6af6b34b1f706318cc0870a459550b7"} \ No newline at end of file diff --git a/lib/io/hyped_can/src/lib.rs b/lib/io/hyped_can/src/lib.rs new file mode 100644 index 00000000..d4ae4308 --- /dev/null +++ b/lib/io/hyped_can/src/lib.rs @@ -0,0 +1,135 @@ +#![no_std] + +/// CAN errors that can occur +/// From: https://docs.embassy.dev/embassy-stm32/git/stm32f767zi/can/enums/enum.BusError.html, +/// https://docs.embassy.dev/embassy-stm32/git/stm32f767zi/can/enum.TryWriteError.html +/// https://docs.embassy.dev/embassy-stm32/git/stm32f767zi/can/enum.TryReadError.html, +/// and https://docs.embassy.dev/embassy-stm32/git/stm32f767zi/can/enums/enum.FrameCreateError.html +#[derive(Debug)] +pub enum CanError { + Stuff, + Form, + Acknowledge, + BitRecessive, + BitDominant, + Crc, + Software, + BusOff, + BusPassive, + BusWarning, + Full, + Empty, + Unknown, + NotEnoughData, + InvalidDataLength, + InvalidCanId, +} + +#[derive(Clone)] +pub struct HypedCanFrame { + pub can_id: u32, // 32 bit CAN_ID + EFF/RTR/ERR flags + pub data: [u8; 8], // data that is sent over CAN, split into bytes +} + +pub type Timestamp = embassy_time::Instant; + +#[derive(Clone)] +pub struct HypedEnvelope { + /// Reception time. + pub ts: Timestamp, + /// The actual CAN frame. + pub frame: HypedCanFrame, +} + +/// CAN trait used to abstract the CAN operations +pub trait HypedCan { + /// Attempts to read a CAN frame without blocking. + /// + /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. + fn read_frame(&mut self) -> Result; + /// Attempts to transmit a frame without blocking. + /// + /// Returns [Err(CanError::Full)] if the frame can not be queued for transmission now. + /// + /// The frame will only be accepted if there is no frame with the same priority already queued. This is done + /// to work around a hardware limitation that could lead to out-of-order delivery of frames with the same priority. + fn write_frame(&mut self, frame: &HypedCanFrame) -> Result<(), CanError>; +} + +pub mod mock_can { + use core::cell::RefCell; + use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex}; + use heapless::Deque; + + use crate::HypedCanFrame; + + /// A fixed-size map of CAN frames + type CanValues = Deque; + + /// A mock CAN instance which can be used for testing + pub struct MockCan<'a> { + /// Values that have been read from the CAN bus + frames_to_read: &'a Mutex>, + /// Values that have been sent over the CAN bus + frames_sent: CanValues, + /// Whether to fail reading frames + fail_read: &'a Mutex, + /// Whether to fail writing frames + fail_write: &'a Mutex, + } + + impl crate::HypedCan for MockCan<'_> { + fn read_frame(&mut self) -> Result { + if self.fail_read.lock(|fail_read| *fail_read) { + return Err(super::CanError::Unknown); + } + self.frames_to_read.lock(|frames_to_read| { + match frames_to_read.borrow_mut().pop_front() { + Some(frame) => Ok(super::HypedEnvelope { + ts: embassy_time::Instant::now(), + frame, + }), + None => Err(super::CanError::Empty), + } + }) + } + + fn write_frame(&mut self, frame: &super::HypedCanFrame) -> Result<(), super::CanError> { + if self.fail_write.lock(|fail_write| *fail_write) { + return Err(super::CanError::Unknown); + } + match self.frames_sent.push_front(frame.clone()) { + Ok(_) => Ok(()), + Err(_) => Err(super::CanError::Unknown), + } + } + } + + impl MockCan<'_> { + pub fn new( + frames_to_read: &'static Mutex>, + ) -> Self { + static FAIL_READ: Mutex = Mutex::new(false); + static FAIL_WRITE: Mutex = Mutex::new(false); + MockCan::new_with_failures(frames_to_read, &FAIL_READ, &FAIL_WRITE) + } + + pub fn new_with_failures( + frames_to_read: &'static Mutex>, + fail_read: &'static Mutex, + fail_write: &'static Mutex, + ) -> Self { + MockCan { + frames_to_read, + frames_sent: CanValues::new(), + fail_read, + fail_write, + } + } + + /// Get the values that have been sent over the CAN bus + pub fn get_can_frames(&self) -> &CanValues { + &self.frames_sent + } + } +} diff --git a/lib/io/hyped_i2c/hyped_i2c_derive/Cargo.lock b/lib/io/hyped_i2c/hyped_i2c_derive/Cargo.lock index 38de2875..724a162e 100644 --- a/lib/io/hyped_i2c/hyped_i2c_derive/Cargo.lock +++ b/lib/io/hyped_i2c/hyped_i2c_derive/Cargo.lock @@ -3,7 +3,7 @@ version = 3 [[package]] -name = "hyped_adc_derive" +name = "hyped_i2c_derive" version = "0.1.0" dependencies = [ "quote", diff --git a/lib/io/hyped_i2c/hyped_i2c_derive/src/lib.rs b/lib/io/hyped_i2c/hyped_i2c_derive/src/lib.rs index e9b93dcd..347df560 100644 --- a/lib/io/hyped_i2c/hyped_i2c_derive/src/lib.rs +++ b/lib/io/hyped_i2c/hyped_i2c_derive/src/lib.rs @@ -16,7 +16,7 @@ fn impl_hyped_i2c(ast: &syn::DeriveInput) -> TokenStream { let (impl_generics, ty_generics, _) = generics.split_for_impl(); let gen = quote! { impl #impl_generics HypedI2c for #name #ty_generics{ - /// Read a byte from a register on a device + fn read_byte(&mut self, device_address: u8, register_address: u8) -> Option { let mut read = [0]; let result = self.i2c.lock(|i2c| { @@ -32,26 +32,24 @@ fn impl_hyped_i2c(ast: &syn::DeriveInput) -> TokenStream { } } - /// Read a byte from a register with a 16-bit address on a device - fn read_byte_16(&mut self, device_address: u8, register_address: u16) -> Option { - let register_addr_hi = (register_address >> 8) as u8 & 0xFF; - let register_addr_lo = register_address as u8 & 0xFF; - let mut read = [0]; - let result = self.i2c.lock(|i2c| { - i2c.borrow_mut().blocking_write_read( - device_address, - [register_addr_hi, register_addr_lo].as_ref(), - &mut read, - ) - }); - match result { - Ok(_) => Some(read[0]), - Err(_) => None, - } - } + fn read_byte_16(&mut self, device_address: u8, register_address: u16) -> Option { + let register_addr_hi = (register_address >> 8) as u8 & 0xFF; + let register_addr_lo = register_address as u8 & 0xFF; + let mut read = [0]; + let result = self.i2c.lock(|i2c| { + i2c.borrow_mut().blocking_write_read( + device_address, + [register_addr_hi, register_addr_lo].as_ref(), + &mut read, + ) + }); + match result { + Ok(_) => Some(read[0]), + Err(_) => None, + } + } - /// Write a byte to a register on a device fn write_byte_to_register( &mut self, device_address: u8, @@ -76,7 +74,6 @@ fn impl_hyped_i2c(ast: &syn::DeriveInput) -> TokenStream { } } - /// Write a byte to a device fn write_byte(&mut self, device_address: u8, data: u8) -> Result<(), I2cError> { let result = self.i2c.lock(|i2c| { i2c.borrow_mut().blocking_write(device_address, [data].as_ref()) @@ -95,33 +92,31 @@ fn impl_hyped_i2c(ast: &syn::DeriveInput) -> TokenStream { } } - // Write a byte to a register with a 16-bit address on a device - fn write_byte_to_register_16( - &mut self, - device_address: u8, - register_address: u16, - data: u8, - ) -> Result<(), I2cError> { - let register_addr_hi = (register_address >> 8) as u8; - let register_addr_lo = register_address as u8; - let result = self.i2c.lock(|i2c| { - i2c.borrow_mut() - .blocking_write(device_address, [register_addr_hi, register_addr_lo, data].as_ref()) - }); - match result { - Ok(_) => Ok(()), - Err(e) => Err(match e { - embassy_stm32::i2c::Error::Bus => I2cError::Bus, - embassy_stm32::i2c::Error::Arbitration => I2cError::Arbitration, - embassy_stm32::i2c::Error::Nack => I2cError::Nack, - embassy_stm32::i2c::Error::Timeout => I2cError::Timeout, - embassy_stm32::i2c::Error::Crc => I2cError::Crc, - embassy_stm32::i2c::Error::Overrun => I2cError::Overrun, - embassy_stm32::i2c::Error::ZeroLengthTransfer => I2cError::ZeroLengthTransfer, - }), - } - } - + fn write_byte_to_register_16( + &mut self, + device_address: u8, + register_address: u16, + data: u8, + ) -> Result<(), I2cError> { + let register_addr_hi = (register_address >> 8) as u8; + let register_addr_lo = register_address as u8; + let result = self.i2c.lock(|i2c| { + i2c.borrow_mut() + .blocking_write(device_address, [register_addr_hi, register_addr_lo, data].as_ref()) + }); + match result { + Ok(_) => Ok(()), + Err(e) => Err(match e { + embassy_stm32::i2c::Error::Bus => I2cError::Bus, + embassy_stm32::i2c::Error::Arbitration => I2cError::Arbitration, + embassy_stm32::i2c::Error::Nack => I2cError::Nack, + embassy_stm32::i2c::Error::Timeout => I2cError::Timeout, + embassy_stm32::i2c::Error::Crc => I2cError::Crc, + embassy_stm32::i2c::Error::Overrun => I2cError::Overrun, + embassy_stm32::i2c::Error::ZeroLengthTransfer => I2cError::ZeroLengthTransfer, + }), + } + } } impl #impl_generics #name #ty_generics { diff --git a/lib/io/hyped_i2c/src/lib.rs b/lib/io/hyped_i2c/src/lib.rs index ff5703ca..507c4055 100644 --- a/lib/io/hyped_i2c/src/lib.rs +++ b/lib/io/hyped_i2c/src/lib.rs @@ -18,20 +18,25 @@ pub enum I2cError { /// I2C trait used to abstract the I2C peripheral pub trait HypedI2c { + /// Read a byte from a register on a device fn read_byte(&mut self, device_address: u8, register_address: u8) -> Option; + /// Read a byte from a 16-bit register on a device fn read_byte_16(&mut self, device_address: u8, register_address: u16) -> Option; + /// Write a byte to a register on a device fn write_byte_to_register( &mut self, device_address: u8, register_address: u8, data: u8, ) -> Result<(), I2cError>; + // Write a byte to a 16-bit register on a device fn write_byte_to_register_16( &mut self, device_address: u8, register_address: u16, data: u8, ) -> Result<(), I2cError>; + /// Write a byte to a device fn write_byte(&mut self, device_address: u8, data: u8) -> Result<(), I2cError>; }