* I2C: prepare to support 10-bit addresses later

* Introduce I2C Timeout enum

* Migration guide

* CHANGELOG.md

* Timeout::Maximum

* Internal Enum

* Check timeout

* Address review comments

* Timeout -> SclTimeout

* Remove `as_u8`

* Implement idea from review

* Review

* Renaming

* Fix migration guide

---------

Co-authored-by: Juraj Sadel <juraj.sadel@espressif.com>
This commit is contained in:
Björn Quentin 2025-01-14 15:16:02 +01:00 committed by GitHub
parent 2da7cbc812
commit 5e05402e98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 210 additions and 72 deletions

View File

@ -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)

View File

@ -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<u32>` 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:

View File

@ -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<u8> 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<u32>,
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<Dm: DriverMode> 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<Item = Operation<'a>>,
) -> Result<(), Error> {
let mut last_op: Option<OpKind> = 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<A: Into<I2cAddress>>(&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<A: Into<I2cAddress>>(
&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<A: Into<I2cAddress>>(
&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<I2cAddress>>(
&mut self,
address: u8,
address: A,
operations: impl IntoIterator<Item = &'a mut Operation<'a>>,
) -> 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<A: Into<I2cAddress>>(
&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<A: Into<I2cAddress>>(
&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<A: Into<I2cAddress>>(
&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<I2cAddress>>(
&mut self,
address: u8,
address: A,
operations: impl IntoIterator<Item = &'a mut Operation<'a>>,
) -> 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<Item = Operation<'a>>,
) -> Result<(), Error> {
let mut last_op: Option<OpKind> = 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<u32>,
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<u32>,
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<u32>,
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<u32>,
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<u32, ConfigError> {
if v <= max {
Ok(v)
} else {
Err(ConfigError::TimeoutInvalid)
}
}
/// Peripheral state for an I2C instance.
#[doc(hidden)]
#[non_exhaustive]