| // Licensed under the Apache-2.0 license |
| |
| //! # I2C Device Implementation Traits |
| //! |
| //! This module provides application-level traits for implementing I2C devices such as sensors, |
| //! EEPROMs, display controllers, ADCs, DACs, and other peripheral devices that respond |
| //! to I2C master/controller requests. |
| //! |
| //! ## Purpose |
| //! |
| //! These traits focus on the **application logic** of how devices should respond to I2C |
| //! transactions initiated by a master/controller. They are **not** for controlling I2C |
| //! hardware controllers - that's handled by hardware abstraction layer (HAL) traits. |
| //! |
| //! ## Layer Distinction |
| //! |
| //! ```text |
| //! ┌─────────────────────────────────────────────────────────────────┐ |
| //! │ Application Layer │ |
| //! │ i2c_device.rs - Implement device behavior (this module) │ |
| //! │ Examples: Sensor readings, EEPROM storage, register access │ |
| //! ├─────────────────────────────────────────────────────────────────┤ |
| //! │ Hardware Abstraction Layer │ |
| //! │ i2c_hardware.rs - Control I2C controller peripherals │ |
| //! │ Examples: Buffer management, interrupt handling, bus control │ |
| //! └─────────────────────────────────────────────────────────────────┘ |
| //! ``` |
| //! |
| //! ## Design Philosophy |
| //! |
| //! The traits in this module are designed around **device behavior patterns**: |
| //! |
| //! - **Event-driven**: Devices respond to master-initiated transactions |
| //! - **Callback-based**: Use `on_*` methods to handle different transaction types |
| //! - **Stateful**: Devices can maintain internal state between transactions |
| //! - **Protocol-aware**: Support common I2C device patterns (register access, etc.) |
| //! |
| //! ## Common Use Cases |
| //! |
| //! ### Temperature Sensor |
| //! ```rust,ignore |
| //! struct TemperatureSensor { |
| //! temperature: f32, |
| //! address: u8, |
| //! } |
| //! |
| //! impl I2CCoreTarget for TemperatureSensor { |
| //! fn on_read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> { |
| //! let temp_bytes = self.temperature.to_le_bytes(); |
| //! buffer[..4].copy_from_slice(&temp_bytes); |
| //! Ok(4) |
| //! } |
| //! } |
| //! ``` |
| //! |
| //! ### EEPROM Device |
| //! ```rust,ignore |
| //! struct EepromDevice { |
| //! memory: [u8; 256], |
| //! address_pointer: u8, |
| //! } |
| //! |
| //! impl WriteTarget for EepromDevice { |
| //! fn on_write(&mut self, data: &[u8]) -> Result<(), Self::Error> { |
| //! if let Some(&addr) = data.first() { |
| //! self.address_pointer = addr; |
| //! // Write remaining data to memory starting at addr |
| //! for (i, &byte) in data[1..].iter().enumerate() { |
| //! if let Some(mem_slot) = self.memory.get_mut(addr as usize + i) { |
| //! *mem_slot = byte; |
| //! } |
| //! } |
| //! } |
| //! Ok(()) |
| //! } |
| //! } |
| //! |
| //! impl ReadTarget for EepromDevice { |
| //! fn on_read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error> { |
| //! let start = self.address_pointer as usize; |
| //! let end = (start + buffer.len()).min(self.memory.len()); |
| //! let bytes_to_read = end - start; |
| //! buffer[..bytes_to_read].copy_from_slice(&self.memory[start..end]); |
| //! Ok(bytes_to_read) |
| //! } |
| //! } |
| //! ``` |
| //! |
| //! ## Trait Overview |
| //! |
| //! The traits are organized in a composable hierarchy: |
| //! |
| //! - `I2CCoreTarget`: Core transaction lifecycle (required for all devices) |
| //! - `WriteTarget`: Handle write operations from master |
| //! - `ReadTarget`: Handle read operations from master |
| //! - `WriteReadTarget`: Handle combined write-read transactions |
| //! - `RegisterAccess`: Higher-level register-based access patterns |
| //! - `I2CTarget`: Convenience trait combining all capabilities |
| //! |
| //! ## Transaction Flow |
| //! |
| //! A typical I2C transaction from the device perspective: |
| //! |
| //! 1. **Address Match**: `on_address_match(address)` - Should this device respond? |
| //! 2. **Transaction Start**: `on_transaction_start(direction, repeated)` - Initialize for transaction |
| //! 3. **Data Phase**: `on_write(data)` or `on_read(buffer)` - Handle the actual data |
| //! 4. **Transaction End**: `on_stop()` - Clean up after transaction |
| //! |
| //! ## Error Handling |
| //! |
| //! All traits use associated `Error` types that must implement `embedded_hal::i2c::Error`, |
| //! providing standard I2C error conditions while allowing device-specific error extensions. |
| |
| #![allow(clippy::doc_overindented_list_items)] |
| use embedded_hal::i2c::ErrorType as I2CErrorType; |
| |
| /// Transaction response |
| pub enum TransactionResponse { |
| /// For read transactions with immediate data |
| ByteToSend(u8), |
| /// For transactions without immediate data |
| NoImmediateData, |
| } |
| |
| /// Direction of an I2C transaction. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| pub enum TransactionDirection { |
| /// Write transaction (controller writing to target). |
| Write, |
| /// Read transaction (controller reading from target). |
| Read, |
| } |
| |
| /// A convenience trait alias that represents a fully-featured I2C device implementation. |
| /// |
| /// This trait combines all the core and extended I2C device traits into a single interface: |
| /// |
| /// - [`I2CCoreTarget`]: Handles transaction lifecycle and address matching. |
| /// - [`ReadTarget`]: Supports reading data from the device. |
| /// - [`WriteTarget`]: Supports writing data to the device. |
| /// - [`WriteReadTarget`]: Supports combined write-read transactions. |
| /// - [`RegisterAccess`]: Supports register-level read/write operations. |
| /// |
| /// Implementing this trait means the device is capable of handling all standard I2C device behaviors, |
| /// making it suitable for use in generic drivers or frameworks that require full I2C functionality. |
| /// |
| /// ## When to Use |
| /// |
| /// Use this trait when you need a device that supports all I2C interaction patterns: |
| /// - Simple read/write operations |
| /// - Register-based access (common for sensors, configuration devices) |
| /// - Complex transaction sequences |
| /// - Generic device drivers that work with multiple device types |
| /// |
| /// ## Implementation Pattern |
| /// |
| /// Most implementations will implement the individual traits and get `I2CTarget` automatically: |
| /// |
| /// ```rust,ignore |
| /// struct MyComplexDevice { |
| /// registers: [u8; 256], |
| /// current_register: u8, |
| /// } |
| /// |
| /// impl I2CCoreTarget for MyComplexDevice { /* ... */ } |
| /// impl ReadTarget for MyComplexDevice { /* ... */ } |
| /// impl WriteTarget for MyComplexDevice { /* ... */ } |
| /// impl WriteReadTarget for MyComplexDevice { /* ... */ } |
| /// impl RegisterAccess for MyComplexDevice { /* ... */ } |
| /// |
| /// // Now MyComplexDevice automatically implements I2CTarget |
| /// fn use_any_device<T: I2CTarget>(device: &mut T) { |
| /// // Can use all I2C device capabilities |
| /// } |
| /// ``` |
| pub trait I2CTarget: |
| I2CCoreTarget + ReadTarget + WriteTarget + WriteReadTarget + RegisterAccess |
| { |
| } |
| |
| impl<T> I2CTarget for T where |
| T: I2CCoreTarget + ReadTarget + WriteTarget + WriteReadTarget + RegisterAccess |
| { |
| } |
| |
| /// Trait representing core I2C device behavior. |
| /// |
| /// This trait defines the fundamental methods that an I2C device must implement to handle |
| /// transactions initiated by an I2C master/controller. It covers the essential lifecycle |
| /// of I2C transactions from the device's perspective. |
| /// |
| /// ## Core Responsibilities |
| /// |
| /// - **Address matching**: Decide whether to respond to a specific I2C address |
| /// - **Transaction initialization**: Set up device state for incoming transactions |
| /// - **Transaction lifecycle**: Handle start conditions, repeated starts, and stop conditions |
| /// |
| /// ## Implementation Notes |
| /// |
| /// This trait focuses on **device logic**, not hardware control. Implementations should: |
| /// - Maintain device state (registers, memory, sensor readings, etc.) |
| /// - Implement device-specific address matching logic |
| /// - Handle transaction setup and teardown |
| /// - Prepare for data exchange (actual data handling is in `ReadTarget`/`WriteTarget`) |
| /// |
| /// For hardware I2C controller management, use hardware abstraction layer traits instead. |
| pub trait I2CCoreTarget: I2CErrorType { |
| /// Initialize the target with a specific address. |
| fn init(&mut self, address: u8) -> Result<(), Self::Error>; |
| |
| /// Called when a new I2C transaction begins. |
| /// |
| /// This method is invoked at the start of a transaction initiated by the controller (master). |
| /// It provides a flag indicating whether the transaction is a repeated start, which allows |
| /// the target (slave) device to preserve or reset internal state accordingly. |
| /// |
| /// # Arguments |
| /// |
| /// * `direction` - Indicates whether the upcoming transaction is a Read or Write. |
| /// * `repeated` - A boolean flag indicating whether this transaction is a repeated start (`true`) |
| /// or a fresh start (`false`). A repeated start means the controller has not |
| /// released the bus between transactions. |
| /// |
| /// # Return |
| /// |
| /// * `Ok(TransactionResponse::ByteToSend(byte))` - For read transactions, the first byte to send to the controller |
| /// * `Ok(TransactionResponse::NoImmediateData)` - No immediate data to send or this is a write transaction |
| /// * `Err(e)` - An error occurred during transaction setup |
| /// # Usage Model |
| /// |
| /// This method is distinct from `on_address_match(address: u8) -> bool`: |
| /// |
| /// - `on_address_match` is called during the address phase to decide whether the target should respond. |
| /// - `on_transaction_start` is called after the address is matched and before the data phase begins. |
| /// |
| /// ## Typical Use Cases: |
| /// - Reset internal state if `repeated == false`. |
| /// - Preserve context (e.g., register pointer) if `repeated == true`. |
| /// - Prepare buffers or state machines for read/write. |
| /// |
| /// ## When it's called: |
| /// - After `on_address_match` returns `true`. |
| /// - Before `on_read` or `on_write` is invoked. |
| /// |
| /// # Example |
| /// |
| /// ```rust,ignore |
| /// fn on_transaction_start(&mut self, direction: TransactionDirection, repeated: bool) { |
| /// if repeated { |
| /// // Continue using previous state |
| /// } else { |
| /// // Reset internal state |
| /// } |
| /// } |
| /// ``` |
| fn on_transaction_start( |
| &mut self, |
| direction: TransactionDirection, |
| repeated: bool, |
| ) -> Result<TransactionResponse, Self::Error>; |
| |
| /// Optional: handle stop condition or reset. |
| /// |
| /// This method is called when the master sends a stop condition, indicating the end of a transaction. |
| fn on_stop(&mut self); |
| |
| /// Optional: handle address match event. |
| /// |
| /// # Arguments |
| /// |
| /// * `address` - The address sent by the master. |
| /// |
| /// # Returns |
| /// |
| /// * `bool` - Returns `true` if the address matches the target's address, `false` otherwise. |
| fn on_address_match(&mut self, address: u8) -> bool; |
| } |
| |
| /// Trait for I2C targets that support write operations. |
| pub trait WriteTarget: I2CCoreTarget { |
| /// Called when the master initiates a write to this target. |
| /// |
| /// # Arguments |
| /// |
| /// * `data` - A slice containing the data to be written to the target. |
| /// |
| /// # Returns |
| /// |
| /// * `Result<(), I2CError>` - Returns `Ok(())` if the write is successful, or an `I2CError` if an error occurs. |
| fn on_write(&mut self, data: &[u8]) -> Result<(), Self::Error>; |
| } |
| |
| /// Trait for I2C targets that support read operations. |
| pub trait ReadTarget: I2CCoreTarget { |
| /// Called when the master initiates a read from this target. |
| /// |
| /// # Arguments |
| /// |
| /// * `buffer` - A mutable slice where the read data will be stored. |
| /// |
| /// # Returns |
| /// |
| /// * `Result<usize, I2CError>` - Returns the number of bytes read if successful, or an `I2CError` if an error occurs. |
| fn on_read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>; |
| } |
| |
| /// Trait for I2C targets that support combined write-read transactions. |
| pub trait WriteReadTarget: WriteTarget + ReadTarget { |
| /// Performs a combined write-read transaction on the device. |
| /// |
| /// This method writes data from `write_buffer` and then reads data into `read_buffer` |
| /// in a single, atomic operation (if supported by the underlying hardware). |
| /// |
| /// # Parameters |
| /// - `write_buffer`: The buffer containing data to write. |
| /// - `read_buffer`: The buffer to store the data read from the device. |
| /// |
| /// # Returns |
| /// - `Ok(usize)`: The number of bytes read into `read_buffer`. |
| /// - `Err(Self::Error)`: If the transaction fails. |
| /// |
| /// # Errors |
| /// This function returns an error if the write or read operation fails. |
| /// |
| /// # Example |
| /// ```ignore |
| /// device.on_write_read(&mut [0x01, 0x02], &mut [0; 4])?; |
| /// ``` |
| fn on_write_read( |
| &mut self, |
| write_buffer: &mut [u8], |
| read_buffer: &mut [u8], |
| ) -> Result<usize, Self::Error> { |
| self.on_write(write_buffer)?; |
| self.on_read(read_buffer) |
| } |
| } |
| |
| /// Provides register-level access to a device that supports both reading and writing operations. |
| /// |
| /// This trait combines the capabilities of `WriteTarget` and `ReadTarget` to provide a higher-level |
| /// interface for accessing device registers. It simplifies common operations like reading and writing |
| /// individual registers by address. |
| pub trait RegisterAccess: WriteTarget + ReadTarget { |
| /// Writes a single byte to a specified register address. |
| /// |
| /// # Parameters |
| /// |
| /// * `address` - The register address to write to |
| /// * `data` - The byte value to write to the register |
| /// |
| /// # Returns |
| /// |
| /// * `Ok(())` - The write operation was successful |
| /// * `Err(e)` - An error occurred during the write operation |
| fn write_register(&mut self, address: u8, data: u8) -> Result<(), Self::Error>; |
| |
| /// Reads data from a specified register address into a buffer. |
| /// |
| /// # Parameters |
| /// |
| /// * `address` - The register address to read from |
| /// * `buffer` - The buffer to store the read data |
| /// |
| /// # Returns |
| /// |
| /// * `Ok(usize)` - The number of bytes successfully read into the buffer |
| /// * `Err(e)` - An error occurred during the read operation |
| fn read_register(&mut self, address: u8, buffer: &mut [u8]) -> Result<usize, Self::Error>; |
| } |