i2c-controller

CLI reference

glasgow run i2c-controller

Initiate transactions on the I²C bus.

The following optional bus features are supported:

  • Clock stretching

  • Device ID

usage: glasgow run i2c-controller [-h] [-V SPEC] [--scl PIN] [--sda PIN]
                                  [-f FREQ]
                                  OPERATION ...
-h, --help

show this help message and exit

-V <spec>, --voltage <spec>

configure I/O port voltage to SPEC (e.g.: ‘3.3’, ‘A=5.0,B=3.3’, ‘A=SA’)

--scl <pin>

bind the applet I/O line ‘scl’ to PIN (default: ‘A0’, required)

--sda <pin>

bind the applet I/O line ‘sda’ to PIN (default: ‘A1’, required)

-f <freq>, --frequency <freq>

set SCL frequency to FREQ kHz (default: 100, range: 100…4000)

glasgow run i2c-controller scan

usage: glasgow run i2c-controller scan [-h] [--device-id] [--probe]
                                       [--accept-risk]
-h, --help

show this help message and exit

--device-id

read device ID from devices responding to scan

--probe

(DANGEROUS) attempt to detect device identity by probing known registers

--accept-risk

accept risks inherent in blindly probing I²C devices

API reference

exception glasgow.applet.interface.i2c_controller.I2CNotAcknowledged
class glasgow.applet.interface.i2c_controller.I2CControllerInterface(logger: Logger, assembly: AbstractAssembly, *, scl: GlasgowPin, sda: GlasgowPin)
property clock: ClockDivisor

SCL clock divisor.

transaction()

Perform a transaction.

While a transaction is active, calls to write() and read() do not generate a STOP condition; only one STOP condition is generated once the transaction ends. This also means that each call to write() or read() after the first such call in a transaction will generate a repeated START condition.

For example, to perform S 0x50 nW 0x01 A Sr 0x50 R 0x?? nA 0x?? P (read of two bytes from a 24-series single address byte EEPROM, starting at address 0x01), use the following code:

async with iface.transaction():
    await iface.write(0x50, [0x01])
    data = await iface.read(0x50, 2)

An empty transaction (where the body does not call write() or read()) is allowed and produces no bus activity. (A START condition followed by a STOP condition is prohibited by the I²C specification.)

async write(address: int, data: Buffer)

Write bytes.

Generates a START condition followed by a WRITE target address ((address << 1) | 0), writes data, then generates a STOP condition (unless used within a transaction).

Raises:

I2CNotAcknowledged – If either the target address or the written data receives a not-acknowledgement.

async read(address: int, count: int) bytes

Read bytes.

Generates a START condition followed by a READ target address ((address << 1) | 1), reads data, then generates a STOP condition (unless used within a transaction).

The I²C bus design requires count to be 1 or more.

Raises:

I2CNotAcknowledged – If the target address receives a not-acknowledgement.

async ping(address: int) bool

Check address for presence.

Generates a START condition followed by a WRITE target address, then generates a STOP condition (unless used within a transaction). This is done using a write() call with no data.

Returns True if the target adddress receives an acknowledgement, False otherwise.

async scan(addresses: range = range(0b0001_000, 0b1111_000)) set[int]

Scan address range for presence.

Calls ping() for each of addresses. The default address range includes every non-reserved I²C address.

Returns the set of addresses receiving an acknowledgement.

async device_id(address: int) tuple[int, int, int]

Retrieve Device ID.

The standard I²C Device ID command (which uses the reserved address 0b1111_100) must not be confused with various vendor-specific device identifiers (which use a vendor-specific mechanism). This command is optional and rarely implemented.

Returns a 3-tuple (manufacturer, part_ident, revision).

Raises:

I2CNotAcknowledged – If the command is not implemented.

async probe(address: int, sequence: list[ProbeStep]) bool

Run a probe sequence.

Executes the sequence against an I²C target at address.

Danger

This is an inherently dangerous action. If the device at address does not conform to the assumptions used when designing sequence, the outcome is unpredictable and may cause damage to the device and/or the assembly it is a part of.

Returns True if the sequence matches, False otherwise.

class glasgow.arch.i2c.ProbeStep(type: Type, data: bytes | None = None, mask: bytes | None = None)

One step of a device probe sequence.

Describes an action taken by an I²C controller taken to check whether a specific I²C target address matches the identity of a particular chip.

While probe sequences can be created by constructing the underlying Python objects, it is recomended to use parse() with a compact string representation. Not all probe sequences are well-formed; use verify() to ensure that a sequence describes a legal I²C transaction.

The string representation is a whitespace-separated sequence of the following tokens (chosen to resemble the diagrams in the I²C specification):

  • S: START condition.

  • Sr: repeated START condition.

  • P: STOP condition.

  • AW: WRITE target address.

  • AR: READ target address.

  • 0dDDD where DDD is a 1 to 3 digit decimal number: data to read or write, in decimal.

  • 0xHH where H is a hex digit or ?: data to read or write, in hexadecimal.

  • 0bBBBBBBBB where B is a binary digit or ?: data to read or write, in binary.

The AW and AR tokens refer to the actual device address, which is not known until the moment when the probing is done; probe sequences are address-independent.

Consider the following example probe sequences:

  • S AW 0xD0 Sr AR 0x60 P: detects Bosch BME280 sensors by writing 0xD0 to select the register, then reading the register value and comparing it with 0x60.

  • S AW 117 Sr AR 0b?110100? P: detects InvenSense MPU-60X0 by writing 117 to select the register, then reading the register value and comparing it with 0b01101000 while masking off (ignoring) the first and last bit.

enum Type(value)

Valid values are as follows:

Start = <Type.Start: 'S'>
RepStart = <Type.RepStart: 'Sr'>
Stop = <Type.Stop: 'P'>
AddrWrite = <Type.AddrWrite: 'AW'>
AddrRead = <Type.AddrRead: 'AR'>
DataWrite = <Type.DataWrite: 'DW'>
DataRead = <Type.DataRead: 'DR'>
type: Type

Step type.

data: bytes | None = None

Data bytes. Only present if type in (Type.DataWrite, Type.DataRead).

mask: bytes | None = None

Mask bytes. Only present if type in (Type.MaskWrite, Type.MaskRead).

classmethod parse(source: str) list[Self]

Parse a string into a probe sequence.

Returns a sequence of steps corresponding to source. Does not perform any semantic correctness checks; use the check() method for that.

Raises:

SyntaxError – If the syntax of source is invalid.

classmethod check(steps: list[Self])

Verify well-formedness of a probe sequence.

Raises:

ValueError – If the sequence is not allowed by the I²C specification.

__str__() str

Unparse the step.

Converts the step to a representation recognized by parse().

class glasgow.arch.i2c.ProbeDevice(vendor: str, product: str, addresses: set[int], sequence: str)
vendor: str

Vendor name.

If the company has been acquired, list the old and the new names separated by /, e.g.: InvenSense/TDK.

product: str

Product name.

If the probe sequence identifies a set of products, separate every product name by /. e.g.: FUSB302BMPX/FUSB302BVMPX/FUSB302BUCX.

addresses: set[int]

Set of every I2C address the device can be configured to use.

sequence: list[ProbeStep]

Probe sequence positively identifying this specific device.

property name: str

Device name.

Returns f"{self.vendor} {self.product}".