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>
This commit is contained in:
Wilfried Chauveau 2021-11-08 12:23:28 +00:00 committed by GitHub
parent 612c44db46
commit 990d964a93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 1115 additions and 389 deletions

View file

@ -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"]

View file

@ -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<Block, Pins>(i2c: &mut I2C<Block, Pins>) -> Result<(), rp2040_hal::i2c::Error>
where
Block: Deref<Target = I2CBlock>,
{
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(())
}

View file

@ -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<bank0::Gpio0, FunctionI2C>,
Pin<bank0::Gpio1, FunctionI2C>,
),
>,
) {
controller::run_demo(&mut i2c).await.expect("Demo failed")
}
#[embassy::task]
async fn prph_demo(
mut i2c: I2CPeripheralEventIterator<
pac::I2C1,
(
Pin<bank0::Gpio2, FunctionI2C>,
Pin<bank0::Gpio3, FunctionI2C>,
),
>,
) {
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<Executor> = Forever::new();
let executor = EXECUTOR.put(Executor::new());
executor.run(|spawner| {
spawner.spawn(ctrl_demo(i2c0)).unwrap();
spawner.spawn(prph_demo(i2c1)).unwrap();
});
}

View file

@ -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<Block, Pins>(
i2c: &mut I2CPeripheralEventIterator<Block, Pins>,
) -> Result<(), rp2040_hal::i2c::Error>
where
Block: Deref<Target = I2CBlock>,
{
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(())
}

View file

@ -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"]

View file

@ -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<I2C0> for Gpio21 {}
impl SdaPin<I2C1> for Gpio26 {}
impl SclPin<I2C1> for Gpio27 {}
/// I2C peripheral operating in master mode
pub struct I2C<I2C, Pins> {
/// 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, Pins, Mode = Controller> {
i2c: I2C,
pins: Pins,
mode: PhantomData<Mode>,
}
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<Block, Sda, Scl, Mode> I2C<Block, (Pin<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>), Mode>
where
Block: SubsystemReset + Deref<Target = I2CBlock>,
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<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>)) {
self.i2c.reset_bring_down(resets);
(self.i2c, self.pins)
}
}
impl<Block: Deref<Target = I2CBlock>, PINS, Mode> I2C<Block, PINS, Mode> {
/// 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<Sda: PinId + BankPinId, Scl: PinId + BankPinId> I2C<$I2CX, (Pin<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>)> {
impl<Sda: PinId + BankPinId, Scl: PinId + BankPinId>
I2C<$I2CX, (Pin<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>)> {
/// Configures the I2C peripheral to work in master mode
pub fn $i2cX<F, SystemF>(
i2c: $I2CX,
@ -146,385 +229,12 @@ macro_rules! hal {
Scl: SclPin<$I2CX>,
SystemF: Into<Hertz<u32>>,
{
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<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>)) {
(self.i2c, self.pins)
Self::new_controller(i2c, sda_pin, scl_pin, freq, resets, system_clock)
}
}
impl<PINS> 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<PINS> 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<PINS> 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<PINS> 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<PINS> 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<PINS> 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<PINS> 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),

View file

@ -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<T: SubsystemReset + Deref<Target = Block>, Sda: PinId + BankPinId, Scl: PinId + BankPinId>
I2C<T, (Pin<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>), Controller>
{
/// Configures the I2C peripheral to work in controller mode
pub fn new_controller<F, SystemF>(
i2c: T,
sda_pin: Pin<Sda, FunctionI2C>,
scl_pin: Pin<Scl, FunctionI2C>,
freq: F,
resets: &mut RESETS,
system_clock: SystemF,
) -> Self
where
F: Into<Hertz<u64>>,
Sda: SdaPin<T>,
Scl: SclPin<T>,
SystemF: Into<Hertz<u32>>,
{
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<T: Deref<Target = Block>, PINS> I2C<T, PINS, Controller> {
fn validate(
addr: u16,
opt_tx_empty: Option<bool>,
opt_rx_empty: Option<bool>,
) -> 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<u32> {
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<T: Deref<Target = Block>, PINS> Read for I2C<T, PINS, Controller> {
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<T: Deref<Target = Block>, PINS> WriteRead for I2C<T, PINS, Controller> {
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<T: Deref<Target = Block>, PINS> Write for I2C<T, PINS, Controller> {
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<T: Deref<Target = Block>, PINS> eh1::Write for I2C<T, PINS, Controller> {
type Error = Error;
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
Write::write(self, addr, bytes)
}
}
#[cfg(feature = "eh1_0_alpha")]
impl<T: Deref<Target = Block>, PINS> eh1::WriteRead for I2C<T, PINS, Controller> {
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<T: Deref<Target = Block>, PINS> eh1::Read for I2C<T, PINS, Controller> {
type Error = Error;
fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> {
Read::read(self, addr, buffer)
}
}

View file

@ -0,0 +1,218 @@
use core::{future::Future, iter::Peekable, ops::Deref, task::Poll};
use super::{Block, Controller, Error, I2C};
impl<T: Deref<Target = Block>, PINS> I2C<T, PINS, Controller> {
async fn non_blocking_read_internal<'a, U: Iterator<Item = &'a mut u8> + 'a>(
&mut self,
mut buffer: Peekable<U>,
) -> 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<Item = u8>,
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<F: FnMut() -> Poll<T>, T>(mut f: F) -> T {
futures::future::poll_fn(|cx| {
// always ready to scan
cx.waker().wake_by_ref();
f()
})
.await
}
impl<T, PINS, A> embassy_traits::i2c::I2c<A> for I2C<T, PINS, Controller>
where
T: Deref<Target = Block>,
A: embassy_traits::i2c::AddressMode + 'static + Into<u16>,
{
type Error = Error;
#[rustfmt::skip]
type WriteFuture<'a>
where
Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
#[rustfmt::skip]
type ReadFuture<'a>
where
Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
#[rustfmt::skip]
type WriteReadFuture<'a>
where
Self: 'a = impl Future<Output = Result<(), Self::Error>> + '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<T, PINS, A> embassy_traits::i2c::WriteIter<A> for I2C<T, PINS, Controller>
where
T: Deref<Target = Block>,
A: embassy_traits::i2c::AddressMode + 'static + Into<u16>,
{
type Error = Error;
#[rustfmt::skip]
type WriteIterFuture<'a, U>
where
U: 'a + IntoIterator<Item = u8>,
Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
fn write_iter<'a, U>(&'a mut self, address: A, bytes: U) -> Self::WriteIterFuture<'a, U>
where
U: IntoIterator<Item = u8> + '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
}
}
}

View file

@ -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<Block, Pins> {
i2c: I2C<Block, Pins, Peripheral>,
state: State,
}
impl<T, Sda, Scl> I2C<T, (Pin<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>), Peripheral>
where
T: SubsystemReset + Deref<Target = I2CBlock>,
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<Sda, FunctionI2C>,
scl_pin: Pin<Scl, FunctionI2C>,
resets: &mut RESETS,
addr: u16,
) -> I2CPeripheralEventIterator<T, (Pin<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>)>
where
Sda: SdaPin<T>,
Scl: SclPin<T>,
{
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<T: Deref<Target = I2CBlock>, PINS> I2CPeripheralEventIterator<T, PINS> {
/// 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<T: Deref<Target = I2CBlock>, PINS> Iterator for I2CPeripheralEventIterator<T, PINS> {
type Item = I2CEvent;
fn next(&mut self) -> Option<Self::Item> {
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<Block, Sda, Scl>
I2CPeripheralEventIterator<Block, (Pin<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>)>
where
Block: SubsystemReset + Deref<Target = I2CBlock>,
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<Sda, FunctionI2C>, Pin<Scl, FunctionI2C>)) {
self.i2c.free(resets)
}
}

View file

@ -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;