Hallo Community!
Ich habe vor kurzer Zeit einen GPIO Treiber für den I2C Baustein PCF8574(A) von NXP geschrieben
und möchte gerne dieses Wissen anhand dieses Treiber weitergeben. Dieses Tutorial soll also eine
kleine Hilfe für eure eigenen Treiber sein.
Was Ihr benötigt:
- Optimal wären zwei Systeme (Ein System zum entwickeln und ein System zum testen)
- Dokumentation für den Baustein (Falls ein öffentliches Datenblatt nicht verfügbar ist, dann
könnt Ihr auch die Informationen im Quellcode von anderen freien Betriebssystemen erhalten, was
natürlich meiner Meinung nach schwerer ist)
- Quellcode von OpenBSD
In diesem Beispiel nehme ich meinen Treiber her. Also wir schreiben einen GPIO Treiber. Codefragmente
davon sind jeweils bei anderen Treibern immer dieselben.
Die Treiber befinden sich im Quellcode von OpenBSD unter /usr/src/sys/dev. Schaut in diese Treiber
auch mal rein. Hier findet Ihr auch nützliche Informationen für eure eigenen Treiber. Eine gute Anlaufstelle
ist folgendes Dokument:
https://www.openbsd.org/papers/eurobsdcon2017-device-drivers.pdf
Wir schreiben einen Treiber für den guten alten I2C Baustein PCF8574. Da dieser Baustein über das I2C Interface
angesprochen wird, platzieren wir den Treiber unter /usr/src/sys/dev/i2c. Ich habe die Datei vom Treiber
hier pcf8574.c benannt.
Das Grundkonstrukt einer Treibers in OpenBSD Treibers, damit er vom Betriebssystem eingebunden werden kann,
besteht aus folgenden Codefragmenten:
Das ist die Struktur die wir nachfolgend in unseren Funktionen verwenden. sc_dev (struct device) und
sc_node ist das Minimum (für den Kernel) was enthalten sein muss. Die anderen Variablen und Strukturen
sind dementsprechend anzupassen. Das es sich um einen I2C Treiber handelt, definieren wir in dieser Struktur noch
sc_tag (i2c_tag_t) und die I2C Adresse sc_addr (i2c_addr_t) des Bausteines. Jetzt kommen wir zu den
nötigen Definitionen für das GPIO Interface. Diese besteht aus den Variablen sc_npins (die Anzahl der PINs die
angesteuert werden können), sc_gpio_gc ist der gpio controller (struct gpio_chipset_tag) und noch
sc_gpio_pins (gpio_pin_t). Dieses Array enthält die PIN Information (Status, Input oder Output und
sonstige Information).
Eine Struktur mit der der Kernel den Treiber ansteuert. Es enthält die Größe der vorigen Struktur und dann kommen
die Funktionszeiger mit der das Gerät erkannt wird (match) und der Funktionszeiger mit der das Gerät eingebunden
wird (attach).
Hier wird der Name des Treibers definiert.
Die Match Funktion. Wie Ihr sehen könnt werden zwei Typen vom PCF8574 unterstützt.
Beide haben unterschiedliche I2C Adressen.
Die Attach Funktion. Diese Funktion wird für die Initialisierung benötigt. Die for Schleife setzt die Eigenschaften
der PINs vom GPIO device. Das Statement pcfgpio_write(sc, 0xFF) sorgt dafür, dass im Anfangsstadium die PINs
als Eingänge definiert sind. Danach werden die Funktionszeiger für den GPIO controller zugewiesen. Anschließend
werden noch die jeweiligen Variablen gesetzt, damit der Treiber im Userland angesprochen werden kann.
Die restlichen Funktionen sollten eigentlich selbsterklärend sein. Ich habe den Treiber zwar in einem anderen Thread
schon gepostet, aber hier nochmal komplett:
Damit der Treiber vom Config System vom Kernel erkannt wird und kompiliert werden kann, folgendes in
der Datei /usr/src/sys/dev/i2c/files.i2c hinzufügen:
Da dies mein erster OpenBSD Treiber ist und ich somit noch ein Grünschnabel bin, können in den Erklärungen
noch falsche Annahmen (bezüglich Kernel) von mir enthalten sein. Sollte jemand diese finden, dann könnt Ihr
mich gerne darauf hinweisen und korrigieren.
Ich hoffe euch gefällt mein Tutorial. Freue mich über jedes Feedback!
Edit:
Habe gerade bemerkt, dass es ein eigenes Unterforum mit Tutorials gibt. Bitte diesen wenn möglich Thread verschieben.
Ich habe vor kurzer Zeit einen GPIO Treiber für den I2C Baustein PCF8574(A) von NXP geschrieben
und möchte gerne dieses Wissen anhand dieses Treiber weitergeben. Dieses Tutorial soll also eine
kleine Hilfe für eure eigenen Treiber sein.
Was Ihr benötigt:
- Optimal wären zwei Systeme (Ein System zum entwickeln und ein System zum testen)
- Dokumentation für den Baustein (Falls ein öffentliches Datenblatt nicht verfügbar ist, dann
könnt Ihr auch die Informationen im Quellcode von anderen freien Betriebssystemen erhalten, was
natürlich meiner Meinung nach schwerer ist)
- Quellcode von OpenBSD
In diesem Beispiel nehme ich meinen Treiber her. Also wir schreiben einen GPIO Treiber. Codefragmente
davon sind jeweils bei anderen Treibern immer dieselben.
Die Treiber befinden sich im Quellcode von OpenBSD unter /usr/src/sys/dev. Schaut in diese Treiber
auch mal rein. Hier findet Ihr auch nützliche Informationen für eure eigenen Treiber. Eine gute Anlaufstelle
ist folgendes Dokument:
https://www.openbsd.org/papers/eurobsdcon2017-device-drivers.pdf
Wir schreiben einen Treiber für den guten alten I2C Baustein PCF8574. Da dieser Baustein über das I2C Interface
angesprochen wird, platzieren wir den Treiber unter /usr/src/sys/dev/i2c. Ich habe die Datei vom Treiber
hier pcf8574.c benannt.
Das Grundkonstrukt einer Treibers in OpenBSD Treibers, damit er vom Betriebssystem eingebunden werden kann,
besteht aus folgenden Codefragmenten:
Code:
struct pcfgpio_softc {
struct device sc_dev;
i2c_tag_t sc_tag;
i2c_addr_t sc_addr;
int sc_node;
u_int8_t sc_npins;
struct gpio_chipset_tag sc_gpio_gc;
gpio_pin_t sc_gpio_pins[PCFGPIO_NPINS];
};
Das ist die Struktur die wir nachfolgend in unseren Funktionen verwenden. sc_dev (struct device) und
sc_node ist das Minimum (für den Kernel) was enthalten sein muss. Die anderen Variablen und Strukturen
sind dementsprechend anzupassen. Das es sich um einen I2C Treiber handelt, definieren wir in dieser Struktur noch
sc_tag (i2c_tag_t) und die I2C Adresse sc_addr (i2c_addr_t) des Bausteines. Jetzt kommen wir zu den
nötigen Definitionen für das GPIO Interface. Diese besteht aus den Variablen sc_npins (die Anzahl der PINs die
angesteuert werden können), sc_gpio_gc ist der gpio controller (struct gpio_chipset_tag) und noch
sc_gpio_pins (gpio_pin_t). Dieses Array enthält die PIN Information (Status, Input oder Output und
sonstige Information).
Code:
struct cfattach pcfgpio_ca = {
sizeof(struct pcfgpio_softc), pcfgpio_match, pcfgpio_attach
};
die Funktionszeiger mit der das Gerät erkannt wird (match) und der Funktionszeiger mit der das Gerät eingebunden
wird (attach).
Code:
struct cfdriver pcfgpio_cd = {
NULL, "pcfgpio", DV_DULL
};
Code:
int
pcfgpio_match(struct device *parent, void *match, void *aux)
{
struct i2c_attach_args *ia = aux;
if ((strcmp(ia->ia_name, "nxp,pcf8574") == 0) ||
(strcmp(ia->ia_name, "nxp,pcf8574a") == 0))
return (1);
return (0);
}
Die Match Funktion. Wie Ihr sehen könnt werden zwei Typen vom PCF8574 unterstützt.
Beide haben unterschiedliche I2C Adressen.
Code:
void
pcfgpio_attach(struct device *parent, struct device *self, void *aux)
{
struct pcfgpio_softc *sc = (struct pcfgpio_softc *)self;
struct i2c_attach_args *ia = aux;
struct gpiobus_attach_args gba;
int i;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
sc->sc_npins = PCFGPIO_NPINS;
sc->sc_node = *(int *)ia->ia_cookie;
printf("\n");
for (i = 0; i < sc->sc_npins; i++) {
sc->sc_gpio_pins[i].pin_num = i;
sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INOUT |
GPIO_PIN_OPENDRAIN | GPIO_PIN_INVOUT;
sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INPUT;
sc->sc_gpio_pins[i].pin_state = 0;
}
pcfgpio_write(sc, 0xFF);
sc->sc_gpio_gc.gp_cookie = sc;
sc->sc_gpio_gc.gp_pin_read = pcfgpio_pin_read;
sc->sc_gpio_gc.gp_pin_write = pcfgpio_pin_write;
sc->sc_gpio_gc.gp_pin_ctl = pcfgpio_pin_ctl;
gba.gba_name = "gpio";
gba.gba_gc = &sc->sc_gpio_gc;
gba.gba_pins = sc->sc_gpio_pins;
gba.gba_npins = sc->sc_npins;
config_found(&sc->sc_dev, &gba, gpiobus_print);
}
der PINs vom GPIO device. Das Statement pcfgpio_write(sc, 0xFF) sorgt dafür, dass im Anfangsstadium die PINs
als Eingänge definiert sind. Danach werden die Funktionszeiger für den GPIO controller zugewiesen. Anschließend
werden noch die jeweiligen Variablen gesetzt, damit der Treiber im Userland angesprochen werden kann.
Die restlichen Funktionen sollten eigentlich selbsterklärend sein. Ich habe den Treiber zwar in einem anderen Thread
schon gepostet, aber hier nochmal komplett:
Code:
/* Driver for the NXP PCF8574(A) Remote 8-bit I/O expander */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/gpio.h>
#include <dev/i2c/i2cvar.h>
#include <dev/gpio/gpiovar.h>
#define PCFGPIO_NPINS 8
struct pcfgpio_softc {
struct device sc_dev;
i2c_tag_t sc_tag;
i2c_addr_t sc_addr;
int sc_node;
u_int8_t sc_npins;
struct gpio_chipset_tag sc_gpio_gc;
gpio_pin_t sc_gpio_pins[PCFGPIO_NPINS];
};
int pcfgpio_match(struct device *, void *, void *);
void pcfgpio_attach(struct device *, struct device *, void *);
uint8_t pcfgpio_read(struct pcfgpio_softc *);
void pcfgpio_write(struct pcfgpio_softc *, uint8_t);
int pcfgpio_pin_read(void *, int);
void pcfgpio_pin_write(void *, int, int);
void pcfgpio_pin_ctl(void *, int, int);
struct cfattach pcfgpio_ca = {
sizeof(struct pcfgpio_softc), pcfgpio_match, pcfgpio_attach
};
struct cfdriver pcfgpio_cd = {
NULL, "pcfgpio", DV_DULL
};
int
pcfgpio_match(struct device *parent, void *match, void *aux)
{
struct i2c_attach_args *ia = aux;
if ((strcmp(ia->ia_name, "nxp,pcf8574") == 0) ||
(strcmp(ia->ia_name, "nxp,pcf8574a") == 0))
return (1);
return (0);
}
void
pcfgpio_attach(struct device *parent, struct device *self, void *aux)
{
struct pcfgpio_softc *sc = (struct pcfgpio_softc *)self;
struct i2c_attach_args *ia = aux;
struct gpiobus_attach_args gba;
int i;
sc->sc_tag = ia->ia_tag;
sc->sc_addr = ia->ia_addr;
sc->sc_npins = PCFGPIO_NPINS;
sc->sc_node = *(int *)ia->ia_cookie;
printf("\n");
for (i = 0; i < sc->sc_npins; i++) {
sc->sc_gpio_pins[i].pin_num = i;
sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INOUT |
GPIO_PIN_OPENDRAIN | GPIO_PIN_INVOUT;
sc->sc_gpio_pins[i].pin_flags = GPIO_PIN_INPUT;
sc->sc_gpio_pins[i].pin_state = 0;
}
pcfgpio_write(sc, 0xFF);
sc->sc_gpio_gc.gp_cookie = sc;
sc->sc_gpio_gc.gp_pin_read = pcfgpio_pin_read;
sc->sc_gpio_gc.gp_pin_write = pcfgpio_pin_write;
sc->sc_gpio_gc.gp_pin_ctl = pcfgpio_pin_ctl;
gba.gba_name = "gpio";
gba.gba_gc = &sc->sc_gpio_gc;
gba.gba_pins = sc->sc_gpio_pins;
gba.gba_npins = sc->sc_npins;
config_found(&sc->sc_dev, &gba, gpiobus_print);
}
uint8_t
pcfgpio_read(struct pcfgpio_softc *sc)
{
uint8_t val;
iic_acquire_bus(sc->sc_tag, 0);
if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
NULL, 0, &val, sizeof(val), 0)) {
printf("%s: pcfgpio_read: failed to read\n",
sc->sc_dev.dv_xname);
iic_release_bus(sc->sc_tag, 0);
return (0);
}
iic_release_bus(sc->sc_tag, 0);
return val;
}
void
pcfgpio_write(struct pcfgpio_softc *sc, uint8_t val)
{
iic_acquire_bus(sc->sc_tag, 0);
if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
&val, sizeof val, NULL, 0, 0)) {
printf("%s: pcfgpio_write: failed to write\n",
sc->sc_dev.dv_xname);
iic_release_bus(sc->sc_tag, 0);
return;
}
iic_release_bus(sc->sc_tag, 0);
}
int
pcfgpio_pin_read(void *arg, int pin)
{
struct pcfgpio_softc *sc = arg;
uint8_t tmp;
if (pin >= sc->sc_npins)
return 0;
if (!ISSET(sc->sc_gpio_pins[pin].pin_flags, GPIO_PIN_INPUT))
pcfgpio_pin_write(sc, pin, GPIO_PIN_HIGH);
tmp = pcfgpio_read(sc);
if (tmp & (1 << pin))
sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_HIGH;
else
sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_LOW;
return sc->sc_gpio_pins[pin].pin_state;
}
void
pcfgpio_pin_write(void *arg, int pin, int val)
{
struct pcfgpio_softc *sc = arg;
int i;
uint8_t tmp = 0x00;
if (pin >= sc->sc_npins)
return;
for (i = 0; i < sc->sc_npins; i++)
if (sc->sc_gpio_pins[i].pin_state == GPIO_PIN_LOW)
tmp |= (1 << i);
if (val == GPIO_PIN_HIGH) {
tmp &= ~(1 << pin);
pcfgpio_write(sc, tmp);
sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_HIGH;
} else {
tmp |= 1 << pin;
pcfgpio_write(sc, tmp);
sc->sc_gpio_pins[pin].pin_state = GPIO_PIN_LOW;
}
}
void
pcfgpio_pin_ctl(void *arg, int pin, int flags)
{
struct pcfgpio_softc *sc = arg;
if (ISSET(flags, GPIO_PIN_INPUT)) {
pcfgpio_pin_write(sc, pin, GPIO_PIN_HIGH);
sc->sc_gpio_pins[pin].pin_flags = GPIO_PIN_INPUT;
} else
sc->sc_gpio_pins[pin].pin_flags = GPIO_PIN_OUTPUT;
}
Damit der Treiber vom Config System vom Kernel erkannt wird und kompiliert werden kann, folgendes in
der Datei /usr/src/sys/dev/i2c/files.i2c hinzufügen:
Code:
device pcfgpio: gpiobus
attach pcfgpio at i2c
file dev/i2c/pcf8574.c pcfgpio
Da dies mein erster OpenBSD Treiber ist und ich somit noch ein Grünschnabel bin, können in den Erklärungen
noch falsche Annahmen (bezüglich Kernel) von mir enthalten sein. Sollte jemand diese finden, dann könnt Ihr
mich gerne darauf hinweisen und korrigieren.
Ich hoffe euch gefällt mein Tutorial. Freue mich über jedes Feedback!
Edit:
Habe gerade bemerkt, dass es ein eigenes Unterforum mit Tutorials gibt. Bitte diesen wenn möglich Thread verschieben.
Zuletzt bearbeitet: