Skip to content

Commit

Permalink
Merge pull request #16 from atlas-aero/15_spi-read-transaction
Browse files Browse the repository at this point in the history
Fixed wrong transaction handling for register operations
  • Loading branch information
marius-meissner authored Jan 25, 2025
2 parents 11f6513 + bcbfe46 commit 3b8f4e2
Show file tree
Hide file tree
Showing 4 changed files with 916 additions and 334 deletions.
80 changes: 37 additions & 43 deletions src/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,59 @@ use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice};

#[derive(Default)]
pub struct ExampleSPIDevice {
poll_count: usize,
command: u8,
}
pub struct ExampleSPIDevice {}

impl ErrorType for ExampleSPIDevice {
type Error = Infallible;
}

impl SpiDevice<u8> for ExampleSPIDevice {
fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> {
if operations.len() != 1 {
panic!("Only one operation supported by example SPI client");
}
let mut command = 0x0;

match &mut operations[0] {
Operation::Read(buffer) => {
// Poll call
if buffer.len() == 1 {
self.poll_count += 1;
if self.poll_count >= 2 {
buffer[0] = 0xff
} else {
buffer[0] = 0x0
};
for operation in operations {
match operation {
Operation::Read(buffer) => {
Self::response(command, buffer);
}

match self.command {
// Status register A
0b0001_0000 => buffer.copy_from_slice(&[0x12, 0x62, 0xA8, 0x62, 0x00, 0x7D, 0x31, 0x8A]),
// Status register B
0b0001_0010 => buffer.copy_from_slice(&[0x00, 0xC8, 0x00, 0x66, 0x00, 0x1B, 0xF1, 0x40]),
// Cell voltage register B
0b0000_0100 => buffer.copy_from_slice(&[0x93, 0x61, 0xBB, 0x1E, 0xAE, 0x22, 0x9A, 0x1C]),
// Cell voltage register B
0b0000_0110 => buffer.copy_from_slice(&[0xDD, 0x66, 0x72, 0x1D, 0xA2, 0x1C, 0x11, 0x94]),
// Cell voltage register C
0b0000_1000 => buffer.copy_from_slice(&[0x61, 0x63, 0xBD, 0x1E, 0xE4, 0x22, 0x3F, 0x42]),
// Cell voltage register E
0b0000_1001 => buffer.copy_from_slice(&[0xDE, 0x64, 0x8F, 0x21, 0x8A, 0x21, 0x8F, 0xDA]),
// Aux voltage register A
0b0000_1100 => buffer.copy_from_slice(&[0x93, 0x61, 0xBB, 0x1E, 0xAE, 0x22, 0x9A, 0x1C]),
// Aux voltage register C
0b0000_1101 => buffer.copy_from_slice(&[0x61, 0x63, 0xBD, 0x1E, 0xE4, 0x22, 0x3F, 0x42]),
_ => buffer.copy_from_slice(&[0x0; 8]),
};
}
Operation::Write(words) => {
self.command = words[1];
Operation::Transfer(buffer, write) => {
command = write[1];
Self::response(command, &mut buffer[4..]);
}
Operation::TransferInPlace(_) => panic!("Unexpected TransferInPlace operation"),
Operation::DelayNs(_) => panic!("Unexpected DelayNs operation"),
Operation::Write(_) => {}
}
Operation::Transfer(_, _) => panic!("Transfer operation not supported by example SPI client"),
Operation::TransferInPlace(_) => panic!("TransferInPlace operation not supported by example SPI client"),
Operation::DelayNs(_) => panic!("DelayNs operation not supported by example SPI client"),
}

Ok(())
}
}

impl ExampleSPIDevice {
fn response(command: u8, buffer: &mut [u8]) {
match command {
// Status register A
0b0001_0000 => buffer.copy_from_slice(&[0x12, 0x62, 0xA8, 0x62, 0x00, 0x7D, 0x31, 0x8A]),
// Status register B
0b0001_0010 => buffer.copy_from_slice(&[0x00, 0xC8, 0x00, 0x66, 0x00, 0x1B, 0xF1, 0x40]),
// Cell voltage register B
0b0000_0100 => buffer.copy_from_slice(&[0x93, 0x61, 0xBB, 0x1E, 0xAE, 0x22, 0x9A, 0x1C]),
// Cell voltage register B
0b0000_0110 => buffer.copy_from_slice(&[0xDD, 0x66, 0x72, 0x1D, 0xA2, 0x1C, 0x11, 0x94]),
// Cell voltage register C
0b0000_1000 => buffer.copy_from_slice(&[0x61, 0x63, 0xBD, 0x1E, 0xE4, 0x22, 0x3F, 0x42]),
// Cell voltage register E
0b0000_1001 => buffer.copy_from_slice(&[0xDE, 0x64, 0x8F, 0x21, 0x8A, 0x21, 0x8F, 0xDA]),
// Aux voltage register A
0b0000_1100 => buffer.copy_from_slice(&[0x93, 0x61, 0xBB, 0x1E, 0xAE, 0x22, 0x9A, 0x1C]),
// Aux voltage register C
0b0000_1101 => buffer.copy_from_slice(&[0x61, 0x63, 0xBD, 0x1E, 0xE4, 0x22, 0x3F, 0x42]),
_ => buffer.copy_from_slice(&[0x0; 8]),
};
}
}

#[derive(Default)]
pub struct ExampleSPIBus {
poll_count: usize,
Expand Down
86 changes: 69 additions & 17 deletions src/mocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,42 @@ impl DeviceMockBuilder {
self
}

pub fn expect_register_read(mut self, data: &'static [u8; 8]) -> Self {
self.device.expect_transaction().times(1).returning(move |operation| {
assert_eq!(1, operation.len());

match &mut operation[0] {
Operation::Read(buffer) => {
buffer.copy_from_slice(data);
pub fn expect_register_read<const N: usize>(
mut self,
cmd0: u8,
cmd1: u8,
pec0: u8,
pec1: u8,
data: [&'static [u8; 8]; N],
) -> Self {
self.device.expect_transaction().times(1).returning(move |operations| {
assert_eq!(N, operations.len());

for (i, operation) in operations.iter_mut().enumerate() {
if i == 0 {
match operation {
Operation::Transfer(buffer, command) => {
assert_eq!(12, buffer.len());
assert_eq!(12, command.len());

assert_eq!(cmd0, command[0]);
assert_eq!(cmd1, command[1]);
assert_eq!(pec0, command[2]);
assert_eq!(pec1, command[3]);

buffer[4..].copy_from_slice(data[i]);
}
_ => panic!("Received unexpected operation type {:?}", operations[0]),
}
} else {
match operation {
Operation::Read(buffer) => {
assert_eq!(8, buffer.len());
buffer.copy_from_slice(data[i]);
}
_ => panic!("Received unexpected operation type {:?}", operations[0]),
}
}
_ => panic!("Received unexpected operation type {:?}", operation[0]),
}

Ok(())
Expand All @@ -108,13 +135,15 @@ impl DeviceMockBuilder {
self
}

pub fn expect_register_write(mut self, expected: &'static [u8; 8]) -> Self {
pub fn expect_register_write<const N: usize>(mut self, expected: &'static [&'static [u8]; N]) -> Self {
self.device.expect_transaction().times(1).returning(move |operation| {
assert_eq!(1, operation.len());
assert_eq!(N, operation.len());

match operation[0] {
Operation::Write(cmd) => assert_eq!(expected, cmd),
_ => panic!("Received unexpected operation type {:?}", operation[0]),
for (i, expected) in expected.iter().enumerate() {
match operation[i] {
Operation::Write(cmd) => assert_eq!(*expected, cmd),
_ => panic!("Received unexpected operation type {:?}", operation[0]),
}
}

Ok(())
Expand Down Expand Up @@ -151,16 +180,39 @@ impl BusMockBuilder {
self
}

pub fn expect_register_read(mut self, data: &'static [u8; 8]) -> Self {
self.bus.expect_read().times(1).returning(move |buffer| {
buffer.copy_from_slice(data);
pub fn expect_register_read<const N: usize>(
mut self,
cmd0: u8,
cmd1: u8,
pec0: u8,
pec1: u8,
data: &'static [[u8; 8]; N],
) -> Self {
self.bus.expect_transfer().times(1).returning(move |read, write| {
assert_eq!(12, read.len());
assert_eq!(12, write.len());

assert_eq!(cmd0, write[0]);
assert_eq!(cmd1, write[1]);
assert_eq!(pec0, write[2]);
assert_eq!(pec1, write[3]);

read[4..].copy_from_slice(&data[0]);
Ok(())
});

for item in &data[1..] {
self.bus.expect_read().times(1).returning(move |buffer| {
assert_eq!(8, buffer.len());
buffer.copy_from_slice(item);
Ok(())
});
}

self
}

pub fn expect_register_write(mut self, expected: &'static [u8; 8]) -> Self {
pub fn expect_register_write(mut self, expected: &'static [u8]) -> Self {
self.bus.expect_write().times(1).returning(move |data| {
assert_eq!(expected, data);
Ok(())
Expand Down
100 changes: 70 additions & 30 deletions src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ use core::fmt::{Debug, Display, Formatter};
use core::marker::PhantomData;
use core::slice::Iter;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{SpiBus, SpiDevice};
use embedded_hal::spi::{Operation, SpiBus, SpiDevice};
use fixed::types::I16F16;
use heapless::Vec;

Expand Down Expand Up @@ -747,20 +747,31 @@ where
Err(_) => return Err(Error::ReadOnlyRegister),
};

self.bus.write(&pre_command).map_err(Error::BusError)?;
// Buffer for first operation
let mut first_operation = [0xff_u8; 12];

for item in &data {
let mut full_command: [u8; 8] = [0x0; 8];
full_command[..6].clone_from_slice(item);
// Buffer for operations to daisy-chained devices
// As generic_const_exprs feature is not yet supported, the last item is not used (wasted)
let mut shifted_data = [[0x0_u8; 8]; L];

let pec = PEC15::calc(item);
full_command[6] = pec[0];
full_command[7] = pec[1];
// The first operation includes the pre-command + data bytes of master
first_operation[..4].copy_from_slice(&pre_command);
first_operation[4..10].copy_from_slice(&data[0]);
self.add_pec_checksum(&mut first_operation[4..]);

self.bus.write(&full_command).map_err(Error::BusError)?;
let mut operations: Vec<Operation<u8>, L> = Vec::new();
let _ = operations.push(Operation::Write(&first_operation));

// Adding data of daisy-chained devices
for (i, item) in shifted_data[..L - 1].iter_mut().enumerate() {
item[..6].copy_from_slice(&data[i + 1]);
self.add_pec_checksum(item);
let _ = operations.push(Operation::Write(item));
}

self.poll_method.end_sync_command(&mut self.bus).map_err(Error::BusError)?;
self.bus.transaction(&mut operations).map_err(BusError)?;

self.poll_method.end_sync_command(&mut self.bus).map_err(BusError)?;
Ok(())
}

Expand Down Expand Up @@ -884,44 +895,73 @@ where
/// Sends the given command. Calculates and attaches the PEC checksum
fn send_command(&mut self, command: u16) -> Result<(), B::Error> {
let mut data = [(command >> 8) as u8, command as u8, 0x0, 0x0];
let pec = PEC15::calc(&data[0..2]);

data[2] = pec[0];
data[3] = pec[1];
self.add_pec_checksum(&mut data);

self.bus.write(&data)?;
Ok(())
}

/// Calculates and attaches the PEC15 checksum
fn add_pec_checksum(&self, data: &mut [u8]) {
let pec = PEC15::calc(&data[0..data.len() - 2]);

data[data.len() - 2] = pec[0];
data[data.len() - 1] = pec[1];
}

/// Send the given read command and returns the response of all devices in daisy chain
fn read_daisy_chain(&mut self, command: [u8; 4]) -> Result<[[u16; 3]; L], Error<B>> {
self.bus.write(&command).map_err(Error::BusError)?;
let data = self.read_trans_daisy_chain(command)?;

let mut result = [[0, 0, 0]; L];
for item in result.iter_mut().take(L) {
*item = self.read()?;
for (i, item) in result.iter_mut().take(L).enumerate() {
let response = data[i];

let pec = PEC15::calc(&response[0..6]);
if pec[0] != response[6] || pec[1] != response[7] {
return Err(Error::ChecksumMismatch);
}

item[0] = response[0] as u16;
item[0] |= (response[1] as u16) << 8;

item[1] = response[2] as u16;
item[1] |= (response[3] as u16) << 8;

item[2] = response[4] as u16;
item[2] |= (response[5] as u16) << 8;
}

self.poll_method.end_sync_command(&mut self.bus).map_err(Error::BusError)?;
Ok(result)
}

/// Reads a register
fn read(&mut self) -> Result<[u16; 3], Error<B>> {
let mut result = [0xff_u8; 8];
self.bus.read(&mut result).map_err(BusError)?;

let pec = PEC15::calc(&result[0..6]);
if pec[0] != result[6] || pec[1] != result[7] {
return Err(Error::ChecksumMismatch);
/// Creates SPI transactions for reading from daisy chain and returns the raw data
fn read_trans_daisy_chain(&mut self, command: [u8; 4]) -> Result<[[u8; 8]; L], Error<B>> {
let command_write = [
command[0], command[1], command[2], command[3], 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
];
let mut command_read = [0xff_u8; 12];

// Read buffer for all daisy-chained devices
// As generic_const_exprs feature is not yet supported the first buffer item will not be used.
// This wastes eight bytes of stack memory
let mut buffers = [[0xff_u8; 8]; L];

// Result from connected device will be directly received on command transaction
let mut operations: Vec<Operation<u8>, L> = Vec::new();
let _ = operations.push(Operation::Transfer(&mut command_read, &command_write));

// Read operations for all dasi-chained devices
for buffer_item in &mut buffers[1..].iter_mut() {
operations.push(Operation::Read(buffer_item)).unwrap()
}

let mut registers = [result[0] as u16, result[2] as u16, result[4] as u16];
registers[0] |= (result[1] as u16) << 8;
registers[1] |= (result[3] as u16) << 8;
registers[2] |= (result[5] as u16) << 8;
self.bus.transaction(&mut operations).map_err(Error::BusError)?;
drop(operations);
buffers[0].copy_from_slice(&command_read[4..]);

Ok(registers)
Ok(buffers)
}

/// Calculates the temperature in °C based on raw register value
Expand Down
Loading

0 comments on commit 3b8f4e2

Please sign in to comment.