Skip to content

Create i2c (and spi) generic interface #637

@Grazfather

Description

@Grazfather

The current Datagram_Device interface, while functional for basic communication, presents several limitations when working with I²C and SPI peripherals:

  1. No type-safety: Drivers cannot enforce that they receive the correct bus type (I²C vs SPI), allowing runtime errors when incompatible buses are provided.
  2. Missing bus-specific features: Important features like I²C addressing, SPI chip select management, and bus-specific configuration options are not expressible through the generic datagram interface.
  3. Clients must handle bus-specific setup (like I²C addresses) outside of the driver, leading to scattered configuration and potential inconsistencies.
  4. Less flexibility: Cannot leverage bus-specific optimizations or handle bus-specific error conditions appropriately.
  5. Difficulty adapting to Datagram_device: The current interface requires readv, which on some chips (e.g. nrf52) will not work.

I propose that we introduce dedicated I2C_Device and SPI_Device interfaces that provide type safety and expose bus-specific functionality while maintaining compatibility with existing code where appropriate.

Having specific types will additionally allow us to expose bus-specific errors (e.g. Nack) while providing compile-time validation of the provided bus.

// Old approach
pub const TempSensor = struct {
    device: mdf.base.Datagram_Device,
    
    pub fn init(device: mdf.base.Datagram_Device) TempSensor {
        // Address handling is external
        return TempSensor{ .device = device };
    }
};

// New approach
pub const TempSensor = struct {
    i2c: I2C_Device,
    
    pub fn init(i2c: I2C_Device, address: I2C_Address) !TempSensor {
        try i2c.set_address(address);
        if (!try i2c.probe_device()) return error.SensorNotFound;
        
        return TempSensor{ .i2c = i2c };
    }
    
    pub fn read_temperature(self: *TempSensor) !f32 {
        var buffer: [2]u8 = undefined;
        try self.i2c.writev_then_readv(&.{&.{0x00}}, .{&buffer}); // Register read
        // ...
    }
};

Implementation could be done rather easily in four steps:

  1. Introduce new interface
  2. Add implementations for each port's driver.
  3. Update drivers to use the bus-specific interface
  4. Update existing examples to use new driver interface.

Steps 3 & 4 would need to be done in single PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions