Eigener Linux-GPIO-Treiber für Raspberry Pi 3 Model B

Eigener Linux-GPIO-Treiber für Raspberry Pi 3 Model B

Seit geraumer Zeit liegt ein Raspi 3 ganz einsam und verstaubt auf meinem Schreibtisch. Es ist an der Zeit, seinen gelangweilten Prozessor in Schwung zu bringen! Wie fängt man an? Natürlich mit einer LED-Bliki-App und als Special mit eigenen Linux-Treiber. Okay, ist nicht weltbewegend, aber als „Hello World App“ akzeptabel.

Inhalt und Ausblick

In diesem Beitrag gebe ich einen kurzen Überblick, wie man einen Linux-Treiber erstellt und auf einen Raspi 3 anwendet. In den folgenden Beiträgen werden ein Bustreiber und eine Qt-GUI mit Threads erstellt.

Breadboard mit Schaltung

Die Raspi-Pins sind auf ein Breadboard herausgeführt. Auf dem Breadboard befindet sich eine LED und ein Schalter nebst Entprellung. Der Taster-Eingang liegt auf Pin 5 (Breadboard: 105) und LED-Ausgang liegt auf Pin 4 (Breadboard: 104).

Brake-out-Panal

Linux aktualisieren

Zuerst muss der Linux-Software aktualisiert werden:

pi@raspberrypi:~ $ sudo apt-get update
pi@raspberrypi:~ $ sudo apt-get uprade

Linux-Headers sind passend zum neusten Kernel (aktuell 4.14.34-v7+)  down-zu-loaden und zu überprüfen:

pi@raspberrypi:~ $ sudo apt-get install raspberrypi-kernel-headers
pi@raspberrypi:~ $ uname -r

Treiber erstellen

Nun wird die Treiberdatei led_device_driver.c erstellt. Der Treiber beinhaltet die Treiberfunktionen led_device_open(), led_device_close()led_device_read() und led_device_write() .

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "led_device"
#define CLASS_NAME "chardrv"


static dev_t first; // Globale Variable: device number
static struct cdev c_dev; // Globale Variable: character device structure
static struct class *cl; // Globale Variable: device class

static int led_device_open(struct inode *inode_str, struct file *file_str)
{
    int err;

    printk("led_device_open() \n");

    // Pin 4 belegen
    err = gpio_request(4, "rpi-gpio-4");

    if(err)
    {
        printk("request failed %d : rpi-gpio-4 \n", err);
        return -1;
    }

    // Pin als Output festlegen
    err = gpio_direction_output(4, 0);

    if(err)
    {
        printk("gpio_direction_output failed %d : rpi-gpio-4 \n", err);
        gpio_free(4);
        return -1;
    }

    //Pin 5 belegen
    err = gpio_request(5, "rpi-gpio-5");

    if(err)
    {
        printk("request failed %d : rpi-gpio-5 \n", err);
        return -1;
    }
    
    // Pin 5 aus Input festlegen
    err = gpio_direction_input(5);

    if(err)
    {
        printk("gpio_direction_output failed %d : rpi-gpio-5 \n", err);
        gpio_free(5);
        return -1;
    }

    return 0;
}

static int led_device_close(struct inode *inode_str, struct file *file_str)
{
    printk("led_device_close() \n");

    //Pins freigeben
    gpio_free(4);
    gpio_free(5);

    return 0;
}


static ssize_t led_device_read( struct file *file_str,
				char __user *user,
				size_t	size,
				loff_t *offset)
{
    unsigned long not_copied, copied;
    size_t delta_copied = 0;
    u32 value = 0;

    printk("led_device_read() \n");

    //Input 5
    value = gpio_get_value(5);
    copied = min(size, sizeof(value));
    // Pin-Wert einlesen
    not_copied = copy_to_user(user, &value, copied);
    delta_copied = copied - not_copied;

    return delta_copied;
}


static ssize_t led_device_write( struct file *file_str,
				 const char __user *user,
				 size_t size,
				 loff_t *offset)
{
    unsigned long not_copied, copied;
    size_t delta_copied;
  	u32 value = 0;

    printk("led_device_write() \n");

    copied = min(size, sizeof(value));
    // Pin-Wert setzen
    not_copied = copy_from_user(&value, user, copied);

  	if (value == 0 )
  		gpio_set_value(4,0);
  	else
  		gpio_set_value(4,1);

    delta_copied = copied - not_copied;

  	return delta_copied;
}

Nun die Funktionen den Systemcalls zuweisen:

static struct file_operations hellomike_fops =
{
    .owner = THIS_MODULE,
    .open = led_device_open,
    .release = led_device_close,
    .write = led_device_write,
    .read = led_device_read,
};

