Aus Linux-Magazin 02/2014

Kernel- und Treiberprogrammierung mit dem Linux-Kernel – Folge 72

© psdesign1, Fotolia

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).

Abbildung 1: I2C benötigt für den Hardwarezugriff verschiedene Treiber.

Abbildung 1: I2C benötigt für den Hardwarezugriff verschiedene Treiber.

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).

Abbildung 2: SDA und SCL des I2C-Busses werden mit Pin 3 und Pin 5 des Raspberry Pi verbunden.

Abbildung 2: SDA und SCL des I2C-Busses werden mit Pin 3 und Pin 5 des Raspberry Pi verbunden.

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).

Abbildung 3: Im Sys-Dateiystems lassen sich I2C-Geräte verwalten.

Abbildung 3: Im Sys-Dateiystems lassen sich I2C-Geräte verwalten.

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.

Abbildung 4: Bei geladenem Treiber lassen sich Daten einfach per Echo ausgegeben.

Abbildung 4: Bei geladenem Treiber lassen sich Daten einfach per Echo ausgegeben.

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

  1. Bit-Banging: https://de.wikipedia.org/wiki/Bit-Banging
  2. Venkatesh Yadav, “I2c driver in Linux”: http://venkateshabbarapu.blogspot.de/2012/11/i2c-driver-in-linux.html
  3. Linux-Kernel-Dokumentation zu I2C:https://www.kernel.org/doc/Documentation/i2c/instantiating-devices
  4. Quade, Kunst, “Kern-Technik 70 – Gerätetreiber für GPIO”: Linux-Magazin 10/13, S. 102
  5. Quade, Kunst, “Kern-Technik 69 – ARM-Kernel generieren”: Linux-Magazin 08/13, S. 86
  6. Linux-Kernel-Dokumentation zum I2C-Device-Interface: https://www.kernel.org/doc/Documentation/i2c/dev-interface

Der Autor

Eva-Katharina Kunst, Journalistin, und Jürgen Quade, Professor an der Hochschule Niederrhein, sind seit den Anfängen von Linux Fans von Open Source. In der Zwischenzeit ist die dritte Auflage ihres Buches “Linux Treiber entwickeln” erschienen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben