Skip to content

Commit

Permalink
SNS - CAN IO Trait (#72)
Browse files Browse the repository at this point in the history
Co-authored-by: David Beechey <[email protected]>
  • Loading branch information
jpfbastos and davidbeechey authored Feb 7, 2025
1 parent dd18d71 commit 25bf8a5
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 49 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion boards/stm32f767zi/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
9 changes: 9 additions & 0 deletions lib/io/hyped_can/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"}
135 changes: 135 additions & 0 deletions lib/io/hyped_can/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<HypedEnvelope, CanError>;
/// 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<HypedCanFrame, 8>;

/// 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<CriticalSectionRawMutex, RefCell<CanValues>>,
/// Values that have been sent over the CAN bus
frames_sent: CanValues,
/// Whether to fail reading frames
fail_read: &'a Mutex<CriticalSectionRawMutex, bool>,
/// Whether to fail writing frames
fail_write: &'a Mutex<CriticalSectionRawMutex, bool>,
}

impl crate::HypedCan for MockCan<'_> {
fn read_frame(&mut self) -> Result<super::HypedEnvelope, super::CanError> {
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<CriticalSectionRawMutex, RefCell<CanValues>>,
) -> Self {
static FAIL_READ: Mutex<CriticalSectionRawMutex, bool> = Mutex::new(false);
static FAIL_WRITE: Mutex<CriticalSectionRawMutex, bool> = Mutex::new(false);
MockCan::new_with_failures(frames_to_read, &FAIL_READ, &FAIL_WRITE)
}

pub fn new_with_failures(
frames_to_read: &'static Mutex<CriticalSectionRawMutex, RefCell<CanValues>>,
fail_read: &'static Mutex<CriticalSectionRawMutex, bool>,
fail_write: &'static Mutex<CriticalSectionRawMutex, bool>,
) -> 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
}
}
}
2 changes: 1 addition & 1 deletion lib/io/hyped_i2c/hyped_i2c_derive/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 42 additions & 47 deletions lib/io/hyped_i2c/hyped_i2c_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> {
let mut read = [0];
let result = self.i2c.lock(|i2c| {
Expand All @@ -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<u8> {
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<u8> {
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,
Expand All @@ -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())
Expand All @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions lib/io/hyped_i2c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>;
/// Read a byte from a 16-bit register on a device
fn read_byte_16(&mut self, device_address: u8, register_address: u16) -> Option<u8>;
/// 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>;
}

Expand Down

0 comments on commit 25bf8a5

Please sign in to comment.