3.24. I2C im Code

machine.I2C kapselt einen Hardware-I2C-Controller. Erzeugen Sie einen mit der Bus-ID und einer optionalen Taktfrequenz:

from machine import I2C

i2c = I2C(2, freq=400_000)

id wählt aus, welcher Hardware-I2C-Block verwendet wird; die verfügbaren Nummern und die SDA-/SCL-Pins, auf die sie abgebildet werden, hängen vom Board ab (siehe OpenMV-Boards). freq ist die SCL-Taktrate – 100_000 ist der Standard Mode, 400_000 ist die übliche „Fast“-Geschwindigkeit, die die meisten Sensoren unterstützen.

3.24.1. Den Bus scannen

scan() durchläuft den 7-Bit-Adressraum und gibt eine Liste aller Geräte zurück, die ihre Adresse bestätigt haben:

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

Das ist das Erste, was man nach dem Verdrahten eines neuen Busses ausprobieren sollte. Steht ein Gerät auf der Liste, sind Verdrahtung und Pull-ups in Ordnung. Ist die Liste leer (oder kürzer als erwartet), verdächtigen Sie die Pull-ups, die Verdrahtung oder die Adress-Pins des Geräts, bevor Sie den Code verdächtigen.

3.24.2. Das Memory-Mapped-Muster

Die meisten I2C-Sensoren stellen ihren Zustand als eine Reihe interner Register dar: Statusflags an einer Adresse, Messbytes an einer anderen, Konfiguration an einer dritten. Die Bibliothek bietet zwei Komfortmethoden, die direkt auf das Muster „das Gerät adressieren, auf ein Register zeigen, dann Daten lesen oder schreiben“ abgebildet sind.

Ein Register lesen:

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

Dieser einzelne Aufruf gibt ein START aus, sendet die Geräteadresse mit dem Schreibbit, sendet die Registeradresse (ein wiederholter Start), sendet die Adresse erneut mit dem Lesebit, liest 6 Bytes, gibt beim letzten ein NACK und beendet mit STOP. Das Gerät sieht die Standardsequenz „aus Register lesen“; das Skript sieht einen Python-Aufruf.

In ein Register schreiben:

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

Diese Sequenz ist dasselbe spiegelverkehrt: START, Adresse + Schreiben, Register, Nutzdatenbyte, STOP.

Für Geräte, deren Registeradresse zwei Bytes breit ist (einige EEPROMs und 16-Bit-adressierte Speicher), geben Sie addrsize=16 an:

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

3.24.3. Einen Sensor auslesen

Eine kurze Schleife, die die WHO_AM_I-Kennung und die X-Achse des Beschleunigungssensors eines typischen MEMS-Sensors ausliest:

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() gibt ein bytes-Objekt zurück, daher indiziert der WHO_AM_I-Lesevorgang das erste (und einzige) Byte mit [0]. Der Beschleunigungssensor-Lesevorgang liefert zwei Roh-Bytes, die zu einem vorzeichenbehafteten 16-Bit-Wert zusammengesetzt werden müssen; das struct-Format ">h" erledigt das als Big-Endian mit Vorzeichen – die Bytereihenfolge, die die meisten I2C-Sensoren verwenden, das Gegenteil der bei UART-Verbindungen üblichen Little-Endian-Konvention. struct.unpack() gibt immer ein Tupel zurück (ein Element pro Formatzeichen), daher holt [0] den einzelnen Wert heraus.

3.24.4. Zugriff auf niedrigerer Ebene

Für Geräte, die nicht dem Register-Map-Muster folgen, geben zwei Methoden auf niedrigerer Ebene direkte Kontrolle über die Adressphase und die Datenrichtung:

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

Diese sind nützlich für Geräte mit Befehlsstrom-Schnittstellen (Displays, einige Echtzeituhren), bei denen die komfortablen Register-Map-Hilfsmethoden nicht ganz passen.

3.24.5. Bit-Banging, wenn kein Hardware-Block verfügbar ist

machine.SoftI2C stellt dieselbe API auf beliebigen GPIO-Pins bereit und setzt das Protokoll per Bit-Banging in Software um:

from machine import SoftI2C, Pin

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

Verwenden Sie es, wenn das Gerät an Pins liegen muss, die nicht mit einem Hardware-I2C-Block verdrahtet sind, oder wenn jeder Hardware-Block belegt ist. Die Bit-Bang-Schleife ist langsamer als Hardware-I2C und die CPU bleibt während der gesamten Transaktion beschäftigt, aber die API ist identisch zu I2C, sodass bestehender Code allein durch Ändern des Konstruktors umgestellt werden kann.