-
Notifications
You must be signed in to change notification settings - Fork 6
spi: Add SPI master implementation #53
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
Conversation
fac51d0
to
15a6a7a
Compare
src/spi.rs
Outdated
@@ -0,0 +1,1008 @@ | |||
//! Serial Peripheral Interface (SPI) | |||
//! | |||
//! This module provides functionality for SPI as both a Master. It |
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.
Remove both
here?
assert_eq!( | ||
config.hardware_cs.enabled(), | ||
PINS::HCS_PRESENT, | ||
"If the hardware cs is enabled in the config, an HCS pin must be present in the given pins" | ||
); |
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.
What if the chip select defaulted to none and instead configured using a builder-like method with both the pin and the corresponding config? Then this sort of check would not be needed?
Correct me if I am wrong here, as a bonus that would then ensure PINS
is always clk
, miso
, mosi
so then we do not need the tuple to emulate variable number of arguments
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.
So are you suggesting we pass the CS pin to a method on the Config struct? or add a builder like method to Spi
to provide the CS pin (e.g. SPI1.spi(...).with_hardware_cs<P>(pin: P)
)?
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.
I did not really think that far to be honest. :)
In my mind I think it reads quite nice as SPI1.spi(pins...).with_hardware_cs<P>(pin: P, config: CsCfg)).finalize(rcc_thing)
. Where .finalize()
or something actually sets up the entire peripheral, unless the cs setup can be done after init?
I do think it might be a bit of a plus to keep cs Pin and cs config together for usability and discoverability. But I do not have any too strong opinions.
Anyways that was just a quick thought without really thinking through the consequences. :)
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.
I'll give it some thought. I do take the point that letting the compiler do the work here would be nicer, but will have to think about what structure makes sense.
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.
I think I would prefer to leave this as is for now. I think the pin allocation pattern in this HAL could use some work in general (it would be nice to replicate what stm32f4xx-hal did), so I would defer this sort of change to during or after that effort.
!read.is_empty() && !write.is_empty(), | ||
"Transfer buffers should not be non-zero length" | ||
); | ||
if self.inner.communication_mode() != CommunicationMode::FullDuplex { |
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.
Since this is only set once in init, how about we make this part of the type? Then we can just not implement the features that should be unavailable? However I suppose the array of operations thing might be harder to do in that case..
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.
Here is a very quick and messy attempt to get that working. A lot of cleanup needed, there is probably a lot of the weird where
clauses and traits that can be simplified and a lot of other things that i have missed :)
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.
I agree it would be nice. I made an effort to do it too, but it requires so much refactoring, that I'm not sure it's worth it. In your example you don't actually remove the runtime checks either, they're just pushed into the trait implementations as unimplemented
calls. You'd need to break the non-blocking trait into separate traits, and it just becomes a lot for minimal gain, in my opinion.
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.
Fair enough :)
src/spi.rs
Outdated
) -> Result<Transaction<TransferInplace<'a, W>, W>, Error> { | ||
assert!( | ||
!words.is_empty(), | ||
"Transfer buffer should not be non-zero length" |
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.
"Transfer buffer should not be non-zero length" | |
"Transfer buffer should not be of zero length" |
src/spi.rs
Outdated
) -> Result<Transaction<Transfer<'a, W>, W>, Error> { | ||
assert!( | ||
!read.is_empty() && !write.is_empty(), | ||
"Transfer buffers should not be non-zero length" |
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.
"Transfer buffers should not be non-zero length" | |
"Transfer buffers should not be of zero length" |
I tested this branch and I found that the following pin is not correctly configured (it exists also on h533) :
|
I haven't observed that. What mode is the SPI being used in (phase/polarity)? I can take a look to see if I can reproduce. |
Sorry, here's the configuration :
|
When using(taken from the w5500): fn read_frame(&mut self, block: u8, address: u16, data: &mut [u8]) -> Result<(), SPI::Error> {
let address_phase = address.to_be_bytes();
let control_phase = block << 3;
self.spi.transaction(&mut [
Operation::Write(&address_phase), // 2 bytes
Operation::Write(&[control_phase]), // 1 byte
Operation::TransferInPlace(data), // 1 byte
])?;
Ok(())
} I get this in my logic analyser:
Here's my config if it is of any value to you: // Initialise the SPI peripheral.
let spi = dp.SPI1.spi(
// Give ownership of the pins
(sck, miso, mosi, hcs),
// Create a config with the hardware chip select given
spi::Config::new(spi::MODE_0)
// Put 1 us idle time between every word sent
.inter_word_delay(0.000001)
// Specify that we use the hardware cs
.hardware_cs(spi::HardwareCS {
// See the docs of the HardwareCSMode to see what the different modes do
mode: spi::HardwareCSMode::FrameTransaction,
// Put 1 us between the CS being asserted and the first clock
assertion_delay: 0.000001,
// Our CS should be high when not active and low when asserted
polarity: spi::Polarity::IdleHigh,
})
.communication_mode(CommunicationMode::FullDuplex),
1.MHz(),
ccdr.peripheral.SPI1,
&ccdr.clocks,
); |
Signals in the post above:
|
In other words, it seems that even when Atleast that seems to be what is going on from what we can tell. |
Thanks for testing! The data in the Rx FIFO should be discarded during the write operation. I'll do some testing to see what might be going on there. |
// Keep emptying Rx FIFO if data to write is longer than data to read | ||
let discard_remainder = transaction.rx_remainder_to_discard(); | ||
if transaction.is_read_complete() && discard_remainder > 0 { | ||
let count = self.discard_rx_fifo(discard_remainder)?; |
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.
Hm, shouldn't this ensure that we discard the received data during a Write?
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.
it should, yes
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.
I'll have to double check that we are not using some old version of your branch, just in case. However I will not have access to @Wratox's files until tomorrow
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.
I reproduced it. It's a bug in checking whether a transaction is complete that does not consider whether there are is any data to discard.
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.
This should be fixed in d099991
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.
That did indeed solve the problem we were having. Thanks!
Ok, I do see this in MODE 2/3 (CPOL=1). So what is happening is that the SPI is relinquishing control of the pins when the SPI is disabled at the end of a transaction, which brings the pins low. When the peripheral is enabled at the beginning of a transaction it then pulls the line high (just before the transaction starts), which is that first edge you (and your logic analyzer) are seeing. The delay is the delay between enabling the peripheral and writing the first byte. When I count the clock pulses subsequent to that edge, I still get the correct number for a byte. Unfortunately, because you're manually controlling the CS line, when the clock is brought high when the SPI peripheral is enabled, it is indistinguishable from an extra clock pulse. When I give the SPI peripheral control of the CS line (see The AFCNTR pin controls whether the SPI relinquishes control of the pin when the SPI is disabled. Setting this bit holds the clock line high all the time and the extra clock pulse is not an issue. I've set this bit now during initial configuration (see a794a62) . Given that the pins are passed to the peripheral during initialization, there is no currently supported use case to allow sharing of the pins or reason for the SPI peripheral to relinquish control of the pins. |
Excellent, thanks @astapleton . I found the same thing in parallel but was late seeing your message. |
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.
This looks good to me now! Very nice :)
This adds a master mode implementation for the SPI peripheral.
The driver is split such that the core logic is implemented in spi.rs, and the rest is contained within a submodule: