3.24. I2C in code

machine.I2C omhult een hardware I2C-controller. Construeer er een met de bus-id en een optionele klokfrequentie:

from machine import I2C

i2c = I2C(2, freq=400_000)

id selecteert welk hardware I2C-blok wordt gebruikt; de beschikbare nummers en de SDA / SCL-pinnen waaraan ze gekoppeld zijn, hangen af van het bord (zie de OpenMV-boards). freq is de SCL-kloksnelheid – 100_000 is standard mode, 400_000 is de gangbare “fast”-snelheid die de meeste sensoren ondersteunen.

3.24.1. De bus scannen

scan() doorloopt de 7-bits adresruimte en geeft een lijst terug van elk apparaat dat zijn adres heeft bevestigd:

>>> i2c.scan()
[0x40, 0x68]

Dit is het eerste wat je moet proberen na het bedraden van een nieuwe bus. Als een apparaat in de lijst staat, zijn de bedrading en de pull-ups in orde. Als de lijst leeg is (of korter dan verwacht), verdenk dan de pull-ups, de bedrading of de adrespinnen van het apparaat voordat je de code verdenkt.

3.24.2. Het memory-mapped patroon

De meeste I2C-sensoren stellen hun toestand beschikbaar als een set interne registers: statusvlaggen op het ene adres, meetbytes op een ander, configuratie op een derde. De bibliotheek heeft twee hulpmethoden die rechtstreeks aansluiten op het patroon “adresseer het apparaat, wijs naar een register, lees of schrijf vervolgens data”.

Een register lezen:

addr = 0x68
reg  = 0x3B
data = i2c.readfrom_mem(addr, reg, 6)
# `data` is 6 bytes starting at register 0x3B

Die ene aanroep geeft een START, stuurt het apparaatadres met de schrijfbit, stuurt het registeradres (een herhaalde start), stuurt het adres opnieuw met de leesbit, leest 6 bytes, NACK’t de laatste, en geeft een STOP. Het apparaat ziet de standaard “lezen uit register”-sequentie; het script ziet één Python-aanroep.

Naar een register schrijven:

i2c.writeto_mem(addr, 0x6B, b"\x00")

Die sequentie is hetzelfde in spiegelbeeld: START, adres + schrijven, register, payload-byte, STOP.

Voor apparaten waarvan het registeradres twee bytes breed is (sommige EEPROM’s en 16-bits geadresseerde geheugens) geef je addrsize=16 mee:

data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)

3.24.3. Een sensor lezen

Een korte lus die de WHO_AM_I-identifier en de X-as van de versnellingsmeter op een typische MEMS-sensor leest:

import time
import struct
from machine import I2C

i2c         = I2C(2, freq=400_000)
ADDR        = 0x68
WHO_AM_I    = 0x75
ACCEL_X_HI  = 0x3B

print("WHO_AM_I:", i2c.readfrom_mem(ADDR, WHO_AM_I, 1)[0])

while True:
    raw = i2c.readfrom_mem(ADDR, ACCEL_X_HI, 2)
    x   = struct.unpack(">h", raw)[0]
    print("accel X:", x)
    time.sleep_ms(100)

readfrom_mem() geeft een bytes-object terug, dus de WHO_AM_I-leesbewerking indexeert de eerste (en enige) byte met [0]. De leesbewerking van de versnellingsmeter levert twee ruwe bytes die tot één signed 16-bits waarde moeten worden samengesteld; het struct-formaat ">h" doet dat als big-endian signed – de byte-volgorde die de meeste I2C-sensoren gebruiken, het tegenovergestelde van de little-endian-conventie die gangbaar is op UART-verbindingen. struct.unpack() geeft altijd een tuple terug (één element per formaatkarakter), dus [0] haalt de enkele waarde eruit.

3.24.4. Toegang op lager niveau

Voor apparaten die het register-map-patroon niet volgen, geven twee methoden op lager niveau directe controle over de adresfase en de datarichting:

i2c.writeto(addr, b"\x01\x02\x03")    # write 3 bytes to addr
data = i2c.readfrom(addr, 4)          # read 4 bytes from addr

Deze zijn nuttig voor apparaten met command-stream-interfaces (displays, sommige realtimeklokken) waar de register-map-hulpmethoden niet helemaal passen.

3.24.5. Bit-bangen wanneer geen hardwareblok beschikbaar is

machine.SoftI2C biedt dezelfde API op willekeurige GPIO-pinnen en bit-bangt het protocol in software:

from machine import SoftI2C, Pin

i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)

Gebruik het wanneer het apparaat op pinnen moet zitten die niet zijn aangesloten op een hardware I2C-blok, of wanneer elk hardwareblok in gebruik is. De bit-bang-lus is trager dan hardware I2C en de CPU blijft de hele transactie bezig, maar de API is identiek aan I2C, zodat bestaande code overschakelt door alleen de constructor te wijzigen.