From 990d964a93e9624cc2da8d564f8ce831ea0bfa28 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Mon, 8 Nov 2021 12:23:28 +0000 Subject: [PATCH] Implement peripheral support for i2c and an advanced example (#162) * Implement peripheral support for i2c and an advanced example for the pico board. * Simplify i2c peripheral bootstrap and add a "free" function to allow switching modes. * Set dependency to futures & nostd_async to specific version/revision. * move enum & struct to the start of the file * Add a bit of documentation to the pico_i2c_controller_peripheral demo. * Migrate to pico_i2c_controller_peripheral to embassy & simplify the peripheral support nostd_async is broken since last stable roll out. The pico_i2c_controller_peripheral is being migrated to use embassy's executor. The Controller Async API is now aligned with embassy's traits to facilitate integration. The peripheral no longer require async to run and now appears as an event iterator. Embassy's support relies on unstable features (generic_associated_types and type_alias_impl_traits) and is therefore gated behind the `embassy-traits` feature flag. * make futures & embassy optional for the pico board too * Pin embassy to a specific rev. * Impl embassy_traits::i2c::WriteIter & enable unlimited transfer size on i2c * Applies comment suggestion from @9names for the advanced i2c example. Co-authored-by: 9names <60134748+9names@users.noreply.github.com> * use `I2C block` instead of `IP`. * Fix formatting (unnecessary space at end of line) * Enhance explanation for why `rd_req()` is not cleared in `Iterator::next`'s implementation. Co-authored-by: 9names <60134748+9names@users.noreply.github.com> --- boards/pico/Cargo.toml | 19 + .../controller.rs | 47 ++ .../pico_i2c_controller_peripheral/main.rs | 123 +++++ .../peripheral.rs | 95 ++++ rp2040-hal/Cargo.toml | 12 + rp2040-hal/src/i2c.rs | 488 ++++-------------- rp2040-hal/src/i2c/controller.rs | 300 +++++++++++ .../src/i2c/controller/embassy_support.rs | 218 ++++++++ rp2040-hal/src/i2c/peripheral.rs | 200 +++++++ rp2040-hal/src/lib.rs | 2 + 10 files changed, 1115 insertions(+), 389 deletions(-) create mode 100644 boards/pico/examples/pico_i2c_controller_peripheral/controller.rs create mode 100644 boards/pico/examples/pico_i2c_controller_peripheral/main.rs create mode 100644 boards/pico/examples/pico_i2c_controller_peripheral/peripheral.rs create mode 100644 rp2040-hal/src/i2c/controller.rs create mode 100644 rp2040-hal/src/i2c/controller/embassy_support.rs create mode 100644 rp2040-hal/src/i2c/peripheral.rs diff --git a/boards/pico/Cargo.toml b/boards/pico/Cargo.toml index feae766..7c2bfd0 100644 --- a/boards/pico/Cargo.toml +++ b/boards/pico/Cargo.toml @@ -17,6 +17,20 @@ embedded-time = "0.12.0" usb-device= "0.2.8" usbd-serial = "0.1.1" usbd-hid = "0.5.1" +futures = { version = "0.3", default-features = false, optional = true } + +[dependencies.embassy] +git = "https://github.com/embassy-rs/embassy" +rev = "a8797f84f69b7668a3f89b6cba3e39bce5649079" +optional = true + +# namespaced features will let use use "dep:embassy-traits" in the features rather than using this +# trick of renaming the crate. +[dependencies.embassy_traits] +git = "https://github.com/embassy-rs/embassy" +rev = "a8797f84f69b7668a3f89b6cba3e39bce5649079" +package = "embassy-traits" +optional = true [dev-dependencies] panic-halt= "0.2.0" @@ -29,3 +43,8 @@ i2c-pio = { git = "https://github.com/ithinuel/i2c-pio-rs", rev = "fb6167d02b7fb [features] default = ["rt"] rt = ["cortex-m-rt","rp2040-hal/rt"] +embassy-traits = ["futures", "embassy", "embassy_traits"] + +[[example]] +name = "pico_i2c_controller_peripheral" +required-features = ["embassy-traits"] diff --git a/boards/pico/examples/pico_i2c_controller_peripheral/controller.rs b/boards/pico/examples/pico_i2c_controller_peripheral/controller.rs new file mode 100644 index 0000000..5c2ad2e --- /dev/null +++ b/boards/pico/examples/pico_i2c_controller_peripheral/controller.rs @@ -0,0 +1,47 @@ +//! I2C Controller demo +//! +//! This module implements a demonstration of an I2C controller sending read & write requests to a +//! peripheral. +//! This demo takes advandage of rust's async.await support to run read & write operation while +//! serving those request from an independant context. + +use super::ADDRESS; +use core::ops::Deref; +use rp2040_hal::i2c::I2C; +use rp2040_hal::pac::i2c0::RegisterBlock as I2CBlock; + +use embassy_traits::i2c::I2c; + +/// Controller demo +pub async fn run_demo(i2c: &mut I2C) -> Result<(), rp2040_hal::i2c::Error> +where + Block: Deref, +{ + let mut tx_filler = 0; + let mut tx = [0u8; 24]; + let mut rx = [0u8; 24]; + + i2c.read(ADDRESS, &mut rx).await?; + rx.iter() + .cloned() + .zip(0x80..) + .for_each(|(a, b)| assert_eq!(a, b)); + + tx.iter_mut().for_each(|b| { + *b = tx_filler; + tx_filler += 1; + }); + + i2c.write_read(ADDRESS, &tx, &mut rx).await?; + rx.iter() + .cloned() + .zip(0x80 + 24..) // follows the inital read + .for_each(|(a, b)| assert_eq!(a, b)); + + tx.iter_mut().for_each(|b| { + *b = tx_filler; + tx_filler += 1; + }); + i2c.write(ADDRESS, &tx).await?; + Ok(()) +} diff --git a/boards/pico/examples/pico_i2c_controller_peripheral/main.rs b/boards/pico/examples/pico_i2c_controller_peripheral/main.rs new file mode 100644 index 0000000..814b9e9 --- /dev/null +++ b/boards/pico/examples/pico_i2c_controller_peripheral/main.rs @@ -0,0 +1,123 @@ +//! I2C controller and I2C peripheral async demo. +//! +//! This example demonstrates use of both I2C peripherals (I2C0 and I2C1) at the same time on a single Pico using [Embassy](https://github.com/embassy-rs/embassy), an async executor. +//! +//! Each peripheral is passed to an async task, which allows them to operate independently of each other: +//! - The controller task (ctrl_demo) uses I2C0. It calls the demo controller code in `controller.rs` +//! - The peripheral task (prph_demo) uses I2C1. It calls the demo peripheral code in `peripheral.rs` +//! +//! ### Wiring notes: +//! +//! I2C0 uses pin `GP0` for `SDA`, and `GP1` for `SCL`. +//! +//! I2C1 uses `GP2` for `SDA`, and `GP3` for `SCL`. +//! +//! For this demo to function you must connect the `SDA` signals (`GP0` and `GP2`) to each other using wires. +//! You must also connect the `SCL` signals (`GP1` and `GP3`) to each other. +//! +//! A pull up resistor (to 3.3V, which is available on pin `36`) is required on SCL & SDA lines in order to reach the expected 1MHz. Although it +//! depends on the hardware context (wire length, impedance & capacitance), a typical value of 2KOhm +//! should generally work fine. +//! +//! If you do not connect the resistor and instead use the internal pull-ups on the I2C pins, you may need to lower the I2C frequency to avoid transmission errors. +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy::{executor::Executor, util::Forever}; +use embedded_time::rate::Extensions; +use hal::{ + clocks::{init_clocks_and_plls, Clock}, + gpio::{bank0, FunctionI2C, Pin}, + i2c::{peripheral::I2CPeripheralEventIterator, I2C}, + pac, + sio::Sio, + watchdog::Watchdog, +}; +use pico::{hal, Pins, XOSC_CRYSTAL_FREQ}; + +use panic_halt as _; + +mod controller; +mod peripheral; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; + +const ADDRESS: u16 = 0x55; + +#[embassy::task] +async fn ctrl_demo( + mut i2c: I2C< + pac::I2C0, + ( + Pin, + Pin, + ), + >, +) { + controller::run_demo(&mut i2c).await.expect("Demo failed") +} + +#[embassy::task] +async fn prph_demo( + mut i2c: I2CPeripheralEventIterator< + pac::I2C1, + ( + Pin, + Pin, + ), + >, +) { + peripheral::run_demo(&mut i2c).await.expect("Demo failed") +} + +#[cortex_m_rt::entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + let mut watchdog = Watchdog::new(pac.WATCHDOG); + + let clocks = init_clocks_and_plls( + XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + Sio::new(pac.SIO).gpio_bank0, + &mut pac.RESETS, + ); + let i2c0 = I2C::new_controller( + pac.I2C0, + pins.gpio0.into_mode(), + pins.gpio1.into_mode(), + 1_000.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + + let i2c1 = I2C::new_peripheral_event_iterator( + pac.I2C1, + pins.gpio2.into_mode(), + pins.gpio3.into_mode(), + &mut pac.RESETS, + ADDRESS, + ); + + static EXECUTOR: Forever = Forever::new(); + let executor = EXECUTOR.put(Executor::new()); + + executor.run(|spawner| { + spawner.spawn(ctrl_demo(i2c0)).unwrap(); + spawner.spawn(prph_demo(i2c1)).unwrap(); + }); +} diff --git a/boards/pico/examples/pico_i2c_controller_peripheral/peripheral.rs b/boards/pico/examples/pico_i2c_controller_peripheral/peripheral.rs new file mode 100644 index 0000000..eb2cc43 --- /dev/null +++ b/boards/pico/examples/pico_i2c_controller_peripheral/peripheral.rs @@ -0,0 +1,95 @@ +//! I2C Peripheral demo +//! +//! This module implements a state machine serving the I2C requests from the controller in this +//! demo. In a real-life application the state machine may not need to be validated as thoroughly +//! demonstrated here. + +use core::ops::Deref; +use rp2040_hal::i2c::peripheral::I2CEvent; +use rp2040_hal::i2c::peripheral::I2CPeripheralEventIterator; +use rp2040_hal::pac::i2c0::RegisterBlock as I2CBlock; + +pub async fn run_demo( + i2c: &mut I2CPeripheralEventIterator, +) -> Result<(), rp2040_hal::i2c::Error> +where + Block: Deref, +{ + let mut expected_value = 0..; + let mut output = 128; + + #[derive(Debug, PartialEq)] + enum Stage { + Idle0, + FirstRead, + Idle1, + FirstWrite, + SecondRead, + Idle2, + SecondWrite, + Done, + } + let mut stage = Stage::Idle0; + + while stage != Stage::Done { + let ev = futures::future::poll_fn(|cx| { + cx.waker().wake_by_ref(); + i2c.next() + .map(core::task::Poll::Ready) + .unwrap_or(core::task::Poll::Pending) + }) + .await; + match ev { + I2CEvent::Start => { + stage = match stage { + Stage::Idle0 => Stage::FirstRead, + Stage::Idle1 => Stage::FirstWrite, + Stage::Idle2 => Stage::SecondWrite, + _ => panic!("Unexpected {:?} while in {:?}", ev, stage), + } + } + I2CEvent::TransferRead => { + if stage != Stage::FirstRead && stage != Stage::SecondRead { + panic!("Unexpected {:?} while in {:?}", ev, stage); + } + + i2c.write(&[output, output + 1, output + 2, output + 3]); + output += 4; + } + I2CEvent::TransferWrite => { + if stage != Stage::FirstWrite && stage != Stage::SecondWrite { + panic!("Unexpected {:?} while in {:?}", ev, stage); + } + + let mut buf = [0; 16]; + loop { + let read = i2c.read(&mut buf); + if read == 0 { + break; + } + + buf.iter() + .take(read) + .cloned() + .zip(&mut expected_value) + .for_each(|(a, b)| assert_eq!(a, b)); + } + } + I2CEvent::Stop => { + stage = match stage { + Stage::FirstRead => Stage::Idle1, + Stage::SecondRead => Stage::Idle2, + Stage::SecondWrite => Stage::Done, + _ => panic!("Unexpected {:?} while in {:?}", ev, stage), + } + } + I2CEvent::Restart => { + stage = match stage { + Stage::FirstWrite => Stage::SecondRead, + _ => panic!("Unexpected {:?} while in {:?}", ev, stage), + } + } + } + } + Ok(()) +} diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index 16f3796..a9dad14 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -24,6 +24,16 @@ vcell = "0.1" void = { version = "1.0.2", default-features = false } rand_core = "0.6.3" +futures = { version = "0.3", default-features = false, optional = true } + +# namespaced features will let use use "dep:embassy-traits" in the features rather than using this +# trick of renaming the crate. +[dependencies.embassy_traits] +git = "https://github.com/embassy-rs/embassy" +rev = "3dcf899babff5b735b1f1a5a99a9005ce06f517f" +package = "embassy-traits" +optional = true + [dev-dependencies] cortex-m-rt = "0.7" panic-halt = "0.2.0" @@ -33,3 +43,5 @@ pio-proc = { git = "https://github.com/rp-rs/pio-rs.git", branch = "main" } [features] rt = ["rp2040-pac/rt"] +embassy-traits = ["embassy_traits", "futures"] + diff --git a/rp2040-hal/src/i2c.rs b/rp2040-hal/src/i2c.rs index d6a3a99..3778419 100644 --- a/rp2040-hal/src/i2c.rs +++ b/rp2040-hal/src/i2c.rs @@ -42,6 +42,9 @@ //! //! See [examples/i2c.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal/examples/i2c.rs) //! for a complete example + +use core::{marker::PhantomData, ops::Deref}; + use crate::{ gpio::pin::bank0::{ BankPinId, Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, @@ -52,11 +55,13 @@ use crate::{ resets::SubsystemReset, typelevel::Sealed, }; -#[cfg(feature = "eh1_0_alpha")] -use eh1_0_alpha::i2c::blocking as eh1; use embedded_time::rate::Hertz; -use hal::blocking::i2c::{Read, Write, WriteRead}; -use rp2040_pac::{I2C0, I2C1, RESETS}; +use pac::{i2c0::RegisterBlock as I2CBlock, I2C0, I2C1, RESETS}; + +/// Controller implementaion +pub mod controller; +/// Peripheral implementation +pub mod peripheral; /// I2C error #[non_exhaustive] @@ -64,14 +69,14 @@ use rp2040_pac::{I2C0, I2C1, RESETS}; pub enum Error { /// I2C abort with error Abort(u32), - /// User passed in a read buffer that was 0 or >255 length - InvalidReadBufferLength(usize), - /// User passed in a write buffer that was 0 or >255 length - InvalidWriteBufferLength(usize), + /// User passed in a read buffer that was 0 length + InvalidReadBufferLength, + /// User passed in a write buffer that was 0 length + InvalidWriteBufferLength, /// Target i2c address is out of range - AddressOutOfRange(u8), + AddressOutOfRange(u16), /// Target i2c address is reserved - AddressReserved(u8), + AddressReserved(u16), } /// SCL pin @@ -116,22 +121,100 @@ impl SclPin for Gpio21 {} impl SdaPin for Gpio26 {} impl SclPin for Gpio27 {} -/// I2C peripheral operating in master mode -pub struct I2C { +/// Operational mode of the I2C peripheral. +pub trait I2CMode: Sealed { + /// Indicates whether this mode is Controller or Peripheral. + const IS_CONTROLLER: bool; +} +/// Marker for an I2C peripheral operating as a controller. +pub enum Controller {} +impl Sealed for Controller {} +impl I2CMode for Controller { + const IS_CONTROLLER: bool = true; +} +/// Marker for an I2C peripheral operating as a peripehral. +pub enum Peripheral {} +impl Sealed for Peripheral {} +impl I2CMode for Peripheral { + const IS_CONTROLLER: bool = false; +} + +/// I2C peripheral +pub struct I2C { i2c: I2C, pins: Pins, + mode: PhantomData, } const TX_FIFO_SIZE: u8 = 16; +const RX_FIFO_SIZE: u8 = 16; -fn i2c_reserved_addr(addr: u8) -> bool { +fn i2c_reserved_addr(addr: u16) -> bool { (addr & 0x78) == 0 || (addr & 0x78) == 0x78 } +impl I2C, Pin), Mode> +where + Block: SubsystemReset + Deref, + Sda: PinId + BankPinId, + Scl: PinId + BankPinId, + Mode: I2CMode, +{ + /// Releases the I2C peripheral and associated pins + #[allow(clippy::type_complexity)] + pub fn free( + self, + resets: &mut RESETS, + ) -> (Block, (Pin, Pin)) { + self.i2c.reset_bring_down(resets); + + (self.i2c, self.pins) + } +} + +impl, PINS, Mode> I2C { + /// Number of bytes currently in the RX FIFO + #[inline] + pub fn rx_fifo_used(&self) -> u8 { + self.i2c.ic_rxflr.read().rxflr().bits() + } + + /// Remaining capacity in the RX FIFO + #[inline] + pub fn rx_fifo_free(&self) -> u8 { + RX_FIFO_SIZE - self.rx_fifo_used() + } + + /// RX FIFO is empty + #[inline] + pub fn rx_fifo_empty(&self) -> bool { + self.rx_fifo_used() == 0 + } + + /// Number of bytes currently in the TX FIFO + #[inline] + pub fn tx_fifo_used(&self) -> u8 { + self.i2c.ic_txflr.read().txflr().bits() + } + + /// Remaining capacity in the TX FIFO + #[inline] + pub fn tx_fifo_free(&self) -> u8 { + TX_FIFO_SIZE - self.tx_fifo_used() + } + + /// TX FIFO is at capacity + #[inline] + pub fn tx_fifo_full(&self) -> bool { + self.tx_fifo_free() == 0 + } +} + macro_rules! hal { ($($I2CX:ident: ($i2cX:ident),)+) => { $( - impl I2C<$I2CX, (Pin, Pin)> { + impl + I2C<$I2CX, (Pin, Pin)> { /// Configures the I2C peripheral to work in master mode pub fn $i2cX( i2c: $I2CX, @@ -146,385 +229,12 @@ macro_rules! hal { Scl: SclPin<$I2CX>, SystemF: Into>, { - let freq = freq.into().0; - assert!(freq <= 1_000_000); - assert!(freq > 0); - let freq = freq as u32; - - i2c.reset_bring_down(resets); - i2c.reset_bring_up(resets); - - i2c.ic_enable.write(|w| w.enable().disabled()); - - i2c.ic_con.modify(|_,w| { - w.speed().fast(); - w.master_mode().enabled(); - w.ic_slave_disable().slave_disabled(); - w.ic_restart_en().enabled(); - w.tx_empty_ctrl().enabled() - }); - - i2c.ic_tx_tl.write(|w| unsafe { w.tx_tl().bits(0) }); - i2c.ic_rx_tl.write(|w| unsafe { w.rx_tl().bits(0) }); - - i2c.ic_dma_cr.write(|w| { - w.tdmae().enabled(); - w.rdmae().enabled() - }); - - let freq_in = system_clock.into().0; - - // There are some subtleties to I2C timing which we are completely ignoring here - // See: https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 - let period = (freq_in + freq / 2) / freq; - let lcnt = period * 3 / 5; // oof this one hurts - let hcnt = period - lcnt; - - // Check for out-of-range divisors: - assert!(hcnt <= 0xffff); - assert!(lcnt <= 0xffff); - assert!(hcnt >= 8); - assert!(lcnt >= 8); - - // Per I2C-bus specification a device in standard or fast mode must - // internally provide a hold time of at least 300ns for the SDA signal to - // bridge the undefined region of the falling edge of SCL. A smaller hold - // time of 120ns is used for fast mode plus. - let sda_tx_hold_count = if freq < 1000000 { - // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) - // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. - // Add 1 to avoid division truncation. - ((freq_in * 3) / 10000000) + 1 - } else { - // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) - // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. - // Add 1 to avoid division truncation. - ((freq_in * 3) / 25000000) + 1 - }; - assert!(sda_tx_hold_count <= lcnt - 2); - - unsafe { - i2c.ic_fs_scl_hcnt - .write(|w| w.ic_fs_scl_hcnt().bits(hcnt as u16)); - i2c.ic_fs_scl_lcnt - .write(|w| w.ic_fs_scl_lcnt().bits(lcnt as u16)); - i2c.ic_fs_spklen.write(|w| { - w.ic_fs_spklen() - .bits(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }) - }); - i2c.ic_sda_hold - .modify(|_r,w| w.ic_sda_tx_hold().bits(sda_tx_hold_count as u16)); - } - - i2c.ic_enable.write(|w| w.enable().enabled()); - - I2C { i2c, pins: (sda_pin, scl_pin) } - } - - /// Releases the I2C peripheral and associated pins - pub fn free(self) -> ($I2CX, (Pin, Pin)) { - (self.i2c, self.pins) + Self::new_controller(i2c, sda_pin, scl_pin, freq, resets, system_clock) } } - - impl I2C<$I2CX, PINS> { - /// Number of bytes currently in the TX FIFO - #[inline] - fn tx_fifo_used(&self) -> u8 { - self.i2c.ic_txflr.read().txflr().bits() - } - - /// Remaining capacity in the TX FIFO - #[inline] - fn tx_fifo_free(&self) -> u8 { - TX_FIFO_SIZE - self.tx_fifo_used() - } - - /// TX FIFO is at capacity - #[inline] - fn tx_fifo_full(&self) -> bool { - self.tx_fifo_free() == 0 - } - } - - impl Write for I2C<$I2CX, PINS> { - type Error = Error; - - fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { - // TODO support transfers of more than 255 bytes - if (bytes.len() > 255 || bytes.len() == 0) { - return Err(Error::InvalidWriteBufferLength(bytes.len())); - } else if addr >= 0x80 { - return Err(Error::AddressOutOfRange(addr)); - } else if i2c_reserved_addr(addr) { - return Err(Error::AddressReserved(addr)); - } - - self.i2c.ic_enable.write(|w| w.enable().disabled()); - self.i2c - .ic_tar - .write(|w| unsafe { w.ic_tar().bits(addr as u16) }); - self.i2c.ic_enable.write(|w| w.enable().enabled()); - - let mut abort = false; - let mut abort_reason = 0; - - for (i, byte) in bytes.iter().enumerate() { - let last = i == bytes.len() - 1; - - self.i2c.ic_data_cmd.write(|w| { - if last { - w.stop().enable(); - } else { - w.stop().disable(); - } - unsafe { w.dat().bits(*byte) } - }); - - // Wait until the transmission of the address/data from the internal - // shift register has completed. For this to function correctly, the - // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag - // was set in i2c_init. - while self.i2c.ic_raw_intr_stat.read().tx_empty().is_inactive() {} - - abort_reason = self.i2c.ic_tx_abrt_source.read().bits(); - if abort_reason != 0 { - // Note clearing the abort flag also clears the reason, and - // this instance of flag is clear-on-read! Note also the - // IC_CLR_TX_ABRT register always reads as 0. - self.i2c.ic_clr_tx_abrt.read().clr_tx_abrt(); - abort = true; - } - - if abort || last { - // If the transaction was aborted or if it completed - // successfully wait until the STOP condition has occured. - - while self.i2c.ic_raw_intr_stat.read().stop_det().is_inactive() {} - - self.i2c.ic_clr_stop_det.read().clr_stop_det(); - } - - // Note the hardware issues a STOP automatically on an abort condition. - // Note also the hardware clears RX FIFO as well as TX on abort, - // ecause we set hwparam IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0. - if abort { - break; - } - } - - if abort { - Err(Error::Abort(abort_reason)) - } else { - Ok(()) - } - } - } - - impl WriteRead for I2C<$I2CX, PINS> { - type Error = Error; - - fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { - // TODO support transfers of more than 255 bytes - if (bytes.len() > 255 || bytes.len() == 0) { - return Err(Error::InvalidWriteBufferLength(bytes.len())); - } else if (buffer.len() > 255 || buffer.len() == 0) { - return Err(Error::InvalidReadBufferLength(buffer.len())); - } else if addr >= 0x80 { - return Err(Error::AddressOutOfRange(addr)); - } else if i2c_reserved_addr(addr) { - return Err(Error::AddressReserved(addr)); - } - - self.i2c.ic_enable.write(|w| w.enable().disabled()); - self.i2c - .ic_tar - .write(|w| unsafe { w.ic_tar().bits(addr as u16) }); - self.i2c.ic_enable.write(|w| w.enable().enabled()); - - let mut abort = false; - let mut abort_reason = 0; - - for byte in bytes { - self.i2c.ic_data_cmd.write(|w| { - w.stop().disable(); - unsafe { w.dat().bits(*byte) } - }); - - // Wait until the transmission of the address/data from the internal - // shift register has completed. For this to function correctly, the - // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag - // was set in i2c_init. - while self.i2c.ic_raw_intr_stat.read().tx_empty().is_inactive() {} - - abort_reason = self.i2c.ic_tx_abrt_source.read().bits(); - if abort_reason != 0 { - // Note clearing the abort flag also clears the reason, and - // this instance of flag is clear-on-read! Note also the - // IC_CLR_TX_ABRT register always reads as 0. - self.i2c.ic_clr_tx_abrt.read().clr_tx_abrt(); - abort = true; - } - - if abort { - // If the transaction was aborted or if it completed - // successfully wait until the STOP condition has occured. - - while self.i2c.ic_raw_intr_stat.read().stop_det().is_inactive() {} - - self.i2c.ic_clr_stop_det.read().clr_stop_det(); - } - - // Note the hardware issues a STOP automatically on an abort condition. - // Note also the hardware clears RX FIFO as well as TX on abort, - // ecause we set hwparam IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0. - if abort { - break; - } - } - - if abort { - return Err(Error::Abort(abort_reason)); - } - - let buffer_len = buffer.len(); - for (i, byte) in buffer.iter_mut().enumerate() { - let first = i == 0; - let last = i == buffer_len - 1; - - // wait until there is space in the FIFO to write the next byte - while self.tx_fifo_full() {} - - self.i2c.ic_data_cmd.write(|w| { - if first { - w.restart().enable(); - } else { - w.restart().disable(); - } - - if last { - w.stop().enable(); - } else { - w.stop().disable(); - } - - w.cmd().read() - }); - - while !abort && self.i2c.ic_rxflr.read().bits() == 0 { - abort_reason = self.i2c.ic_tx_abrt_source.read().bits(); - abort = self.i2c.ic_clr_tx_abrt.read().bits() > 0; - } - - if abort { - break; - } - - *byte = self.i2c.ic_data_cmd.read().dat().bits(); - } - - if abort { - Err(Error::Abort(abort_reason)) - } else { - Ok(()) - } - } - } - - impl Read for I2C<$I2CX, PINS> { - type Error = Error; - - fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { - // TODO support transfers of more than 255 bytes - if (buffer.len() > 255 || buffer.len() == 0) { - return Err(Error::InvalidReadBufferLength(buffer.len())); - } else if addr >= 0x80 { - return Err(Error::AddressOutOfRange(addr)); - } else if i2c_reserved_addr(addr) { - return Err(Error::AddressReserved(addr)); - } - - self.i2c.ic_enable.write(|w| w.enable().disabled()); - self.i2c - .ic_tar - .write(|w| unsafe { w.ic_tar().bits(addr as u16) }); - self.i2c.ic_enable.write(|w| w.enable().enabled()); - - let mut abort = false; - let mut abort_reason = 0; - - let lastindex = buffer.len() -1; - for (i, byte) in buffer.iter_mut().enumerate() { - let first = i == 0; - let last = i == lastindex; - - // wait until there is space in the FIFO to write the next byte - while self.tx_fifo_full() {} - - self.i2c.ic_data_cmd.write(|w| { - if first { - w.restart().enable(); - } else { - w.restart().disable(); - } - - if last { - w.stop().enable(); - } else { - w.stop().disable(); - } - - w.cmd().read() - }); - - while !abort && self.i2c.ic_rxflr.read().bits() == 0 { - abort_reason = self.i2c.ic_tx_abrt_source.read().bits(); - abort = self.i2c.ic_clr_tx_abrt.read().bits() > 0; - } - - if abort { - break; - } - - *byte = self.i2c.ic_data_cmd.read().dat().bits(); - } - - if abort { - Err(Error::Abort(abort_reason)) - } else { - Ok(()) - } - } - } - - #[cfg(feature = "eh1_0_alpha")] - impl eh1::Write for I2C<$I2CX, PINS> { - type Error = Error; - - fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { - Write::write(self, addr, bytes) - } - } - #[cfg(feature = "eh1_0_alpha")] - impl eh1::WriteRead for I2C<$I2CX, PINS> { - type Error = Error; - fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { - WriteRead::write_read(self, addr, bytes, buffer) - } - } - #[cfg(feature = "eh1_0_alpha")] - impl eh1::Read for I2C<$I2CX, PINS> { - type Error = Error; - - fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { - Read::read(self, addr, buffer) - } - } - - )+ + )+ } } - hal! { I2C0: (i2c0), I2C1: (i2c1), diff --git a/rp2040-hal/src/i2c/controller.rs b/rp2040-hal/src/i2c/controller.rs new file mode 100644 index 0000000..8d70e00 --- /dev/null +++ b/rp2040-hal/src/i2c/controller.rs @@ -0,0 +1,300 @@ +use core::{marker::PhantomData, ops::Deref}; + +use crate::{ + gpio::pin::bank0::BankPinId, + gpio::pin::{FunctionI2C, Pin, PinId}, + resets::SubsystemReset, +}; +use embedded_time::rate::Hertz; +use hal::blocking::i2c::{Read, Write, WriteRead}; +use pac::{i2c0::RegisterBlock as Block, RESETS}; + +#[cfg(feature = "eh1_0_alpha")] +use eh1_0_alpha::i2c::blocking as eh1; + +use super::{i2c_reserved_addr, Controller, Error, SclPin, SdaPin, I2C}; + +#[cfg(feature = "embassy-traits")] +mod embassy_support; + +impl, Sda: PinId + BankPinId, Scl: PinId + BankPinId> + I2C, Pin), Controller> +{ + /// Configures the I2C peripheral to work in controller mode + pub fn new_controller( + i2c: T, + sda_pin: Pin, + scl_pin: Pin, + freq: F, + resets: &mut RESETS, + system_clock: SystemF, + ) -> Self + where + F: Into>, + Sda: SdaPin, + Scl: SclPin, + SystemF: Into>, + { + let freq = freq.into().0; + assert!(freq <= 1_000_000); + assert!(freq > 0); + let freq = freq as u32; + + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable.write(|w| w.enable().disabled()); + + // select controller mode & speed + i2c.ic_con.modify(|_, w| { + w.speed().fast(); + w.master_mode().enabled(); + w.ic_slave_disable().slave_disabled(); + w.ic_restart_en().enabled(); + w.tx_empty_ctrl().enabled() + }); + + // Clear FIFO threshold + i2c.ic_tx_tl.write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl.write(|w| unsafe { w.rx_tl().bits(0) }); + + let freq_in = system_clock.into().0; + + // There are some subtleties to I2C timing which we are completely ignoring here + // See: https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let period = (freq_in + freq / 2) / freq; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + assert!(hcnt <= 0xffff); + assert!(lcnt <= 0xffff); + assert!(hcnt >= 8); + assert!(lcnt >= 8); + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if freq < 1000000 { + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 10000000) + 1 + } else { + // fast mode plus requires a clk_in > 32MHz + assert!(freq_in >= 32_000_000); + + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 25000000) + 1 + }; + assert!(sda_tx_hold_count <= lcnt - 2); + + unsafe { + i2c.ic_fs_scl_hcnt + .write(|w| w.ic_fs_scl_hcnt().bits(hcnt as u16)); + i2c.ic_fs_scl_lcnt + .write(|w| w.ic_fs_scl_lcnt().bits(lcnt as u16)); + i2c.ic_fs_spklen.write(|w| { + w.ic_fs_spklen() + .bits(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }) + }); + i2c.ic_sda_hold + .modify(|_r, w| w.ic_sda_tx_hold().bits(sda_tx_hold_count as u16)); + } + + // Enable I2C block + i2c.ic_enable.write(|w| w.enable().enabled()); + + Self { + i2c, + pins: (sda_pin, scl_pin), + mode: PhantomData, + } + } +} +impl, PINS> I2C { + fn validate( + addr: u16, + opt_tx_empty: Option, + opt_rx_empty: Option, + ) -> Result<(), Error> { + // validate tx parameters if present + if opt_tx_empty.unwrap_or(false) { + return Err(Error::InvalidWriteBufferLength); + } + + // validate rx parameters if present + if opt_rx_empty.unwrap_or(false) { + return Err(Error::InvalidReadBufferLength); + } + + // validate address + if addr >= 0x80 { + Err(Error::AddressOutOfRange(addr)) + } else if i2c_reserved_addr(addr) { + Err(Error::AddressReserved(addr)) + } else { + Ok(()) + } + } + + fn setup(&mut self, addr: u16) { + self.i2c.ic_enable.write(|w| w.enable().disabled()); + self.i2c.ic_tar.write(|w| unsafe { w.ic_tar().bits(addr) }); + self.i2c.ic_enable.write(|w| w.enable().enabled()); + } + + fn read_and_clear_abort_reason(&mut self) -> Option { + let abort_reason = self.i2c.ic_tx_abrt_source.read().bits(); + if abort_reason != 0 { + // Note clearing the abort flag also clears the reason, and + // this instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + self.i2c.ic_clr_tx_abrt.read(); + Some(abort_reason) + } else { + None + } + } + + fn read_internal(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + let lastindex = buffer.len() - 1; + for (i, byte) in buffer.iter_mut().enumerate() { + let first = i == 0; + let last = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + while self.tx_fifo_full() {} + + self.i2c.ic_data_cmd.write(|w| { + if first { + w.restart().enable(); + } else { + w.restart().disable(); + } + + if last { + w.stop().enable(); + } else { + w.stop().disable(); + } + + w.cmd().read() + }); + + while self.i2c.ic_rxflr.read().bits() == 0 { + if let Some(abort_reason) = self.read_and_clear_abort_reason() { + return Err(Error::Abort(abort_reason)); + } + } + + *byte = self.i2c.ic_data_cmd.read().dat().bits(); + } + + Ok(()) + } + + fn write_internal(&mut self, bytes: &[u8], do_stop: bool) -> Result<(), Error> { + for (i, byte) in bytes.iter().enumerate() { + let last = i == bytes.len() - 1; + + self.i2c.ic_data_cmd.write(|w| { + if do_stop && last { + w.stop().enable(); + } else { + w.stop().disable(); + } + unsafe { w.dat().bits(*byte) } + }); + + // Wait until the transmission of the address/data from the internal + // shift register has completed. For this to function correctly, the + // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag + // was set in i2c_init. + while self.i2c.ic_raw_intr_stat.read().tx_empty().is_inactive() {} + + let abort_reason = self.read_and_clear_abort_reason(); + + if abort_reason.is_some() || (do_stop && last) { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occured. + + while self.i2c.ic_raw_intr_stat.read().stop_det().is_inactive() {} + + self.i2c.ic_clr_stop_det.read().clr_stop_det(); + } + + // Note the hardware issues a STOP automatically on an abort condition. + // Note also the hardware clears RX FIFO as well as TX on abort, + // ecause we set hwparam IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0. + if let Some(abort_reason) = abort_reason { + return Err(Error::Abort(abort_reason)); + } + } + Ok(()) + } +} +impl, PINS> Read for I2C { + type Error = Error; + + fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + let addr: u16 = addr.into(); + + Self::validate(addr, None, Some(buffer.is_empty()))?; + + self.setup(addr); + self.read_internal(buffer) + } +} +impl, PINS> WriteRead for I2C { + type Error = Error; + + fn write_read(&mut self, addr: u8, tx: &[u8], rx: &mut [u8]) -> Result<(), Error> { + let addr: u16 = addr.into(); + + Self::validate(addr, Some(tx.is_empty()), Some(rx.is_empty()))?; + self.setup(addr); + + self.write_internal(tx, false)?; + self.read_internal(rx) + } +} +impl, PINS> Write for I2C { + type Error = Error; + + fn write(&mut self, addr: u8, tx: &[u8]) -> Result<(), Error> { + let addr: u16 = addr.into(); + Self::validate(addr, Some(tx.is_empty()), None)?; + self.setup(addr); + + self.write_internal(tx, true) + } +} + +#[cfg(feature = "eh1_0_alpha")] +impl, PINS> eh1::Write for I2C { + type Error = Error; + + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + Write::write(self, addr, bytes) + } +} +#[cfg(feature = "eh1_0_alpha")] +impl, PINS> eh1::WriteRead for I2C { + type Error = Error; + + fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { + WriteRead::write_read(self, addr, bytes, buffer) + } +} +#[cfg(feature = "eh1_0_alpha")] +impl, PINS> eh1::Read for I2C { + type Error = Error; + + fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + Read::read(self, addr, buffer) + } +} diff --git a/rp2040-hal/src/i2c/controller/embassy_support.rs b/rp2040-hal/src/i2c/controller/embassy_support.rs new file mode 100644 index 0000000..e1e81eb --- /dev/null +++ b/rp2040-hal/src/i2c/controller/embassy_support.rs @@ -0,0 +1,218 @@ +use core::{future::Future, iter::Peekable, ops::Deref, task::Poll}; + +use super::{Block, Controller, Error, I2C}; + +impl, PINS> I2C { + async fn non_blocking_read_internal<'a, U: Iterator + 'a>( + &mut self, + mut buffer: Peekable, + ) -> Result<(), Error> { + let first = true; + while let Some(byte) = buffer.next() { + let last = buffer.peek().is_none(); + + // wait until there is space in the FIFO to write the next byte + block_on(|| { + if self.tx_fifo_full() { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + self.i2c.ic_data_cmd.write(|w| { + if first { + w.restart().enable(); + } else { + w.restart().disable(); + } + + if last { + w.stop().enable(); + } else { + w.stop().disable(); + } + + w.cmd().read() + }); + + block_on(|| { + if let Some(abort_reason) = self.read_and_clear_abort_reason() { + Poll::Ready(Err(Error::Abort(abort_reason))) + } else if self.i2c.ic_rxflr.read().bits() != 0 { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }) + .await?; + + *byte = self.i2c.ic_data_cmd.read().dat().bits(); + } + + Ok(()) + } + + async fn non_blocking_write_internal( + &mut self, + bytes: impl IntoIterator, + do_stop: bool, + ) -> Result<(), Error> { + let mut bytes = bytes.into_iter().peekable(); + while let Some(byte) = bytes.next() { + let last = bytes.peek().is_none(); + + self.i2c.ic_data_cmd.write(|w| { + if do_stop && last { + w.stop().enable(); + } else { + w.stop().disable(); + } + unsafe { w.dat().bits(byte) } + }); + + // Wait until the transmission of the address/data from the internal + // shift register has completed. For this to function correctly, the + // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag + // was set in i2c_init. + block_on(|| { + if self.i2c.ic_raw_intr_stat.read().tx_empty().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + let abort_reason = self.read_and_clear_abort_reason(); + + if abort_reason.is_some() || (do_stop && last) { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occured. + + block_on(|| { + if self.i2c.ic_raw_intr_stat.read().stop_det().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + self.i2c.ic_clr_stop_det.read().clr_stop_det(); + } + + // Note the hardware issues a STOP automatically on an abort condition. + // Note also the hardware clears RX FIFO as well as TX on abort, + // ecause we set hwparam IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0. + if let Some(abort_reason) = abort_reason { + return Err(Error::Abort(abort_reason)); + } + } + + Ok(()) + } +} +async fn block_on Poll, T>(mut f: F) -> T { + futures::future::poll_fn(|cx| { + // always ready to scan + cx.waker().wake_by_ref(); + + f() + }) + .await +} + +impl embassy_traits::i2c::I2c for I2C +where + T: Deref, + A: embassy_traits::i2c::AddressMode + 'static + Into, +{ + type Error = Error; + + #[rustfmt::skip] + type WriteFuture<'a> + where + Self: 'a = impl Future> + 'a; + + #[rustfmt::skip] + type ReadFuture<'a> + where + Self: 'a = impl Future> + 'a; + + #[rustfmt::skip] + type WriteReadFuture<'a> + where + Self: 'a = impl Future> + 'a; + + fn read<'a>(&'a mut self, address: A, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { + let mut buffer = buffer.iter_mut().peekable(); + let addr: u16 = address.into(); + + async move { + self.setup(addr); + + Self::validate(addr, None, Some(buffer.peek().is_none()))?; + + self.non_blocking_read_internal(buffer).await + } + } + + fn write<'a>(&'a mut self, address: A, bytes: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + let addr: u16 = address.into(); + Self::validate(addr, Some(bytes.is_empty()), None)?; + self.setup(addr); + + self.non_blocking_write_internal(bytes.iter().cloned(), true) + .await + } + } + + fn write_read<'a>( + &'a mut self, + address: A, + bytes: &'a [u8], + buffer: &'a mut [u8], + ) -> Self::WriteReadFuture<'a> { + async move { + let addr: u16 = address.into(); + Self::validate(addr, Some(bytes.is_empty()), Some(buffer.is_empty()))?; + self.setup(addr); + + self.non_blocking_write_internal(bytes.iter().cloned(), false) + .await?; + self.non_blocking_read_internal(buffer.iter_mut().peekable()) + .await + } + } +} +impl embassy_traits::i2c::WriteIter for I2C +where + T: Deref, + A: embassy_traits::i2c::AddressMode + 'static + Into, +{ + type Error = Error; + + #[rustfmt::skip] + type WriteIterFuture<'a, U> + where + U: 'a + IntoIterator, + Self: 'a = impl Future> + 'a; + + fn write_iter<'a, U>(&'a mut self, address: A, bytes: U) -> Self::WriteIterFuture<'a, U> + where + U: IntoIterator + 'a, + { + let addr: u16 = address.into(); + async move { + let mut bytes = bytes.into_iter().peekable(); + Self::validate(addr, Some(bytes.peek().is_none()), None)?; + + self.setup(addr); + + self.non_blocking_write_internal(bytes, true).await + } + } +} diff --git a/rp2040-hal/src/i2c/peripheral.rs b/rp2040-hal/src/i2c/peripheral.rs new file mode 100644 index 0000000..2a4a2ec --- /dev/null +++ b/rp2040-hal/src/i2c/peripheral.rs @@ -0,0 +1,200 @@ +use core::{marker::PhantomData, ops::Deref}; + +use crate::{ + gpio::pin::bank0::BankPinId, + gpio::pin::{FunctionI2C, Pin, PinId}, + resets::SubsystemReset, +}; +use pac::{i2c0::RegisterBlock as I2CBlock, RESETS}; + +use super::{Peripheral, SclPin, SdaPin, I2C}; + +/// I2C bus events +#[derive(Debug, PartialEq, Eq)] +pub enum I2CEvent { + /// Start condition has been detected. + Start, + /// Restart condition has been detected. + Restart, + /// The controller requests data. + TransferRead, + /// The controller sends data. + TransferWrite, + /// Stop condition detected. + Stop, +} + +#[derive(Debug)] +enum State { + Idle, + Read, + Write, +} + +/// Provides Async features to I2C peripheral. +pub struct I2CPeripheralEventIterator { + i2c: I2C, + state: State, +} + +impl I2C, Pin), Peripheral> +where + T: SubsystemReset + Deref, + Sda: PinId + BankPinId, + Scl: PinId + BankPinId, +{ + /// Configures the I2C peripheral to work in peripheral mode + /// + /// The bus *MUST* be idle when this method is called. + #[allow(clippy::type_complexity)] + pub fn new_peripheral_event_iterator( + i2c: T, + sda_pin: Pin, + scl_pin: Pin, + resets: &mut RESETS, + addr: u16, + ) -> I2CPeripheralEventIterator, Pin)> + where + Sda: SdaPin, + Scl: SclPin, + { + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable.write(|w| w.enable().disabled()); + + // TODO: rp2040 supports 10bits addressing + //i2c_reserved_addr(addr) + i2c.ic_sar.write(|w| unsafe { w.ic_sar().bits(addr) }); + // select peripheral mode & speed + i2c.ic_con.modify(|_, w| { + // run in fast mode + w.speed().fast(); + // setup slave mode + w.master_mode().disabled(); + w.ic_slave_disable().slave_enabled(); + // hold scl when fifo's full + w.rx_fifo_full_hld_ctrl().enabled(); + w.ic_restart_en().enabled(); + w.tx_empty_ctrl().enabled() + }); + + // Clear FIFO threshold + i2c.ic_tx_tl.write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl.write(|w| unsafe { w.rx_tl().bits(0) }); + + // Enable I2C block + i2c.ic_enable.write(|w| w.enable().enabled()); + + I2CPeripheralEventIterator { + i2c: Self { + i2c, + pins: (sda_pin, scl_pin), + mode: PhantomData, + }, + state: State::Idle, + } + } +} + +impl, PINS> I2CPeripheralEventIterator { + /// Pushs up to `usize::min(TX_FIFO_SIZE, buf.len())` bytes to the TX FIFO. + /// Returns the number of bytes pushed to the FIFO. Note this does *not* reflect how many bytes + /// are effectively received by the controller. + pub fn write(&mut self, buf: &[u8]) -> usize { + // just in case, clears previous tx abort. + self.i2c.i2c.ic_clr_tx_abrt.read(); + + let mut sent = 0; + for &b in buf.iter() { + if self.i2c.tx_fifo_full() { + break; + } + + self.i2c + .i2c + .ic_data_cmd + .write(|w| unsafe { w.dat().bits(b) }); + sent += 1; + } + // serve a pending read request + self.i2c.i2c.ic_clr_rd_req.read(); + sent + } + + /// Pulls up to `usize::min(RX_FIFO_SIZE, buf.len())` bytes from the RX FIFO. + pub fn read(&mut self, buf: &mut [u8]) -> usize { + let mut read = 0; + + for b in buf.iter_mut() { + if self.i2c.rx_fifo_empty() { + break; + } + + *b = self.i2c.i2c.ic_data_cmd.read().dat().bits(); + read += 1; + } + read + } +} +impl, PINS> Iterator for I2CPeripheralEventIterator { + type Item = I2CEvent; + + fn next(&mut self) -> Option { + let stat = self.i2c.i2c.ic_raw_intr_stat.read(); + self.i2c.i2c.ic_clr_activity.read(); + + match self.state { + State::Idle if stat.start_det().bit_is_set() => { + self.i2c.i2c.ic_clr_start_det.read(); + self.state = if stat.rd_req().bit_is_set() { + State::Read + } else { + State::Write + }; + Some(I2CEvent::Start) + } + State::Read if stat.rd_req().bit_is_set() => { + // `rd_req` is used by the hardware to detect when the I2C block can stop stretching + // the clock and start process the data pushed to the FIFO (if any). + // This is done in `Self::write`. + + Some(I2CEvent::TransferRead) + } + State::Read if stat.restart_det().bit_is_set() => { + self.i2c.i2c.ic_clr_restart_det.read(); + self.state = State::Write; + Some(I2CEvent::Restart) + } + State::Write if !self.i2c.rx_fifo_empty() => Some(I2CEvent::TransferWrite), + State::Write if stat.restart_det().bit_is_set() => { + self.i2c.i2c.ic_clr_restart_det.read(); + self.state = State::Read; + Some(I2CEvent::Restart) + } + _ if stat.stop_det().bit_is_set() => { + self.i2c.i2c.ic_clr_stop_det.read(); + self.state = State::Idle; + Some(I2CEvent::Stop) + } + _ => None, + } + } +} + +impl + I2CPeripheralEventIterator, Pin)> +where + Block: SubsystemReset + Deref, + Sda: PinId + BankPinId, + Scl: PinId + BankPinId, +{ + /// Releases the I2C peripheral and associated pins + #[allow(clippy::type_complexity)] + pub fn free( + self, + resets: &mut RESETS, + ) -> (Block, (Pin, Pin)) { + self.i2c.free(resets) + } +} diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index 8651af9..4cf48c9 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -5,6 +5,8 @@ #![warn(missing_docs)] #![no_std] +#![cfg_attr(feature = "embassy-traits", feature(generic_associated_types))] +#![cfg_attr(feature = "embassy-traits", feature(type_alias_impl_trait))] extern crate cortex_m; extern crate embedded_hal as hal;