Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the SpiBus trait from embedded-hal 1.0. #503

Conversation

ghismary
Copy link
Contributor

Here is the implementation of the embedded-hal 1.0 traits for SPI.
I need to do further testing, but from what I have seen up to now, it seems to work quite well.

Copy link
Owner

@Rahix Rahix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thanks a lot for picking up this work! I have two preliminary comments, see below.

avr-hal-generic/src/spi.rs Outdated Show resolved Hide resolved
avr-hal-generic/src/spi.rs Outdated Show resolved Hide resolved
@ghismary ghismary force-pushed the feature/implement-embedded-hal-1.0-spi-traits branch from 208d4a3 to d061a7c Compare January 30, 2024 20:50
@ghismary
Copy link
Contributor Author

Hi, thanks for your comments.
I had not seen this stuff about hardware CS management, and had not taken a look to embedded-hal-bus yet.
It makes sense, I'll rework on that.

@ghismary ghismary force-pushed the feature/implement-embedded-hal-1.0-spi-traits branch from d061a7c to 0461fa4 Compare January 31, 2024 12:08
@ghismary ghismary changed the title Implement SPI traits from embedded-hal 1.0. Implement the SpiBus trait from embedded-hal 1.0. Jan 31, 2024
@ghismary
Copy link
Contributor Author

I made the changes for the problems reported in the previous comments.
I also adapted an arduino example to the usage of this SpiBus trait implementation via the ExclusiveDevice from embedded-hal-bus.

Copy link
Owner

@Rahix Rahix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a closer look at the changes now. Please check my comments below :)

/// Read n bytes from the data register, n being to the size of
/// the given buffer.
fn raw_read_buf(&self, buf: &mut [u8]) {
buf.iter_mut().for_each(|b| *b = self.spdr.read().bits())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right to me: You can't just read from a SPI bus without somehow triggering a transfer. You'll also have to wait for the transfer to have completed somewhere.

The documentation from embedded-hal says this:

The word value sent on MOSI during reading is implementation-defined, typically 0x00, 0xFF, or configurable.

So something like this is needed:

            fn raw_read_buf(&self, buf: &mut [u8]) {
                for b in buf.iter_mut() {
                    // We send 0x00 on MOSI during "pure" reading
                    self.spdr.write(|w| unsafe { w.bits(0x00) });
                    while self.spsr.read().spif().bit_is_clear() {}
                    *b = self.spdr.read().bits();
                }
            }

Comment on lines +483 to +486
buf.iter().for_each(|b| {
self.spdr.write(|w| unsafe { w.bits(*b) });
while self.spsr.read().spif().bit_is_clear() {}
})
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer a plain for loop over .for_each() when the .for_each isn't strictly required for some reason:

Suggested change
buf.iter().for_each(|b| {
self.spdr.write(|w| unsafe { w.bits(*b) });
while self.spsr.read().spif().bit_is_clear() {}
})
for b in buf.iter() {
self.spdr.write(|w| unsafe { w.bits(*b) });
while self.spsr.read().spif().bit_is_clear() {}
}

Comment on lines +376 to +380
let write = unsafe {
let p = buffer.as_ptr();
core::slice::from_raw_parts(p, buffer.len())
};
self.p.raw_read_write_buf(buffer, write);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my knowledge, this is illegal: You are constructing an immutable reference to the same data that is referenced mutably by buffer. This violates aliasing rules.

I think we have to provide a separate low-level raw_read_write_in_place() here - there is no way to reuse raw_read_write_buf().

Comment on lines +359 to +371
match read.len().cmp(&write.len()) {
Ordering::Less => {
let (write1, write2) = write.split_at(read.len());
self.p.raw_read_write_buf(read, write1);
self.p.raw_write_buf(write2);
}
Ordering::Equal => self.p.raw_read_write_buf(read, write),
Ordering::Greater => {
let (read1, read2) = read.split_at_mut(write.len());
self.p.raw_read_write_buf(read1, write);
self.p.raw_read_buf(read2);
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought: The following implementation would be equivalent. But I am not sure which one is more elegant. I think yours might still be more explicit about the intent and thus easier to understand. But I'll let you decide which one to use.

let common_length = read.len().min(write.len());
let (write1, write2) = write.split_at(common_length);
let (read1, read2) = read.split_at(common_length);
self.p.raw_read_write_buf(read1, write1);

debug_assert!(write2.len() == 0 || read2.len() == 0);
self.p.raw_write_buf(write2);
self.p.raw_read_buf(read2);

}

fn flush(&mut self) -> Result<(), Self::Error> {
if self.write_in_progress {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure I understand this correctly:

You are only making use of the write_in_progress flag here to ensure correct behavior when both e-h0.2::FullDuplex and e-h1.0::SpiBus are used at the same time, right?

All the other methods of e-h1.0::SpiBus synchronize with the end of the transfer internally and thus they don't need to set write_in_progress. Is that correct?

And related: What if write_in_progress is set when calling e.g. SpiBus::write()? Shouldn't SpiBus::write() call <Self as SpiBus>::flush() before operating on the bus to ensure any FullDuplex transfer from before has actually finished?

Comment on lines +497 to +504
let mut buffer_it = buffer.iter_mut();
let mut bytes_it = bytes.iter();
while let Some(byte) = bytes_it.next() {
self.raw_write(*byte);
while self.spsr.read().spif().bit_is_clear() {}
let mut data = buffer_it.next().unwrap();
*data = self.raw_read();
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to zip the two iterators:

for (byte, data) in bytes.iter().zip(buffer.iter_mut()) {
    self.raw_write(*byte);
    while self.spsr.read().spif().bit_is_clear() {}
    *data = self.raw_read();
}

Comment on lines -98 to -107

#[cfg(any(feature = "atmega8"))]
avr_hal_generic::impl_spi! {
hal: crate::Atmega,
peripheral: crate::pac::SPI,
sclk: port::PB5,
mosi: port::PB3,
miso: port::PB4,
cs: port::PB2,
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! I took the liberty to merge this change already in commit b2be3f9 ("atmega-hal: Drop redundant atmega8 macro invocation"). Please rebase your PR on latest main when you send the next iteration :)

Comment on lines +156 to +157
/// This can be used both with the embedded-hal 0.2 spi::FullDuplex trait, and
/// with the embedded-hal 1.0 spi::SpiBus trait.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can link the traits like this:

Suggested change
/// This can be used both with the embedded-hal 0.2 spi::FullDuplex trait, and
/// with the embedded-hal 1.0 spi::SpiBus trait.
/// This can be used both with the embedded-hal 0.2 [`spi::FullDuplex`] trait, and
/// with the embedded-hal 1.0 [`spi::SpiBus`] trait.
///
/// [`spi::FullDuplex`]: `embedded_hal_v0::spi::FullDuplex`
/// [`spi::SpiBus`]: `embedded_hal::spi::SpiBus`

@Rahix
Copy link
Owner

Rahix commented Mar 10, 2024

@ghismary, thanks a lot for your work on this. @rubend056 picked up your changeset and incorporated my review comments over in #517. We'll continue work there - but I'll make sure you will keep appropriate credit for your work in the merged changes.

@Rahix Rahix closed this Mar 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants