Kampis Elektroecke

Lektion 1

Raspberry Pi

In diesem Artikel geht es darum, wie ohne die Unterstützung durch ein Betriebssystems, eine LED zum leuchten, bzw. zum blinken gebracht werden kann. Dazu wird das fertig erstellte Programm den Linuxkernel des Raspbberry Pi ersetzen.

Für ein besseres Verständnis ist es ratsam das BCM2835 Peripherie Datenblatt zur Hand zu haben, da für die Lösung der Aufgabenstellung einige Registeradressen benötigt werden. Es kann außerdem nicht schaden, wenn ihr zusätzlich das ARM Befehlsdatenblatt offen habt. In diesem Dokument sind alle verfügbaren ARM Befehlscodes vermerkt.

Ok los geht´s…

Die Ordnerstruktur:

Für die spätere Kompilierung wird ein Makefile benötigt, welches definiert, wie der Code kompiliert und zu einem Kernelimage gelinkt wird. Alle benötigten Dateien sind in diesem Ordnertemplate zu finden. In diesem Ordner ist ein Verzeichnis mit dem Namen source zu finden, in dem alle Assemblerdateien abgelegt werden. Der Inhalt dieses Verzeichnisses wird während des Kompilierens durch das Tool make abgearbeitet und abschließend zu einem Programm (hier kernel.img) gelinkt.

Genauere Informationen hierzu stehen in der Datei Makefile, welche wir uns mal anschauen. Diese Datei kann z. B. mit Programmer´s Notepad geöffnet werden. Wirklich wichtig sind für uns nur die ersten paar Punkte, da ich (noch) nicht so tief in den Aufbau eines Make-Files einsteigen will:

  • Der erste Punkt gibt den verwendeten Compiler an. In diesem Fall ist es der Compiler arm-none-eabi für eine sogenannte bare metall Programmierung. Als bare metall Programmierung wird das Programmieren ohne darunter liegendes Betriebssystem bezeichnet – man startet ohne jegliche Software und Schnittstellen und baut sich alles selber zusammen.
  • Der zweite und der dritte Punkt gibt das Verzeichnis für die kompilierten Objekte bzw. Sourcefiles an. Dabei wird das Verzeichnis des Makefiles als Ausgangspunkt verwendet.
  • Im vierten Punkt legt man den Namen des erzeugen Kernelimages fest. Der Name muss immer kernel.img heißen, damit der Bootloader des Raspberry Pi das erstellte Image laden kann!
  • Die nächsten beiden Punkte geben die Dateinamen des Listingfiles und des Mapfiles an und interessieren uns nicht wirklich.
  • Und der letzte Punkt gibt das verwendeten Linkerskript an. Ein Linker sorgt dafür, dass die Programmteile zu einem ausführbaren Programm zusammen gesetzt werden. Damit der Linker dies machen kann benötigt er Informationen darüber, an welche Speicheradressen die einzelnen Programmteile (z. B. die Code- und die Datensektion) gespeichert werden. Dies ist die Aufgabe des Linkerskripts.

Eine LED einschalten:

Für das erste Programm wird eine neue Assemblerdatei angelegt und als main.s gespeichert. Das Ziel soll die Aktivierung einer LED an GPIO 24 sein. 

Dazu wird das Datenblatt der BCM2835 Peripherie zu benötigt. Auf S. 89 beginnt die Sektion über die GPIO, die für die Ansteuerung der GPIO benötigt wird und auf S. 90 befindet sich die Registerübersicht. Zu allererst wird der GPIO über die Register GPFSELn als Ausgang konfiguriert. Wenn man das Datenblatt etwas weiter liest, sieht man ab S. 92 das die GPIO unterteilt sind. Register 0 deckt die GPIO 0 – 9 ab, Register 1 die GPIO 10 – 19, usw.

Da der GPIO 24 angesteuert werden soll wird das Register GPFSEL2 benötigt. Ok, also wieder zurück scrollen auf die S. 90 für die Registerübersicht. Dort lässt sich entnehmen, dass das Register GPFSEL2 bei der Adresse 0x20000008 anfängt. Das Datenblatt zeigt eine genaue Auflistung der Unterteilung des Registers für GPIO 24. Um den GPIO 24 als Ausgang zu deklarieren, muss also in den Bereich FSEL24 der Wert 0x01 stehen.

Nachdem der GPIO als Ausgang deklariert wurde, kann der Ausgang über das GPSETn-Register gesetzt werden. In diesem Register repräsentiert jedes Bit für einen einzelnen I/O.

Hier ein kleiner Hinweis:
Das Thema Registeradressen ist leider etwas komplizierter. In der Registerübersicht des GPIO-Controllers steht, dass die Startadresse des GPIO-Controllers bei 0x7E200000 liegt. Bei dieser Adresse handelt es sich um eine virtuelle Speicheradresse, die üblicherweise von einem Betriebssystem genutzt und durch eine MMU auf eine physische Adresse gemappt wird. Ohne Betriebssystem ist man an die physikalischen Adressen des Chips gebunden. Eine Übersicht dazu findet sich auf S. 5 des Peripheriedatenblattes:

Da keine MMU genutzt wird, gelten die Adressen aus dem mittleren Adressbereich. Daraus ergibt sich die Adresse 0x20200000 und nicht 0x7E200000 für den GPIO-Controller (das 7E wird mit 20 ersetzt). Nun sind aber alle Komponenten vorhanden. Zeit um aus diesen Erkenntnissen Code zu generieren…

Die ersten drei Zeilen müssen immer gleich bleiben. Sie dienen als Einstigspunkt für das Programm. Das globale Label _start kann mit dem Namen der Hauptfunktion main in C verglichen werden. Die Adresse dieses Labels wird vom Linker als Referenz für den Anfang der Codesektion im erzeugten Programm genutzt (die sogenannte .text Sektion).

Im nächsten Schritt wird die Basisadresse bzw. die Anfangsadresse des GPIO Controllers im Register r0 gespeichert. Diese lautet 0x20200000 und ist in der Tabelle auf S. 90 zu finden (das Umrechnen der Adresse nicht vergessen!).

Mit den nächsten beiden Schritten wird der GPIO 24 als Ausgang deklariert. 

Jetzt wird der Wert in das entsprechende GPFSEL-Register geschrieben.

Dieser Befehl kopiert den Wert von Register r1 zu der in Register r0 und um den Wert 8 erhöhte Adresse.

Der nächste Schritt besteht darin, den Ausgang auf High zu setzen. Der Chip besitzt dazu ein GPIO Pin Output Set Register (GPSETn). Jedes Register deckt bis zu 32 GPIO ab, wobei die einzelnen Bits für einen GPIO stehen (Register 0 Bit 0 → GPIO 0, Register 0 Bit 1 → GPIO 1, etc.).

Um den GPIO 24 zu setzen muss also das Bit 24 des Registers GPSET0 gesetzt werden.

Der Adressoffset zur Adresse des GPIO-Controllers muss auf 28 und die Anzahl der zu schiebenden Stellen auf 24 gesetzt werden. Als letztes wird noch eine Endlosschleife eingefügt um den Prozessor beschäftigt zu halten.

Die Datei wird nun als main.s im source-Verzeichnis des Verzeichnistemplates abgespeichert. Der Kompiliervorgang kann nun in der Konsole mit dem Befehl make gestartet werden:

Wenn der Kompiliervorgang erfolgreich beendet wurde, ist in dem Verzeichnis eine Datei namens kernel.img zu finden. Alles was jetzt noch benötigt wird ist eine SD-Karte mit einem bereits installierten Betriebssystem. Auf dieser SD-Karte wird die vorhandene Datei kernel.img durch die von euch erzeugte Datei kernel.img ersetzt.

Nun kann der Raspberry Pi eingeschaltet werden und kurz nach dem Einschalten leuchtet die LED an GPIO 24 auf.

Die LED blinken lassen:

Im nächsten Schritt soll die LED blinken. Dazu wird das erstellte Programm geringfügig abgeändert und um eine Endlosschleife erweitert:

Die neue Endlosschleife besteht aus einem Verzögerungsblock, wo der Wert 0x3F0000 in das Register r2 geladen und dann in einer Schleife so lange um 1 reduziert wird, bis der Wert 0x00 erreicht wird:

Sobald die Schleife durchgelaufen ist, wird die LED entweder an oder aus geschaltet. Es resultiert eine Blinkfrequenz von (etwa) 2 Hz.

Einlesen eines Tasters:

Im letzten Beispiel möchte ich zeigen, wie die LED über einen Button geschaltet werden kann. Der Taster ist an GPIO 13 angeschlossen und schaltet gegen Masse, sodass bei einem Tastendruck der I/O einen Lowpegel führt.

Als erstes wird der I/O für die LED als Ausgang und der I/O für den Button als Eingang deklariert, indem die entsprechenden Bits gelöscht, bzw. gesetzt werden.

Für die weitere Abarbeitung des Programms habe ich mir Masken für den GPIO 13 und 24 in je ein Register kopiert:

Anschließend wird in einer Endlosschleife der Zustand von GPIO 13 abgefragt, wobei der aktuelle Zustand eines GPIO den GPLEVn-Registern entnommen werden kann. Jedes Register beinhaltet 32 GPIO, wobei der Zustand von GPIO 13 in GPLEV0 gespeichert ist. Das Register wird zuerst ausgelesen und mit dem tst-Befehl kann geprüft werden ob ein oder mehrere spezifische Bits in einem Register gesetzt sind. Dazu wird werden die Bits des Quellregisters mit dem zu testenden Wert bitweise UND verknüpft. Das Ergebnis des tst-Befehls wird in das Z-Flag geschrieben.

Wenn das Bit gesetzt ist, ist der Taster nicht betätigt worden und das entsprechende Bit ist 1, wodurch das Z-Flag gesetzt wird. Wird der Taster betätigt, wird das Bit im GPLEV0-Register und das Z-Flag auf 0 gesetzt.

Jetzt kommt eine Besonderheit der ARM-Architektur zum Einsatz: Das sogenannte Condition Field der einzelnen Instruktionen. Befehle mit einem Condition Field lassen sich durch den Zusatz {cond} in der Syntax erkennen, wie z. B. der LDR-Befehl:

Der Vorteil einer konditionalen Befehlsausführung liegt darin, dass weniger Branches, also Sprünge, durchgeführt werden müssen und somit die Pipeline des Prozessors nicht mehr so oft neu gefüllt werden muss. Dies spart Rechenzeit, macht die Befehlsabarbeitung schneller und spart nebenbei noch Code.

Jeder Befehl mit einem Conditional Field kann mit einer von 16 Konditionen versehen werden, wobei jede Kondition ein oder mehrere Flags abfragt:

EQ Z = 1 HI (C = 1) & (Z = 0)
NE Z = 0 LS (C = 0) | Z
HS / CS C = 1 GE ((N = 1) & (V = 1)) | (V = 0)
LO / CC C = 0 LT ((N = 1) & (V = 0)) | ((N = 0) & (V = 1))
MI N = 1 GT (Z = 0) & (((N = 1) & (V = 1)) | ((N = 0) & (V = 1)))
PL N = 0 LE ((Z = 1) | (N = 1)) & ((V = 0) | (N = 0)) & (V = 1)
VS  V = 1 AL Immer
VC V = 0 NV

Bei anderen Architekturen (z. B. bei AVR Mikrocontrollern) würde man nun prüfen ob das Z-Bit gesetzt ist und einen Branch, also einen Sprung, vollführen um die LED zu aktivieren, bzw. zu deaktivieren. Durch die konditionelle Befehlsausführung kann die LED ohne Branch geschaltet werden, indem der str-Befehl zum Beschreiben der Register mit einer passenden Bedingung versehen wird.

Der erste str-Befehl wird nur ausgeführt, wenn das Z-Flag gesetzt, also das Bit im GPLVL0-Register gesetzt ist und damit der I/O einen High-Pegel führt. Dann wird der Inhalt von r2, also 0x1000, in das Register GPCLR0 kopiert, wodurch der LED-Pin auf Low gesetzt wird.

Der zweite str-Befehl hingehen wird nur dann ausgeführt, wenn das Z-Flag gelöscht, also das Bit im GPLVL0-Register nicht gesetzt ist. Dann wird der Wert 0x1000 in das GPSET0-Register kopiert, wodurch der LED-Pin gesetzt wird.

Jetzt kann ein neues Kernelimage erstellt und auf die SD-Karte kopiert werden. Sobald der Raspberry Pi mit Spannung versorgt wird, kann über den angeschlossenen Taster die LED ein- und ausgeschaltet werden.

Alle Programme stehen in meinem Gitlab Repository zum Download bereit.

→ Zurück zu Raspberry Pi Assembler

24 Kommentare

  1. C:\Users\patrick\Desktop\template\template>make
    arm-none-eabi-ld –no-undefined -Map kernel.map -o build/output.elf -T kernel.l
    d
    process_begin: CreateProcess(NULL, arm-none-eabi-ld –no-undefined -Map kernel.m
    ap -o build/output.elf -T kernel.ld, …) failed.
    make (e=2): Das System kann die angegebene Datei nicht finden.
    make: *** [build/output.elf] Error 2

    was funktioniert denn hier nicht? die led.asm datei ist im source ordner ??
    Danke

      1. Hallo Kampi,
        danke für die rückmeldung, leider kommt jetzt eine andere fehlermeldung…

        C:\Users\patrick\Desktop\template\template>make
        mkdir build
        arm-none-eabi-as -I source/ source/main.s -o build/main.o
        process_begin: CreateProcess(NULL, arm-none-eabi-as -I source/ source/main.s -o
        build/main.o, …) failed.
        make (e=2): Das System kann die angegebene Datei nicht finden.
        make: *** [build/main.o] Error 2

        leider werde ich aus dieser fehlermeldung auch nicht schlau, da hier irgendwie garnicht angegeben ist, welche datei denn nun fehlt….

        Gruß

        1. Hallo Patrick,

          habe es gerade mal getestet.
          Als Software brauchst du:

          http://www.emb4fun.de/archive/gabmt/download/yagarto-tools-20121018-setup.exe

          http://sourceforge.net/projects/yagarto/

          Sowie MiniGW als Compiler

          http://sourceforge.net/projects/mingw/files/MSYS/Base/msys-core/msys-1.0.10/MSYS-1.0.10.exe/download

          Danach öffnest du die Konsole, wechselst in das Verzeichnis und gibst “make” ein (musst den build-Ordner vorher aber löschen).

          Gruß
          Daniel

  2. Hallo Daniel,

    vielen Dank für die wirklich schnelle Antwort und entschuldige bitte, dass ich schon wieder störe. Aller Anfang ist schwer. Beim Compiilieren erhalte ich folgende Fehlermeldungen:
    C:\ASM>make
    MAKE Version 5.2 Copyright (c) 1987, 1998 Inprise Corp.
    ld –no-undefined -Map kernel.map -o build/output.elf -T kernel.ld
    Der Befehl “ld” ist entweder falsch geschrieben oder konnte nicht gefunden werden.
    objcopy build/output.elf -O binary kernel.img
    Der Befehl “objcopy” ist entweder falsch geschrieben oder konnte nicht gefunden werden.
    objdump -d build/output.elf > kernel.list
    Der Befehl “objdump” ist entweder falsch geschrieben oder konnte nicht gefunden werden.

    Ich habe die 3 Tools mehrmals neu installiert und habe dabei nie eine Fehlermeldung erhalten. Was habe ich falsch gemacht? Im Ordner ASM liegen 3 Dateien und der Ordner source mit der Datei main.s .

      1. Hi Daniel,

        das ging ja extrem schnell – Danke!

        make – version wird beantwortet mit “Incorrect command line argument: – version” und ca. 20 Optionen zur Syntax.
        Die zweite Antwort lautet:
        arm-none-eabi-gcc: error: unrecognized command line option ‘-version’
        arm-none-eabi-gcc: fatal error: no input files
        compilation terminated.

        Gruß
        Bernd

          1. Hi Daniel,

            zur 1. Abfrage:
            MAKE Version 5.2 Copyright (c) 1987, 1998 Inprise Corp.
            Incorrect command line argument: –version
            und dann wieder die 20 Optionen
            zur 2. Abfrage:
            arm-none-eabi-gcc (GCC) 4.7.2
            Copyright (C) 2012 Free Software Foundation, Inc.
            This is free software; see the source for copying conditions. There is NO waranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

            Grüße
            Bernd

        1. Bitte die Windows Enviroment Variable PATH anschauen! Damit das “richtige” make benutzt wird, muss das richtige make Verzeichnis am Anfang stehen.

  3. Hi Daniel,

    meine letzte Frage: An welcher Stelle erkennst Du was? Lasse Dir Zeit! Wichtig ist für mich nur das Ergebnis, dass ich den Cross Compiler nutzen kann.

    MfG
    Bernd

    1. Hallo Bernd,

      ich habe es gerade noch mal getestet.
      Du installierst die Tools (als Admin und mit den Defaultpfaden) und lädst dir mein Beispielprogramm runter.
      Das entpackst du z.B. bei dir auf dem Desktop und löscht die Dateien “kernel.img”, “kernel.list”, “kernel.ld” und den Inhalt von “build”.
      Anschließend öffnest du eine neue Konsole (wenn du die bei der Installation der Tools offen hattest, musst du die einmal schließen, damit die PATH-Variable neu geladen wird) und wechselst in das Verzeichnis des Beispielprogrammes.
      Dort tippst du dann “make” ein und dann sollte es funktionieren.

      Probier das mal bitte so und wenn es nicht klappt einfach noch mal melden (wenn du einen Linux Rechner hast, kannst du es auch damit machen…da ist es einfacher als mit Windows…).

      Gruß
      Daniel

      1. Hallo Daniel,

        habe alle 3 Tools nochmals als Admin in die Defaultpfade installiert. Dein Beispielprogramm in meinem Arbeitsordner entpackt, die 3 Kernel-Dateien gelöscht und auch die 2 Dateien im build ordner gelöscht. Die Windowseingabeaufforderung geöffnet, den Pfad zu meinem Arbeitsordner eingestellt und make eingetippt. Das Ergebnis ist:
        MAKE Version 5.2 Copyright (c) 1987,1998 Inprise Corp.
        Fatal: ‘kernel.ld’ does not exist – don’t know how to make it
        Kopiere ich nun zusätzlich kernel.ld in meinen Arbeitsordner erscheint wieder die Fehlermeldung mit falsch geschrieben oder nicht gefunden.
        Vielleicht liegt’s an meinem System. Habe leider nur Win7 Ultimate 32bit. Oder es liegen noch alte Fragmente von den vorherigen Installationsversuchen im System. Werde heute Abend mal meinen XP-Laptop aktivieren. Es sieht aber eher nach einem Versionsproblem aus oder mir fehlt noch ein Tool.
        Warum geht’s bei Dir und bei mir nicht?

        Grüße
        Bernd

        1. Hallo Bernd,

          tut mir leid das du so viele Probleme hast :(
          Das ist natürlich schwer zu erkennen woran es liegt und ich kann dir auch nicht sagen warum es bei mir funktioniert.
          Aber das sieht mir nach einem Problem mit dem Linkerskript aus.
          Hast du eventuell Leerzeichen oder Sonderzeichen im Dateipfad drin? Weil das kann schon mal gerne Probleme verursachen.

          Wie gesagt….so ein Cross Compiler unter Windows ist ein Krampf schlechthin. Mittlerweile nutze ich ein Ubunu für den Cross Compile Vorgang, da es dafür zahlreiche Anleitungen gibt und es auch einfacher funktioniert wie ich finde…

          Gruß
          Daniel

          1. Hallo Daniel,
            nur zur Rückmeldung:
            Auch unter XP habe ich die gleichen Fehlermeldungen. Nur bei der Installation von MinGW mußte ich ein paar Fragen mehr beantworten. Gerade hab ich nochmals alles deinstalliert und neu angefangen. In der Konsole bei der MinGW Inst. fällt mir ein Abschnitt auf, der vielleicht die Ursache ist:
            c:\msys\1.0\postinstall>..\bin\sh.exe pi.sh
            AllocationBase 0x0, BaseAddress 0x715B0000, RegionSize 0xB0000, State 0x10000
            C:\msys\1.0\bin\sh.exe: *** Couldn’t reserve space for cygwin’s heap, Win32 error 0
            C:\msys\1.0\postinstall>pause
            Drücken Sie eine beliebige Taste . . .

            Mein Arbeitspfag lautet: C:\asm. Der sollte also auch keine Probleme machen. Vielleicht fällt Dir zur Fehlermeldung noch was ein.
            Vielen Dank für Deine Bemühungen

            Gruß
            Bernd

          2. Hey Bernd,

            mmh mir fällt leider auch nichts mehr ein :(
            Was spricht den gegen eine VM (z.B. mit VirtualBox) und einem Ubuntu als OS? Dann kannst du den Cross Compiler unter Linux nutzen…da kann ich dir mehr mit helfen und das funktioniert sehr oft auch viel besser.

            Gruß
            Daniel

  4. Hallo Daniel,

    tolle Seite, habe schon cross compiled und muß noch das led-image am Raspi ausprobieren. Installationen haben super geklappt. Großes Lob für die Beschreibung ein Crossentwicklungssystem unter Windows für ARM zu installieren.

    Zwei kleine Fehler sind mir aufgefallen in Deiner Beschreibung und einen Vorschlag hätte ich noch.
    1. Im Text: müssen wir also in den Bereich FSEL24 den Wert 010 rein schreiben.
    Korrektur: der richtige Wert müsste 001 sein.
    2. Im Text: Um den GPIO 24 zu löschen müssen wir laut dem Datenblatt also Bit 24 des Registers GPCLR1 setzen.
    Korrektur: Registers GPCLR0 setzen

    Vorschlag:
    3. Im Text: Jetzt wird die Datei gespeichert und dann eine Konsole geöffnet. In der Konsole müsst ihr nun in das Verzeichnis des Projektes wechseln:

    Noch einmal Danke für die tolle Anleitung
    bachitoph

    Vorschlag: Mein Template-Verzeichnis habe ich umbenannt und ist bei C:\Users\Daniel\Desktop\Raspberry Pi ARM\LED(1) zu finden. Jetzt wird die Datei in C:\Users\Daniel\Desktop\Raspberry Pi ARM\LED(1)\source als main.s gespeichert und dann eine Konsole geöffnet. In der Konsole müsst ihr nun in das Verzeichnis des Projektes wechseln:

  5. Hallo Daniel,

    ich habe dein Tutorial seit mehr als 3 Stunden offen und versuche das nachzuvollziehen. Meiner Meinung nach sieht alles gut aus. Nur leider kriege ich meinen Raspberry v2 nicht dzau die LED zum leuchten zu bringen.
    Benutze normal Raspian und habe die Kernel7.img und die Kernel datei ersetzt geht das damit?

      1. Hallo Daniel,

        danke für die HIlfe, mit einem Raspberry 1 habe ich es hinbekommen. Und habe schon erste Erweiterungen vorgenommen, an meinem Betriebssystem.

        7asper

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.