1025: Implement I2C timeouts, second attempt r=Dirbaio a=chemicstry

This is an alterrnative to  as discussed there.

Timeouts are implemented using suggested `check_timeout: impl Fn() -> Result<(), Error>` function, which does not depend on `embassy-time` by default and is a noop for regular I2C.

This also adds `time` feature like in `embassy-nrf` to enable `embassy-time` dependencies. While at it, I also gated some other peripherals that depend on `embassy-time`, notably `usb` and (partially) `subghz`.

`TimeoutI2c` is currently only implemented for i2cv1, because i2cv2 has additional complications:
- Async methods still use a lot of busy waiting code in between DMA transfers, so simple `with_timeout()` will not work and it will have to use both types of timeouts. It could probably be rewritten to replace busy waits with IRQs, but that's outside the scope of this PR.
- I2C definition `I2c<'d, T, TXDMA, RXDMA>` is different from i2cv1 `I2c<'d, T>` making it hard to share single `TimeoutI2c` wrapper. A couple of options here:
  - Duplicate `TimeoutI2c` code
  - Add dummy `TXDMA`, `RXDMA` types to i2cv1 considering that in the future it should also support DMA

Co-authored-by: chemicstry <chemicstry@gmail.com>
This commit is contained in:
bors[bot] 2022-10-26 19:34:43 +00:00 committed by GitHub
commit 01e23bf9dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 508 additions and 81 deletions
examples
stm32f4/src/bin
stm32h7/src/bin

View file

@ -0,0 +1,45 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::dma::NoDma;
use embassy_stm32::i2c::{Error, I2c, TimeoutI2c};
use embassy_stm32::interrupt;
use embassy_stm32::time::Hertz;
use embassy_time::Duration;
use {defmt_rtt as _, panic_probe as _};
const ADDRESS: u8 = 0x5F;
const WHOAMI: u8 = 0x0F;
#[embassy_executor::main]
async fn main(_spawner: Spawner) -> ! {
info!("Hello world!");
let p = embassy_stm32::init(Default::default());
let irq = interrupt::take!(I2C2_EV);
let mut i2c = I2c::new(
p.I2C2,
p.PB10,
p.PB11,
irq,
NoDma,
NoDma,
Hertz(100_000),
Default::default(),
);
// I2C bus can freeze if SCL line is shorted or due to a broken device that clock stretches for too long.
// TimeoutI2c allows recovering from such errors by throwing `Error::Timeout` after a given delay.
let mut timeout_i2c = TimeoutI2c::new(&mut i2c, Duration::from_millis(1000));
let mut data = [0u8; 1];
match timeout_i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data) {
Ok(()) => info!("Whoami: {}", data[0]),
Err(Error::Timeout) => error!("Operation timed out"),
Err(e) => error!("I2c Error: {:?}", e),
}
}

View file

@ -0,0 +1,44 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::i2c::{Error, I2c, TimeoutI2c};
use embassy_stm32::interrupt;
use embassy_stm32::time::Hertz;
use embassy_time::Duration;
use {defmt_rtt as _, panic_probe as _};
const ADDRESS: u8 = 0x5F;
const WHOAMI: u8 = 0x0F;
#[embassy_executor::main]
async fn main(_spawner: Spawner) -> ! {
info!("Hello world!");
let p = embassy_stm32::init(Default::default());
let irq = interrupt::take!(I2C2_EV);
let mut i2c = I2c::new(
p.I2C2,
p.PB10,
p.PB11,
irq,
p.DMA1_CH4,
p.DMA1_CH5,
Hertz(100_000),
Default::default(),
);
// I2C bus can freeze if SCL line is shorted or due to a broken device that clock stretches for too long.
// TimeoutI2c allows recovering from such errors by throwing `Error::Timeout` after a given delay.
let mut timeout_i2c = TimeoutI2c::new(&mut i2c, Duration::from_millis(1000));
let mut data = [0u8; 1];
match timeout_i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data) {
Ok(()) => info!("Whoami: {}", data[0]),
Err(Error::Timeout) => error!("Operation timed out"),
Err(e) => error!("I2c Error: {:?}", e),
}
}