Comments (10)
Are there any developments on the subject or has it been decided not to build a shared public interface for the non-blocking i2c?
from embedded-hal.
I'd prefer statically checking validity of operation. Like this:
pub trait Send {
type Error;
fn send(&mut self, byte: u8) -> nb::Result<(), Error<Self::Error>>;
}
pub trait Receive {
type Error;
fn receive(&mut self, send_ack: bool) -> nb::Result<u8, Error<Self::Error>>;
}
pub trait Master {
type Sender: Send;
type Receiver: Receive;
type Error;
fn send_start(&mut self, address: u8) -> nb::Result<&mut Self::Sender, Error<Self::Error>>;
fn receive_start(&mut self, address: u8) -> nb::Result<&mut Self::Receiver, Error<Self::Error>>;
}
from embedded-hal.
I'm not very experienced with this, but I'm wondering, why there is such a major distinction between this at all and why blocking is the default here.
Because it's much easier to implement.
I'd assume, that most μCs have special hardware for I2C, which makes non-blocking the more natural choice. I also think, that a non-blocking implementation can easily be changed into a blocking one.
There're different kinds of "non-blocking". There's the kind where you basically just setup the data, point the peripheral to it and say do it and then just check whether the operation is done (i.e. poll the status) or receive a notification (i.e. an interrupt) that's how DMA works.
Problem: You may or may not be able to set up multiple operations in flight so you have to be able to reject a new transaction until the previous one has finished. And the caller needs to be able to deal with that situation.
Then there's a "software" implementation of the above. Problem here is: You need to have an internal buffer to handle store the in-flight data and you need to peridiocally poll the implementation (or use interrupts which really can not be done in a generic fashion internally) to make progress by spoon-feeding the next piece of data into the peripheral.
Problems: Buffers are complicated to implement, especially on lowlier MCUs without atomic operations and sizing is always going to be a big fun. Also you usually cannot stretch the clock forever so you need to poll frequently to make progress.
It's tricky to model an interface that works for both cases, and allows for (and actually requires) regular polling to check the status or make progress plus of course the application using that interface needs to be able to handle with errors which happen asynchronously.
from embedded-hal.
I think, we are talking about a different level of asynchronicity here.
I don't think so.
I'm only talking about not (busy-)waiting for the acknowledgement so that this time can be used for computation.
Sure. Depending on how you do your I2C communication you will still be able to do other computation.
Indeed, this is not really nonblocking, because the software would still have to block when there is an ongoing I2C transfer and you want to initialize a second transfer. But it would at least speed up patterns like I2C transfer - computation - I2C transfer - computation, especially if utilizing DMA hardware.
It could be truly non-blocking in this case, but somehow you need to manage the data and DMA accesses and that is simply not easy to abstract over generically.
In contrast, I understood the original proposal like you need a FIFO and a distinct thread that starts the next transfer as soon as the previous one has finished.
You absolutely need an software arbiter taking care of the bus accesses and feeding the data into the peripheral, just as I mentioned before. A separate task is a way of doing that and indeed how most embedded OSes do that but it's not really applicable here.
There might easy specialized implementations with a trivial interface for some MCUs but again we're looking for a universally useful abstraction here.
That is what I meant by driver, too.
Please note that there's literally no use in having a super driver that efficiently uses special traits for which there's exactly the two mandatory proving implementations. You might as well create and use a MCU specific optimized implementation of that driver then.
I'm convinced there's a way to provide so an interface which is easy enough to use and can be universally implemented but it will require quite a bit of work and trial-and-error to come up with something that is acceptable for inclusion here.
Please feel free to take on this task and come up a concrete proposal for such an interface. If you need help or testers, feel free to ping us on matrix or here and I'm sure there're plenty of people glad to help and figure this out with you.
from embedded-hal.
I'm not very experienced with this, but I'm wondering, why there is such a major distinction between this at all and why blocking is the default here.
I'd assume, that most μCs have special hardware for I2C, which makes non-blocking the more natural choice. I also think, that a non-blocking implementation can easily be changed into a blocking one.
Idea
So my naive idea of an I2C trait would be something like this:
We have a module embedded_hal::i2c
which provides the following traits:
read/write
(orsend/receive
) might block or not, depending on the implementation. The only guarantee is, that the transaction was started. This function might block when there is an incomplete previous transmission "in the pipe".blocking_read/blocking_write
guarantee that the transaction is complete when the function returns.
A HAL implementation could then for example implement a non-blocking variant and implement the blocking variant by using busy-waiting or - if there can only be a blocking implementation - use the same function for both of it. I seems like this is the way stm32f1xx_hal implements it.
Driver implementations would then be able to choose the according function for their implementation and save some (precious) μC cycles which are currently wasted by busy-waiting.
Problems
This would be a breaking change. But don't we use semantic versioning for that very reason?
Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable
from embedded-hal.
Thanks for the good summary 👍 .
However, I still think, that a generic read/write
trait that doesn't give specific guarantees would be nice and in addition to this a set of specialized traits (blocking_read
, read_to_buffer
, something with async/await...) for those drivers that require more fine grained control.
At the moment, every driver implementation requires the I2C object to implement embedded_hal::blocking::i2c
, even though they might just send a single byte to a sensor.
from embedded-hal.
At the moment, every driver implementation requires the I2C object to implement embedded_hal::blocking::i2c
Not sure I follow. When you say "driver", are you talking about the HAL impls? We're usally using the word driver for crate that takes an impl of the generic traits and converts them into a an API usable by applications. Of course the HAL impl needs to implement the whole interface at once which is why it has to be simple and generic.
even though they might just send a single byte to a sensor.
This does not really work for I2C because you need to deal with the addressing and the conditions, which the original proposal in this issue did by exposing a low level interface.
from embedded-hal.
This does not really work for I2C because you need to deal with the addressing and the conditions,
which the original proposal in this issue did by exposing a low level interface.
I think, we are talking about a different level of asynchronicity here.
I'm only talking about not (busy-)waiting for the acknowledgement so that this time can be used for computation.
Indeed, this is not really nonblocking, because the software would still have to block when there is an ongoing I2C transfer and you want to initialize a second transfer. But it would at least speed up patterns like I2C transfer - computation - I2C transfer - computation
, especially if utilizing DMA hardware.
In contrast, I understood the original proposal like you need a FIFO and a distinct thread that starts the next transfer as soon as the previous one has finished.
This sounds a lot like it requires async/await and I think, this a bit too high-level for the embedded-hal and would better fit in a specialized crate. But in that case, do you really need to control the start/stop bits from a driver point of view? I'd have guessed the current abstractions (like write(&mut self, addr: u8, bytes: &[u8])
) are sufficient.
Not sure I follow. When you say "driver", are you talking about the HAL impls? We're usally using the word driver for crate that takes an impl of the generic traits and converts them into a an API usable by applications.
That is what I meant by driver, too.
from embedded-hal.
Would replacing all busy_wait
loops with an async suspension point fix the issue? I don't think you need any buffering as the future would require unique (&mut
) access to the I2C
struct, and the state is encoded by the async/await transform.
from embedded-hal.
Will this still be relevant with the embedded-hal-async
crate? https://docs.rs/embedded-hal-async/0.1.0-alpha.3/embedded_hal_async/i2c/index.html It seems like this removes the need for anything nb
-based
from embedded-hal.
Related Issues (20)
- Why do all `Error` traits need to implement `Debug`? HOT 4
- Document SemVer hazards of `spi::Operation::DelayNs` HOT 1
- Add `discard` to `BufRead`. HOT 5
- spi: specify expectations regarding peripheral state between transactions HOT 2
- embedded-io: API stability and 1.0.0 release
- Zero-length I2c transfers HOT 3
- embedded-hal-bus shared i2c usage HOT 5
- embedded-hal-bus does not impliment embedded-hal-async traits, despite claiming to HOT 5
- The SPI sharing utilities are broken for fallible chipselect pins HOT 3
- guidance on error handling/propagation of drivers HOT 3
- Handling of parity and framing errors in embedded-io / embedded-io-async
- CAN FD support HOT 1
- unable to return error with embedded hal i2c example HOT 1
- HAL-Bus SPI Exclusive Device Unsatisfied Traits HOT 5
- i2c: Merging of consecutive operations in transaction contract
- SpiDevice's interface can't be used for streaming transactions HOT 6
- SpiDevice implementations in embedded-hal-bus don't provide a way to use active-high chip select HOT 3
- Create an I3C Trait
- Read not implemented for &mut [u8] HOT 1
- README: Links to LICENSE-APACHE and LICENSE-MIT are not found
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from embedded-hal.