Der APDS9960-Sensor: Lesen und Schreiben von Registern mit I2C

Hier werden einzelne Projekte mit MicroPython vorgestellt
Antworten
Heinrichs
Beiträge: 173
Registriert: Do 21. Okt 2010, 18:31

Der APDS9960-Sensor: Lesen und Schreiben von Registern mit I2C

Beitrag von Heinrichs » Mo 27. Mär 2023, 12:43

Um Daten vom APDS9960-Sensor zu erhalten oder Daten an ihn zu senden, muss man auf die einzelnen Register des APDS9960 zurückgreifen. In dem Beitrag Der APDS9960-Sensor: Ein Blick hinter die Kulissen hatten wir einige dieser Register und ihre Bedeutung im Zusammenhang mit der Proximity-Engine schon vorgestellt. In diesem Beitrag wollen wir darlegen, wie man dies mit Micropython bewerkstelligt. Folgende fünf Fälle wollen wir beispielhaft behandeln:

  • Wert in ein Register des APDS9960 schreiben
  • Wert aus einem Register lesen
  • Wert aus mehreren Registern lesen
  • Einzelnes Bit aus einem Register lesen
  • Einzelnes Bit in einem Register ändern

Die Daten werden mit Hilfe des I2C-Protokolls übertragen. Dazu muss eine entsprechende I2C-Instanz definiert worden sein; wir bezeichnen sie hier mit i2c. Die Adresse des APDS9960 ist

address = 0x39

Grundlegende Informationen zum I2C-Bus erhalten Sie z. B. hier. Für die oben angegebenen Fälle geben wir gleich entsprechende Funktionen an und erläutern sie.

Doch vorher noch ein wichtiger Hinweis: Sehr viele Sensoren und Aktoren arbeiten auf ähnliche Weise; deswegen kann man die folgenden Darlegungen leicht auch auf diese anwenden.


Wert in ein Register schreiben

Die Zahl val (zwischen 0 und 255) soll in das Register mit der Adresse reg geschrieben werden. Dazu benutzen wir die writeto_mem-Methode von i2c:

Code: Alles auswählen

def writeByte(self, reg, val):
	i2c.writeto_mem(address, reg, bytes((val,)))
Als dritten Parameter erwartet die writeto_mem-Methode einen ByteString (Typ: bytes). Nun ist val vom Typ int. Dieser muss in den Typ bytes konvertiert werden. Dazu benutzen wir die Funktion bytes. Diese konvertiert Listen bzw. Tupel in ByteStrings; auf einzelne Zahlen kann sie nicht angewandt werden. Deswegen bilden wir zunächst das Tupel (val, ). Dieses besitzt nur ein einziges Element, nämlich val. (Das Komma ist hier wichtig; würde man es weglassen, dann würde Micropython (val) als val, d. h. als Zahl, und damit nicht als Tupel deuten!) Dieses Tupel kann nun mit der bytes-Funktion in einen ByteString konvertiert werden. Alternativ könnte man auch mit einer Liste arbeiten und die Konvertierung mit bytes([val]) durchführen lassen.


Wert aus einem Register lesen

Dies geschieht mit Hilfe der readfrom_mem-Methode von i2c:

Code: Alles auswählen

def readByte(reg):
	val =i2c.readfrom_mem(address, reg, 1)
	return int.from_bytes(val, 'big', True)
Sie liefert als Rückgabewert den Zahlenwert (Typ int) des Inhalts vom Register reg. Bei der readfrom_mem-Methode sehen wir als dritten Parameter die Zahl 1. Sie gibt die Anzahl der zu lesenden Bytes an; würde hier z. B. die Zahl 4 stehen, dann würde unsere Methode das angegebene Register und zusätzlich auch die nächsten drei Register auslesen. Die readfrom_mem-Methode liefert nun als Rückgabewert val einen Bytestring, welchen wir mit

int.from_bytes(val, 'big', True)

in einen int-Typ konvertieren. Der 2. und 3. Parameter sind in diesem Fall irrelevant (weil der ByteString nur aus einem einzigen Byte besteht); die beiden Paramter dürfen aber nicht weggelassen werden. Die Konvertierung könnte alternativ auch mit Hilfe der list-Funktion durchgeführt werden; diese verwandelt einen ByteString in eines Liste von Zahlen (Typ int). Von dieser müssten wir dann das erste Element auswählen:

list(val)[0] oder kürzer auch nur val[0]


Wert aus mehreren Registern lesen

In einigen Fällen ist es sinnvoll, direkt mehrere aufeinander folgende Register auszulesen. Ein Beispiel stellen die Register GFIFO_U, GFIFO_D, GFIFO_L und GFIFO_R dar, in denen die Messwerte für die Up-, Down-, Left- und Right-Geste bereit gestellt werden. Mit der Funktion

Code: Alles auswählen

def readBytes(reg, number):
	return i2c.readfrom_mem(address, reg, number)

erhalten wir als Rückgabewert einen Bytestring mit 4 Bytes, wenn number = 4 ist. Auf die int-Werte der einzelnen Bytes kann man dann wie oben zurückgreifen, z. B.:

data = readBytes(ADDRESS_GFIFO_U, 4)
GFIFO_L_Wert = data[2]



Einzelnes Bit aus einem Register lesen

Wir wollen uns die Vorgehensweise zunächst an einem Beispiel klar machen: Wir wollen das Bit mit der Nummer 5 aus dem Register 0xA2 auslesen. Als erstes lesen wir den Wert des Registers 0xA2 aus:

val = readByte(0xA2)

Wir bilden zunächst ein Byte, welches nur an der Position 5 eine 1 hat und sonst nur Nullen. Dazu wenden wir auf die Zahl 1 einen 5-fachen Links-Shift an:

mask = 1 << 5

In binärer Darstellung sieht mask jetzt so aus:

mask = 0b00100000

Nun bilden wir die UND-Verknüpfung von val und mask:

val = val & mask

val ist nun in binärer Darstellung 0b00100000 oder 0b00000000, je nachdem ob das Bit 5 den Wert 1 oder 0 hat. Wenn wir jetzt auf val einen 5-fachen Rechts-Shift anwenden, dann gibt val den Wert des Bits mit der Nummer 5 an.

Eine passende Funktion ist nun:

Code: Alles auswählen

def readBit(reg, bitPos):
	val = readByte(reg)
	mask = 1 << bitPos
	val = val & mask
	return val >> bitPos

Einzelnes Bit in einem Register ändern

Auch hier wollen wir uns die Vorgehensweise zunächst an einem Beispiel klar machen: Im Register 0xA2 soll das Bit mit der Nummer 3 den Wert bitVal erhalten. Dieser kann True (bzw. 1) oder False (bzw. 0) sein. Als erstes lesen wir den Wert des Registers 0xA2 aus:

val = readByte(0xA2)

Wir bilden zunächst ein Byte, welches nur an der Position 3 eine 1 hat und sonst nur Nullen. Dazu wenden wir auf die Zahl 1 einen 3-fachen Links-Shift an:

mask = 1 << 3

In binärer Darstellung sieht mask jetzt so aus:

mask = 0b00001000

Wenn nun bitVal den Wert 1 hat, dann erhalten wird den neuen Wert für val erhalten durch eine ODER-Verknüpfung:

val = val | mask

Falls nun bitVal den Wert 0 hat, dann bilden wir von diesem mask-Wert das Einer-Komplement ~mask und verknüpfen dieses mit dem ursprünglichen Wert val durch eine UND-Operation:

val = val & ~ mask

Eine zugehörige Funktion kann dann so aussehen:

Code: Alles auswählen

def regWriteBit(reg, bitPos, bitVal):
	val=readByte(reg)
	mask = 1 << bitPos
	if bitVal == 1:
		val=val | mask
	else:
		val=val & ~mask
	writeByte(reg, val)

Antworten