-
Notifications
You must be signed in to change notification settings - Fork 230
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,7 +1,8 @@ | ||||||||||||||||||
//! SPI Implementation | ||||||||||||||||||
use crate::port; | ||||||||||||||||||
use core::cmp::Ordering; | ||||||||||||||||||
use core::marker::PhantomData; | ||||||||||||||||||
use embedded_hal_v0::spi; | ||||||||||||||||||
use embedded_hal::spi; | ||||||||||||||||||
|
||||||||||||||||||
/// Oscillator Clock Frequency division options. | ||||||||||||||||||
/// | ||||||||||||||||||
|
@@ -75,6 +76,10 @@ pub trait SpiOps<H, SCLK, MOSI, MISO, CS> { | |||||||||||||||||
fn raw_check_iflag(&self) -> bool; | ||||||||||||||||||
fn raw_read(&self) -> u8; | ||||||||||||||||||
fn raw_write(&mut self, byte: u8); | ||||||||||||||||||
|
||||||||||||||||||
fn raw_read_buf(&self, buf: &mut [u8]); | ||||||||||||||||||
fn raw_write_buf(&mut self, buf: &[u8]); | ||||||||||||||||||
fn raw_read_write_buf(&mut self, buffer: &mut [u8], bytes: &[u8]); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Wrapper for the CS pin | ||||||||||||||||||
|
@@ -106,19 +111,50 @@ impl<CSPIN: port::PinOps> embedded_hal_v0::digital::v2::StatefulOutputPin for Ch | |||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<CSPIN: port::PinOps> embedded_hal_v0::digital::v2::ToggleableOutputPin for ChipSelectPin<CSPIN> { | ||||||||||||||||||
impl<CSPIN: port::PinOps> embedded_hal_v0::digital::v2::ToggleableOutputPin | ||||||||||||||||||
for ChipSelectPin<CSPIN> | ||||||||||||||||||
{ | ||||||||||||||||||
type Error = core::convert::Infallible; | ||||||||||||||||||
fn toggle(&mut self) -> Result<(), Self::Error> { | ||||||||||||||||||
self.0.toggle(); | ||||||||||||||||||
Ok(()) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<CSPIN: port::PinOps> embedded_hal::digital::ErrorType for ChipSelectPin<CSPIN> { | ||||||||||||||||||
type Error = core::convert::Infallible; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<CSPIN: port::PinOps> embedded_hal::digital::OutputPin for ChipSelectPin<CSPIN> { | ||||||||||||||||||
fn set_high(&mut self) -> Result<(), Self::Error> { | ||||||||||||||||||
self.0.set_high(); | ||||||||||||||||||
Ok(()) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fn set_low(&mut self) -> Result<(), Self::Error> { | ||||||||||||||||||
self.0.set_low(); | ||||||||||||||||||
Ok(()) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<CSPIN: port::PinOps> embedded_hal::digital::StatefulOutputPin for ChipSelectPin<CSPIN> { | ||||||||||||||||||
fn is_set_high(&mut self) -> Result<bool, Self::Error> { | ||||||||||||||||||
Ok(self.0.is_set_high()) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fn is_set_low(&mut self) -> Result<bool, Self::Error> { | ||||||||||||||||||
Ok(self.0.is_set_low()) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Behavior for a SPI interface. | ||||||||||||||||||
/// | ||||||||||||||||||
/// Stores the SPI peripheral for register access. In addition, it takes | ||||||||||||||||||
/// ownership of the MOSI and MISO pins to ensure they are in the correct mode. | ||||||||||||||||||
/// Instantiate with the `new` method. | ||||||||||||||||||
/// | ||||||||||||||||||
/// This can be used both with the embedded-hal 0.2 spi::FullDuplex trait, and | ||||||||||||||||||
/// with the embedded-hal 1.0 spi::SpiBus trait. | ||||||||||||||||||
pub struct Spi<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> { | ||||||||||||||||||
p: SPI, | ||||||||||||||||||
sclk: port::Pin<port::mode::Output, SCLKPIN>, | ||||||||||||||||||
|
@@ -241,7 +277,7 @@ where | |||||||||||||||||
/// FullDuplex trait implementation, allowing this struct to be provided to | ||||||||||||||||||
/// drivers that require it for operation. Only 8-bit word size is supported | ||||||||||||||||||
/// for now. | ||||||||||||||||||
impl<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> spi::FullDuplex<u8> | ||||||||||||||||||
impl<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> embedded_hal_v0::spi::FullDuplex<u8> | ||||||||||||||||||
for Spi<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> | ||||||||||||||||||
where | ||||||||||||||||||
SPI: SpiOps<H, SCLKPIN, MOSIPIN, MISOPIN, CSPIN>, | ||||||||||||||||||
|
@@ -290,6 +326,70 @@ where | |||||||||||||||||
{ | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> embedded_hal::spi::ErrorType | ||||||||||||||||||
for Spi<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> | ||||||||||||||||||
where | ||||||||||||||||||
SPI: SpiOps<H, SCLKPIN, MOSIPIN, MISOPIN, CSPIN>, | ||||||||||||||||||
SCLKPIN: port::PinOps, | ||||||||||||||||||
MOSIPIN: port::PinOps, | ||||||||||||||||||
MISOPIN: port::PinOps, | ||||||||||||||||||
CSPIN: port::PinOps, | ||||||||||||||||||
{ | ||||||||||||||||||
type Error = core::convert::Infallible; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> embedded_hal::spi::SpiBus | ||||||||||||||||||
for Spi<H, SPI, SCLKPIN, MOSIPIN, MISOPIN, CSPIN> | ||||||||||||||||||
where | ||||||||||||||||||
SPI: SpiOps<H, SCLKPIN, MOSIPIN, MISOPIN, CSPIN>, | ||||||||||||||||||
SCLKPIN: port::PinOps, | ||||||||||||||||||
MOSIPIN: port::PinOps, | ||||||||||||||||||
MISOPIN: port::PinOps, | ||||||||||||||||||
CSPIN: port::PinOps, | ||||||||||||||||||
{ | ||||||||||||||||||
fn read(&mut self, read: &mut [u8]) -> Result<(), Self::Error> { | ||||||||||||||||||
Ok(self.p.raw_read_buf(read)) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fn write(&mut self, write: &[u8]) -> Result<(), Self::Error> { | ||||||||||||||||||
Ok(self.p.raw_write_buf(write)) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { | ||||||||||||||||||
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); | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+359
to
+371
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); |
||||||||||||||||||
Ok(()) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fn transfer_in_place(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> { | ||||||||||||||||||
let write = unsafe { | ||||||||||||||||||
let p = buffer.as_ptr(); | ||||||||||||||||||
core::slice::from_raw_parts(p, buffer.len()) | ||||||||||||||||||
}; | ||||||||||||||||||
self.p.raw_read_write_buf(buffer, write); | ||||||||||||||||||
Comment on lines
+376
to
+380
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I think we have to provide a separate low-level |
||||||||||||||||||
Ok(()) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fn flush(&mut self) -> Result<(), Self::Error> { | ||||||||||||||||||
if self.write_in_progress { | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 All the other methods of And related: What if |
||||||||||||||||||
while !self.p.raw_check_iflag() {} | ||||||||||||||||||
self.write_in_progress = false; | ||||||||||||||||||
} | ||||||||||||||||||
Ok(()) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Implement traits for a SPI interface | ||||||||||||||||||
#[macro_export] | ||||||||||||||||||
macro_rules! impl_spi { | ||||||||||||||||||
|
@@ -370,6 +470,39 @@ macro_rules! impl_spi { | |||||||||||||||||
fn raw_write(&mut self, byte: u8) { | ||||||||||||||||||
self.spdr.write(|w| unsafe { w.bits(byte) }); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// 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()) | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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();
}
} |
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Write n bytes to the data register, n being the size of the | ||||||||||||||||||
/// given buffer. | ||||||||||||||||||
fn raw_write_buf(&mut self, buf: &[u8]) { | ||||||||||||||||||
buf.iter().for_each(|b| { | ||||||||||||||||||
self.spdr.write(|w| unsafe { w.bits(*b) }); | ||||||||||||||||||
while self.spsr.read().spif().bit_is_clear() {} | ||||||||||||||||||
}) | ||||||||||||||||||
Comment on lines
+483
to
+486
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer a plain for loop over
Suggested change
|
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Read and write n bytes at the same time, n being the size of | ||||||||||||||||||
/// the given buffers. | ||||||||||||||||||
/// | ||||||||||||||||||
/// <div class="warning">The input and output buffers must have | ||||||||||||||||||
/// the same size</div> | ||||||||||||||||||
fn raw_read_write_buf(&mut self, buffer: &mut [u8], bytes: &[u8]) { | ||||||||||||||||||
assert_eq!(buffer.len(), bytes.len()); | ||||||||||||||||||
|
||||||||||||||||||
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(); | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+497
to
+504
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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();
} |
||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
}; | ||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/*! | ||
* Example of the SPI bus, by looping back output to input, but with the | ||
* feedback function not being aware of the SPI part of `avr-hal` and using | ||
* only the `embedded-hal` and `embedded-hal-bus` traits. | ||
* | ||
* This example demonstrates how to set up a SPI interface and communicate | ||
* over it. The physical hardware configuation consists of connecting a | ||
* jumper directly from pin `D11` to pin `D12`. | ||
* | ||
* If done correctly, you should see it output the line `data: 15` repeatedly (aka 0b00001111). If | ||
* the output you see is `data: 255`, you may need to check your jumper. | ||
* | ||
* Connections: | ||
* - `D11` connected directly to `D12` (loop MOSI to MISO) | ||
*/ | ||
#![no_std] | ||
#![no_main] | ||
|
||
use arduino_hal::hal::port::{PD0, PD1}; | ||
use arduino_hal::pac::USART0; | ||
use arduino_hal::port::{ | ||
mode::{Input, Output}, | ||
Pin, | ||
}; | ||
use arduino_hal::prelude::*; | ||
use arduino_hal::spi; | ||
use arduino_hal::Usart; | ||
use panic_halt as _; | ||
|
||
#[arduino_hal::entry] | ||
fn main() -> ! { | ||
let dp = arduino_hal::Peripherals::take().unwrap(); | ||
let pins = arduino_hal::pins!(dp); | ||
|
||
// Set up the serial interface for text output | ||
let mut serial = arduino_hal::default_serial!(dp, pins, 57600); | ||
|
||
// Create SPI interface. | ||
let (spi_bus, cs_pin) = arduino_hal::Spi::new( | ||
dp.SPI, | ||
pins.d13.into_output(), | ||
pins.d11.into_output(), | ||
pins.d12.into_pull_up_input(), | ||
pins.d10.into_output(), | ||
spi::Settings::default(), | ||
); | ||
let mut spi = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi_bus, cs_pin); | ||
|
||
feedback(&mut spi, &mut serial); | ||
} | ||
|
||
fn feedback( | ||
spi: &mut impl embedded_hal::spi::SpiDevice, | ||
serial: &mut Usart<USART0, Pin<Input, PD0>, Pin<Output, PD1>>, | ||
) -> ! { | ||
loop { | ||
// Send a byte and read data, which should be the same because MISO is | ||
// connected to MOSI | ||
let write = [15u8]; | ||
let mut read = [255u8; 1]; | ||
spi.transfer(&mut read, &write).unwrap(); | ||
|
||
ufmt::uwriteln!(serial, "data: {}\r", read[0]).unwrap_infallible(); | ||
arduino_hal::delay_ms(1000); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,12 @@ | |
use crate::port; | ||
pub use avr_hal_generic::spi::*; | ||
|
||
#[cfg(any(feature = "atmega128a", feature = "atmega1280", feature = "atmega2560", feature = "atmega32u4"))] | ||
#[cfg(any( | ||
feature = "atmega128a", | ||
feature = "atmega1280", | ||
feature = "atmega2560", | ||
feature = "atmega32u4" | ||
))] | ||
pub type Spi = avr_hal_generic::spi::Spi< | ||
crate::Atmega, | ||
crate::pac::SPI, | ||
|
@@ -11,7 +16,12 @@ pub type Spi = avr_hal_generic::spi::Spi< | |
port::PB3, | ||
port::PB0, | ||
>; | ||
#[cfg(any(feature = "atmega128a", feature = "atmega1280", feature = "atmega2560", feature = "atmega32u4"))] | ||
#[cfg(any( | ||
feature = "atmega128a", | ||
feature = "atmega1280", | ||
feature = "atmega2560", | ||
feature = "atmega32u4" | ||
))] | ||
avr_hal_generic::impl_spi! { | ||
hal: crate::Atmega, | ||
peripheral: crate::pac::SPI, | ||
|
@@ -21,7 +31,12 @@ avr_hal_generic::impl_spi! { | |
cs: port::PB0, | ||
} | ||
|
||
#[cfg(any(feature = "atmega168", feature = "atmega328p", feature = "atmega48p",))] | ||
#[cfg(any( | ||
feature = "atmega168", | ||
feature = "atmega328p", | ||
feature = "atmega48p", | ||
feature = "atmega8" | ||
))] | ||
pub type Spi = avr_hal_generic::spi::Spi< | ||
crate::Atmega, | ||
crate::pac::SPI, | ||
|
@@ -30,7 +45,12 @@ pub type Spi = avr_hal_generic::spi::Spi< | |
port::PB4, | ||
port::PB2, | ||
>; | ||
#[cfg(any(feature = "atmega168", feature = "atmega328p", feature = "atmega48p",))] | ||
#[cfg(any( | ||
feature = "atmega168", | ||
feature = "atmega328p", | ||
feature = "atmega48p", | ||
feature = "atmega8" | ||
))] | ||
avr_hal_generic::impl_spi! { | ||
hal: crate::Atmega, | ||
peripheral: crate::pac::SPI, | ||
|
@@ -95,13 +115,3 @@ avr_hal_generic::impl_spi! { | |
miso: port::PB6, | ||
cs: port::PB4, | ||
} | ||
|
||
#[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, | ||
} | ||
Comment on lines
-98
to
-107
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
There was a problem hiding this comment.
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: