rp-hal-boards/rp2040-hal/src/i2c/controller.rs

301 lines
10 KiB
Rust
Raw Normal View History

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>
2021-11-08 23:23:28 +11:00
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)
}
}