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.
A temperature sensor register that returns a 16-bit signed value:
from instro.i2c.types import DataFormatRegisterDef( alias="TEMPERATURE", # Human-readable name register=0x05, # Register address default_value=0x0000, # Power-on reset value format=DataFormat(transfer_bits=16, signed=True), # 16-bit signed data endianness="big", # "little" or "big" for multi-byte data # No fields - read/write the entire register value)
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:
from instro.i2c.types import FieldDeffields = { "enable": FieldDef(name="enable", lsb=0, width_bits=1), # Single bit (Bit 0) "mode": FieldDef(name="mode", lsb=1, width_bits=2), # Two bits (Bits [2:1]) "rate": FieldDef(name="rate", lsb=3, width_bits=4), # Four bits (Bits [6:3])}
When reading or writing fields, I2CInterface automatically handles the masking and bit shifting:
# Read just a specific LED fieldled3_state = i2c.read("gpio_expander", "LED_OUTPUT_STATE", field="led3")# Write to a specific LED (read-modify-write operation)i2c.write("gpio_expander", "LED_OUTPUT_STATE", 1, field="led0") # Turn on LED 0# Read the entire register (all LEDs at once)all_leds = i2c.read("gpio_expander", "LED_OUTPUT_STATE")
from instro.i2c.types import DataFormat, LinearScalingformat = DataFormat( transfer_bits=16, # Total bits transferred over I2C data_width_bits=12, # Logical data bits (if different from transfer_bits) data_lsb=4, # Starting bit position of logical data signed=True, # Whether data is signed scaling=LinearScaling(gain=0.1, offset=-40.0), # Scaling function units="°C" # Physical units)
from instro.i2c.types import LinearScaling# Temperature sensor: 0.1°C per count, -40°C offset# Raw value 500 → (500 * 0.1) - 40 = 10.0°Cscaling = LinearScaling(gain=0.1, offset=-40.0)
from instro.i2c.types import CustomScaling# ADC with voltage divider: V = (ADC_value / 4095) * 5.0 * 7.2scaling = CustomScaling( to_physical_fn=lambda x: x / 4095 * 5.0 * 7.2, to_raw_fn=lambda v: int(v / 7.2 / 5.0 * 4095))
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.
Use descriptive names: Device and register names should be clear and match your hardware documentation
Define all registers: Include all registers you’ll access, even if not all fields are defined
Use field definitions: For registers with multiple settings, define fields to simplify read-modify-write operations
Set default values: Define default_value for registers to enable reset_reg() functionality
Use scaling functions: Define scaling for all sensor data to work in physical units
Validate addresses: Ensure I2C addresses are 7-bit (0x00-0x7F) and don’t conflict
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.
Was this page helpful?
Assistant
Responses are generated using AI and may contain mistakes.