Raspberry Pi: I2C-Konfiguration und -Programmierung

Prof. Jürgen Plate

Raspberry Pi: I2C-Konfiguration und -Programmierung

In Embedded-Systemen sind Sensoren und Aktoren oft mit dem I2C-Bus angebunden. Linux auf dem Raspberry Pi unterstützt dies mit einem eigenen Subsystem. I2C (Aussprache: "i-quadrat-c", machmal auch "i-zwei-c")ist ein serieller Master-Slave-Datenbus, der für die Kommunikation über kurze Distanzen konzipiert wurde, also hauptsächlich innerhalb von Platinen oder Geräten. Die Technik stammt aus den frühen 1980er-Jahren und wurde ursprünglich von Philips (heute NXP Semiconductors) für den Einsatz in der Unterhaltungselektronik entwickelt. Die Datenübertragung erfolgt synchron über zwei Leitungen: eine Datenleitung (SDA) und eine Taktleitung (SCL), auf denen ein einfaches Übertragungsprotokoll abläuft (siehe auch Serielle Schnittstelle, USB, SPI, I2C, 1-Wire).

Beide Leitungen werden durch Pullup-Widerstände auf ein positives Potenzial gezogen, die I2C-Chips haben Open-Collector-Ausgänge. Ein Bus-Master gibt Takt und Betriebsmodus vor und initiiert die byteweise erfolgende Kommunikation. Der I2C-Master kann bis zu 112 Geräte (Slaves) mit einer 7-Bit-Adresse ansprechen (eine Erweiterung des Busses verwendet 10 Bit für bis zu 1136 Geräte). Für den Bus existieren auf dem Markt die unterschiedlichsten Devices, darunter Temperatursensoren, Echtzeituhr, Portexpander, A/D- und D/A-Wandler sowie Bausteine der Unterhaltungselektronik.

Eine Abwandlung von I2C ist der SMBus, der System Management Bus. Er ist hardwaretechnisch identisch mit I2C, definiert darauf aber ein anderes Übertragungsprotokoll. Er findet sich auf fast allen modernen PC-Boards, um z. B. die Temperatur der CPU zu messen. Linux stellt für diese Variante eine Reihe zusätzlicher Zugriffsfunktionen bereit.

Die I2C-Devices (Clients) nehmen Daten vom Master entgegen und schicken abhängig vom übertragenen Kommando eine Antwort zurück. Kommandos, Parameter und Antworten sind für jedes Device unterschiedlich - insofern gibt es da auch kein einheitliches Programmierschema. Der Linux-Kernel bedient sie, sofern vorhanden, mit einem eigenen I2C-Client-Treiber (Beispiel RTC mit DS1307). Neben diesen I2C-Client-Treibern gibt es auch noch die I2C-Adapter-Treiber. Diese sind für Versand und Empfang der Daten über die beiden Leitungen zuständig. Da I2C recht weit verbreitet ist, unterstützt die Hardware vieler Prozessoren diesen Vorgang. Fehlt diese Unterstützung, kann man aber auch zwei GPIO-Pins verwenden und die seriellen Signale selbst generieren. Der Fachmann spricht von "Bit-Banging".

Der erste I2C-Kanal des RasPi (i2c-0) ist mit zwei Lötaugen des Steckers P5 verbunden, er wird auch für die Raspberry-Pi-Kamera verwendet. Bleibt für allgemeine Aufgaben der zweite Kanal (i2c-1), der an den Pins 3 (SDA) und 5 (SCL) der Doppelstiftleiste P1 angeschlossen ist (siehe Bild). Er ist gut zu erreichen und ermöglicht den Anschluss beliebiger I2C-Peripherie. Beachten Sie dabei, dass auch diese beiden Anschlüsse der Stiftleiste wie auch alle anderen nur mit maximal 3,3 V beaufschalgt werden dürfen.

Mit i2c_dev besitzt der RasPi-Kernel einen generischen Treiber, über den man auf I2C-Devices zugreifen kann. Auf dem Raspberry Pi unter Raspbian lädt man den Treiber für Tests über das Kommando modprobe i2c_dev in den Kernel. Weiter unten wird beschrieben, wie man das Laden des Treibers beim Bootvorgang automatisieren kann. Ist der Treiber geladen, stellt er für jeden I2C-Kanal im Verzeichnis /dev eine Gerätedatei zur Verfügung (/dev/i2c-0 und /dev/i2c-1). Im Notfall kann man die Gerätedateien als Root auch von Hand anlegen:

mknod /dev/i2c-0 c 89 0
mknod /dev/i2c-1 c 89 1
Ab Revision B des Raspberry Pi ist immer der Bus 1 (/dev/i2c-1) die richtige Wahl.

Diese Gerätedateien kann man als Programmierer per open() öffnen. Über das Filehandle gibt man dann per iooctl()-Aufruf die gewünschten Spezifikation einer bestimmten I2C-Adresse dem I2C-Core bekannt. Das folgende C-Beispiel dient hier nur der Erläuterung, weiter unten wird die Programmierung dann ausführlicher behandelt:

if ((file = open("/dev/i2c-1", O_RDWR)) < 0) 
  {
  perror("Failed to open the i2c bus");
  exit(1);
  }
if (ioctl(file, I2C_SLAVE, 0x20) < 0) 
  {
  perror("Failed to acquire bus access and/or talk to slave.\n");
  exit(1);
  }

Anmerkung: Wenn es beim Öffnen des Device eine Fehlermeldung gibt, liegt es meist an den Zugriffsrechten. Tragen Sie den User "pi" (oder den User, der mit I2C arbeiten soll) in der Gruppe "i2c" ein:

sudo usermod -aG i2c pi
Sie können aber auch dauerhaft dafür sorgen, dass die Zugriffsrechte beim Bootvorgang von System festgelegt werden. Hier stützt man sich auf das udev-Subsystem. Normalerweise erstellt es für jedes neue Gerät eine Gerätedatei im Verzeichnis /dev. Man kann aber auch weitere Regeln angeben, indem man im Verzeichnis /etc/udev/rules.d eine Steuerdatei anlegt. Der numerische Präfix des Dateinamens regelt dabei die Reihenfolge der Abarbeitung der Dateien. Für I2C legen Sie im o. g. Verzeichnis die Datei 51-i2c.rules an und tragen darin die folgende Regel ein:
KERNEL=="i2c*", GROUP="users", MODE="0660"
Damit sind die entsprechenden Devices für die Gruppe "users" mit Lese- und Schreibrecht versehen. Jetzt müssen Sie nur noch den udev-Daemon von den Änderungen wissen lassen (beim nächsten Reboot passiert das dann automatisch):
sudo service udev restart
(Andere Distributionen mit systemd verwenden stattdessen das Kommando systemctl restart systemd-udevd)

Anschließend lassen sich I2C-Pakete per read()- und write()-Funktion zwischen dem Treiber und dem am Bus hängenden Device verschicken. Dabei muss man das Protokoll selbst implementieren. Will z. B. ein Programm Daten lesen, muss es zuerst das passende I2C-Kommando per write() an das device senden. Danach kann die Antwort per read() gelesen werden. Das Demoprogramm zur RTC mit DS1307 zeigt das sehr schön.

Das Kernelmodul für die I2C-Funktionen des Raspberry Pi ist standardmäßig deaktiviert. Um I2C nutzen zu können, müssen Sie es zunächst aktivieren. Alle Aktionen sind nur für den Root-User erlaubt, deshalb wechseln Sie gleich mal mittels sudo su in die Administrator-Identität.

Konfiguration

Zuerst wird die Datei /etc/modprobe.d/raspi-blacklist.conf mit einem Editor bearbeitet, indem Sie das Kommentarzeichen (#) vor den Eintrag setzen. Die Datei sollte danach folgenden Inhalt haben:

$ cat /etc/modprobe.d/raspi-blacklist.conf
# blacklist spi and i2c by default (many users don't need them)
# blacklist spi-bcm2708
# blacklist i2c-bcm2708

Anschließend erweitern Sie die Datei /etc/modules um folgenden Eintrag:

i2c-dev
In der Regel ist bis dahin nur die Zeile snd-bcm2835 enthalten. Nun können Sie die Modul schon einmal probeweise laden:
modprobe i2c-bcm2708
modprobe i2c_dev
Mit dem Befehl lsmod wird nun überprüft, ob beide Module geladen wurden. Es sollte sich in etwa folgende Ausgabe ergeben:
Module                  Size  Used by
i2c_dev                 5557  0
spi_bcm2708             4728  0
i2c_bcm2708             3997  0
snd_bcm2835            16165  0
snd_soc_bcm2708_i2s     5474  0
regmap_mmio             2806  1 snd_soc_bcm2708_i2s
snd_soc_core          131268  1 snd_soc_bcm2708_i2s
regmap_spi              1897  1 snd_soc_core
snd_pcm                81593  2 snd_bcm2835,snd_soc_core
snd_page_alloc          5156  1 snd_pcm
regmap_i2c              1645  1 snd_soc_core
snd_compress            8076  1 snd_soc_core
snd_seq                53769  0
snd_timer              20133  2 snd_pcm,snd_seq
snd_seq_device          6473  1 snd_seq
leds_gpio               2059  0
led_class               3688  1 leds_gpio
snd                    61291  7 snd_bcm2835,snd_soc_core,snd_timer,snd_pcm,snd_seq,snd_seq_device,snd_compress
Tauchen beide Module in der Liste auf, ist alles in Ordnung. Durch den Eintrag in der Datei /etc/modules erfolgt das Laden der Kernelmodule automatisch beim Booten. Nach dem Booten (bzw dem Laden der Module von Hand) sollten auch zwei neue Gerätedateien sichtbar sein:
$ ls -l /dev/i2c-*
crw-rw---T 1 root i2c 89, 0 Feb 25 16:10 /dev/i2c-0
crw-rw---T 1 root i2c 89, 1 Feb 25 16:10 /dev/i2c-1

Angeschlossen wird der I2C-Bus an der Stiftleiste des Boards (siehe auch RasPi_GPIO) und zwar an den Pins 3 (I2C SDA), 5 (I2C SCL) und 6 (Masse). Beachten Sie, dass dies seit Raspberry Pi Revision 2.0 der I2C-Bus Nummer 1 ist. Bei älteren Boards hat er dagegen die Nummer 0. Beim Raspberry Pi Revision 2.0, gibt es einen weiteren GPIO-Anschluss, P5, bei dem der I2C-Bus Nummer 0 an den Pins 3 (I2C SDA) und 4 (I2C SCL) herausgeführt ist.

Beim Raspberry Pi 2 ab Kernelversion 3.18 sind zusätzlich zwei Zeilen in der Datei /boot/config.txt notwendig, um Zugang zum I2C-Bus zu erhalten:

dtparam=i2c1=on
dtparam=i2c_arm=on
Siehe auch das Device Tree Kapitel.

Will man den User "pi" und eventuell auch den Webserver ohne Root-Gefickel auf den Bus schreiben lassen, kann man beide User der Gruppe "i2c" hinzufügen. Das kann entweder durch Bearbeiten der Datei /etc/group geschehen oder durch die folgenden Kommandos (zur Erinnerung, wie sind immer noch Root):

adduser pi i2c
adduser www-data i2c

I2C-Treiber-Probleme unter "Stretch"

Der I2C-Treiber der Raspbian-Version vom Oktober 2017 meldet I/O-Errors beim Zugriff auf manche Sensoren. Meine Theorie ist, dass entweder das I2C-Device "repeated starts" nicht unterstützt oder dass der I2C-Treiber das "Clock Stretching" nicht implementiert hat. Für genauere Analyse fehlte mir die Zeit. Es gibt aber eine Lösung:

  1. Das alte Treibermodul herunterladen: i2c1-bcm2708.dtbo
  2. Die Datei i2c1-bcm2708.dtbo in das Verzeichnis /boot/overlays hineinkopieren. Sollte bereits eine Datei gleichen Namens vorhanden sein, diese vorher in i2c1-bcm2708.dtbo.orig umbenennen.
  3. In der Datei /boot/config.txt die folgende Zeile am Ende hinzufügen:
    dtoverlay=i2c1-bcm2708
    
  4. Den Raspberry rebooten.

Einsatz der I2C-Tools

Um die Verbindung testen zu können, wird die Tool-Sammlung i2c-tools benötigt, die mit folgendem Befehl installiert wird (ein Update der Paketlisten vorab lohnt sich immer). Es sind auch noch zwei weitere Pakete aufgelistet, die Sie eventuell brauchen könnten:

apt-get update
apt-get install i2c-tools      # I2C-Toolkit fuer die Kommandozeile
apt-get install python-smbus   # Python-Bibliothek fuer I2C
apt-get install libi2c-dev     # Bibliothek fuer C
Danach finden Sie vier neue Programme im Verzeichnis /usr/sbin/:
ls -l /usr/sbin/i2c*
-rwxr-xr-x 1 root root 14912 Jul 26  2012 /usr/sbin/i2cdetect
-rwxr-xr-x 1 root root 19328 Jul 26  2012 /usr/sbin/i2cdump
-rwxr-xr-x 1 root root 14444 Jul 26  2012 /usr/sbin/i2cget
-rwxr-xr-x 1 root root 18268 Jul 26  2012 /usr/sbin/i2cset

i2cdetect

i2cdetect versucht, den I2C-Bus nach Devices abzuscannen. Es gibt eine Tabelle mit der Liste der erkannten Geräte auf dem angegebenen Bus aus. Da es keinen Standard-I2C-Erkennungsbefehl gibt, verwendet i2cdetect verschiedene SMBus-Befehle (primär SMBus quick write and SMBus receive byte), um Devices zu erkennen. Standardmäßig wird immer ein Befehl verwendet, der vermutlich der sicherste für die jeweilige Chip-Adresse ist. Die Optionen -q und -r ändern dieses Verhalten. i2cdetect kann auch verwendet werden, um die Funktionalitäten eines I2C-Busses abzufragen.

Kommandosyntax:

i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]
i2cdetect -F i2cbus
i2cdetect -V
i2cdetect -l

Optionen

OptionAufgabe
-V Version anzeigen and exit.
-y Deaktivieren des interaktiven Modus. Standardmäßig wartet i2cget auf eine Bestätigung des Benutzers bevor es auf den I2C-Bus zugreift. Wird diese Option verwendet, beginnt die Ausführung sofort.
-F Zeigt die Liste der vom Adapter implementierten Funktionalitäten an.
-l (Kleinbuchstabe "l") Zeigt die Liste der installierten Busse an.
-q Verwendet den SMBus "quick write"-Befehl zum Sondieren. Nicht empfohlen. Unter anderem werden damit die Atmel AT24RF08 EEPROMs auf IBM Thinkpad Laptops beschädigt.
-r Verwendet SMBus "receive byte"-Befehl zum Sondieren. Nicht empfohlen. Blockiert u. U. den SMBus auf verschiedenen "write-only"_Chips (insbesondere Chips an der Adresse 0x69).
-a Erzwingt das Scannen von nicht regulären Adressen. Nicht empfohlen.

Der Parameter i2cbus gibt die Nummer oder den Namen des zu scannenden I2C-Busses an. Diese Nummer sollte einem der von i2cdetect -l aufgeführten Bus entsprechen (beim Raspberry Pi ist das immer "1").

Die optionalen Parameter first und last schränken den Scanbereich ein (Default: 0x03 bis 0x77).

Jede Zelle in der Ausgabetabelle enthält eines der folgenden Symbole:

Beispiele:

i2cdetect -F 1
Functionalities implemented by /dev/i2c-1:
I2C                              yes
SMBus Quick Command              yes
SMBus Send Byte                  yes
SMBus Receive Byte               yes
SMBus Write Byte                 yes
SMBus Read Byte                  yes
SMBus Write Word                 yes
SMBus Read Word                  yes
SMBus Process Call               yes
SMBus Block Write                yes
SMBus Block Read                 no
SMBus Block Process Call         no
SMBus PEC                        yes
I2C Block Write                  yes
I2C Block Read                   yes

i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- 21 -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
 

i2cdetect -l
i2c-0   i2c        bcm2708_i2c.0            I2C adapter
i2c-1   i2c        bcm2708_i2c.1            I2C adapter

i2cdump

Das zweite Programm, i2cdump, ist ein Hilfsprogram, das es erlaubt alle Register eine Slave über den Bus auszulesen. Je nach angesprochenem Chip klappt das mal mehr mal weniger gut.

Kommandosyntax:

i2cdump [-f] [-r first-last] [-y] i2cbus chip-address [mode [bank [bankreg]]]
i2cdump -V

Optionen

OptionAufgabe
-V Version anzeigen and exit.
-f Erzwingen des Zugriffs auf das Device, auch wenn es bereits belegt ist. Normalerweise weigert sich i2cdump, auf ein Gerät zuzugreifen, das bereits unter der Kontrolle eines Kernel-Treibers ist. Zugriff mit dieser Option birgt die Gefahr, dass der Konflikte mit dem entsprechenden Kerneltreiber auftreten oder dass i2cdump fehlerhafte Werte zurückgibt. Verwendung auf eigenes Risiko!
-y Deaktivieren des interaktiven Modus. Standardmäßig wartet i2cget auf eine Bestätigung des Benutzers bevor es auf den I2C-Bus zugreift. Wird diese Option verwendet, beginnt die Ausführung sofort.
-r first-last Beschränkt den Bereich der Register, auf die zugegriffen wird. Diese Option ist nur mit den mode-Angaben 'b', 'w' und 'c' möglich.

Es gibt zwei obligatorische Parameter für i2dump. i2cbus gibt die Nummer oder den Namen des zu scannenden I2C-Busses an. Diese Nummer sollte einem der von i2cdetect -l aufgeführten Bus entsprechen (beim Raspberry Pi ist das immer "1"). chip-address spezifiziert die Adresse des Chips auf diesem Bus und ist eine Ganzzahl zwischen 0x03 und 0x77.

Der optionale Parameter mode ist einer der Buchstaben 'b', 'w', 's' oder 'i', je nachdem, ob ein Byte (8 Bit) oder ein Wort (16 Bit) gelesen werden soll. 's' steht für das Lesen eines SMBus-Blocks und 'i' für ein I2C-Blocklesen. Wird als mode der Buchstabe 'c' angegeben, liest das Programm fortlaufend. Das ist nur sinnvoll bei Chips mit automatischem Register-Inkrement, wie z. B. EEPROMs. Mit Ausnahme von 'i' kann auch noch ein 'p' angehängt werden, um PEC zu aktivieren.

Die optionalen Parameter bank und bankreg sind Beim W83781D und ähnlichen Chips nützlich. bank ist eine Ganzzahl zwischen 0 und 7 und bankreg ist eine Ganzzahl zwischen 0x00 und 0xFF (Standardwert: 0x4E).

i2cget

Die Register des Slave können mit den I2C-Tools auch direkt geschrieben und gelesen werden. Mit demBefehl i2cget kann direkt lesend über den I2C-Bus auf ein Device zugegriffen werden.

Kommandosyntax:

i2cget [-f] [-y] i2cbus chip-address [data-address [mode]]
i2cget -V

Optionen

OptionAufgabe
-V Version anzeigen and exit.
-f Erzwingen des Zugriffs auf das Device, auch wenn es bereits belegt ist. Normalerweise weigert sich i2cget, auf ein Gerät zuzugreifen, das bereits unter der Kontrolle eines Kernel-Treibers ist. Zugriff mit dieser Option birgt die Gefahr, dass der Konflikte mit dem entsprechenden Kerneltreiber auftreten oder dass i2cget einen ungültigen Wert zurückgibt. Verwendung auf eigenes Risiko!
-y Deaktivieren des interaktiven Modus. Standardmäßig wartet i2cget auf eine Bestätigung des Benutzers bevor es auf den I2C-Bus zugreift. Wird diese Option verwendet, beginnt die Ausführung sofort.

Es gibt zwei obligatorische Parameter für i2cget. i2cbus gibt die Nummer oder den Namen des zu scannenden I2C-Busses an. Diese Nummer sollte einem der von i2cdetect -l aufgeführten Bus entsprechen (beim Raspberry Pi ist das immer "1"). chip-address spezifiziert die Adresse des Chips auf diesem Bus und ist eine Ganzzahl zwischen 0x03 und 0x77.

Der optionale Parameter data-address legt die (Register-)Adresse auf dem Chip fest, von der gelesen werden soll. Es ist eine Ganzzahl zwischen 0x00 und 0xFF. Fehlt data-address, wird aus dem aktuell eingestellten Register gelesen (wenn das für den betrachteten Chip sinnvoll ist).

Der optionale Parameter mode ist einer der Buchstaben 'b', 'w' oder 'c', je nachdem, ob ein Byte (8 Bit) oder ein Wort (16 Bit) gelesen werden soll. der Buchstabe 'c' steht für eine Write/Read-Operation. Wird ein 'p' an die mode-Angabe angehängt, dient dies zur Aktivierung von PEC. Fehlt der Parameter mode, wird standardmäßig ein Byte gelesen.

Beispiele:

Ein Lese-Befehl sieht beispielsweise folgendermaßen aus:

i2cget -y 1 0x21
Die "1" besagt, dass auf den I2C-Bus Nummer 1 zugegriffen wird und und "0x21" ist die Adresse des Device. Als Rückgabewert erhält man dann auf der Konsole das ausgelesene Byte.

Bei komplexen Chips versagt das einfache Verfahren, aber bei einfachen Komponenten klappt es wunderbar. So kann beispielsweise die Echtzeituhr auf Adresse 0x68 ausgelesen werden:

i2cget -y 1 0x68 0 # Sekunde
i2cget -y 1 0x68 1 # Minute
i2cget -y 1 0x68 2 # Stunde
Den Temperatursensor LM75 kann man auch ganz leicht auslesen:
$ i2cget -y 1 0x48 0x00 w
0x8019
Hier wird eine 16-Bit-Variable zurückgeliefert, die noch zu analysieren ist. Das MSB (0x80) besagt, dass zum Temperaturwert im LSB noch 0,5 Grad hinzuaddiert werden müssen. Der LSB-Wert von 0x19 ergibt dezimal 25. Also haben wir 25,5 Grad Celsius.

i2cset

Die Register des Slave können mit den I2C-Tools auch direkt geschrieben werden. Mit dem Befehl i2cset kann direkt über den I2C-Bus auf ein Device schreibend zugegriffen werden.

Kommandosyntax:

i2cset [-f] [-y] [-r] i2cbus chip-address data-address [value] ... [mode]
i2cset -V

Optionen

-V Version anzeigen and exit.
-f Erzwingen des Zugriffs auf das Device, auch wenn es bereits belegt ist. Normalerweise weigert sich i2cset, auf ein Gerät zuzugreifen, das bereits unter der Kontrolle eines Kernel-Treibers ist. Zugriff mit dieser Option birgt die Gefahr, dass der Konflikte mit dem entsprechenden Kerneltreiber auftreten oder dass i2set in ein falsches Register schreibt. Verwendung auf eigenes Risiko!
-y Deaktivieren des interaktiven Modus. Standardmäßig wartet i2cset auf eine Bestätigung des Benutzers bevor es auf den I2C-Bus zugreift. Wird diese Option verwendet, beginnt die Ausführung sofort.
-r Zurücklesen des Werts direkt nach dem Schreiben und Vergleichen des Ergebnisses mit dem geschriebenen Wert. Dies war früher das Standardverhalten.

Es gibt drei obligatorische Parameter für i2cset. i2cbus gibt die Nummer oder den Namen des zu scannenden I2C-Busses an. Diese Nummer sollte einem der von i2cdetect -l aufgeführten Bus entsprechen (beim Raspberry Pi ist das immer "1"). chip-address spezifiziert die Adresse des Chips auf diesem Bus und ist eine Ganzzahl zwischen 0x03 und 0x77. data-address legt die (Register-)Adresse auf dem Chip fest, auf welche geschrieben werden soll. Es ist eine Ganzzahl zwischen 0x00 und 0xFF.

Der optionale Parameter value ist der Wert, der in das angegebene Register geschrieben wird. Wenn dieser Parameter weggelassen wird, dann handelt es sich um ein "short write". Bei den meisten Chips setzt dies einfach einen internen Zeiger auf das Zielregister, ohne wirklich in dieses Register zu schreiben.

Der optionale Parameter mode ist einer der Buchstaben 'b', 'w', 's' oder 'i', je nachdem, ob ein Byte (8 Bit) oder ein Wort (16 Bit) geschrieben werden soll. 's' steht für das Schreiben eines SMBus-Blocks und 'i' für ein I2C-Blockwrite. Für die Blockwrite-Operationen wird die Blockgröße durch die Anzahl der angegeben Werte festgelegt. Mit Ausnahme von 'i' kann auch noch ein 'p' angehängt werden, um PEC zu aktivieren. Fehlt der Parameter mode, wird standardmäßig ein Byte geschrieben.

Beispiel:

Soll in das Device geschrieben werden, lautet der Befehl:

i2cset -y 1 0x21 0x00
Die "1" besagt wieder, dass auf den I2C-Bus Nummer 1 zugegriffen wird und und "0x21" ist wieder die Adresse des Device. Am Schluss folgt der zu schreibende Wert, im Beispiel "0x00".

Der Baustein PCF8574 ist ein Ein-/Ausgabechip. Abweichend von anderen I2C-Chips hat der PCF8574 keine Register. Ein Byte, das in den PCF8574 geschreiben wird beeinflusst sofort die digitalen Ausgänge.

i2cset -y 1 0x20 0xEF
Setzt den P4-Pin auf LOW und alle anderen Pins des Chips auf HIGH.

Programmierung in Python und C

Aus Copyrightgründen ist die Bezeichnung "I2C" geschützt, weshalb oft vom "SMBus" (System Management Bus) die Rede ist. Es gibt zwar einige marginale Unterschiede, aber wenn auf I2C-Hardware zugegriffen wird, sollte man nach Möglichkeit die SMBus-Kommandos verwenden. Beachten Sie auch, dass die Adresse ein 7-Bit-Wert ist (0 ... 127), der bei der Übertragung um eine Stelle nach links geschoben und um das Read-Write-Bit ergänzt wird.

Das SMBus-Protokoll von Linux bietet einen relativ starren Funktionsumfang, mit dem manche Anwendungsfälle nicht umgesetzt werden können, für Standard-Devices reicht er aber auf jeden Fall aus. Es gibt sowohl für C wie für Python passende Bibliotheken, welche die Programmierung vereinfachen. Trotzdem ist es an dieser Stelle unmöglich, eine wirklich allgmeingültige Anleitung zu geben - ganz einfach, weil jedes Device anders angesprochen werden muss. Wer nach Beispielen sucht, muss sich gegebenenfalls durch die diversen Raspberry-Pi-Projekte durcharbeiten.

Bei der Python-Programmierung benötigen Sie die Klasse smbus. Deshalb muss gegenbenfalls das Paket python-smbus nachinstalliert werden mit:

sudo apt-get install python-smbus
Im Programm wird dann mittels import smbus die Klasse geladen und ist verwendbar. Vor dem Zugriff auf I2C-Devices muss mit der Anweisung dev = smbus.SMBus(1) die Verbindung zum I2C-Bus hergestellt werden. Bevor auf die einzelnen Methoden eingegangen wird, möchte ich ein kurzes Beispiel aus dem Expander-Projekt vorstellen, aus dem die Zusammenhänge deutlich werden. Grundsätzlich wird ein Device immer über seine Adresse angesprochen. Jedoch enthalten die meisten Bausteine eine ganze Anzahl von Registern, die einzeln adressiert werden müssen. Deshalb besteht der Schreibbefehl für ein Byte in der Regel aus der Angabe von Device, Register und Daten. Der Lesebefehl hat entsprechend Device und Register als Parameter.
#!/usr/bin/python
import smbus
import time

# I2C-Adresse des MCP23017
address = 0x20

# Erzeugen einer I2C-Instanz und Öffnen des Busses
mcp23017 = smbus.SMBus(1)

# Konfiguration des MCP23017
mcp23017.write_byte_data(address,0x00,0x00) # Bank A Output
mcp23017.write_byte_data(address,0x01,0xFF) # Bank B Inputs

# Blinkeschleife
while True:
  # alle LEDs aus
  mcp23017.write_byte_data (address,0x12,0x00)
  time.sleep(1)
  # alle LEDs an
  mcp23017.write_byte_data (address,0x12,0xFF)
  time.sleep(1)

Die Python-Klasse besteht aus einem guten Duzend von Methoden, die im Folgenden knapp behandelt werden. Die Namen sind übrigens von den C-Funktionen abgeleitet, so dass diese Liste auch für die C-Programmierer interessant ist:

Vorsicht ist auch bei den 16-Bit-Operationen geboten. Die ARM-Architektur verwendet little endian. Sollte mit big endian gearbeitet werden, müssen höherwertiges- und niederwertiges Byte vertauscht werden. Das kann man durch folgende Anweisung erreichen:
Wert = ((Wert << 8) & 0xFF00) + (Wert >> 8)

Der oben erwähnte Temperatursensor LM75 läßt sich nicht nur mit i2cget, sondern natürlich auch mit Python auslesen:

#!/user/bin/env python
import time
import smbus

bus = smbus.SMBus(0)

data = bus.read_i2c_block_data(0x48, 0)
# Daten kommen binaer - [Lowbyte, Highbyte]
TempMSB = data[0]
TempLSB = data[1]
Temp = (((TempMSB << 8) | TempLSB) >>7) * 0.5
# negativ?
if Temp > 125:
  Temp = Temp - 256)
print Temp

Das folgende Beispiel demonstriert den Zugriff auf ein EEPROM vom Typ 24C02. Hier sehen Sie, wie das Blockweise Schreiben angewendet wird.

#!/usr/bin/python
import smbus
import sys
import time

# EEPROM-Adresse, Register
address = 0x50
reg = 0x01
# "Hello World\0"
data = [ 0x48, 0x6e, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x00 ]

eeprom = smbus.SMBus(1)

print "I2C: Schreiben auf Device 0x%02X" % address
try:
  eeprom.write_i2c_block_data(address, reg, data)

except IOError, err:
  print "Fehler beim Schreiben auf Device 0x%02X" % address
  exit(-1)

# Erholungszeit fuer EEPROM
time.sleep(0.2)

print "I2C: Lesen von Device 0x%02X" % address
for i in range (len(data)):
  try:
    ret = eeprom.read_byte_data(address, reg + i)

  except IOError, err:
    print "Fehler beim Lesen von Device 0x%02X" % address
    exit(-1)

  print "%c" % ret

Bei C läuft es beinahe genauso geschmeidig ab wie in Python. Nun bei Öffnen des Device gibt es eine kleine Hürde. Nach dem open() muss mittels ioctl()-Aufruf die Kommunikation frei gegeben werden. Wichtig ist die Headerdatei i2c-dev.h, die für die I2C-Funktionen benötigt wird. Dieser Header ist eigentlich nur dann verfügbar, wenn das Paket libi2c-dev mittels apt-get installiert wurde. Ist nur die Headerdatei da, hagelt es Fehlermeldungen beim Compilieren.

Das Öffnen des Device sieht dann folgendermaßen aus:

/* die ueblichen Headerdateien */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>

int fd;

/* Device oeffen */
if ((fd = open(expander.I2CBus, O_RDWR)) < 0)
  {
  perror("Failed to open the i2c bus\n");
  exit(1);
  }

/* Spezifizieren der Slave-Adresse -> Kommunikation frei geben */
if (ioctl(fd, I2C_SLAVE, expander.address) < 0)
  {
  perror("Failed to acquire bus access and/or talk to slave\n");
  exit(1);
  }

Im Prinzip könnte man jetzt sogar mittels write() und read() auf dem Bus herumfuhrwerken. Das geht sogar gut, wenn die Kommunikation mit dem Device sehr einfach ist, wie beim Uhrenbaustein DS1307, wo ein einziges Kommando und ein Blockread genügen, um die Uhrzeit auszulesen. Das Setzen der Uhr oder das Schreiben in das EEPROM des Bausteins wäre dann aber schon eine Herausforderung. Für die beiden Funktionen write() und read() existiert nämlich ein Wrapper in der Bibliothek, wobei jeder Aufruf der Funktionen immer eine komplette Transaktion aus Startbedingung, Datentransfer und Stoppbedingung erzeugt. Insofern ist es ratsamer, die etwas komfortableren Bibliotheksfunktionen einzusetzen. Alle Funktionen liefern im Fehlerfall einen Status ungleich Null zurück. errno wird ebenfalls gesetzt. Es lohnt sich auch, das Kapitel 5.5 in den SMBus-Spezifikationen durchzulesen. Die Funktionsliste ähnelt jener von Python, so dass ich mir hier die einzelerklärungen sparen kann:

Einige Lese-Funktionen liefern einen Long-Wert (32 Bit) zurück, es ist also gegebenenfalls ein Typecast notwendig. Hier hilft ein Blick in die Headerdatei linux/i2c-dev.h, wo alles gut kommentiert ist.

Das folgende Beispiel i2ctest.c erweitert die Open-Funktionalität noch etwas. Das Programm prüft, ob auch alle notwendigen I2C-Bibliotheksfunktionen zur Verfügung stehen (über ioctl-Aufrufe). Die Funktion scan_i2c_bus() scannt den Bus nach I2C-Devices ab. Nicht so komfortabel wie das Programm aus den I2C-Tools, aber im Prinzip mit dem gleichen Ergebnis.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* Suche nach I2C-Adressen */
void scan_i2c_bus(int device)
  {
  int port, res;

  /* Adressbereich 7 Bit */
  for (port = 0; port < 127; port++)
    {
    if (ioctl(device, I2C_SLAVE, port) < 0)
      perror("ioctl() I2C_SLAVE failed\n");
    else
      {
      /* kann gelesen werden? */
      res = i2c_smbus_read_byte(device);
      if (res >= 0)
        printf("i2c chip found at: %x, val = %d\n", port, res);
      }
    }
  }

