In PCs und Embedded-Geräten sind Sensoren und Aktoren oft mit dem I2C-Bus angebunden. Linux unterstützt das mit einem eigenen Subsystem, das sich auf dem Raspberry Pi ausprobieren lässt.
Ob Bewegung, Position, Beschleunigung, Temperatur oder Druck: Mit immer mehr Sensoren erfassen Smartphones, Tablets, PCs und eingebettete Systeme ihre Umwelt und den eigenen Zustand. Um den damit verbundenen Hardware-Aufwand überschaubar zu halten, setzen die Hersteller auf einfache Bussysteme wie I²C, die über zwei Anschlussleitungen serialisiert Daten übertragen (siehe Kasten “Inter-Integrated Circuit”).
Inter-Integrated Circuit
I2C, ausgesprochen “I Quadrat C”, ist ein serieller Datenbus. Da er Hardware-technisch nur über eine Datenleitung (SDA) und eine Taktleitung (SCL) realisiert ist und das Übertragungsprotokoll einfacher Natur ist, lassen sich damit preiswert unterschiedlichste Geräte ansprechen, die typischerweise keine hohen Anforderungen an die Übertragungsbandbreite stellen. Es handelt sich um einen Master-/Slave-Bus, der bei einer 7-Bit-Adresse 112 Geräte (Slaves) ansprechen kann. Eine Variante des Busses verwendet 10 Bit für bis zu 1136 Geräte.
Eine Abwandlung von I2C ist der SMBus, der System Management Bus. Er ist Hardware-technisch identisch mit I2C, definiert darauf aber ein anderes Übertragungsprotokoll. Linux stellt für diese Variante eine Reihe zusätzlicher Zugriffsfunktionen bereit.
Die I²C-Geräte, auch Clients oder Slaves genannt, deserialisieren den Datenstrom, dekodieren ihn und schicken abhängig vom übertragenen Kommando serialisiert eine Antwort zurück. Kommandos, Parameter und Antworten sind für jeden Gerätetyp unterschiedlich. Der Linux-Kernel bedient sie jeweils mit einem eigenen I²C-Client-Treiber.
Neben diesen I²C-Client-Treibern gibt es die I²C-Adapter-Treiber. Diese sind auf dem Steuerrechner für das Serialisieren und Deserialisieren der Daten sowie den Versand und Empfang über die beiden Leitungen zuständig. Da I²C 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 [1]. Dieses Bit-Banging lagert der Linux-Kernel nochmals in einen dritten Treibertyp aus, nämlich den I²C-Algorithmus-Treiber. Der Algorithmus-Treiber und der I²C-Adapter-Treiber bilden damit das I²C-Core-System, über das die unterschiedlichen Client-Treiber die Daten transferieren (siehe Abbildung 1).
Wie von anderen Kernel-Subsystemen gewohnt findet man die Hardware-Hierarchie (Adapter, Algorithmus, Geräte) im Sys-Dateisystem des Linux-Kernels wieder. Unter »/sys/class/i2c-adapter/« sind die bereitstehenden I²C-Adapter aufgelistet. Das können auf modernen Systemen durchaus zehn Stück sein. Der Minicomputer Raspberry Pi zeigt nach dem Laden des I²C-Adapter-Treibers allerdings lediglich die beiden Adapter »i2c-0« und »i2c-1« an:
cd /sys/class modprobe i2c-bcm2708 cd i2c-adapter/ ls # => i2c-0 i2c-1
Ein Algorithmus-Treiber ist im Fall des Himbeer-Kleincomputers nicht notwendig, da der verbaute Broadcom-Chip die beiden angezeigten Adapter in Hardware realisiert.
Zwei Leitungen
Der Chip des ersten Adapters (des »i2c-0« ) ist übrigens mit zwei Leitungen auf dem Stecker P5 verbunden. Auch für die Raspberry-Pi-Kamera kommt er zur Verwendung. Der zweite Adapter (»i2c-1« ) ist über die Doppelstiftleiste P1 mit den Pins 3 (SDA) und 5 (SCL) besser zu erreichen und ermöglicht das Anschließen von I²C-Peripherie, zum Beispiel Temperatursensoren, DA- oder AD-Wandler, LCD-Displays, Uhrenbausteinen oder Portexpandern (siehe Abbildung 2).
Die Autoren haben an ihren Raspberry Pi per I²C einen Portexpander vom Typ PCA 9555 der Firma NXP angeschlossen. Der Chip bietet 16 Portleitungen, die sich frei konfigurierbar zur Eingabe oder zur Ausgabe programmieren lassen (siehe Kasten “Portexpander PCA 9555”). Der Expander hat am I²C-Bus die hexadezimale Adresse 0x20.
Portexpander PCA 9555
Als Portexpander bietet der PCA 9555 zweimal 8 Bit (Port 0 und Port 1) parallele Ein-/Ausgabe (GPIO) über I2C. Die I2C-Adresse ist vom Hersteller auf einen Bereich zwischen 0x20 und 0x27 vorgegeben und wird vom Entwickler über die Anschlusspins A0, A1 und A2 festgelegt. Der Baustein verfügt über insgesamt acht Register (Tabelle 2), die beim Schreiben über I2C vom ersten Byte, dem so genannten Befehl (Command), adressiert werden.
Die Werte 6 und 7 wählen dabei die Konfigurationsregister für den Port 0 und Port 1 aus. Der beim Schreiben in die Registeradressen 6 oder 7 übergebene Parameter legt fest, welcher Pin als Eingabe und welcher als Ausgabe fungiert. Die Ausgabe selbst erfolgt durch Schreiben der Register 2 und 3. Hier enthält der Parameter jeweils das auszugebende Bitmuster. Das Einlesen ist nur über das Schreiben mit nachfolgendem Lesen zu realisieren. Beim Schreiben an Adresse 0 wird der Port 0 zum Einlesen ausgewählt, die Adresse 1 wählt Port 1 aus. Der eingelesene Wert wird mit dem nachfolgenden Lesen übertragen.
Tabelle 2
Register des Portexpanders PCA 9555
|
Adresse |
Bedeutung |
|---|---|
|
Input 0 |
|
|
1 |
Input 1 |
|
2 |
Output 0 |
|
3 |
Output 1 |
|
4 |
Polarity Inversion 0 |
|
5 |
Polarity Inversion 1 |
|
6 |
Configuration 0 |
|
7 |
Configuration 1 |
Unabhängig von der angeschlossenen Peripherie bietet Linux mit dem Kernelmodul »i2c-dev« einen generischen I²C-Client-Treiber an. Der Kasten “Zugriff über den generischen I2C-Client-Treiber” erläutert, wie man damit Kommandos an die Peripherie sendet und Antworten entgegennimmt.
Zugriff über den generischen I2C-Client-Treiber
Mit »i2c_dev« besitzt der Linux-Kernel einen generischen Treiber, über den man ohne sonstigen Client-Treiber auf I2C-Geräte (Slaves) zugreifen kann [6]. Auf dem Raspberry Pi unter Raspbian lädt man den Treiber über das Kommando »modprobe i2c_dev« in den Kernel. Danach stellt er für jeden I2C-Adapter im Verzeichnis »/dev/« eine Gerätedatei zur Verfügung. Diese kann der Programmierer per »open()« öffnen. Das über den zurückgegebenen Filedeskriptor zu bedienende Gerät gibt er dann per »ioctl()« unter Spezifikation der I2C-Adresse dem I2C-Core bekannt. Anschließend lassen sich I2C-Pakete per »read()« und »write()« zwischen dem Adapter und dem am Bus angehängten Gerät verschicken.
Dabei ist zu beachten, dass die Applikation das Protokoll selbst implementieren muss, im Fall des PCA 9555 beispielsweise gehört die Konfiguration der Ein-/Ausgabe-Richtung der I/O-Ports dazu. Will die Applikation Daten lesen, muss sie zuerst das I2C-Kommando per »write()« zum Gerät senden, um die Antwort per »read()« entgegenzunehmen.
Sklaventreiber
Effizienter und für den Applikationsentwickler angenehmer ist aber ein eigenständiger I²C-Client-Treiber, er kann dem Anwender die Konfiguration und das Bitgeschiebe abnehmen. Er greift dann einfach nach dem »open()« per »read()« und »write()« auf die Hardware zu. Der I²C-spezifische Teil eines eigenständigen Slave-Treibers ist durchaus überschaubar [2]. Komplizierter gestaltet sich dagegen die Integration in einen normalen Gerätetreiber.
Kern des spezifischen Teils ist ein I²C-Treiberobjekt (Listing 1, Zeilen 83 bis 90). Es speichert unter anderem einen Namen, eine Liste von Gerätekennungen und die Adressen einer Probe- und einer Remove-Funktion. Der Programmierer übergibt es durch Aufruf von »i2c_add_driver()« dem I²C-Core – meist beim Laden des Treibers innerhalb von »mod_init()« (Tabelle 1). Beim Entladen des Treibers (Funktion »mod_exit()« ) meldet dieser zuvor die Peripherie durch Aufruf von »i2c_del_driver()« beim I²C-Core wieder ab.
Tabelle 1
Liste der I2C-Funktionen
|
Funktionsname |
Aufgabe |
|---|---|
|
i2c_add_driver |
Anmelden des Treibers beim I2C-Core |
|
i2c_del_driver |
Abmelden des Treibers beim I2C-Core |
|
i2c_master_send |
Senden von Daten über den I2C-Bus |
|
i2c_master_rcv |
Datenempfang über den I2C-Bus |
|
i2c_register_board_info |
Gibt dem I2C-Core für den angegebenen Adapter ein neues I2C-Gerät bekannt |
|
i2c_get_adapter |
Gibt auf Basis der Adapternummer die Kernel-Repräsentation (Datenstruktur) zurück |
|
i2c_new_device |
Gibt dem I2C-Core ein neues I2C-Gerät bekannt |
|
i2c_new_probed_device |
Fordert den I2C-Core auf, den Treiber nach I2C-Geräten suchen zu lassen |
Listing 1
Treiber für den Portexpander pca9555.c
001 #include <linux/module.h>
002 #include <linux/fs.h>
003 #include <linux/cdev.h>
004 #include <linux/i2c.h>
005 #include <asm/uaccess.h>
006
007 static dev_t pca9555_dev_number;
008 static struct cdev *driver_object;
009 static struct class *pca9555_class;
010 static struct device *pca9555_dev;
011 static struct i2c_adapter *adapter;
012 static struct i2c_client *slave;
013
014 static struct i2c_device_id pca9555_idtable[] = {
015 { "pca9555", 0 }, { }
016 };
017 MODULE_DEVICE_TABLE(i2c, pca9555_idtable);
018
019 static struct i2c_board_info info_20 = {
020 I2C_BOARD_INFO("pca9555", 0x20),
021 };
022
023 static ssize_t driver_write( struct file *instanz,
024 const char __user *user, size_t count, loff_t *offset )
025 {
026 unsigned long not_copied, to_copy;
027 char value=0, buf[2];
028
029 to_copy = min( count, sizeof(value) );
030 not_copied=copy_from_user(&value, user, to_copy);
031 to_copy -= not_copied;
032
033 if( to_copy > 0 ) {
034 buf[0] = 0x02; // output port 0
035 buf[1] = value;
036 i2c_master_send( slave, buf, 2 );
037 }
038 return to_copy;
039 }
040 static ssize_t driver_read( struct file *instanz,
041 char __user *user, size_t count, loff_t *offset )
042 {
043 unsigned long not_copied, to_copy;
044 char value, command;
045
046 command = 0x01; // input port 1
047 i2c_master_send( slave, &command, 1 );
048 i2c_master_recv( slave, &value, 1 );
049
050 to_copy = min( count, sizeof(value) );
051 not_copied=copy_to_user( user, &value, to_copy);
052 return to_copy - not_copied;
053 }
054
055 static int pca9555_probe( struct i2c_client *client,
056 const struct i2c_device_id *id )
057 {
058 char buf[2];
059
060 printk("pca9555_probe: %p %p \"%s\"- ",client,id,id->name);
061 printk("slaveaddr: %d, name: %s\n",client->addr,client->name);
062 if (client->addr != 0x20 ) {
063 printk("i2c_probe: not found\n");
064 return -1;
065 }
066 // Konfiguration Port 0 als Output
067 buf[0] = 0x06; // direction port 0
068 buf[1] = 0x00; // output
069 i2c_master_send( client, buf, 2 );
070 return 0;
071 }
072
073 static int pca9555_remove( struct i2c_client *client )
074 {
075 return 0;
076 }
077 static struct file_operations fops = {
078 .owner= THIS_MODULE,
079 .write= driver_write,
080 .read = driver_read,
081 };
082
083 static struct i2c_driver pca9555_driver = {
084 .driver = {
085 .name = "pca9555",
086 },
087 .id_table = pca9555_idtable,
088 .probe = pca9555_probe,
089 .remove = pca9555_remove,
090 };
091
092 static int __init mod_init( void )
093 {
094 if( alloc_chrdev_region(&pca9555_dev_number,0,1,"pca9555")<0 )
095 return -EIO;
096 driver_object = cdev_alloc();
097 if( driver_object==NULL )
098 goto free_device_number;
099 driver_object->owner = THIS_MODULE;
100 driver_object->ops = &fops;
101 if( cdev_add(driver_object,pca9555_dev_number,1) )
102 goto free_cdev;
103 pca9555_class = class_create( THIS_MODULE, "pca9555" );
104 if( IS_ERR( pca9555_class ) ) {
105 pr_err( "pca9555: no udev support\n");
106 goto free_cdev;
107 }
108 pca9555_dev = device_create( pca9555_class, NULL,
109 pca9555_dev_number, NULL, "%s", "pca9555" );
110 dev_info(pca9555_dev, "mod_init\n");
111
112 if (i2c_add_driver(&pca9555_driver)) {
113 pr_err("i2c_add_driver failed\n");
114 goto destroy_dev_class;
115 }
116 adapter = i2c_get_adapter(1); // Adapter sind durchnummeriert
117 if (adapter==NULL) {
118 pr_err("i2c_get_adapter failed\n");
119 goto del_i2c_driver;
120 }
121 slave = i2c_new_device( adapter, &info_20 );
122 if (slave==NULL) {
123 pr_err("i2c_new_device failed\n");
124 goto del_i2c_driver;
125 }
126 return 0;
127 del_i2c_driver:
128 i2c_del_driver(&pca9555_driver);
129 destroy_dev_class:
130 device_destroy( pca9555_class, pca9555_dev_number );
131 class_destroy( pca9555_class );
132 free_cdev:
133 kobject_put( &driver_object->kobj );
134 free_device_number:
135 unregister_chrdev_region( pca9555_dev_number, 1 );
136 return -EIO;
137 }
138
139 static void __exit mod_exit( void )
140 {
141 dev_info(pca9555_dev, "mod_exit");
142 device_destroy( pca9555_class, pca9555_dev_number );
143 class_destroy( pca9555_class );
144 cdev_del( driver_object );
145 unregister_chrdev_region( pca9555_dev_number, 1 );
146 i2c_unregister_device( slave );
147 i2c_del_driver(&pca9555_driver);
148 return;
149 }
150 module_init( mod_init );
151 module_exit( mod_exit );
152 MODULE_LICENSE("GPL");
Ist im System ein Gerät mit der im I²C-Treiberobjekt angegebenen Kennung bekannt, ruft das Core die Probe-Funktion auf. Entfernt jemand das Gerät später, tritt die Remove-Funktion in Aktion. Im Rahmen der Probe-Funktion überprüft der Client-Treiber – soweit das Hardware-technisch möglich ist –, ob das mit Namen und Busadresse übergebene Gerät (»struct i2c_client« ) tatsächlich existiert. Abhängig vom Gerät könnte man dazu zum Beispiel ein Statuswort auslesen.
Senden und empfangen
Die Funktion »i2c_master_send()« dient dazu, Kommandos zum Gerät zu senden, »i2c_master_rcv()« dazu, eine Antwort einzulesen. Beide übernehmen im ersten Parameter die Datenstruktur vom Typ »i2c_client« , die den Slave spezifiziert, im zweiten Parameter eine Speicheradresse, ab der die zu schreibenden Daten zu finden sind respektive ab der die Funktion »i2c_master_rcv« die empfangenen Daten ablegt.
Der dritte Parameter gibt die Anzahl der zu schreibenden oder zu empfangenden Bytes an. Die Probe-Funktion im Client-Treiber gibt übrigens 0 zurück, falls sie das angefragte Gerät bedient, sonst einen Wert ungleich 0. Die Remove-Funktion ist in den meisten Fällen noch einfacher aufgebaut: Sie returniert direkt 0.
Knackpunkt bei der Aktivierung der Probe-Funktion durch den Kernel ist die Gerätekennung (»struct i2c_ device_id« ). Anders als USB- oder PCI-Geräte haben I²C-Geräte per se keine (zentral verteilte) Identifikationsnummern (IDs). Linux verwendet alternativ einen Namen und die Slave-Adresse am I²C-Bus. Diese fehlende Standardisierung erschwert das automatische Erkennen von I²C-Geräten, sodass man sie dem System mehr oder minder von Hand bekannt machen muss.
Bekannt machen
Zur Bekanntgabe gibt es drei Methoden [3]. Ein im System fest integriertes Gerät kann der Treiber mit Hilfe der Struktur »i2c_board_info« und des zugehörigen Makros »I2C_BOARD_INFO« bekannt gegeben. Zur Übergabe an den Kernel dient die Funktion »i2c_register_board_info()« , die neben der I²C-Adapternummer die Adresse und die Länge der »struct i2c_board_info« übergeben bekommt.
Sind im Rechner mehrere I²C-Adapter vorhanden und steht im Vorhinein nicht fest, welcher Adapter eine spezifische Peripherie anspricht, meldet der Entwickler die über »struct i2c_board_info« und das Makro »I2C_BOARD_INFO« spezifizierten Slaves mit der Funktion »i2c_new_driver()« an. Ist zusätzlich die Geräteadresse nicht genau bekannt, verwendet man »i2c_new_probed_device()« . Diese Funktion bekommt zusätzlich eine Liste mit möglichen I²C-Adressen übergeben. Die daraufhin vom I²C-Core aufgerufene Probe-Funktion im Treiber kann versuchen an den I²C-Adressen ein unterstütztes Gerät zu identifizieren.
Als dritte Methode gibt der Admin I²C-Slaves über das Sys-Filesystem bekannt. Im Verzeichnis »/sys/class/i2c-adapter/« legt das I²C-Core für jeden vorhandenen Adapter ein Unterverzeichnis an, in diesem Unterverzeichnis unter anderem die Dateien »new_device« und »delete_device« . Das folgende Kommando macht das Gerät bekannt:
echo Devicename I2C-Addresse > neues_Device
Zum Abmelden reicht es aus, die eindeutige I²C-Adresse auf die Datei »delete_device« zu schreiben (»echo 0x20>delete_device« ). Für jedes auf diese Art angemeldete Gerät legt das Sys-Dateisystem einen Ordner an. Dieser hat einen Namen, der sich aus der Adapternummer und der I²C-Adresse zusammensetzt, beispielsweise »1-0020« (siehe Abbildung 3).
Klassengesellschaft
Technisch vorgesehen, aber nicht empfohlen ist eine vierte Methode, bei der der Treiber die komplette Suche nach den Slaves übernimmt. In diesem Fall muss das I²C-Treiber-Objekt die Methode »detect« unterstützen. Da das Erkennen von Geräten durch das plumpe Senden von Daten an alle angeschlossenen Geräte Gefahren in sich birgt, ist es nur für bestimmte Geräteklassen (Hardware-Monitoring, Grafikkarten, Speicherbausteine) erlaubt. Dazu gibt der Adapter an, welche Klassen er unterstützt, und der Gerätetreiber verrät, für welche Klasse er zuständig ist.
Die bisher vorgestellten I²C-Komponenten muss man noch in einem Gerätetreiber kombinieren, der einfachen Zugriff auf das Gerät per »read()« und »write()« ermöglicht. Basis kann ein Standardtreiber sein, wie ihn etwa die Kern-Technik-Folge 70 [4] verwendet hat.
Der in Listing 1 gezeigte Treiber für den Raspberry Pi klinkt sich beim Laden (»mod_init()« , Zeile 110) in das I²C-Core ein und gibt den Slave an Adapter 1 mit der Adresse 0x20 bekannt (Zeile 121). Das ruft die Funktion »pca9555_probe()« auf, die die gewünschte Konfiguration vornimmt: Port 0 des PCA 9555 als Ausgang, Port 1 als Eingang. Die Zugriffsfunktion »driver_write()« stellt ein I²C-Paket zusammen, das aus dem Kommando »0x02« (Ausgabe auf Port 0) und dem von der Applikation übergebenen Wert besteht (Zeile 34).
Die Funktion »i2c_master_send()« schickt es auf den Weg, »driver_read()« zeigt, dass zum Lesen auf I²C-Ebene erst ein Schreibzugriff erfolgen muss (Zeile 47). Den vom I²C-Bus gelesenen Wert kopiert »copy_to_user()« in den Speicherbereich der Applikation. Wer den Treiber auf dem Raspberry Pi ausprobieren möchte, darf nicht vergessen zuerst den I²C-Adapter-Treiber mit dem Kommando »modprobe i2c_bcm2708« zu laden. Wie man den Treiber (cross oder nativ) kompiliert, ist unter [4] und [5] beschrieben.
Der Treiber stellt das Gerät »/dev/pca9555« zur Verfügung, auf das der Programmierer mit »open()« , »close()« , »read()« und »write()« zugreift. Er kann aber auch einfach per »echo« einen Wert ausgeben (Abbildung 4). Der in Listing 1 vorgestellte Treiber für den PCA 9555 ist sehr rudimentär und kann den parallelen Zugriff auf angeschlossene Geräte noch nicht verhindern. Auch ist die Festlegung von Ein- und Ausgabeleitungen fest einkodiert. Kurzum: Er bietet noch reichlich Gelegenheiten zur Verbesserung.
Vorteil für Programmierer
Er zeigt aber, dass I²C-Slaves sich rasch mit einem Linux-Gerätetreiber versorgen lassen, der Anwendungsentwicklern das Programmieren erleichtert.
Infos
- Bit-Banging: https://de.wikipedia.org/wiki/Bit-Banging
- Venkatesh Yadav, “I2c driver in Linux”: http://venkateshabbarapu.blogspot.de/2012/11/i2c-driver-in-linux.html
- Linux-Kernel-Dokumentation zu I2C:https://www.kernel.org/doc/Documentation/i2c/instantiating-devices
- Quade, Kunst, “Kern-Technik 70 – Gerätetreiber für GPIO”: Linux-Magazin 10/13, S. 102
- Quade, Kunst, “Kern-Technik 69 – ARM-Kernel generieren”: Linux-Magazin 08/13, S. 86
- Linux-Kernel-Dokumentation zum I2C-Device-Interface: https://www.kernel.org/doc/Documentation/i2c/dev-interface









