rp-rs / rp-hal

A Rust Embedded-HAL for the rp series microcontrollers
https://crates.io/crates/rp2040-hal
Apache License 2.0
1.46k stars 237 forks source link

I2C reads: executing logic inbetween read bytes. #872

Open PietPtr opened 1 week ago

PietPtr commented 1 week ago

I'm working on a peripheral that returns the length of an I2C message as the second read byte. I have to read exactly that amount of bytes, otherwise the peripheral enters an error state.

My main question is: How am I supposed to execute logic inbetween read bytes, without the library sending the address again?

The specific logic in this case is that after the second byte I know how many bytes I'll need to read. The usual read functions however need a buffer of a size known at compile time and will read that buffer until it's full.

As far as I've found, the transaction_iter function (https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/i2c/controller.rs#L394) looks promising. I've tried to implement an iterator that returns Operations, which first returns a Write Operation with my command data, and then will read byte by byte, and on the second byte determine how much message is left and supply exactly that amount of single-byte Read operations for the rest of the iterator (not the fastest, but speed is not an issue here). However, the Read operation exposed by the i2c-write-iter crate takes an &'a mut [u8] which has caused lifetime issues while implementing the Iterator trait for my iterator.

Is my solution the intended use of the transaction_iter? Is there an example somewhere using transaction_iter with logic inbetween reads (I looked on github, couldn't find any)? Is there some way that I can read a byte, execute code, then decide what to do next (stop the transaction, read the next byte, etc)?

thejpster commented 1 week ago

This might be a good question to ask over on the Rust Embedded matrix channel as it relates to the embedded-hal APIs which we implement.

Which chip are you using? I've not come across one like that before - I've always done one read to get the size and then a second read of the correct size.

PietPtr commented 1 week ago

I'm trying to read/write to an industry specific peripheral, as far as I can see its datasheet isn't available online :sweat_smile:

I've always done one read to get the size and then a second read of the correct size.

What I'm struggling with is how to run a read of a size that I cannot know at compile time. Maybe you have some example code of what you're doing? All read functions that I can find require a &mut [u8] read buffer, which as far as I understand has a fixed size at compile time. Furthermore, those read functions do a call to self.setup() every time, meaning the address is written again, which to my peripheral means a new I2C transaction starts.

I guess if I could have a read function that doesn't call setup my problem would be fixed as well.

thejpster commented 1 week ago

No, that's a slice with a run time size.

  1. Make an array that's always big enough
  2. Read the length
  3. Slice the array to the desired length
  4. Read into the slice

Most devices cope with a restart because what if your MCU crashed mid-transaction?