From 0f690606bed52bf5e291bed86e94ff52d85cdeab Mon Sep 17 00:00:00 2001 From: "Jonathan Pallant (Ferrous Systems)" Date: Fri, 19 Jan 2024 16:12:23 +0000 Subject: [PATCH] Clean up the I2C impls. Also we have inherent methods, so you can ignore the Embedded HAL traits if you want (makes the examples simpler too). Whilst doing this I found out we only support 7-bit I2C addresses. I'll come back and fix that later. --- rp2040-hal/examples/i2c.rs | 3 +- rp2040-hal/examples/i2c_async.rs | 2 +- rp2040-hal/examples/i2c_async_cancelled.rs | 4 +- rp2040-hal/src/i2c.rs | 39 +++-- rp2040-hal/src/i2c/controller.rs | 195 ++++++++++++++++++--- 5 files changed, 193 insertions(+), 50 deletions(-) diff --git a/rp2040-hal/examples/i2c.rs b/rp2040-hal/examples/i2c.rs index 9f325bec1..4ad8c2755 100644 --- a/rp2040-hal/examples/i2c.rs +++ b/rp2040-hal/examples/i2c.rs @@ -17,7 +17,6 @@ use panic_halt as _; use rp2040_hal as hal; // Some traits we need -use embedded_hal_0_2::blocking::i2c::Write; use hal::fugit::RateExtU32; // A shorter alias for the Peripheral Access Crate, which provides low-level @@ -81,7 +80,7 @@ fn main() -> ! { let scl_pin: Pin<_, FunctionI2C, _> = pins.gpio19.reconfigure(); // let not_an_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); - // Create the I²C drive, using the two pre-configured pins. This will fail + // Create the I²C driver, using the two pre-configured pins. This will fail // at compile time if the pins are in the wrong mode, or if this I²C // peripheral isn't available on these pins! let mut i2c = hal::I2C::i2c1( diff --git a/rp2040-hal/examples/i2c_async.rs b/rp2040-hal/examples/i2c_async.rs index 0e5f68d63..3fc5cac9a 100644 --- a/rp2040-hal/examples/i2c_async.rs +++ b/rp2040-hal/examples/i2c_async.rs @@ -109,7 +109,7 @@ async fn demo() { } // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C - i2c.write(0x76u8, &[1, 2, 3]).await.unwrap(); + I2c::write(&mut i2c, 0x76u8, &[1, 2, 3]).await.unwrap(); // Demo finish - just loop until reset core::future::pending().await diff --git a/rp2040-hal/examples/i2c_async_cancelled.rs b/rp2040-hal/examples/i2c_async_cancelled.rs index 1ac756ce4..94235ff37 100644 --- a/rp2040-hal/examples/i2c_async_cancelled.rs +++ b/rp2040-hal/examples/i2c_async_cancelled.rs @@ -128,12 +128,12 @@ async fn demo() { // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C futures::select_biased! { - r = i2c.write(0x76u8, &v).fuse() => r.unwrap(), + r = I2c::write(&mut i2c, 0x76u8, &v).fuse() => r.unwrap(), _ = timeout.fuse() => { defmt::info!("Timed out."); } } - i2c.write(0x76u8, &v).await.unwrap(); + I2c::write(&mut i2c, 0x76u8, &v).await.unwrap(); // Demo finish - just loop until reset core::future::pending().await diff --git a/rp2040-hal/src/i2c.rs b/rp2040-hal/src/i2c.rs index 18d91287f..cd9678d55 100644 --- a/rp2040-hal/src/i2c.rs +++ b/rp2040-hal/src/i2c.rs @@ -20,7 +20,6 @@ //! ); //! //! // Scan for devices on the bus by attempting to read from them -//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Read; //! for i in 0..=127u8 { //! let mut readbuf: [u8; 1] = [0; 1]; //! let result = i2c.read(i, &mut readbuf); @@ -30,14 +29,12 @@ //! } //! } //! -//! // Write some data to a device at 0x2c -//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Write; -//! i2c.write(0x2Cu8, &[1, 2, 3]).unwrap(); +//! // Write some data to a device with 7-bit address 0x2c +//! i2c.write(0x2c_u8, &[1, 2, 3]).unwrap(); //! -//! // Write and then read from a device at 0x3a -//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_WriteRead; +//! // Write and then read from a device with 7-bit address 0x3a //! let mut readbuf: [u8; 1] = [0; 1]; -//! i2c.write_read(0x2Cu8, &[1, 2, 3], &mut readbuf).unwrap(); +//! i2c.write_read(0x3a_u8, &[1, 2, 3], &mut readbuf).unwrap(); //! ``` //! //! See [examples/i2c.rs](https://github.com/rp-rs/rp-hal/tree/main/rp2040-hal/examples/i2c.rs) @@ -92,6 +89,7 @@ pub trait ValidAddress: /// Validates the address against address ranges supported by the hardware. fn is_valid(self) -> Result<(), Error>; } + impl ValidAddress for u8 { const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_7BITS; const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_7BITS; @@ -104,35 +102,40 @@ impl ValidAddress for u8 { } } } + impl ValidAddress for u16 { const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_10BITS; const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_10BITS; fn is_valid(self) -> Result<(), Error> { - Ok(()) + if self >= 0x400 { + Err(Error::AddressOutOfRange(self.into())) + } else { + Ok(()) + } } } -/// I2C error +/// I²C Error #[non_exhaustive] pub enum Error { - /// I2C abort with error + /// I²C abort with error Abort(u32), /// User passed in a read buffer that was 0 length /// - /// This is a limitation of the RP2040 I2C peripheral. - /// If the slave ACKs its address, the I2C peripheral must read + /// This is a limitation of the RP2040 I²C peripheral. + /// If the slave ACKs its address, the I²C peripheral must read /// at least one byte before sending the STOP condition. InvalidReadBufferLength, /// User passed in a write buffer that was 0 length /// - /// This is a limitation of the RP2040 I2C peripheral. - /// If the slave ACKs its address, the I2C peripheral must write + /// This is a limitation of the RP2040 I²C peripheral. + /// If the slave ACKs its address, the I²C peripheral must write /// at least one byte before sending the STOP condition. InvalidWriteBufferLength, - /// Target i2c address is out of range + /// Target I²C address is out of range AddressOutOfRange(u16), - /// Target i2c address is reserved + /// Target I²C address is reserved AddressReserved(u16), } @@ -142,8 +145,8 @@ impl core::fmt::Debug for Error { match self { Error::InvalidReadBufferLength => write!(fmt, "InvalidReadBufferLength"), Error::InvalidWriteBufferLength => write!(fmt, "InvalidWriteBufferLength"), - Error::AddressOutOfRange(addr) => write!(fmt, "AddressOutOfRange({:x})", addr), - Error::AddressReserved(addr) => write!(fmt, "AddressReserved({:x})", addr), + Error::AddressOutOfRange(addr) => write!(fmt, "AddressOutOfRange({:?})", addr), + Error::AddressReserved(addr) => write!(fmt, "AddressReserved({:?})", addr), Error::Abort(_) => { write!(fmt, "{:?}", self.kind()) } diff --git a/rp2040-hal/src/i2c/controller.rs b/rp2040-hal/src/i2c/controller.rs index 92653f686..4f8b76e9e 100644 --- a/rp2040-hal/src/i2c/controller.rs +++ b/rp2040-hal/src/i2c/controller.rs @@ -1,8 +1,17 @@ +//! # I²C Controller-mode code +//! +//! This is for when the RP2040 is actively reading from or writing to other I²C +//! devices on the bus. +//! +//! We implement both the Embedded HAL 1.0 and legacy Embedded HAL 0.2 traits. + +#![deny(missing_docs)] + use core::{ops::Deref, task::Poll}; -use embedded_hal_0_2::blocking::i2c::{Read, Write, WriteIter, WriteIterRead, WriteRead}; -use fugit::HertzU32; use embedded_hal::i2c as eh1; +use embedded_hal_0_2::blocking::i2c::{Read, Write, WriteIter, WriteIterRead, WriteRead}; +use fugit::HertzU32; use crate::{ i2c::{Controller, Error, ValidAddress, ValidPinScl, ValidPinSda, I2C}, @@ -12,13 +21,19 @@ use crate::{ pub(crate) mod non_blocking; +// ============================================================================ +// +// Inherent Methods +// +// ============================================================================ + impl I2C where T: SubsystemReset + Deref, Sda: ValidPinSda, Scl: ValidPinScl, { - /// Configures the I2C peripheral to work in controller mode + /// Configures the I²C peripheral to work in controller mode pub fn new_controller( i2c: T, sda_pin: Sda, @@ -40,6 +55,8 @@ where i2c.ic_con().modify(|_, w| { w.speed().fast(); w.master_mode().enabled(); + w.ic_10bitaddr_master().addr_7bits(); + w.ic_10bitaddr_slave().addr_7bits(); w.ic_slave_disable().slave_disabled(); w.ic_restart_en().enabled(); w.tx_empty_ctrl().enabled() @@ -51,7 +68,7 @@ where let freq_in = system_clock.to_Hz(); - // There are some subtleties to I2C timing which we are completely ignoring here + // There are some subtleties to I²C 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 @@ -63,7 +80,7 @@ where assert!(hcnt >= 8); assert!(lcnt >= 8); - // Per I2C-bus specification a device in standard or fast mode must + // Per I²C-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. @@ -110,7 +127,7 @@ where .modify(|_, w| w.rx_fifo_full_hld_ctrl().enabled()); } - // Enable I2C block + // Enable I²C block i2c.ic_enable().write(|w| w.enable().enabled()); Self { @@ -321,11 +338,118 @@ impl, PINS> I2C { } impl, PINS> I2C { - /// Writes bytes to slave with address `address` + /// Read from a device on the I²C bus. + /// + /// Reads `rx.len()` bytes into `rx` from the device with address + /// `addr`. + /// + /// Supports 7-bit addresses (passed as a `u8`) or 10-bit addresses (passed + /// as a `u16`). + /// + /// Returns an error if the device failed to ACK the data. + /// + /// # I²C Events (contract) + /// + /// ``` text + /// RP2040: ST ADR+R ACK ACK ... NAK SP + /// Device: ACK I0 I1 ... In + /// ``` + /// + /// Where + /// + /// - `ST` = start condition + /// - `ADR+W` = device address followed by bit 0 to indicate writing + /// - `ACK` = acknowledge + /// - `NAK` = no acknowledge + /// - `Oi` = ith outgoing byte of data + /// - `SR` = repeated start condition + /// - `ADR+R` = device address followed by bit 1 to indicate reading + /// - `Ii` = ith incoming byte of data + /// - `SP` = stop condition + pub fn read(&mut self, addr: A, rx: &mut [u8]) -> Result<(), Error> + where + A: ValidAddress, + { + self.setup(addr)?; + self.read_internal(true, rx, true) + } + + /// Writes to, and then reads from, a device on the I²C bus, using slices. + /// + /// Sends the bytes in `tx` to the device with address `addr`, the reads + /// `rx.len()` bytes into `rx`. + /// + /// Useful if you want to read from, say, an I²C EEPROM, which typically + /// involves writing the EEPROM memory address first and then performing a + /// read operation to read from that address. + /// + /// Supports 7-bit addresses (passed as a `u8`) or 10-bit addresses (passed + /// as a `u16`). + /// + /// Returns an error if the device failed to ACK the data. + /// + /// # I²C Events (contract) + /// + /// ``` text + /// RP2040: ST ADR+W O0 O1 ... Om SR ADR+R ACK ACK ... NAK SP + /// Device: ACK ACK ACK ... ACK ACK I0 I1 ... In + /// ``` + /// + /// Where + /// + /// - `ST` = start condition + /// - `ADR+W` = device address followed by bit 0 to indicate writing + /// - `ACK` = acknowledge + /// - `NAK` = no acknowledge + /// - `Oi` = ith outgoing byte of data + /// - `SR` = repeated start condition + /// - `ADR+R` = device address followed by bit 1 to indicate reading + /// - `Ii` = ith incoming byte of data + /// - `SP` = stop condition + pub fn write_read(&mut self, addr: A, tx: &[u8], rx: &mut [u8]) -> Result<(), Error> + where + A: ValidAddress, + { + self.setup(addr)?; + + self.write_internal(true, tx.iter().cloned(), false)?; + self.read_internal(false, rx, true) + } + + /// Writes a slice of bytes to a device on the I²C bus. + /// + /// Sends the bytes in `tx` to the device with address `addr`. + /// + /// Supports 7-bit addresses (passed as a `u8`) or 10-bit addresses (passed + /// as a `u16`). + /// + /// Returns an error if the device failed to ACK the data. + /// + /// # I²C Events (contract) /// - /// # I2C Events (contract) + /// ``` text + /// RP2040: ST ADR+W B0 B1 ... Bn SP + /// Device: ACK ACK ACK ... ACK + /// ``` /// - /// Same as the `write` method + /// Where + /// + /// - `ST` = start condition + /// - `ADR+W` = device address followed by bit 0 to indicate writing + /// - `ACK` = acknowledge + /// - `Bi` = ith byte of data + /// - `SP` = stop condition + pub fn write(&mut self, addr: A, tx: &[u8]) -> Result<(), Error> + where + A: ValidAddress, + { + self.setup(addr)?; + self.write_internal(true, tx.iter().cloned(), true) + } + + /// Writes bytes from an iterator to a device on the I²C bus. + /// + /// See [`Self::write`] for more details. pub fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Error> where B: IntoIterator, @@ -334,12 +458,10 @@ impl, PINS> I2C { self.write_internal(true, bytes, true) } - /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a + /// Writes bytes to device with address `address` and then reads enough bytes to fill `buffer` *in a /// single transaction* /// - /// # I2C Events (contract) - /// - /// Same as the `write_read` method + /// See [`Self::write_read`] for more details. pub fn write_iter_read( &mut self, address: A, @@ -355,17 +477,17 @@ impl, PINS> I2C { self.read_internal(false, buffer, true) } - /// Execute the provided operations on the I2C bus (iterator version). + /// Execute the provided operations on the I²C bus (iterator version). /// /// Transaction contract: - /// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate. + /// - Before executing the first operation an ST is sent automatically. This is followed by ADR+R/W as appropriate. /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. - /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. + /// - Between adjacent operations of a different type an SR and ADR+R/W is sent. /// - After executing the last operation an SP is sent automatically. /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. /// /// - `ST` = start condition - /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing + /// - `ADR+R/W` = device address followed by bit 1 to indicate reading or 0 to indicate writing /// - `SR` = repeated start condition /// - `SP` = stop condition fn transaction<'op: 'iter, 'iter, A: ValidAddress>( @@ -418,18 +540,17 @@ impl, PINS> Read for I2C Result<(), Error> { - self.setup(addr)?; - self.read_internal(true, buffer, true) + // Defer to the inherent implementation + Self::read(self, addr, buffer) } } + impl, PINS> WriteRead for I2C { type Error = Error; fn write_read(&mut self, addr: A, tx: &[u8], rx: &mut [u8]) -> Result<(), Error> { - self.setup(addr)?; - - self.write_internal(true, tx.iter().cloned(), false)?; - self.read_internal(false, rx, true) + // Defer to the inherent implementation + Self::write_read(self, addr, tx, rx) } } @@ -437,8 +558,8 @@ impl, PINS> Write for I2C Result<(), Error> { - self.setup(addr)?; - self.write_internal(true, tx.iter().cloned(), true) + // Defer to the inherent implementation + Self::write(self, addr, tx) } } @@ -449,7 +570,8 @@ impl, PINS> WriteIter for I2C, { - self.write_iter(address, bytes) + // Defer to the inherent implementation + Self::write_iter(self, address, bytes) } } @@ -467,10 +589,17 @@ impl, PINS> WriteIterRead where B: IntoIterator, { - self.write_iter_read(address, bytes, buffer) + // Defer to the inherent implementation + Self::write_iter_read(self, address, bytes, buffer) } } +// ============================================================================ +// +// Embedded HAL 1.0 +// +// ============================================================================ + impl, PINS> eh1::ErrorType for I2C { type Error = Error; } @@ -483,6 +612,18 @@ impl, PINS> eh1::I2c for I2C Result<(), Self::Error> { self.transaction(address, operations.iter_mut()) } + + fn read(&mut self, addr: A, rx: &mut [u8]) -> Result<(), Self::Error> { + Self::read(self, addr, rx) + } + + fn write(&mut self, addr: A, tx: &[u8]) -> Result<(), Self::Error> { + Self::write(self, addr, tx) + } + + fn write_read(&mut self, addr: A, tx: &[u8], rx: &mut [u8]) -> Result<(), Self::Error> { + Self::write_read(self, addr, tx, rx) + } } #[cfg(feature = "i2c-write-iter")]