From 077cba68f521c5aaae099521bef58c5694b893d0 Mon Sep 17 00:00:00 2001 From: Jonathan Nilsson Date: Sat, 24 Jul 2021 18:16:07 +0200 Subject: [PATCH] I2C (#56) * I2C implementation based on C SDK * Basic I2C Example --- rp2040-hal/examples/i2c.rs | 48 +++++ rp2040-hal/src/i2c.rs | 355 ++++++++++++++++++++++++++++++++++++- 2 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 rp2040-hal/examples/i2c.rs diff --git a/rp2040-hal/examples/i2c.rs b/rp2040-hal/examples/i2c.rs new file mode 100644 index 0000000..7d25f86 --- /dev/null +++ b/rp2040-hal/examples/i2c.rs @@ -0,0 +1,48 @@ +//! Sends a message using i2c +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use embedded_hal::blocking::i2c::Write; +use embedded_time::rate::Extensions; +use hal::gpio::FunctionI2C; +use hal::i2c::I2C; +use hal::pac; +use hal::sio::Sio; +use panic_halt as _; +use rp2040_hal as hal; + +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; + +const SYS_HZ: u32 = 125_000_000_u32; + +#[entry] +fn main() -> ! { + let mut pac = pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let sda_pin = pins.gpio18.into_mode::(); + let scl_pin = pins.gpio19.into_mode::(); + + let mut i2c = I2C::i2c1( + pac.I2C1, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + SYS_HZ.Hz(), + ); + + i2c.write(0x2c, &[1, 2, 3]).unwrap(); + + loop {} +} diff --git a/rp2040-hal/src/i2c.rs b/rp2040-hal/src/i2c.rs index 253b33d..3c0679c 100644 --- a/rp2040-hal/src/i2c.rs +++ b/rp2040-hal/src/i2c.rs @@ -1,3 +1,356 @@ //! Inter-Integrated Circuit (I2C) bus +// Based on: https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_i2c/i2c.c +// Structure from: https://github.com/japaric/stm32f30x-hal/blob/master/src/i2c.rs // See [Chapter 4 Section 3](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details -// TODO + +use crate::{ + gpio::pin::bank0::{ + BankPinId, Gpio0, Gpio1, Gpio10, Gpio11, Gpio12, Gpio13, Gpio14, Gpio15, Gpio16, Gpio17, + Gpio18, Gpio19, Gpio2, Gpio20, Gpio21, Gpio26, Gpio27, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7, + Gpio8, Gpio9, + }, + gpio::pin::{FunctionI2C, Pin, PinId}, + resets::SubsystemReset, + typelevel::Sealed, +}; +use embedded_time::rate::Hertz; +use hal::blocking::i2c::{Write, WriteRead}; +use rp2040_pac::{I2C0, I2C1, RESETS}; + +/// I2C error +#[non_exhaustive] +#[derive(Debug)] +pub enum Error { + /// I2C abort with error + Abort(u32), +} + +/// SCL pin +pub trait SclPin: Sealed {} + +/// SDA pin +pub trait SdaPin: Sealed {} + +impl SdaPin for Gpio0 {} +impl SclPin for Gpio1 {} + +impl SdaPin for Gpio2 {} +impl SclPin for Gpio3 {} + +impl SdaPin for Gpio4 {} +impl SclPin for Gpio5 {} + +impl SdaPin for Gpio6 {} +impl SclPin for Gpio7 {} + +impl SdaPin for Gpio8 {} +impl SclPin for Gpio9 {} + +impl SdaPin for Gpio10 {} +impl SclPin for Gpio11 {} + +impl SdaPin for Gpio12 {} +impl SclPin for Gpio13 {} + +impl SdaPin for Gpio14 {} +impl SclPin for Gpio15 {} + +impl SdaPin for Gpio16 {} +impl SclPin for Gpio17 {} + +impl SdaPin for Gpio18 {} +impl SclPin for Gpio19 {} + +impl SdaPin for Gpio20 {} +impl SclPin for Gpio21 {} + +impl SdaPin for Gpio26 {} +impl SclPin for Gpio27 {} + +/// I2C peripheral operating in master mode +pub struct I2C { + i2c: I2C, + pins: Pins, +} + +fn i2c_reserved_addr(addr: u8) -> bool { + (addr & 0x78) == 0 || (addr & 0x78) == 0x78 +} + +macro_rules! hal { + ($($I2CX:ident: ($i2cX:ident),)+) => { + $( + impl I2C<$I2CX, (Pin, Pin)> { + /// Configures the I2C peripheral to work in master mode + pub fn $i2cX( + i2c: $I2CX, + sda_pin: Pin, + scl_pin: Pin, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into>, + Sda: SdaPin<$I2CX>, + 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.write(|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 hcnt = period * 3 / 5; // oof this one hurts + let lcnt = period - hcnt; + + // 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 + .write(|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) + } + } + + 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 + assert!(bytes.len() < 256 && bytes.len() > 0); + + assert!(addr < 0x80); + assert!(!i2c_reserved_addr(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 + assert!(bytes.len() < 256 && bytes.len() > 0); + assert!(buffer.len() < 256 && buffer.len() > 0); + + assert!(addr < 0x80); + assert!(!i2c_reserved_addr(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; + } + } + + for (i, byte) in buffer.iter_mut().enumerate() { + let first = i == 0; + let last = i == bytes.len() - 1; + + while 16 - self.i2c.ic_txflr.read().txflr().bits() > 0 {} + + 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(()) + } + } + } + )+ + } +} + +hal! { + I2C0: (i2c0), + I2C1: (i2c1), +}