Skip to content

pi-i2c-spi

tested on simulator

Tier Tier 3
Protocol I2C through Linux i2c-dev, SPI through Linux spidev
Covers I2C temperature and humidity sensors, SPI and I2C ADCs, SPI and I2C DACs, port expanders and PWM drivers, Raspberry Pi HATs
Hazard default low
Conformance full
Tested with fake smbus2 and spidev buses with simulated TMP102, MCP3008 and MCP4921 chips

Estimated reach. Hundreds of chip models. Most I2C and SPI sensors, converters and expanders expose fixed-width registers whose values are linear in the reading, which a register map covers. Distributors list thousands of I2C and SPI parts, but chips that need calibration math (BME280, most IMU fusion), SMBus block protocols with a length byte, or multi-step command sequences are not covered, so the estimate counts only the linear register-mapped ones that hobby and lab boards commonly carry.

The pi-i2c-spi driver reads and sets I2C and SPI chips on a Raspberry Pi or any Linux board with i2c-dev and spidev, through smbus2 and spidev. The device file is the register map, so temperature sensors, ADCs, DACs and port expanders need no chip-specific code.

Supports

  • I2C chips with 8-bit, 16-bit or no register addresses, and SPI chips that reply in the same transfer, such as ADCs, or only take values, such as DACs.
  • preserve: true read-modify-write for I2C configuration registers.
  • discover() lists /dev/i2c-* and /dev/spidev* without touching a bus.

Setup

uv tool install --compile-bytecode "halkit[pi-i2c-spi] @ git+https://github.com/sohan-bhat/OpenHAL"

Enable I2C and SPI under Interface Options in sudo raspi-config, reboot, and add the hub's user to the i2c and spi groups. spidev builds only on Linux.

ParameterDefaultMeaning
busrequired1, i2c-1 or /dev/i2c-1 for I2C bus 1, spi0.1 or /dev/spidev0.1 for SPI bus 0 chip select 1. pi-i2c-spi: i2c-1 is shorthand
addressrequired for I2C7-bit address such as 0x48. Not for SPI
register_width1I2C register address bytes, 1 or 2
speed_hz500000SPI clock
mode0SPI mode, 0 to 3
timeout1 sPer transaction

Channel maps

read:
  temperature: { unit: degC, map: { register: 0x00, length: 2, signed: true, bit: 4, bits: 12, scale: 0.0625 } }
  potentiometer: { unit: V, map: { command: [0x01, 0x80, 0x00], byte: 1, length: 2, bits: 10, scale: 0.0032258 } }
write:
  shutdown: { type: bool, map: { register: 0x01, length: 2, bit: 8, bits: 1, preserve: true } }
  control_voltage: { unit: V, min: 0, max: 2.5, map: { data: [0x30, 0x00], length: 2, bits: 12, scale: 0.00080566 } }
actions:
  power_down: { map: { data: [0x00, 0x00] } }
KeyDefaultMeaning
registernoneI2C register. I2C commands and actions need one
read_lengththe field's endI2C bytes to read
commandrequired for SPI readsSPI bytes to clock out, zero-padded to the field's end
datazerosCommands and actions. The other payload bytes, or the whole SPI word
preservefalseI2C commands. Change only this field's bits
byte, length0, 1Start byte and length, 1 to 8
endianbigbig or little
signedfalseTwo's complement
bit, bits0, the restBit range from the least significant bit
scale, offset1, 0device value = raw × scale + offset
unitthe channel unitDevice unit, converted to the channel unit
on, off1, 0Raw bool values
lookupnoneRaw numbers to enum values
statenoneControl channels. Read map that shows the result, often the same register
argsnoneActions. Field map per argument

Behavior

  • Connecting changes nothing, and only the registers and bytes in the file's control channels and actions are ever sent. Put chip setup such as starting conversions in an action, so the setup passes the safety kernel.
  • Values round to the field's raw step and the reading returned shows the value sent. A value the field can't hold fails with nothing sent.
  • A control channel without state reads as the last value sent.
  • health() reads the first read channel, so make that channel a register that is safe to read often, not one that clears on read.
  • A chip that doesn't acknowledge raises DriverError with the i2cdetect command to try. Missing nodes and permission problems name the fix, wrong-length replies and unmapped raw values raise DriverError, and a hung transaction raises TimeoutError.
  • Several device files can share one bus.

Examples

  • tmp102-temperature.hal.yaml reads a TMP102 and sets the alert thresholds and shutdown bit.
  • mcp3008-adc.hal.yaml reads three channels of an MCP3008 ADC.
  • mcp4921-dac.hal.yaml sets an MCP4921 DAC that drives a fan controller's speed input.

Limitations

  • Linear conversion only, so chips such as the BME280 that need compensation formulas read as raw registers.
  • No SMBus block protocols, packet error checking or 10-bit I2C addresses.
  • SPI words are whole bytes.
  • describe() returns nothing, since chips can't report a register map.

Hazard default and safe state

Low. A DAC or driver chip can switch a real load, so match the hazard to what the chip controls. safe_state_support is file. A chip keeps the last output if the Pi stops, so wire pull resistors that make power loss safe.

Sources