Raspberry Pi: Hardwarenahe Programmierung

Einleitung
Adressierung
GPIO-Register
Function Select Registers (GPFSELn)
GPIO Pin Output Set Registers (GPSETn)
GPIO Pin Output Clear Registers (GPCLRn)
GPIO Pin Level Registers (GPLEVn)
Programmierung
Header-Datei
Implementierung
Beispielanwendung: LED blinken

Einleitung

Da bereits im Vorfeld für ein paar kleine Projekte C als Programmiersprache gewählt wurde, und diese sich für hardwarenahe Programmierung hervorragend eignet, soll hier nun gezeigt werden, wie man die GPIO möglichst direkt ansprechen kann ohne auf Libraries zurückzugreifen, von denen man nicht weiß, wie diese arbeiten.

Es ist jedoch nicht ganz tivial, außer man ist z.B. in der Programmierung von Mikrocontrollern geübt, aber sehr lohnenswert, um ein wenig Verstädnis zwischen dem Zusammenspiel von Hardware und Programmierung zu erlangen, was einem bei den höheren Programmiersprachen mit deren Libraries und Frameworks nahezu abhanden kommt.

Um die GPIO des Raspberry Pi hardwarenah anzusprechen, ist es erstmal wichtig ein Verständnis für die Funktionsweise der Hardware der GPIO zu entwickeln. Dazu arbeitet man am besten die 200 Seiten Dokumentation zum BCM2835 ARM Peripherals durch :-) oder versucht die nachfolgende deutsche Kurzfassung zu verstehen.

Adressierung

Bei Adressierungen unter Linux eines ARM-Prozessors handelt es sich um virtuelle Adressen. Diese erhalten jeweils eine Zuordnung einer physikalischen Adresse durch die MMU (Memory_Management_Unit) des ARM. Diese phyikalische Adresse wiederumg wird in eine Bus-Adresse übersetzt, welche schließlich dazu verwendet wird, die zugehörige Peripherie (z.B. USB, GPIO, RAM, etc.) anzusprechen.

Die physikalische Adresse für die Peripherie beginnt bei 0x20000000 und endet bei 0x20FFFFFF. Die virutelle Startadresse ist 0xF2000000 und die zugehörige Busadresse startet bei 0x7Ennnnnn. Software, die direkt auf bestimmte Peripherie über die Busadresse zugreifen möchte, muss also die physikalische oder virtuelle Adresse kennen, da ein direkter Zugriff nicht möglich ist.

GPIO - Register

Die GPIO (Genrale Purpose) hat bis zu 54 I/Os mit mindestens zwei Funktionen und ggf. darüber hinausgehenden Alternativfunktionen. Der GPIO stehen 41 Register zur Verfügung, über welche die Ansteuerung, das Auslesen, die Auswahl der Funktion, die Auswahl von Pull-Up oder Pull-Down, etc. erfolgen.

Alle Register bestehen aus 32Bit.

Die nachfolgende Tabelle zeigt die Adressierung der einzelnen Register und ist später für das Nachvollziehen des C-Codes von Bedeutung:

Adresse Feldname Beschreibung Größe Read/Write
0x7E20 0000 GPFSEL0 GPIO Function Select 0 32 R/W
0x7E20 0004 GPFSEL1 GPIO Function Select 1 32 R/W
0x7E20 0008 GPFSEL2 GPIO Function Select 2 32 R/W
0x7E20 000C GPFSEL3 GPIO Function Select 3 32 R/W
0x7E20 0010 GPFSEL4 GPIO Function Select 4 32 R/W
0x7E20 0014 GPFSEL5 GPIO Function Select 5 32 R/W
0x7E20 0018 --- Reserve --- ---
0x7E20 001C GPSET0 GPIO Pin Output Set 0 32 W
0x7E20 0020 GPSET1 GPIO Pin Output Set 1 32 W
0x7E20 0024 --- Reserve --- ---
0x7E20 0028 GPCLR0 GPIO Pin Output Clear 0 32 W
0x7E20 002C GPCLR1 GPIO Pin Output Clear 1 32 W
0x7E20 0030 --- Reserve --- ---
0x7E20 0034 GPLEV0 GPIO Pin Level 0 32 R
0x7E20 0038 GPLEV1 GPIO Pin Level 1 32 R
0x7E20 003C --- Reserve --- ---
0x7E20 0040 GPEDS0 GPIO Pin Event Detect Status 0 32 R/W
0x7E20 0044 GPEDS1 GPIO Pin Event Detect Status 1 32 R/W
0x7E20 0048 --- Reserve --- ---
0x7E20 004C GPREN0 GPIO Pin Rising Edge Detect Enable 0 32 R/W
0x7E20 0050 GPREN1 GPIO Pin Rising Edge Detect Enable 1 32 R/W
0x7E20 0054 --- Reserve --- ---
0x7E20 0058 GPFEN0 GPIO Pin Falling Edge Detect Enable 0 32 R/W
0x7E20 005C GPFEN1 GPIO Pin Falling Edge Detect Enable 1 32 R/W
0x7E20 0060 --- Reserve --- ---
0x7E20 0064 GPHEN0 GPIO Pin High Detect Enable 0 32 R/W
0x7E20 0068 GPHEN1 GPIO Pin High Detect Enable 1 32 R/W
0x7E20 006C --- Reserve --- ---
0x7E20 0070 GPLEN0 GPIO Pin Low Detect Enable 0 32 R/W
0x7E20 0074 GPLEN1 GPIO Pin Low Detect Enable 1 32 R/W
0x7E20 0078 --- Reserve --- ---
0x7E20 007C GPAREN0 GPIO Pin Async. Rising Edge Detect 0 32 R/W
0x7E20 0080 GPAREN1 GPIO Pin Async. Rising Edge Detect 1 32 R/W
0x7E20 0084 --- Reserve --- ---
0x7E20 0088 GPAFEN0 GPIO Pin Async. Falling Edge Detect 0 32 R/W
0x7E20 008C GPAFEN1 GPIO Pin Async. Falling Edge Detect 1 32 R/W
0x7E20 0090 --- Reserve --- ---
0x7E20 0094 GPPUD GPIO Pin Pull-up/down Enable 32 R/W
0x7E20 0098 GPPUDCLK0 GPIO Pin Pull-up/down Enable Clock 0 32 R/W
0x7E20 009C GPPUDCLK1 GPIO Pin Pull-up/down Enable Clock 1 32 R/W
0x7E20 00A0 --- Reserve --- ---
0x7E20 00B0 GPFSEL1 Test 4 R/W

GPIO - Function Select Registers (GPFSELn)

Über die Function Select Registers wird definiert, welche Funktion der jeweilige GPIO-PIN erfüllen soll. Insgesamt stehen 6 Register á 32bit zur Verfügung (in Summe 192 Bit). Jedem GPIO-PIN werden 3 Bit zugeordnet, über welche die jeweilige Funktion definiert wird. Damit können bis zu 8 Funktionen codiert werden.

BitFunktion
000GPIO-PIN ist Input-Pin
001GPIO-PIN ist Output-Pin
100GPIO-PIN Alternativfunktion 0
101GPIO-PIN Alternativfunktion 1
110GPIO-PIN Alternativfunktion 2
111GPIO-PIN Alternativfunktion 3
011GPIO-PIN Alternativfunktion 4
010GPIO-PIN Alternativfunktion 5

Die möglichen Alternativfunktionen sind in einer separaten Tabelle unter Punkt 6.2 der BCM2835-Peripherals Dokumentation beschrieben und dem jeweiligen Pin zugeordnet, wobei nicht jeder GPIO-PIN jede Alternativfunktion annehmen kann. Alternativfunktionen können die Verwendung des I2C, SPI, spezielle Rx/Tx eingänge für serielle Übertragungen sein. Es gibt zum Beispiel auch Pin die eine Pulsweitenmodulation ermöglichen (PWM). Es können bis zu 54 Pins mit einer Funktion belegt werden Die nachfolgende Tabelle zeigt die Zuordnung der 54 GPIO-PINs zu den jeweiligen Bits der 6 Function Select Registers.

Bits 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Register 0 Reserve PIN 9 PIN 8 PIN 7 PIN 6 PIN 5 PIN 4 PIN 3 PIN 2 PIN 1 PIN 0
Register 1 Reserve PIN 19 PIN 18 PIN 17 PIN 16 PIN 15 PIN 14 PIN 13 PIN 12 PIN 11 PIN 10
Register 2 Reserve PIN 29 PIN 28 PIN 27 PIN 26 PIN 25 PIN 24 PIN 23 PIN 22 PIN 21 PIN 20
Register 3 Reserve PIN 39 PIN 38 PIN 37 PIN 36 PIN 35 PIN 34 PIN 33 PIN 32 PIN 31 PIN 30
Register 4 Reserve PIN 49 PIN 48 PIN 47 PIN 46 PIN 45 PIN 44 PIN 43 PIN 42 PIN 41 PIN 40
Register 5 Reserve Reserve Reserve Reserve Reserve Reserve Reserve PIN 53 PIN 52 PIN 51 PIN 50

Möchte man also ein bestimmtes GPIO-PIN als Input oder Output-PIN nutzen, muss man die entsprechenden Bits des zugehörigen Registers mit 000 oder 001 beschreiben bzw. eine entsprechende hardwarenahe Programmierung vornehmen.

GPIO Pin Output Set Registers (GPSETn)

Ist ein GPIO-PIN als Output-/Ausgabe-Pin über die zuvor beschriebenen Function Select Registers als Output-Pin festgelegt worden, so lässt sich der Pin über die Output Set Register (GPSETn) beschreiben. Davon gibt es 2 Register á 32 Bit. Jedes der insgesamt 64 Bit ist einem Pin zugeordnet (Reserven ausgenommen):

RegisterBitsGPIO-PINs
Register 0 (GPSET0)31 - 031 - 0
Register 1 (GPSET1)31 - 22Reserve
Register 1 (GPSET1)21 - 053 - 32

Beschreibt man das jeweilige Bit mit 1 so ist der GPIO-PIN gesetzt. Ist der Bit-Wert 0, so hat dies keinem Einfluss. Warum das so ist siehe Output Clear Registers. Ist der Pin als Input-Pin definiert worden, so hat das Beschreiben des Registers keinen Einfluss.

GPIO Pin Output Clear Registers (GPCLRn)

Wie für das Setzen von GPIO-PINs gibt es ein Pendant an zwei Registers, um einen als Output definierten Pin zu löschen bzw. zurückzusetzen. Dass es fürs Setzen und Zurücksetzen separate Register gibt ist eine hardwareseitige Optimierung. Dadurch ist keine zeitintensive "Read-Modify-Write"-Operation erforderlich. Es gibt wieder 2 Register á 32 Bit. Jedes der insgesamt 64 Bit ist einem Pin zugeordnet (Reserven ausgenommen):

RegisterBitsGPIO-PINs
Register 0 (GPCLR0)31 - 031 - 0
Register 1 (GPCLR1)31 - 22Reserve
Register 1 (GPCLR1)21 - 053 - 32

Beschreibt man das entsprechende Bit mit einer 0 so hat dies keinen Einfluss. Beschreibt man das entsprechende Bit mit einer 1 so wir der zugehörige GPIO-PIN zurückgesetzt. Handelt es sich bei dem GPIO-PIN um ein Input-Pin so wird der Wert ignoriert.

GPIO Pin Level Registers (GPLEVn)

Die Level Registers (GPLEVn) beinhalten den aktuellen Wert der GPIO-PINs. Diese Register müssen also ausgelesen werden, wenn man GPIO-PINs als Inputs-Pins verwendet. Es handelt sich wieder um 2 Register á 32 Bit mit einer entsprechenden Zuordnung der einzelnen Bits zum GPIO-PIN:

RegisterBitsGPIO-PINs
Register 0 (GPLEV0)31 - 031 - 0
Register 1 (GPLEV1)31 - 22Reserve
Register 1 (GPLEV1)21 - 053 - 32

Ist das betreffende Bit 0, so ist der GPIO-PIN low. Ist das betreffende Bit 0, so ist der GPIO-PIN High. Dies muss jedoch immer im Zusammenhang mit der Definition des Pullup-/Pulldown-Widerstand gesehen werden.

Programmierung

Header-Datei

Jetzt wird es spannend. Denn das beschrieben der einzelnen Register soll mit C hardwarenah programmiert werden. Dazu schreibt man folgende Header-Datei, welche in meinem Fall rpi_io.h heißt. In der Header-Datei werden ein paar Konstanten und Makros sowie Funktionen defieniert, die nachfolgend etwas genauer unter die Lupe genommen werden.

#include <stdio.h>

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>

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

//phys. Adressraum/Startadresse für Periperiekomponenten, wie USB, GPIO
#define BCM2708_PERI_BASE   0x20000000 
//phys. Adressraum/Startadresse für GPIO
#define GPIO_BASE           (BCM2708_PERI_BASE + 0x200000)

#define BLOCK_SIZE          (4*1024)

//IO Zugriff Struct
struct bcm2835_peripheral 
{
    unsigned long addr_p;
    int mem_fd; //file descriptor to referer memory file /dev/mem
    void *map;
    volatile unsigned int *addr;
};
//volatile sorgt dafür, dass Wert im aus Hauptspeicher geholt wird und
//nicht aus irgend ein Register in CPU; 

extern struct bcm2835_peripheral gpio;

// Makros für GPIO-Zugriff; immer INP_GPIO(x) vor OUT_GPIO(x) benutzen
#define INP_GPIO(g)   *(gpio.addr + ((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g)   *(gpio.addr + ((g)/10)) |=  (1<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio.addr + (((g)/10))) |= (((a)<=3?(a) + 4:(a)==4?3:2)<<(((g)%10)*3))

#define GPIO_SET  *(gpio.addr + 7)  // setzt Bits die 1 sind und ignoriert Bits die 0 sind
#define GPIO_CLR  *(gpio.addr + 10) // löscht Bits die 1 sind und ignoriert Bits die 0 sind

#define GPIO_READ(g)  *(gpio.addr + 13) &= (1<<(g))
#define GPIO_PULL  *(gpio.addr + 37)  //Pull up oder Pull Down Aktivierungen
#define GPIO_PULLCLK(g) *(gpio.addr + 38) &= (1<<(g)) //Clock pull up oder pull down

//Zugriff auf physikalischen Speicher über Datei dev/mem von Linux-Kernel
extern int map_peripherals(struct bcm2835_peripheral *p);
extern void unmap_peripherals(struct bcm2835_peripheral *p);

extern void delay(ulong seconds, ulong nanoseconds);

Wie oben zu lesen war, startet die GPIO-Adressierung mit einem Offset von 0x20000. Dies wird durch folgende Define-Anweisungen festgelegt:

#define BCM2708_PERI_BASE   0x20000000 
#define GPIO_BASE           (BCM2708_PERI_BASE + 0x200000)

Die Struktur bcm2835_peripheral wird benötigt für den Zugriff auf den virtuellen Adressraum unter Linux über die Datei dev/mem, welche ein image zur Anbindung des physikalischen Speichers enthält. Die Wirkungsweise der Struktur versteht sich am besten im Zusammenhang mit der Implementierung derFunktion map_peripherals und unmap_peripherals.

Das wirklich Interessante sind die Makros. Wer sich diese Makros ausgedacht hat schwebt in anderen Spähren. Die Erläuterung soll hier auf die 5 fürs Verständnis wichtigen Makros beschränkt sein:

Makro zum Festlegen eines Input-Pin

Makros werden vom Präprozessor vor bzw. während der Kompilierung ausgeführt. Das Vorkommen des Makro-Bezeichners wird dabei durch das Ergebnis des Makros eretzt.
Im folgenden Makro wird der GPIO-Pin g als Input-Pin definiert.

#define INP_GPIO(g)   *(gpio.addr + ((g)/10)) &= ~(7<<(((g)%10)*3))

Hier müssen wir uns die Tabellen von oben zur Adressierung und zu den Function Select Registern vergegenwärtigen. Dort sind jeweils 10 GPIO-PINs einem Register zugeordnet. Nehmen wir als Beispiel den GPIO-PIN 17, also g=17

gpio.addr + ((g)/10)

Man dividiert den Pin mit 10 als Integer-Division. Das Ergebnis beinhalte in C keinen Wert nach dem Komma. In unserem Beispiel wäre das Ergebnis 1. Die Funktion des GPIO-Pins 17 wird im Register GPFSEL1 definiert. Die GPIO-Basis-Adresse wird entsprechend erhöht, um die Adresse von GPFSEL1 zu erhalten. Über den *-Operator wird die Adresse dereffenziert umd Zugriff auf den Inhalt zu bekommen. Um GPIO-PIN 17 in seiner Funktion als Input-Pin zu defnieren müssen wir nun die Pins 23-21 von GPFSEL1 beschreiben bzw. auf null setzen. Dazu rechnet man

g%10 => g modulo 10

was den Rest der Division ausgibt; in unserem Beispiel 17 modulo 10 = 7. Dieses Ergebnis wir mit 3 multiplizerit; als 7x3=21 Nun wird über den Operator << ein Linksshift, also ein bitweises verschieben nach links durchgeführt; in unserem Beispiel um 21 Bit.

77<<21
0000000000000000000000000000011100000000111000000000000000000000

Die 7 entspricht also die ersten 3 Bit die High sind. Diese drei Bit werden über die Linksshift-Operation in das entsprechende Triplet für das GPIO-Pin geschoben und nun mit dem Tilde(~)-Operator negiert:

~00000000111000000000000000000000 = 11111111000111111111111111111111

Damit haben wir eine Bit-Maske mit welcher man über eine UND-Verknüpfung die Bits 23-21 null setzen kann und die anderen Bits ausblendet. Über den Operator &= erfolgt nun die logische UND-Verknüpfung mit anschließender neuer Zuweisung des Inhalts ins Register.

Makro zum Festlegen eines Output-Pin

#define OUT_GPIO(g)   *(gpio.addr + ((g)/10)) |=  (1<<(((g)%10)*3))

Wie beim Festlegen der Funktion als Input-Pin wird für die Festlegung als Output-Pin die Adresse des betreffenden Registers erstmal über

gpio.addr + ((g)/10)

ermittelt und anschließende mit dem Sternchen-Operator dereferenziert. Der Unterschied besteht nun darin, dass das entsprechende Bit-Triplet mit 001 beschrieben werden muss, um den GPIO-PIN in seiner Funktion als Output-Pin festzulegen. Dazu rechnet man ebenfalls wie beim Input-Makro

((g)%10)*3)

um den erforderlichen Wert für die bitweise Linksverschiebung zu erhalten. Nehmen wir unser Beispiel mit dem GPIO-Pin 17 so ergibt sich, dass 1 Bit um 21 Stellen nach links verschoben werden muss

11<<21
0000000000000000000000000000000100000000001000000000000000000000

Das Ergebnis der Verschiebung wird nun logisch Oder-verknüpft mit dem Inhalt des Registers. Auch in diesem Fall wirkt es wie ein Bitmaske. Alle Bits außer in unserem Fall das Bit 21 werden ignoriet. Nur das Bit 21 wird durch die Oder-Verknüpfung und anschließenden Neuzuweisung (&=) auf 1 gesetzt, wodurch GPIO-PIN 17 als Output definiert ist.

Makro zum Setzen eines GPIO-PIN

Dieses Makro wird benötigt zum konkreten Beschreiben bzw. Setzen eines GPIO-PIN.

#define GPIO_SET  *(gpio.addr + 7)

Dieses Makro ist relativ einfach und wer die beiden vorhergehenden Makros verstanden hat, weiß nun, dass hier einfach die GPIO-Basisadresse um 7 hochgezählt wird, damit der Zeiger auf dem ersten Register GPSET0 steht. Durch die Derefenrenzierung über den *-Operator hat man Zugriff auf den Inhalt.

Makro zum Zurücksetzen eines GPIO-PIN

Dieses Makro wird benötigt zum konkreten Löschen bzw. Zurücksetzen eines GPIO-PIN.

#define GPIO_CLR  *(gpio.addr + 10)

Dieses Makro ist relativ einfach und wer die vorhergehenden Makros verstanden hat, weiß nun, dass hier einfach die GPIO-Basisadresse um 10 hochgezählt wird, damit der Zeiger auf dem ersten Register GPCLR0 steht. Durch die Derefenrenzierung über den *-Operator bekommt man Zugriff auf den Inhalt.

Makro zum Auslesen eines GPIO-PIN

#define GPIO_READ(g)  *(gpio.addr + 13) &= (1<<(g))

Um ein GPIO-PIN auzulesen, benötigen wir den Zugriff auf die GPLEVn-Register. Dazu Addieren wir 13 zur GPIO-Basisadresse. Jetzt bauen wir uns wieder eine entsprechende Bitmaske. Da wir wissen das unser beispielhafter GPIO-Pin 17 auf dem Bit 17 von GPLEV0 seinen Wert aufgibt verschieben wir 1Bit um 17 Stellen nach links:

11<<17
0000000000000000000000000000000100000000000000100000000000000000

Durch die anschließende logische UND-Verknüpfung werden alle anderen Bits außer Bit 17 ausgeblendet. Das Ergebnis der UND-Verknüpfung entsprich genau dem Status des Bit 17.

Implementierung

Sind die Deklarationen in der Header-Datei erfolgt, so muss noch die Implementierung in der C-Datei, in meinem Fall rpi_io.c erfolgen:

#include "rpi_io.h"

struct bcm2835_peripheral gpio = {GPIO_BASE};

//Zugriff auf physikalischen Speicher über Datei dev/mem von Linux-Kernel
int map_peripherals(struct bcm2835_peripheral *p)
{
    //öffnen /dev/mem 
    if((p->mem_fd = open("/dev/mem", O_RDWR|O_SYNC)) < 0)
    {
        printf("Fehler beim Öffnen von /dev/mem. Überprüfe Berechtigungen.\n");
        return -1;
    }

    //mmap creates a new mapping in the virtual address space
    p->map = mmap(
        NULL,
        BLOCK_SIZE,
        PROT_READ|PROT_WRITE,
        MAP_SHARED,
        p->mem_fd, //file descriptor zu phys.Speicher des virtual file '/dev/mem'
        p->addr_p   
    );

    if (p->map == MAP_FAILED)
    {
        perror("mmap");
        return -1;
    }

    p->addr = (volatile unsigned int *)p->map;
    return 0;   
}

void unmap_peripherals(struct bcm2835_peripheral *p)
{
    munmap(p->map, BLOCK_SIZE);
    close(p->mem_fd);
}

void delay(ulong seconds, ulong nanoseconds)
{
    struct timespec req = {0};
    req.tv_sec = seconds;
    req.tv_nsec = nanoseconds; //;
    nanosleep(&req, NULL);  
}

Es sind hier drei Funktionen implementiert, wobei die interessanteste die Funktion map_peripherals ist. Mit

p->mem_fd = open("/dev/mem", O_RDWR|O_SYNC))

wird versucht die Datei /dev/mem, welche das Mapping der virtuellen auf die physikalischen Adressbereiche enthält, zu öffnen. Gelingt dies nicht wird eine Fehlermeldung ausgegeben und die Funktion beendet. Zu beachten ist, dass der Zugriff auf /dev/mem Administratorrechte benötigt. Das heißt, wenn man später ein Programm ausführt, welches diese Funktion benutzt, muss man es mit sudo starten.
Danach erfolgt der Aufruf von mmap. Das ist eine spezielle Funktion die das Abbild zum virtuellen Adressraum erzeugt. Sollte das Mapping fehlschlagen, wird auch hier die Funktion verlassen unter Ausgabe einer Fehlermeldung.

Die Funktion unmap_peripherals sollte man aufrufen, wenn das Mapping nicht benötigt wird, um Ressourcen freizugeben und die Datei zu schließen.

delay ist eine Hilfsfunktion für Nanosekundengenaue-Verzögerungen. Diese findet aber keine Anwendung in unserem Beispiel.

Beispielanwendung: LED blinken

Nun wollen mit unserer kleinen hardwarenahen Bibliothek eine LED zum Blinken bringen. Mit diesem Anwendungsbeispiel, sollten dann die Funktionen auch verständlicher werden. Bevor wir uns in den Code stürzen betrachten wir erstmal ein kleines Prinzipschaltbild zum Anschluss der LED

GPIO LED-Schaltbild

In unserem Beispiel wollen wir den GPIO-PIN 17 verwenden, welcher auf der Anschlussleiste des Raspberry Pi den Pin 11 entspricht. Den C-Code dafür schreiben wir in die die Datei blink.c.

#include "rpi_io.h"

#define PIN     (17)

int main(){

    if(map_peripherals(&gpio) == -1)
    {
        printf("Fehler beim Mapping des physikalischen GPIO-Registers in den virtuellen Speicherbereich.\n");
        return -1;
    }

    //Pin 17 als Output-Pin definieren
    INP_GPIO(PIN);
    OUT_GPIO(PIN);

    while(1)
    {
        GPIO_SET = 1 << PIN;
        sleep(1);

        GPIO_CLR = 1 << PIN;
        sleep(1);
    }

    return 0;
}

In dem Programm wird unsere zuvor geschriebene Header-Datei über #include "rpi_io.h" eingebunden, damit wir Zugriff auf die Funktionen erhalten und es wird der Pin als GPIO-PIN 17 definiert (#define PIN (17)).
In der main-Routine unseres Programms wird als erstes versucht, dass Mapping auf den virtuellen Adressraum durchzuführen. Dazu wird die Funktion map_peripherals(&gpio) aufgerufen. Schlägt das fehl, wird das Programm beendet und eine Fehlermeldung ausgegeben. Ist dies erfogreich, so beinhaltet gpio.addr nun die richtige Basisadresse für das Ausführen nachfolgender Makros.

Als nächstes muss GPIO-PIN 17 als Output-Pin festgelegt werden. Dazu ist es notwendig immer zuerst das Makro INP_GPIO(PIN) aufzurufen. Wir errinnern uns, dass dieses Makro das entsprechende Bit-Triplet der Function Select Register auf 000 setzt. Dies ist wichtig, um einen definierten Ausgangszustand zu schaffen, weil wir nicht wissen was dort drin steht. Danach rufen wir das Makro OUT_GPIO(PIN) auf, welches den Pin als Output-Pin festlegt und 001 in das Bit-Triplet schreibt.

Um das eigentliche Blinken zu erzeugen begeben wir uns in eine Dauerschleife und beschreiben jeweils abwechselnd die Output-Set- und Output-Clear-Register des Pins durch ein bitweises verschieben von 1 zum betreffenden Register-Bit. Die Funktion sleep sorgt dabei für eine Verzögerung von 1 Sekunde.

Zum Abschluss noch ein Orginalfoto der Verkabelung der LED am Raspberry Pi, wobei hier fälschlicher Weise auf den Vorwiderstand verzichtet wurde. Die LED hat es aber überlebt ;-)

Foto der Raspberry Pi Verkabelung

Das Kompilieren erfolgt über das Terminal mit

gcc blink.c rpi_io.c -o blink

Und anschließend ausführen des Programms im Terminal und schon sollte die LED im Sekundentakt blinken

./blink

[Datum: 17.09.2016]