Skip to main content

System Definition

The SystemDefinition is a required configuration object that describes all I2C devices in your system, their registers, commands, and data formats. It serves as the bridge between your high-level application code and the low-level I2C hardware details.

Overview

When you create a I2CInterface instrument, you must provide a SystemDefinition that describes:
  • All I2C devices on the bus (by name and I2C address)
  • For register-based devices: register maps, bit fields, and data formats
  • For command-based devices: command definitions and response formats
The SystemDefinition enables I2CInterface to:
  • Access devices and registers by human-readable names instead of raw addresses
  • Automatically handle bit field extraction and masking
  • Convert between raw register values and physical units using scaling functions
  • Validate and structure command operations

SystemDefinition Structure

The SystemDefinition is a container that holds all device definitions:

Adding Devices

Add devices to the system definition using add_device():

Retrieving Devices

Access devices by name when using I2CInterface methods:

Device Types

I2CInterface supports defining two types of I2C devices in the SystemDefinition.
  1. RegisterDevice - Devices that use register addressing (e.g., GPIO expanders, sensors with register maps)
  2. CommandDevice - Devices that respond to commands (e.g., ADCs that accept command bytes)
Both device types require:
  • name: Human-readable identifier used in I2CInterface method calls
  • address: 7-bit I2C address (0x00 to 0x7F)

Register-Based Devices

RegisterDevice is used for devices that expose a register map, where each register has an address and can contain multiple bit fields.

RegisterDevice Configuration

RegisterDevice Parameters

  • name: Device identifier (used in i2c.read(), i2c.write(), etc.)
  • address: 7-bit I2C device address
  • addr_width_bytes: Number of bytes for register addresses (1 or 2). Most devices use 1 byte.
  • registers: Dictionary of RegisterDef objects keyed by register alias

Register Definitions

Each register is defined with a RegisterDef. Registers can be simple (no bit fields) or contain multiple bit fields for accessing individual settings.

Example 1: Simple Register (No Bit Fields)

A temperature sensor register that returns a 16-bit signed value:

Example 2: Register with Bit Fields

A GPIO output register where each bit controls an LED:

Bit Fields

Bit fields allow you to access specific bits within a register without manually masking and shifting. Each field can span multiple bits if needed. FieldDef Parameters:
  • name: Field identifier
  • lsb: Least significant bit position (0-indexed from the right)
  • width_bits: Number of bits in the field (default: 1)
Example with Multi-Bit Fields:
When reading or writing fields, I2CInterface automatically handles the masking and bit shifting:

Command-Based Devices

CommandDevice is used for devices that respond to command bytes rather than register addressing (e.g., ADCs that accept selection commands).

CommandDevice Configuration

CommandDevice Parameters

  • name: Device identifier (used in i2c.query())
  • address: 7-bit I2C device address
  • data_format: DataFormat for command responses (required)
  • endianness: Byte order for multi-byte responses (“little” or “big”)
  • valid_commands: Dictionary of CommandDef objects defining command enums
  • batch_commands: Predefined combinations of commands (list of enum values)

Batch Commands

Batch commands allow you to combine multiple command enum values using OR operations:
When you call i2c.query("adc", "read_ch0_fast"), I2CInterface automatically ORs ADCChannel.CH0 | FastMode.ENABLE and sends the result.

Data Format

The DataFormat class defines how raw I2C data is interpreted, extracted, and scaled to physical units.

DataFormat Overview

DataFormat Parameters

  • transfer_bits: Total bits transferred in the I2C transaction (must be multiple of 8)
  • data_width_bits: Logical data width if different from transfer bits (None = use all transfer bits)
  • data_lsb: Starting bit position of logical data (default: 0)
  • signed: Whether to interpret data as signed (2’s complement) (default: False)
  • scaling: Optional ScalingFunction to convert raw values to physical units
  • units: Physical units string (default: "")

Data Extraction

The DataFormat handles several data extraction scenarios: 1. Full Transfer Width (no extraction)
2. Subset of Transfer Bits
3. Signed Data

Scaling Functions

Scaling functions convert raw integer values to physical units and vice versa.

Linear Scaling

Most sensors use linear scaling:
Formula: physical = offset + gain × raw

Custom Scaling

For non-linear sensors or complex conversions:
CustomScaling Parameters:
  • to_physical_fn: Function to convert raw integer → physical float
  • to_raw_fn: Optional function to convert physical float → raw integer (required for write operations)
Inverse Transformation Required for WritesIf you plan to write values to registers that use custom scaling, you must provide to_raw_fn. Otherwise, NotImplementedError will be raised when attempting to convert physical values back to raw.

Complete Examples

Example 1: GPIO Expander (RegisterDevice)

A common use case: configuring a GPIO expander with multiple registers and bit fields.
Usage:

Example 2: Temperature Sensor (CommandDevice)

A command-based ADC that reads temperature when sent a command.
Usage:

Example 3: Mixed System

A system with both register-based and command-based devices:

Best Practices

  1. Use descriptive names: Device and register names should be clear and match your hardware documentation
  2. Define all registers: Include all registers you’ll access, even if not all fields are defined
  3. Use field definitions: For registers with multiple settings, define fields to simplify read-modify-write operations
  4. Set default values: Define default_value for registers to enable reset_reg() functionality
  5. Use scaling functions: Define scaling for all sensor data to work in physical units
  6. Validate addresses: Ensure I2C addresses are 7-bit (0x00-0x7F) and don’t conflict
  7. Document units: Always specify units in DataFormat for clarity
SystemDefinition ReusabilityYou can reuse a SystemDefinition across multiple I2CInterface instances if they share the same I2C bus configuration. Create the definition once and pass it to multiple instruments.