blob: 1672c567b9ef9739e5f03fd28ffd62479837790c [file]
// Licensed under the Apache-2.0 license
//! I2C Address types and validation
//!
//! This module provides type-safe I2C address handling with validation
//! for 7-bit addresses (the most common format).
use core::fmt;
/// Error returned when an invalid I2C address is provided.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressError {
/// Address is outside the valid 7-bit range (0x00-0x7F).
OutOfRange(u8),
/// Address is in the reserved range (0x00-0x07 or 0x78-0x7F).
Reserved(u8),
}
impl fmt::Display for AddressError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AddressError::OutOfRange(addr) => {
write!(f, "I2C address 0x{addr:02X} is out of 7-bit range")
}
AddressError::Reserved(addr) => {
write!(f, "I2C address 0x{addr:02X} is reserved")
}
}
}
}
/// A validated 7-bit I2C address.
///
/// I2C addresses are 7 bits wide (0x00-0x7F), but certain ranges are reserved:
/// - 0x00-0x07: Reserved for special purposes (general call, etc.)
/// - 0x78-0x7F: Reserved for 10-bit addressing extension
///
/// This type validates addresses upon construction and provides type safety
/// for I2C operations.
///
/// # Examples
///
/// ```rust,ignore
/// use i2c_api::I2cAddress;
///
/// // Create a valid address
/// let addr = I2cAddress::new(0x48).expect("valid address");
/// assert_eq!(addr.value(), 0x48);
///
/// // Reserved addresses are rejected by default
/// assert!(I2cAddress::new(0x00).is_err());
///
/// // Use new_unchecked for reserved addresses if needed
/// let general_call = I2cAddress::new_unchecked(0x00);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct I2cAddress(u8);
impl I2cAddress {
/// The general call address (0x00).
pub const GENERAL_CALL: I2cAddress = I2cAddress(0x00);
/// Creates a new I2C address, validating that it's in the valid range
/// and not in the reserved ranges.
///
/// # Errors
///
/// Returns `AddressError::OutOfRange` if the address is greater than 0x7F.
/// Returns `AddressError::Reserved` if the address is in 0x00-0x07 or 0x78-0x7F.
pub const fn new(addr: u8) -> Result<Self, AddressError> {
if addr > 0x7F {
return Err(AddressError::OutOfRange(addr));
}
if addr <= 0x07 || addr >= 0x78 {
return Err(AddressError::Reserved(addr));
}
Ok(I2cAddress(addr))
}
/// Creates a new I2C address without validation.
///
/// This is useful for reserved addresses like the general call address,
/// which are valid but not typically used for device communication.
///
/// # Safety
///
/// The caller must ensure the address is valid for the intended use.
/// Using an address outside 0x00-0x7F may cause undefined behavior
/// on the I2C bus.
pub const fn new_unchecked(addr: u8) -> Self {
I2cAddress(addr & 0x7F)
}
/// Returns the raw 7-bit address value.
#[inline]
pub const fn value(self) -> u8 {
self.0
}
/// Returns the address formatted for a write operation (R/W bit = 0).
///
/// The returned value is the address shifted left by 1 with bit 0 = 0.
#[inline]
pub const fn write_address(self) -> u8 {
self.0 << 1
}
/// Returns the address formatted for a read operation (R/W bit = 1).
///
/// The returned value is the address shifted left by 1 with bit 0 = 1.
#[inline]
pub const fn read_address(self) -> u8 {
(self.0 << 1) | 1
}
}
impl TryFrom<u8> for I2cAddress {
type Error = AddressError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
I2cAddress::new(value)
}
}
impl From<I2cAddress> for u8 {
fn from(addr: I2cAddress) -> u8 {
addr.0
}
}
impl fmt::Display for I2cAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:02X}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_addresses() {
// Typical device address range
assert!(I2cAddress::new(0x08).is_ok());
assert!(I2cAddress::new(0x48).is_ok());
assert!(I2cAddress::new(0x77).is_ok());
}
#[test]
fn test_reserved_low_addresses() {
for addr in 0x00..=0x07 {
assert_eq!(I2cAddress::new(addr), Err(AddressError::Reserved(addr)));
}
}
#[test]
fn test_reserved_high_addresses() {
for addr in 0x78..=0x7F {
assert_eq!(I2cAddress::new(addr), Err(AddressError::Reserved(addr)));
}
}
#[test]
fn test_out_of_range() {
assert_eq!(I2cAddress::new(0x80), Err(AddressError::OutOfRange(0x80)));
assert_eq!(I2cAddress::new(0xFF), Err(AddressError::OutOfRange(0xFF)));
}
#[test]
fn test_unchecked() {
let addr = I2cAddress::new_unchecked(0x00);
assert_eq!(addr.value(), 0x00);
// Out of range gets masked
let addr = I2cAddress::new_unchecked(0xFF);
assert_eq!(addr.value(), 0x7F);
}
#[test]
fn test_wire_format() {
let addr = I2cAddress::new(0x48).unwrap();
assert_eq!(addr.write_address(), 0x90); // 0x48 << 1
assert_eq!(addr.read_address(), 0x91); // (0x48 << 1) | 1
}
}