Skip to content
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

I2C with interrupts #198

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

I2C with interrupts #198

wants to merge 12 commits into from

Conversation

clebi
Copy link
Contributor

@clebi clebi commented Jan 26, 2021

It is a first attempt to implement I2C communications using interrupts. I know that this is not ready for merge but I wanted to have a feedback on my work on I2C and see if I am going into the right direction. Thruthfully, I am not an experienced embedded developer.
As of now, I don't think there is someting in embedded-hal define to support i2c communication with interrupts.

The interrupt function is responsible for computing the state of the I2C device on every interruption. For the moment, calling this function is the responsability of the consumer.

You can find an example of use here: example. The example is using cortex-m-rtic. In this example the interrupt function is called for I2C1_EV_EXTI23 and I2C1_ER.

@clebi clebi changed the title [WIP] I2C with interrupts I2C with interrupts Jan 27, 2021
@clebi clebi marked this pull request as ready for review January 27, 2021 22:09
Copy link
Member

@Sh3Rm4n Sh3Rm4n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you really much for your contribution. This looks promising. Code looks pretty clean, which I like.

Small nit's here and there. I just have a few suggestions about the design for now.
But I think we can improve on it further. For that, I have to read into the code a little more in detail.

I'm not so sure, if it is a good idea to keep internal tx and rx (ring) buffers. I'd rather wish this implementation would be a thin wrapper around the State function, so that the user can get the received byte by themselves and put it into their preferred buffer type, like heapless::spsc::Queue.

This would look something like this:

fn i2cinterrupt() {
    interrupt_type = cx.i2cinstance.get_interrupt_type();
    match interrupt_type {
        Some(TxReady) => cx.i2cinstance.read(),
        _ => ...
    }
}

where get_interrupt_type() is

pub fn get_interrupt_type(&self) -> Option<State> { ... }

or even provide a function like:

pub fn read_on_rx(&self) -> Option<[u8]> {
    if self.state == State::RxReady {
        Some(self.dev.i2c.rxdr.read().rxdata().bits())
    } else {
        None
    }
}

Pretty much all in all a thin wrapper on the I2cInstance itself.

Maybe we could even implement the embedded hal traits (at least Read and Write for it)

Much rambling. I hope it helped, but maybe you have better ideas :)

src/i2cint.rs Show resolved Hide resolved
src/i2cint.rs Outdated
Comment on lines 324 to 326
if isr.nackf().bit() {
self.dev.i2c.icr.write(|w| w.nackcf().bit(true));
self.state = State::TxNack;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer an if expression to change self.state. It is more readable IMO

Suggested change
if isr.nackf().bit() {
self.dev.i2c.icr.write(|w| w.nackcf().bit(true));
self.state = State::TxNack;
self.sate = if isr.nackf().bit() {
self.dev.i2c.icr.write(|w| w.nackcf().bit(true));
State::TxNack

}

/// Debug function which returns the content of the ISR register.
pub fn get_isr(&self) -> u32 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I see, that this is useful, I would like to see that this function returns a type (or list of types e.g. enums) instead of an u32.
What exactly does u32 mean for a potential user of this function? This does not seem clear right now.

///
/// # Errors
/// * `I2cError::DeviceBusy` if the device is already busy.
pub fn stop(&mut self, addr: u8) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When specifying I2C addresses, always note, if the LSB is still part of the address or assumed to be the R/W bit, to void confusion. In this case, it is the latter.

tx_ind: usize,
tx_buf: Option<&'static [u8]>,
rx_ind: usize,
rx_buf: [u8; 256], // for the moment use a static buffer here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the long term, I'd like to see a user configurable slice (e.g. Option<&'static [u8]> as for tx_buf) or something similar, so that the user themselves can choose, which kind of buffer he needs (size wise and type wise).

}

/// I2c1Int provides interface to communicate with i2c devices using interruptions.
pub struct I2cInt<I2C, PINS> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A free function is missing, so that the user can regain access to the pins, if I2c is no longer needed:

stm32f3xx-hal/src/i2c.rs

Lines 201 to 204 in 9e29ec6

/// Releases the I2C peripheral and associated pins
pub fn free(self) -> (I2C, (SCL, SDA)) {
(self.i2c, self.pins)
}

src/i2cint.rs Outdated

/// This function must be called when there is an interruption on i2c device.
/// It will compute the current state based on ISR register and execute work based on the state.
pub fn interrupt(&mut self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interrupt sound like, this function would return the type of the interrupt, which got triggered rather than reaction on the interrupt. I'd look for a more descriptive name. Maybe react_on_interrupt()? Better suggestions are welcome.

src/i2cint.rs Outdated
}

/// Get the state of the device.
pub fn get_tx_state(&self) -> State {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State does include RX and TX states. Therefor this function name does not make sense?

Suggested change
pub fn get_tx_state(&self) -> State {
pub fn get_state(&self) -> State {

Comment on lines +352 to +354
/// # Returns
/// true if busy, false if not.
pub fn is_busy(&mut self) -> bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you already have chosen a perfect name for a boolean function, I find this description redundant:

Suggested change
/// # Returns
/// true if busy, false if not.
pub fn is_busy(&mut self) -> bool {
pub fn is_busy(&mut self) -> bool {

}

/// Enables all interrupts for i2c device.
pub fn enable_interrupts(&mut self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be more flexible in its configuration, I guess 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants