I2C (Grove connector)
hub.i2c is a hub._i2c singleton that speaks the
internal I2C bus — the same bus that already drives the
PCA9685, SC16IS750 (I2C variant), the BNO085 IMU and the on-board
Grove connector pads.
The public API mirrors machine.I2C (hardware variant)
one-to-one. Code written against machine.I2C moves across
unchanged — swap the constructor for the pre-built hub.i2c
singleton.
Warning
Do not create a machine.I2C(0, ...) on the same pins —
two competing drivers on one peripheral will deadlock or corrupt
transactions. Use hub.i2c instead.
Coexistence with the C++ side
The Arduino-ESP32 TwoWire driver serialises individual
transactions with a per-bus mutex, so calls from Python and from
the C++ hub tick are safe to interleave. On top of that, hub.i2c
takes a recursive mutex around every method so a compound
operation (readfrom_mem = memaddr write + repeated-start read)
cannot be split by another Python caller between its two halves.
Currently there is no cross-language mutex for multi-transaction sequences — if you need atomicity across several transactions against a device that the C++ tick could also touch, keep the sequence short (the tick touches the bus once every few ms).
Pins
The bus is fixed to the board’s I2C_SDA / I2C_SCL pins.
Passing different sda / scl to hub._i2c.init() raises
ValueError.
Quickstart
Scan the bus and print anything that ACKs:
import hub
print("devices:", [hex(a) for a in hub.i2c.scan()])
Change the clock:
hub.i2c.init(freq=100_000) # slow down to 100 kHz
hub.i2c.init(freq=400_000) # back to default
Raw write / read
ADDR = 0x40
hub.i2c.writeto(ADDR, b"\\x00\\x01") # single transaction
data = hub.i2c.readfrom(ADDR, 4) # read 4 bytes
For a scatter/gather write in one transaction:
hub.i2c.writevto(ADDR, [b"\\x10", payload])
Register access
Convenience wrappers around the common register-file pattern (write memaddr, repeated-start, read/write):
who = hub.i2c.readfrom_mem(0x68, 0x75, 1) # MPU6050 WHO_AM_I
hub.i2c.writeto_mem(0x68, 0x6B, b"\\x00") # PWR_MGMT_1 = 0
buf = bytearray(6)
hub.i2c.readfrom_mem_into(0x68, 0x3B, buf) # 6-byte accel burst
16-bit register addresses:
hub.i2c.writeto_mem(0x50, 0x1234, b"\\xaa", addrsize=16)
hub.i2c.readfrom_mem(0x50, 0x1234, 1, addrsize=16)
Full example — MPU-6050 accelerometer on Grove
"""MPU-6050 accelerometer over the Grove I2C connector.
Reads WHO_AM_I, wakes the chip, then streams 6-byte accel bursts.
Uses the shared internal I2C bus via ``hub.i2c`` — coexists with the
PCA9685, SC16IS750, BNO085 and everything else already on Wire1.
"""
import hub
import struct
ADDR = 0x68 # MPU-6050 / MPU-6500 default (AD0 low)
WHO_AM_I = 0x75
PWR_MGMT_1 = 0x6B
ACCEL_XOUT_H = 0x3B
@hub.on("setup")
def setup():
print("scanning I2C bus...")
devs = hub.i2c.scan()
print("addresses:", [hex(a) for a in devs])
if ADDR not in devs:
print(f"MPU-6050 not found at {hex(ADDR)}")
return
who = hub.i2c.readfrom_mem(ADDR, WHO_AM_I, 1)[0]
print(f"WHO_AM_I: 0x{who:02X}")
# Wake up (default is sleep mode after reset).
hub.i2c.writeto_mem(ADDR, PWR_MGMT_1, b"\x00")
_buf = bytearray(6)
_last_print = 0
@hub.on("loop")
def loop():
global _last_print
import time
now = time.ticks_ms()
if time.ticks_diff(now, _last_print) < 200:
return
_last_print = now
hub.i2c.readfrom_mem_into(ADDR, ACCEL_XOUT_H, _buf)
ax, ay, az = struct.unpack(">hhh", _buf)
print(f"accel x={ax:+6} y={ay:+6} z={az:+6}")
API reference
hub._i2c — mirrors machine.I2C:
init()(*, scl=None, sda=None, freq=-1)— apply new frequency;scl/sdapinned to board defaults.scan()()— probe 0x08..0x77.readfrom()(addr, nbytes, stop=True)— bytes.readfrom_into()(addr, buf, stop=True).writeto()(addr, buf, stop=True)— returns bytes-written count.writevto()(addr, vector, stop=True)— scatter/gather.readfrom_mem()(addr, memaddr, nbytes, *, addrsize=8).readfrom_mem_into()(addr, memaddr, buf, *, addrsize=8).writeto_mem()(addr, memaddr, buf, *, addrsize=8).
Errors:
OSError(ENODEV)— device NACK’d (bad address / not present).OSError(ETIMEDOUT)— bus timeout.OSError(EIO)— other bus error.OSError(EBUSY)— could not take the Python-side mutex within 1 s (another Python caller holds it and never released).