use core::future; use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::{into_ref, PeripheralRef}; use pac::i2c; use super::{ i2c_reserved_addr, AbortReason, Async, Blocking, Error, Instance, InterruptHandler, Mode, SclPin, SdaPin, FIFO_SIZE, }; use crate::gpio::sealed::Pin; use crate::gpio::AnyPin; use crate::interrupt::typelevel::{Binding, Interrupt}; use crate::{interrupt, pac, peripherals, Peripheral}; /// I2C error abort reason #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AbortReason { /// A bus operation was not acknowledged, e.g. due to the addressed device /// not being available on the bus or the device not being ready to process /// requests at the moment NoAcknowledge, /// The arbitration was lost, e.g. electrical problems with the clock signal ArbitrationLoss, /// Transmit ended with data still in fifo TxNotEmpty(u16), Other(u32), } /// I2C error #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// I2C abort with error Abort(AbortReason), /// 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(u16), /// Target i2c address is reserved AddressReserved(u16), } #[non_exhaustive] #[derive(Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Config { pub frequency: u32, } impl Default for Config { fn default() -> Self { Self { frequency: 100_000 } } } pub const FIFO_SIZE: u8 = 16; pub struct I2c<'d, T: Instance, M: Mode> { phantom: PhantomData<(&'d mut T, M)>, } impl<'d, T: Instance> I2c<'d, T, Blocking> { pub fn new_blocking( peri: impl Peripheral
+ 'd, scl: impl Peripheral
> + 'd, sda: impl Peripheral
> + 'd, config: Config, ) -> Self { into_ref!(scl, sda); Self::new_inner(peri, scl.map_into(), sda.map_into(), config) } } impl<'d, T: Instance> I2c<'d, T, Async> { pub fn new_async( peri: impl Peripheral
+ 'd, scl: impl Peripheral
> + 'd, sda: impl Peripheral
> + 'd,
_irq: impl Binding + 'd,
scl: PeripheralRef<'d, AnyPin>,
sda: PeripheralRef<'d, AnyPin>,
config: Config,
) -> Self {
into_ref!(_peri);
assert!(config.frequency <= 1_000_000);
assert!(config.frequency > 0);
let p = T::regs();
let reset = T::reset();
crate::reset::reset(reset);
crate::reset::unreset_wait(reset);
p.ic_enable().write(|w| w.set_enable(false));
// Select controller mode & speed
p.ic_con().modify(|w| {
// Always use "fast" mode (<= 400 kHz, works fine for standard
// mode too)
w.set_speed(i2c::vals::Speed::FAST);
w.set_master_mode(true);
w.set_ic_slave_disable(true);
w.set_ic_restart_en(true);
w.set_tx_empty_ctrl(true);
});
// Set FIFO watermarks to 1 to make things simpler. This is encoded
// by a register value of 0.
p.ic_tx_tl().write(|w| w.set_tx_tl(0));
p.ic_rx_tl().write(|w| w.set_rx_tl(0));
// Configure SCL & SDA pins
scl.gpio().ctrl().write(|w| w.set_funcsel(3));
sda.gpio().ctrl().write(|w| w.set_funcsel(3));
scl.pad_ctrl().write(|w| {
w.set_schmitt(true);
w.set_ie(true);
w.set_od(false);
w.set_pue(true);
w.set_pde(false);
});
sda.pad_ctrl().write(|w| {
w.set_schmitt(true);
w.set_ie(true);
w.set_od(false);
w.set_pue(true);
w.set_pde(false);
});
// Configure baudrate
// 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 clk_base = crate::clocks::clk_peri_freq();
let period = (clk_base + config.frequency / 2) / config.frequency;
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 config.frequency < 1_000_000 {
// sda_tx_hold_count = clk_base [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.
((clk_base * 3) / 10_000_000) + 1
} else {
// fast mode plus requires a clk_base > 32MHz
assert!(clk_base >= 32_000_000);
// sda_tx_hold_count = clk_base [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.
((clk_base * 3) / 25_000_000) + 1
};
assert!(sda_tx_hold_count <= lcnt - 2);
p.ic_fs_scl_hcnt().write(|w| w.set_ic_fs_scl_hcnt(hcnt as u16));
p.ic_fs_scl_lcnt().write(|w| w.set_ic_fs_scl_lcnt(lcnt as u16));
p.ic_fs_spklen()
.write(|w| w.set_ic_fs_spklen(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }));
p.ic_sda_hold()
.modify(|w| w.set_ic_sda_tx_hold(sda_tx_hold_count as u16));
// Enable I2C block
p.ic_enable().write(|w| w.set_enable(true));
Self { phantom: PhantomData }
}
fn setup(addr: u16) -> Result<(), Error> {
if addr >= 0x80 {
return Err(Error::AddressOutOfRange(addr));
}
if i2c_reserved_addr(addr) {
return Err(Error::AddressReserved(addr));
}
let p = T::regs();
p.ic_enable().write(|w| w.set_enable(false));
p.ic_tar().write(|w| w.set_ic_tar(addr));
p.ic_enable().write(|w| w.set_enable(true));
Ok(())
}
#[inline]
fn tx_fifo_full() -> bool {
Self::tx_fifo_capacity() == 0
}
#[inline]
fn tx_fifo_capacity() -> u8 {
let p = T::regs();
FIFO_SIZE - p.ic_txflr().read().txflr()
}
#[inline]
fn rx_fifo_len() -> u8 {
let p = T::regs();
p.ic_rxflr().read().rxflr()
}
fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> {
let p = T::regs();
let abort_reason = p.ic_tx_abrt_source().read();
if abort_reason.0 != 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.
p.ic_clr_tx_abrt().read();
let reason = if abort_reason.abrt_7b_addr_noack()
| abort_reason.abrt_10addr1_noack()
| abort_reason.abrt_10addr2_noack()
{
AbortReason::NoAcknowledge
} else if abort_reason.arb_lost() {
AbortReason::ArbitrationLoss
} else {
AbortReason::Other(abort_reason.0)
};
Err(Error::Abort(reason))
} else {
Ok(())
}
}
fn read_blocking_internal(&mut self, read: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> {
if read.is_empty() {
return Err(Error::InvalidReadBufferLength);
}
let p = T::regs();
let lastindex = read.len() - 1;
for (i, byte) in read.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() {}
p.ic_data_cmd().write(|w| {
w.set_restart(restart && first);
w.set_stop(send_stop && last);
w.set_cmd(true);
});
while Self::rx_fifo_len() == 0 {
self.read_and_clear_abort_reason()?;
}
*byte = p.ic_data_cmd().read().dat();
}
Ok(())
}
fn write_blocking_internal(&mut self, write: &[u8], send_stop: bool) -> Result<(), Error> {
if write.is_empty() {
return Err(Error::InvalidWriteBufferLength);
}
let p = T::regs();
for (i, byte) in write.iter().enumerate() {
let last = i == write.len() - 1;
p.ic_data_cmd().write(|w| {
w.set_stop(send_stop && last);
w.set_dat(*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 !p.ic_raw_intr_stat().read().tx_empty() {}
let abort_reason = self.read_and_clear_abort_reason();
if abort_reason.is_err() || (send_stop && last) {
// If the transaction was aborted or if it completed
// successfully wait until the STOP condition has occurred.
while !p.ic_raw_intr_stat().read().stop_det() {}
p.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.
abort_reason?;
}
Ok(())
}
// =========================
// Blocking public API
// =========================
pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> {
Self::setup(address.into())?;
self.read_blocking_internal(read, true, true)
// Automatic Stop
}
pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> {
Self::setup(address.into())?;
self.write_blocking_internal(write, true)
}
pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
Self::setup(address.into())?;
self.write_blocking_internal(write, false)?;
self.read_blocking_internal(read, true, true)
// Automatic Stop
}
}
mod eh02 {
use super::*;
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> {
type Error = Error;
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_read(address, buffer)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> {
type Error = Error;
fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(address, bytes)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> {
type Error = Error;
fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_write_read(address, bytes, buffer)
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> {
type Error = Error;
fn exec(
&mut self,
address: u8,
operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
Self::setup(address.into())?;
for i in 0..operations.len() {
let last = i == operations.len() - 1;
match &mut operations[i] {
embedded_hal_02::blocking::i2c::Operation::Read(buf) => {
self.read_blocking_internal(buf, false, last)?
}
embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?,
}
}
Ok(())
}
}
}
#[cfg(feature = "unstable-traits")]
mod eh1 {
use super::*;
impl embedded_hal_1::i2c::Error for Error {
fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
match *self {
Self::Abort(AbortReason::ArbitrationLoss) => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
Self::Abort(AbortReason::NoAcknowledge) => {
embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address)
}
Self::Abort(AbortReason::TxNotEmpty(_)) => embedded_hal_1::i2c::ErrorKind::Other,
Self::Abort(AbortReason::Other(_)) => embedded_hal_1::i2c::ErrorKind::Other,
Self::InvalidReadBufferLength => embedded_hal_1::i2c::ErrorKind::Other,
Self::InvalidWriteBufferLength => embedded_hal_1::i2c::ErrorKind::Other,
Self::AddressOutOfRange(_) => embedded_hal_1::i2c::ErrorKind::Other,
Self::AddressReserved(_) => embedded_hal_1::i2c::ErrorKind::Other,
}
}
}
impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> {
type Error = Error;
}
impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> {
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_read(address, read)
}
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
self.blocking_write(address, write)
}
fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
self.blocking_write_read(address, write, read)
}
fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
Self::setup(address.into())?;
for i in 0..operations.len() {
let last = i == operations.len() - 1;
match &mut operations[i] {
embedded_hal_1::i2c::Operation::Read(buf) => self.read_blocking_internal(buf, false, last)?,
embedded_hal_1::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?,
}
}
Ok(())
}
}
}
#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
mod nightly {
use embedded_hal_1::i2c::Operation;
use embedded_hal_async::i2c::AddressMode;
use super::*;
impl<'d, A, T> embedded_hal_async::i2c::I2c for I2c<'d, T, Async>
where
A: AddressMode + Into