int main(void)
  {
  int device;
  unsigned long funcs;

  /* Geraetedatei oeffnen */
  printf("Opening device...");
  if ((device = open("/dev/i2c-1", O_RDWR)) < 0)
    {
    perror("open() failed");
    exit (1);
    }
  printf(" OK\n");

  /* Abfragen, ob die I2C-Funktionen da sind */
  if (ioctl(device,I2C_FUNCS,&funcs) < 0)
    {
    perror("ioctl() I2C_FUNCS failed");
    exit (1);
    }

  /* Ergebnis untersuchen */
  if (funcs & I2C_FUNC_I2C)
    printf("I2C\n");
  if (funcs & (I2C_FUNC_SMBUS_BYTE))
    printf("I2C_FUNC_SMBUS_BYTE\n");

  /* und Bus abscannen */
  scan_i2c_bus(device);

  return 0;
  }

Ein weiteres Beispiel bildet die Bibliothek zum I2C-IO-Expander mit MCP23017, bei der aufbauend auf den Grundfunktionen passende Funktionen für den Baustein MCP23017 geschrieben wurden. Wie schon gesagt, sind allgemein gültige Beispiele schwierig, da jedes Device anders angesprochen werden muss.

Eine weitere Unterstützung bei der C-Programmierung bietet die WiringPi-Bibliothek, deren Gebrauch und Installation an anderer Stelle beschrieben wurde. Diese Bibliothek bietet auch Funktionen für den I2C-Bus, deren Syntax sich an die entsprechenden Arduino-Funktionen anlehnt. Der Zugriff auf ein Device ist dem Zugriff auf eine Datei nachempfunden. Mit der Funktion wiringPiI2CSetup(Device-Adresse) erzeugen Sie zunächst ein Datei-Handle, auf das dann mit den anderen Funktionen zugegriffen wird.

FunktionAufgabe
int wiringPiI2CRead(int handle)einfaches Lesen
int wiringPiI2CWrite(int handle, int data)einfaches Schreiben
int wiringPiI2CReadReg8(int fd, int reg)Lesen von 8-Bit-Werten aus einem Register
int wiringPiI2CWriteReg8(int fd, int reg, int data)Schreiben von 8-Bit-Werten in ein Register
int wiringPiI2CReadReg16(int fd, int reg)Lesen von 16-Bit-Werten aus einem Register
int wiringPiI2CWriteReg16(int fd, int reg, int data)Schreiben von 16-Bit-Werten in ein Register

Geschwindigkeit der I2C-Schnittstelle einstellen

Manchmal ist es notwendig die Übertragungsrate des I2C-Busses zu ändern - wenn beispielsweise die Peripheriebausteine das erfordern oder man längere Kabelverbindungen benötigt. Auch bei elektromagnetischen Störungen kann eine geringere Datenrate mitunter hilfreich sein (siehe auch Serielle Schnittstelle, USB, SPI, I2C, 1-Wire).

Die Geschwindigkeit der I2C-Schnittstelle des Raspberry Pi lässt sich über das Kernel-Modul i2c_bcm2708 einstellen. Der Default-Wert der Übertragungsrate beträgt 100 kHz. Mit dem folgenden Befehl können Sie die aktuelle Übertragungsrate des Moduls auslesen. Erhalten Sie hier eine "0", handelt es sich um eine Übertragungsrate von 100 kHz.

sudo cat /sys/module/i2c_bcm2708/parameters/baudrate
Um die Übertragungsrate zu ändern, muss zuerst das Kernel-Modul entladen werden. Anschließend wird es mit neuen Parametern wieder geladen. Das funktioniert nicht nur für I2C-Treiber so, sondern für fast alle ladbaren Kernel-Module. Die beiden folgenden Kommandos senken die Geschwindigkeit auf 40 kHz ab.
sudo modprobe -r i2c_bcm2708              # Modul entladen
sudo modprobe i2c_bcm2708 baudrate=40000  # und wieder neu laden
Achten Sie beim Herabsetzen der Busgeschwindigkeit auch darauf, dass die Übertragungsrate für Ihre Anwendung noch ausreicht. Der Busmaster kann die Übertragungsgeschwindigkeit frei wählen. Es muss sich also nicht wie oben um einen glatten Wert handeln. Für die Fehlersuche mit dem Oszilloskop sollte man aber einen glatten Wert wählen, der sich als Zeitbasis am Oszilloskop einstellen lässt.

Hardware-Tipp

Sie erinnern sich vielleicht - der Raspberry Pi arbeitet mit 3,3 V. Bei der Verwendung von 5-V-Devices am Bus (oder auch 1,8-V-Devices) ist eine Pegelwandlung notwendig. Das kann man mit zwei MOSFETs und einigen Widerständen erledigen, wie es im Schnittstellen-Skript beschrieben ist, aber es gibt auch massenhaft integrierte Bausteine für diesen Zweck, etwa den I2C Level Shifter TCA9406 von Texas Instruments. Er arbeitet bidirektional, daher ist es egal, auf welcher Seite welche Spannung verwendet wird. Über einen Output-Enable-Pin kann er entweder vom Master aktiviert werden (s. Bild) oder man legt den Pin einfach auf High-Potential.

Links


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate
Letzte Aktualisierung: