-
Notifications
You must be signed in to change notification settings - Fork 221
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
Added transactional SPI interface #191
Added transactional SPI interface #191
Conversation
r? @therealprof (rust_highfive has picked a reviewer for you, use r? to override) |
bors try |
tryBuild succeeded |
Thank you for this! So far looks good to me. The only point where I am not completely sold is the single array for This works fine for the Linux kernel (and probably any situation with a kernel with user / kernel space separation) due to forced copying and doing it at different timepoints as we discussed in rust-embedded/linux-embedded-hal#33. Additionally it saves some memory. However, I also see the appeal of having separate arrays for tx/rx so that the tx array can be immutable. However, this is only a thought and a very particular situation. I have not actually encountered this and it is probable that in this hypothetical situation I cannot just pipe the data through but some kind of manipulation is required and then it wouldn't matter anymore as I would need to copy it. Can somebody think of such a situation where the data copy would actually be relevant? Something like DMA-based SPI implementation or so? Anyway, thanks for keeping at it! I look forward to the transaction-based approach. It is already a lot of fun to use in embedded-hal-mock :) |
thanks for the feedback!
|
|
if you did it twice, yeah, but most spi interactions tend to be command then response / data, for which
afaik (and i have used it reasonably extensively) the approach used in this PR does not cause any of these issues. |
Is it? Maybe for async-style interactions but from drivers I have written, for example DS3234 and BMI160 expose a simple write/read interface, similar to I2C's so this would not be an option.
Yes, I was trying to highlight a benefit of this new approach in contrast to the current |
AFAIK your interface methods here and here are functionally equivalent to the transactional version here, except the transactional version supports read/write over slices (without allocation) as well as single bytes (and I manage CS elsewhere).
Where the transfer is simplex, the same thing can be achieved using multiple operations as illustrated in the previous paragraph, and the immutable tx data creates a whole pile of almost insurmountable problems so, I do not think it preferable (or even viable) at this stage.
Ahh, I completely missed that it was the return that was significant here. I am not opposed to passing the |
I am sorry I think we are having a misunderstanding. I do not understand how this would be equivalent. I call
This would totally work fine for me.
Since some data is sent twice. What am I missing?
Ok, if immutable tx data would cause problems then I am fine with the current approach.
Again just a misunderstanding. I wanted to highlight a benefit from the transactional approach (this PR) as-is. I was referring to the transactional approach when I said "this new approach", sorry I was not clear enough. |
Aha, thank you for the clarification and for baring with me there! I appreciate your effort and had totally misunderstood 🙃
At the moment you have something like: fn read_reg(&mut self, reg: u8) -> Result<u8, Error> {
let mut data: [u8; 2] = { reg, 0 };
let ops = [spi::Operation::Transfer(&mut data)]; // Send reg, read value
...
Ok(data[1])
} However, you're basically writing the register, throwing away the first byte response, then reading the next byte, which could also be represented as something like: fn read_reg(&mut self, reg: u8) -> Result<u8, Error> {
let mut data = [0u8; 1];
let ops = [
spi::Operation::Write(&[reg]), // Send reg
spi::Operation::Transfer(&mut data), // Read value
]
...
Ok(data[0])
} In this instance the prefix is only one byte, and you're only returning one byte, so the As a more complex example, with 16-bit LE addresses and multi-byte reads I tend to use something like: fn read_reg(&mut self, reg: u16, values: &mut[u8]) -> Result<(), Error> {
let prefix = [
reg as u8,
(reg >> 8) as u8,
];
let ops = [
spi::Operation::Write(&prefix), // Send reg
spi::Operation::Transfer(values), // Read value(s)
]
...
Ok(data[0])
} Which can be extended to support arbitrary length immutable prefixes if it's useful, and afaik all of these approaches are equivalent, does that help clarify? |
Aah! It does. I got it now, thank you. I'm all for the merge! :) |
d9b7a86
to
449f091
Compare
f90e6c1
to
d0efa3c
Compare
how long did it take me to notice the rebase had added |
Time to land the breaking changes then? |
See: rust-embedded#178 for work to get here
d0efa3c
to
fbff647
Compare
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.
Other than my comment, looks good to me. thanks!
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.
Looks good to me, thanks!
@therealprof would you also agree to merging this?
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.
LGTM, too. Let's land this!
bors r+ |
@ryankurte And as usual the CHANGELOG entry was forgotten (and also as usual that was not noticed until after the fact 😅). Would you mind adding one? |
257: Added SPI transactional interface to changelog r=therealprof a=ryankurte Missing from #191 cc. @therealprof Co-authored-by: ryan <ryan@kurte.nz>
223: Add transactional I2C interface r=therealprof a=eldruin An example where a transactional I2C interface would be an improvement is when facing the problem of sending an array of data where the first item is the destination address. At the moment this requires copying the data into a bigger array and assigning the first item to the address. With a transactional I2C two `write` operations could be chained into one transaction so that it is possible to send the address and data without an extra copy. This is specially problematic if the data length is unknown e.g. because it comes from the user. You can see the problem [here in the eeprom24x driver](https://github.com/eldruin/eeprom24x-rs/blob/f75770c6fc6dd8d591e3695908d5ef4ce8642566/src/eeprom24x.rs#L220). In this case I am lucky enough to have the page upper limit so that the copy workaround is possible. With this PR a bunch of code and macros could be replaced by doing something similar to this: ```rust let user_data = [0x12, 0x34, ...]; // defined somewhere else. length may be unknown. let target_address = 0xAB; let mut ops = [ Operation::Write(&[target_address]), Operation::Write(&user_data), ]; i2cdev.try_exec(DEV_ADDR, &ops) ``` I added a PoC [here in linux-embedded-hal](https://github.com/eldruin/linux-embedded-hal/blob/7512dbcc09faa58344a5058baab8826a0e0d0160/src/lib.rs#L211) including [an example](https://github.com/eldruin/linux-embedded-hal/blob/transactional-i2c/examples/transactional-i2c.rs) of a driver where a classical combined write/read is performed through the transactional interface. Note: A default implementation of the `Transactional` trait like in #191 is not possible because STOPs would always be sent after each operation. What is possible is to do is the other way around. This includes an implementation of the `Write`, `Read` and `WriteRead` traits for `Transactional` implementers. This is based on previous work from #178 by @ryankurte and it is similar to #191 TODO: - [x] Add changelog entry Co-authored-by: Diego Barrios Romero <eldruin@gmail.com>
35: Add transactional SPI r=ryankurte a=ryankurte Implementation of transactional SPI - replaces #33 - blocked on: - ~rust-embedded/embedded-hal#191 - ~#34 ~Important: remove patch and bump hal version before merging~ Co-authored-by: ryan <ryan@kurte.nz> Co-authored-by: Ryan <ryankurte@users.noreply.github.com>
This PR adds a transactional interface for SPI devices (#94), compatible with linux spidev.
Split from #178 as I believe this is complete and useful, but that there is more experimentation required before (if?) the I2C component is landed, check there for previous reviews / discussion.
Demonstrated in:
Notes:
Operation::Transfer
uses one buffer to allow polyfill using the existingTransfer
trait (with the convenient side effect of reducing memory requirements)W
has a static bound as it should only ever be a type with static lifetime (u8, u16 etc., not a reference), and to differentiate this from'a
which is the lifetime of the data in the object and only bound to the functionexec(.., &mut [Operation])
is chosen overexec<O: AsMut<[Operation]>(..)
as the latter imposes limits on generic types using this trait (which i ran into, see E0038)cc. @rust-embedded/hal folks, @eldruin, @RandomInsano, @Rahix, @austinglaser for opinions / review