I2C Controller Method Compatibility¶
PxI2CController exposes a high-level I2C controller API that matches MicroPython machine.I2C and CircuitPython. If you already use writeto, readfrom, readfrom_mem, or scan on embedded firmware, you can port that code to AQPXLIB with minimal changes.
The main setup difference is that bus pins are configured on PxI2CBus, and the controller must be attached to the bus before any transaction.
See
Refer to CircuitPython/MicroPython documentation for the original API definitions.
Setup¶
MicroPython constructs the bus and controller in one step. AQPXLIB separates bus creation from the controller object:
| MicroPython | AQPXLIB |
|---|---|
I2C(freq=400000) |
PxI2CBus(px, scl=..., sda=...) |
| (same object) | PxI2CController(px) + attach_to_bus(i2c_bus) |
from aqpxlib import AqProtocolExerciser
from aqpxlib.i2c import PxI2CBus, PxI2CController
with AqProtocolExerciser.connect(port=60600) as px:
i2c_bus = PxI2CBus(px, scl=0, sda=1)
i2c = PxI2CController(px)
i2c.attach_to_bus(i2c_bus)
addrs = i2c.scan()
print(f"Found devices at: {[hex(a) for a in addrs]}")
i2c.detach_from_bus()
Method compatibility¶
| MicroPython / CircuitPython | PxI2CController |
Notes |
|---|---|---|
scan() |
scan() |
Scans 0x08-0x77, returns sorted 7-bit addresses |
writeto(addr, buf, stop=True) |
writeto() |
Optional start/end buffer slice (AQPX extension); stop accepted but ignored |
readfrom(addr, nbytes, stop=True) |
readfrom() |
Returns bytes; raises on NACK |
readfrom_into(addr, buf, stop=True) |
readfrom_into() |
Reads into an existing buffer |
writevto(addr, vector, stop=True) |
writevto() |
Concatenates vector then writes |
writeto_then_readfrom(...) |
writeto_then_readfrom() |
Combined write-then-read (repeated START) |
readfrom_mem(addr, memaddr, nbytes, *, addrsize=8) |
readfrom_mem() |
addrsize 8, 16, 24, or 32 |
readfrom_mem_into(...) |
readfrom_mem_into() |
Reads len(buf) bytes |
writeto_mem(addr, memaddr, buf, *, addrsize=8) |
writeto_mem() |
Returns None |
init() / deinit() |
— | Use attach_to_bus() / detach_from_bus() instead |
start() / stop() / write() / readinto() |
— | SoftI2C primitives not exposed |
Examples¶
The examples below mirror the MicroPython I2C documentation. Each block shows the MicroPython call as a comment for easy porting.
Bus scan¶
Simple write and read¶
# MicroPython: i2c.writeto(42, b'123')
i2c.writeto(0x42, b"123")
# MicroPython: i2c.readfrom(42, 4)
data = i2c.readfrom(0x42, 4) # returns bytes
Read into a buffer¶
Scatter write¶
Memory / register access¶
For devices that expose a register or memory map (EEPROM, sensor registers, etc.):
# MicroPython: i2c.readfrom_mem(42, 8, 3)
reg_data = i2c.readfrom_mem(0x42, 8, 3)
# MicroPython: i2c.writeto_mem(42, 2, b'\x10')
i2c.writeto_mem(0x42, 2, b"\x10")
# 16-bit register address
data = i2c.readfrom_mem(0x42, 0x0100, 4, addrsize=16)
# 24-bit or 32-bit register address (large EEPROMs, flash, etc.)
data = i2c.readfrom_mem(0x50, 0x123456, 4, addrsize=24)
data = i2c.readfrom_mem(0x50, 0x12345678, 4, addrsize=32)
Write-then-read (register pointer)¶
Use this when the device expects a register address write followed by a data read without a STOP between them:
write_buf = b"\x00" # register pointer
read_buf = bytearray(4)
# MicroPython: i2c.writeto_then_readfrom(42, write_buf, read_buf)
i2c.writeto_then_readfrom(0x42, write_buf, read_buf)
Buffer slicing¶
AQPXLIB extends writeto, readfrom_into, and writeto_then_readfrom with optional start/end slice parameters. This is useful when reusing a large buffer:
buf = bytearray(8)
i2c.writeto(0x42, buf, start=2, end=6) # write buf[2:6]
i2c.readfrom_into(0x42, buf, start=1, end=5) # read into buf[1:5]
Behavioral differences¶
NACK handling¶
MicroPython-compat methods raise OSError(errno.EIO, "I2C NACK") when the target does not acknowledge the transaction:
For writeto and writeto_mem, if some bytes were ACKed before a NACK, the partial byte count is returned (matching MicroPython). A NACK on the address phase raises instead.
The native helpers i2c_write(), i2c_read(), i2c_write_addr(), and i2c_read_addr() use softer error handling: they return None or an empty list on NACK rather than raising.
stop parameter¶
The stop keyword argument is accepted on all compat methods for API compatibility, but is currently ignored. Each method call maps to a complete START…STOP transaction sequence on the bus.
Return types¶
| API style | Write return | Read return |
|---|---|---|
MicroPython-compat (writeto, readfrom, …) |
int (bytes ACKed) or None |
bytes |
Native (i2c_write, i2c_read, …) |
int or None |
list[int] |
Bus timing¶
MicroPython sets freq and timeout on the I2C constructor. In AQPXLIB, electrical and timing settings are configured on PxI2CBus when the bus is created. There is no per-controller freq/timeout parameter.
AQPX-specific extensions¶
These methods are not part of the MicroPython API but are available on PxI2CController:
| Method | Description |
|---|---|
i2c_scan_all() |
Scan all 128 addresses; returns a list of booleans indexed by address |
send_transactions() |
Send a custom sequence of read/write transactions |
When to use which API¶
- Porting MicroPython or CircuitPython scripts — use
scan,writeto,readfrom,readfrom_mem, and related compat methods. Error handling and return types will match what you expect from embedded code. - AQPX-native code — use
i2c_write,i2c_read,i2c_write_addr, andi2c_read_addrwhen you prefer soft NACK handling (None/[]) orlist[int]return values. - I3C controllers doing I2C-format transfers —
PxI3CBaseControllerinherits the same compat methods for I2C-format bus operations.