Der Treiber wird mit der Funktion led_device_init() geladen und mit der Funktion led_device_exit() entladen, dazu müssen die beiden Funktionen jeweils module_init() und module_exit() zugewiesen werden. Wichtig! Die richtige Lizenz auswählen.

static int __init led_device_init(void)
{
  	printk("led_device_init() \n");

  	// Vom Kernel wird eine Grätenummer vergeben und zugewiesen
    // Für Embedded Geräte kann alternativ mit MKDEV() eine feste Gerätenummer vergeben
    // und register_chrdev_region() zugewiesen werden.
    // Bspw. MKDEV(0,248): Gerätenummer wird aus beiden Nummern gebildet.
  	if (alloc_chrdev_region(&first, 0, 1, DEVICE_NAME) < 0)
  	{
  	    return -1;
  	}

  	//Erzeugt: struct class structure
  	if ((cl = class_create(THIS_MODULE, CLASS_NAME)) == NULL)
  	{
  	    unregister_chrdev_region(first, 1);
  	    return -1;
  	}

    // In Sys-Filsystem eintragen und Gerätedatei erzeugen.
  	if (device_create(cl, NULL, first, NULL, DEVICE_NAME) == NULL)
  	{
  	    class_destroy(cl);
  	    unregister_chrdev_region(first, 1);
  	    return -1;
  	}

  	cdev_init(&c_dev, &hellomike_fops);

    // Treiber beim Kernel anmelden
  	if (cdev_add(&c_dev, first, 1) == -1)
  	{
  	    device_destroy(cl, first);
  	    class_destroy(cl);
  	    unregister_chrdev_region(first, 1);
  	    return -1;
  	}

    return 0;
}

static void __exit led_device_exit(void)
{
    printk("led_device_exit() \n");

    // Abmelden und freigeben
    cdev_del(&c_dev);
    device_destroy(cl, first);
    class_destroy(cl);
    unregister_chrdev_region(first, 1);
}

module_init(led_device_init);
module_exit(led_device_exit);

MODULE_LICENSE("GPL");

Makefile erstellen:

obj-m += led_device_driver.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Treiber erstellen mit make

pi@raspberrypi:~/embedded/driver/led_device_driver $ make

laden:

pi@raspberrypi:~/embedded/driver/led_device_driver $ sudo insmod led_device_driver.ko

Zugriff für andere ermöglichen:

pi@raspberrypi:~/embedded/driver/led_device_driver $ sudo chmod 777 /dev/led_device

Ist der Treiber geladen?

pi@raspberrypi:~/embedded/driver/led_device_driver $ cat /proc/devices

Treiber testen

pi@raspberrypi:~/embedded/driver/led_device_driver $ echo "test" > /dev/led_device
pi@raspberrypi:~/embedded/driver/led_device_driver $ dmesg

Applikation

Nun wird eine kleine Applikation in C erstellt.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>

#define BUFFER_LENGTH 1
static char switch_value[BUFFER_LENGTH];

int main(int argc, char **argv, char **envp )
{
    int fd_i, led_value, switch_ret, count_i;
    struct timespec timesleep_str;

    switch_value[0] = 0;

    // Treiben für Lesen und Schreiben öffnen
    fd_i = open("/dev/led_device", O_RDWR);

    if(fd_i < 0)
    {
        printf("Kann Treiber led_device nicht öffnen :-( !\n");
        return -1;
    }

    // Aktuellen Wert des Tasters abfragen
    switch_ret = read(fd_i, &switch_value, BUFFER_LENGTH);

    if(switch_value[0] == 1)
    {
          printf("Ausgeschaltet !\n");
    }
    else
    {
          printf("Eingeschaltet !\n");
    }

    if(switch_ret < 0)
    {
        printf("Keine gelesende Daten !\n");

        return -1;
    }

    // Die LED soll mit 0,5s blinken
    timesleep_str.tv_sec = 0;
    timesleep_str.tv_nsec = 500000000;

    // Blinken für 2min oder bist der Taster betätigt wurde
    while(count_i < 120 && switch_value[0] == 0)
    {
        // Status des Tasters abfragen
        switch_ret = read(fd_i, &switch_value, BUFFER_LENGTH);
        led_value = 1;
        // LED einschalten
        write(fd_i, &led_value, sizeof(led_value));
        clock_nanosleep(CLOCK_MONOTONIC, 0, &timesleep_str, NULL);
        led_value = 0;
        //LED ausschalten
        write(fd_i, &led_value, sizeof(led_value));
        clock_nanosleep(CLOCK_MONOTONIC, 0, &timesleep_str, NULL);
        count_i++;
    }

Programm kompelieren:

pi@raspberrypi:~/embedded/apps/led-device-app $ gcc -I . -o led_device_app led_device_app.c

Programm ausführen

pi@raspberrypi:~/embedded/apps/led-device-app $ ./led_device_app

Die LED leuchtet für 2 Minuten oder bis der Taster betätigt wird.