use core::marker::PhantomData; use embassy_hal_common::into_ref; use pac::i2c; use crate::{pac, peripherals, Peripheral}; /// I2C error #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// I2C abort with error Abort(u32), /// 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)] pub struct Config { pub frequency: u32, pub sda_pullup: bool, pub scl_pullup: bool, } impl Default for Config { fn default() -> Self { Self { frequency: 100_000, sda_pullup: false, scl_pullup: false, } } } const TX_FIFO_SIZE: u8 = 16; const RX_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!(_peri, scl, sda);
assert!(config.frequency <= 1_000_000);
assert!(config.frequency > 0);
let p = T::regs();
unsafe {
p.ic_enable().write(|w| w.set_enable(false));
// Select controller mode & speed
p.ic_con().write(|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.io().ctrl().write(|w| w.set_funcsel(3));
sda.io().ctrl().write(|w| w.set_funcsel(3));
scl.pad_ctrl().write(|w| {
w.set_schmitt(true);
w.set_pue(config.scl_pullup);
});
sda.pad_ctrl().write(|w| {
w.set_schmitt(true);
w.set_pue(config.sda_pullup);
});
// 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_sys_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()
.write(|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 }
}
}
impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
/// Number of bytes currently in the RX FIFO
#[inline]
pub fn rx_fifo_used(&self) -> u8 {
unsafe { T::regs().ic_rxflr().read().rxflr() }
}
/// 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 {
unsafe { T::regs().ic_txflr().read().txflr() }
}
/// 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
}
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();
unsafe {
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(())
}
fn read_and_clear_abort_reason(&mut self) -> Option