diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index c45fc8e26..1b2b9318c 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -104,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ClockSource` enums are now `#[non_exhaustive]` (#2912) - `macros` module is now private (#2900) - `gpio::{Input, Flex}::wakeup_enable` now returns an error instead of panicking. (#2916) +- I2C: Have a dedicated enum to specify the timeout (#2864) - Removed the `I` prefix from `DriveStrength` enum variants. (#2922) - Removed the `Attenuation` prefix from `Attenuation` enum variants. (#2922) - Renamed / changed some I2C error variants (#2844, #2862) diff --git a/esp-hal/MIGRATING-0.22.md b/esp-hal/MIGRATING-0.22.md index 7edf5579a..44dce47be 100644 --- a/esp-hal/MIGRATING-0.22.md +++ b/esp-hal/MIGRATING-0.22.md @@ -333,6 +333,26 @@ The `AckCheckFailed` variant changed to `AcknowledgeCheckFailed(AcknowledgeCheck + Err(Error::AcknowledgeCheckFailed(reason)) ``` +## I2C Configuration changes + +The timeout field in `Config` changed from `Option` to a dedicated `BusTimeout` enum. + +```diff +- timeout: Some(10) ++ timeout: BusTimeout::BusCycles(10) +``` + +```diff +- timeout: None ++ timeout: BusTimeout::Max +``` + +(Disabled isn't supported on ESP32 / ESP32-S2) +```diff +- timeout: None ++ timeout: BusTimeout::Disabled +``` + ## The crate prelude has been removed The reexports that were previously part of the prelude are available through other paths: diff --git a/esp-hal/src/i2c/master/mod.rs b/esp-hal/src/i2c/master/mod.rs index cc8f36d0d..16b05b353 100644 --- a/esp-hal/src/i2c/master/mod.rs +++ b/esp-hal/src/i2c/master/mod.rs @@ -85,6 +85,83 @@ const I2C_CHUNK_SIZE: usize = 254; // on ESP32 there is a chance to get trapped in `wait_for_completion` forever const MAX_ITERATIONS: u32 = 1_000_000; +/// Representation of I2C address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum I2cAddress { + /// 7-bit address mode type. + /// + /// Note that 7-bit addresses defined by drivers should be specified in + /// **right-aligned** form, e.g. in the range `0x00..=0x7F`. + /// + /// For example, a device that has the seven bit address of `0b011_0010`, + /// and therefore is addressed on the wire using: + /// + /// * `0b0110010_0` or `0x64` for *writes* + /// * `0b0110010_1` or `0x65` for *reads* + SevenBit(u8), +} + +impl From for I2cAddress { + fn from(value: u8) -> Self { + I2cAddress::SevenBit(value) + } +} + +/// I2C SCL timeout period. +/// +/// When the level of SCL remains unchanged for more than `timeout` bus +/// clock cycles, the bus goes to idle state. +/// +/// Default value is `BusCycles(10)`. +#[doc = ""] +#[cfg_attr( + not(esp32), + doc = "Note that the effective timeout may be longer than the value configured here." +)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, strum::Display)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +// TODO: when supporting interrupts, document that SCL = high also triggers an +// interrupt. +pub enum BusTimeout { + /// Use the maximum timeout value. + Maximum, + + /// Disable timeout control. + #[cfg(not(any(esp32, esp32s2)))] + Disabled, + + /// Timeout in bus clock cycles. + BusCycles(u32), +} + +impl BusTimeout { + fn cycles(&self) -> u32 { + match self { + #[cfg(esp32)] + BusTimeout::Maximum => 0xF_FFFF, + + #[cfg(esp32s2)] + BusTimeout::Maximum => 0xFF_FFFF, + + #[cfg(not(any(esp32, esp32s2)))] + BusTimeout::Maximum => 0x1F, + + #[cfg(not(any(esp32, esp32s2)))] + BusTimeout::Disabled => 1, + + BusTimeout::BusCycles(cycles) => *cycles, + } + } + + #[cfg(not(esp32))] + fn is_set(&self) -> bool { + matches!(self, BusTimeout::BusCycles(_) | BusTimeout::Maximum) + } +} + /// I2C-specific transmission errors #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -178,6 +255,8 @@ impl core::fmt::Display for Error { pub enum ConfigError { /// Provided bus frequency is invalid for the current configuration. FrequencyInvalid, + /// Provided timeout is invalid for the current configuration. + TimeoutInvalid, } impl core::error::Error for ConfigError {} @@ -189,6 +268,10 @@ impl core::fmt::Display for ConfigError { f, "Provided bus frequency is invalid for the current configuration" ), + ConfigError::TimeoutInvalid => write!( + f, + "Provided timeout is invalid for the current configuration" + ), } } } @@ -329,20 +412,7 @@ pub struct Config { pub frequency: HertzU32, /// I2C SCL timeout period. - /// - /// When the level of SCL remains unchanged for more than `timeout` bus - /// clock cycles, the bus goes to idle state. - /// - /// The default value is about 10 bus clock cycles. - #[doc = ""] - #[cfg_attr( - not(esp32), - doc = "Note that the effective timeout may be longer than the value configured here." - )] - #[cfg_attr(not(esp32), doc = "Configuring `None` disables timeout control.")] - #[cfg_attr(esp32, doc = "Configuring `None` equals to the maximum timeout value.")] - // TODO: when supporting interrupts, document that SCL = high also triggers an interrupt. - pub timeout: Option, + pub timeout: BusTimeout, } impl core::hash::Hash for Config { @@ -357,7 +427,7 @@ impl Default for Config { use fugit::RateExtU32; Config { frequency: 100.kHz(), - timeout: Some(10), + timeout: BusTimeout::BusCycles(10), } } } @@ -393,8 +463,11 @@ impl embedded_hal::i2c::I2c for I2c<'_, Dm> { address: u8, operations: &mut [embedded_hal::i2c::Operation<'_>], ) -> Result<(), Self::Error> { - self.transaction_impl(address, operations.iter_mut().map(Operation::from)) - .inspect_err(|_| self.internal_recover()) + self.transaction_impl( + I2cAddress::SevenBit(address), + operations.iter_mut().map(Operation::from), + ) + .inspect_err(|_| self.internal_recover()) } } @@ -424,7 +497,7 @@ impl<'d, Dm: DriverMode> I2c<'d, Dm> { fn transaction_impl<'a>( &mut self, - address: u8, + address: I2cAddress, operations: impl Iterator>, ) -> Result<(), Error> { let mut last_op: Option = None; @@ -567,27 +640,33 @@ impl<'d> I2c<'d, Blocking> { } /// Writes bytes to slave with address `address` - pub fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + pub fn write>(&mut self, address: A, buffer: &[u8]) -> Result<(), Error> { self.driver() - .write_blocking(address, buffer, true, true) + .write_blocking(address.into(), buffer, true, true) .inspect_err(|_| self.internal_recover()) } /// Reads enough bytes from slave with `address` to fill `buffer` - pub fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + pub fn read>( + &mut self, + address: A, + buffer: &mut [u8], + ) -> Result<(), Error> { self.driver() - .read_blocking(address, buffer, true, true, false) + .read_blocking(address.into(), buffer, true, true, false) .inspect_err(|_| self.internal_recover()) } /// Writes bytes to slave with address `address` and then reads enough bytes /// to fill `buffer` *in a single transaction* - pub fn write_read( + pub fn write_read>( &mut self, - address: u8, + address: A, write_buffer: &[u8], read_buffer: &mut [u8], ) -> Result<(), Error> { + let address = address.into(); + self.driver() .write_blocking(address, write_buffer, true, read_buffer.is_empty()) .inspect_err(|_| self.internal_recover())?; @@ -617,12 +696,12 @@ impl<'d> I2c<'d, Blocking> { /// to indicate writing /// - `SR` = repeated start condition /// - `SP` = stop condition - pub fn transaction<'a>( + pub fn transaction<'a, A: Into>( &mut self, - address: u8, + address: A, operations: impl IntoIterator>, ) -> Result<(), Error> { - self.transaction_impl(address, operations.into_iter().map(Operation::from)) + self.transaction_impl(address.into(), operations.into_iter().map(Operation::from)) .inspect_err(|_| self.internal_recover()) } } @@ -765,29 +844,39 @@ impl<'d> I2c<'d, Async> { } /// Writes bytes to slave with address `address` - pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { + pub async fn write>( + &mut self, + address: A, + buffer: &[u8], + ) -> Result<(), Error> { self.driver() - .write(address, buffer, true, true) + .write(address.into(), buffer, true, true) .await .inspect_err(|_| self.internal_recover()) } /// Reads enough bytes from slave with `address` to fill `buffer` - pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { + pub async fn read>( + &mut self, + address: A, + buffer: &mut [u8], + ) -> Result<(), Error> { self.driver() - .read(address, buffer, true, true, false) + .read(address.into(), buffer, true, true, false) .await .inspect_err(|_| self.internal_recover()) } /// Writes bytes to slave with address `address` and then reads enough /// bytes to fill `buffer` *in a single transaction* - pub async fn write_read( + pub async fn write_read>( &mut self, - address: u8, + address: A, write_buffer: &[u8], read_buffer: &mut [u8], ) -> Result<(), Error> { + let address = address.into(); + self.driver() .write(address, write_buffer, true, read_buffer.is_empty()) .await @@ -820,19 +909,19 @@ impl<'d> I2c<'d, Async> { /// to indicate writing /// - `SR` = repeated start condition /// - `SP` = stop condition - pub async fn transaction<'a>( + pub async fn transaction<'a, A: Into>( &mut self, - address: u8, + address: A, operations: impl IntoIterator>, ) -> Result<(), Error> { - self.transaction_impl_async(address, operations.into_iter().map(Operation::from)) + self.transaction_impl_async(address.into(), operations.into_iter().map(Operation::from)) .await .inspect_err(|_| self.internal_recover()) } async fn transaction_impl_async<'a>( &mut self, - address: u8, + address: I2cAddress, operations: impl Iterator>, ) -> Result<(), Error> { let mut last_op: Option = None; @@ -888,7 +977,7 @@ impl embedded_hal_async::i2c::I2c for I2c<'_, Async> { address: u8, operations: &mut [EhalOperation<'_>], ) -> Result<(), Self::Error> { - self.transaction_impl_async(address, operations.iter_mut().map(Operation::from)) + self.transaction_impl_async(address.into(), operations.iter_mut().map(Operation::from)) .await .inspect_err(|_| self.internal_recover()) } @@ -961,7 +1050,7 @@ fn configure_clock( scl_stop_setup_time: u32, scl_start_hold_time: u32, scl_stop_hold_time: u32, - timeout: Option, + timeout: BusTimeout, ) -> Result<(), ConfigError> { unsafe { // divider @@ -1017,13 +1106,13 @@ fn configure_clock( if #[cfg(esp32)] { register_block .to() - .write(|w| w.time_out().bits(unwrap!(timeout))); + .write(|w| w.time_out().bits(timeout.cycles())); } else { register_block .to() - .write(|w| w.time_out_en().bit(timeout.is_some()) + .write(|w| w.time_out_en().bit(timeout.is_set()) .time_out_value() - .bits(timeout.unwrap_or(1) as _) + .bits(timeout.cycles() as _) ); } } @@ -1240,7 +1329,7 @@ impl Driver<'_> { &self, source_clk: HertzU32, bus_freq: HertzU32, - timeout: Option, + timeout: BusTimeout, ) -> Result<(), ConfigError> { let source_clk = source_clk.raw(); let bus_freq = bus_freq.raw(); @@ -1252,8 +1341,9 @@ impl Driver<'_> { let sda_sample = scl_high / 2; let setup = half_cycle; let hold = half_cycle; - let timeout = timeout.map_or(Some(0xF_FFFF), |to_bus| { - Some((to_bus * 2 * half_cycle).min(0xF_FFFF)) + let timeout = BusTimeout::BusCycles(match timeout { + BusTimeout::Maximum => 0xF_FFFF, + BusTimeout::BusCycles(cycles) => check_timeout(cycles * 2 * half_cycle, 0xF_FFFF)?, }); // SCL period. According to the TRM, we should always subtract 1 to SCL low @@ -1321,7 +1411,7 @@ impl Driver<'_> { &self, source_clk: HertzU32, bus_freq: HertzU32, - timeout: Option, + timeout: BusTimeout, ) -> Result<(), ConfigError> { let source_clk = source_clk.raw(); let bus_freq = bus_freq.raw(); @@ -1352,6 +1442,11 @@ impl Driver<'_> { let scl_start_hold_time = hold - 1; let scl_stop_hold_time = hold; + let timeout = BusTimeout::BusCycles(match timeout { + BusTimeout::Maximum => 0xFF_FFFF, + BusTimeout::BusCycles(cycles) => check_timeout(cycles * 2 * half_cycle, 0xFF_FFFF)?, + }); + configure_clock( self.register_block(), 0, @@ -1364,7 +1459,7 @@ impl Driver<'_> { scl_stop_setup_time, scl_start_hold_time, scl_stop_hold_time, - timeout.map(|to_bus| (to_bus * 2 * half_cycle).min(0xFF_FFFF)), + timeout, )?; Ok(()) @@ -1378,7 +1473,7 @@ impl Driver<'_> { &self, source_clk: HertzU32, bus_freq: HertzU32, - timeout: Option, + timeout: BusTimeout, ) -> Result<(), ConfigError> { let source_clk = source_clk.raw(); let bus_freq = bus_freq.raw(); @@ -1423,6 +1518,18 @@ impl Driver<'_> { let scl_start_hold_time = hold - 1; let scl_stop_hold_time = hold - 1; + let timeout = match timeout { + BusTimeout::Maximum => BusTimeout::BusCycles(0x1F), + BusTimeout::Disabled => BusTimeout::Disabled, + BusTimeout::BusCycles(cycles) => { + let to_peri = (cycles * 2 * half_cycle).max(1); + let log2 = to_peri.ilog2(); + // Round up so that we don't shorten timeouts. + let raw = if to_peri != 1 << log2 { log2 + 1 } else { log2 }; + BusTimeout::BusCycles(check_timeout(raw, 0x1F)?) + } + }; + configure_clock( self.register_block(), clkm_div, @@ -1435,13 +1542,7 @@ impl Driver<'_> { scl_stop_setup_time, scl_start_hold_time, scl_stop_hold_time, - timeout.map(|to_bus| { - let to_peri = (to_bus * 2 * half_cycle).max(1); - let log2 = to_peri.ilog2(); - // Round up so that we don't shorten timeouts. - let raw = if to_peri != 1 << log2 { log2 + 1 } else { log2 }; - raw.min(0x1F) - }), + timeout, )?; Ok(()) @@ -1475,7 +1576,7 @@ impl Driver<'_> { /// - `cmd_iterator` is an iterator over the command registers. fn setup_write<'a, I>( &self, - addr: u8, + addr: I2cAddress, bytes: &[u8], start: bool, cmd_iterator: &mut I, @@ -1509,10 +1610,14 @@ impl Driver<'_> { if start { // Load address and R/W bit into FIFO - write_fifo( - self.register_block(), - addr << 1 | OperationType::Write as u8, - ); + match addr { + I2cAddress::SevenBit(addr) => { + write_fifo( + self.register_block(), + addr << 1 | OperationType::Write as u8, + ); + } + } } Ok(()) } @@ -1527,7 +1632,7 @@ impl Driver<'_> { /// - `cmd_iterator` is an iterator over the command registers. fn setup_read<'a, I>( &self, - addr: u8, + addr: I2cAddress, buffer: &mut [u8], start: bool, will_continue: bool, @@ -1588,7 +1693,11 @@ impl Driver<'_> { if start { // Load address and R/W bit into FIFO - write_fifo(self.register_block(), addr << 1 | OperationType::Read as u8); + match addr { + I2cAddress::SevenBit(addr) => { + write_fifo(self.register_block(), addr << 1 | OperationType::Read as u8); + } + } } Ok(()) } @@ -2023,7 +2132,7 @@ impl Driver<'_> { fn start_write_operation( &self, - address: u8, + address: I2cAddress, bytes: &[u8], start: bool, stop: bool, @@ -2060,7 +2169,7 @@ impl Driver<'_> { /// - `cmd_iterator` is an iterator over the command registers. fn start_read_operation( &self, - address: u8, + address: I2cAddress, buffer: &mut [u8], start: bool, stop: bool, @@ -2095,7 +2204,7 @@ impl Driver<'_> { /// - `cmd_iterator` is an iterator over the command registers. fn write_operation_blocking( &self, - address: u8, + address: I2cAddress, bytes: &[u8], start: bool, stop: bool, @@ -2127,7 +2236,7 @@ impl Driver<'_> { /// - `cmd_iterator` is an iterator over the command registers. fn read_operation_blocking( &self, - address: u8, + address: I2cAddress, buffer: &mut [u8], start: bool, stop: bool, @@ -2157,7 +2266,7 @@ impl Driver<'_> { /// - `cmd_iterator` is an iterator over the command registers. async fn write_operation( &self, - address: u8, + address: I2cAddress, bytes: &[u8], start: bool, stop: bool, @@ -2189,7 +2298,7 @@ impl Driver<'_> { /// - `cmd_iterator` is an iterator over the command registers. async fn read_operation( &self, - address: u8, + address: I2cAddress, buffer: &mut [u8], start: bool, stop: bool, @@ -2211,7 +2320,7 @@ impl Driver<'_> { fn read_blocking( &self, - address: u8, + address: I2cAddress, buffer: &mut [u8], start: bool, stop: bool, @@ -2233,7 +2342,7 @@ impl Driver<'_> { fn write_blocking( &self, - address: u8, + address: I2cAddress, buffer: &[u8], start: bool, stop: bool, @@ -2256,7 +2365,7 @@ impl Driver<'_> { async fn read( &self, - address: u8, + address: I2cAddress, buffer: &mut [u8], start: bool, stop: bool, @@ -2279,7 +2388,7 @@ impl Driver<'_> { async fn write( &self, - address: u8, + address: I2cAddress, buffer: &[u8], start: bool, stop: bool, @@ -2302,6 +2411,14 @@ impl Driver<'_> { } } +fn check_timeout(v: u32, max: u32) -> Result { + if v <= max { + Ok(v) + } else { + Err(ConfigError::TimeoutInvalid) + } +} + /// Peripheral state for an I2C instance. #[doc(hidden)] #[non_exhaustive]