Aus Raspberry Pi Geek 02/2022

Assembler-Programmierung auf dem Raspberry Pi (Seite 2)

Zeile 1 definiert das Label main global, damit der Linker es findet. In Zeile 2 kommt es dann zum Einsatz. Direkt darauf folgt der erste Befehl: das Kommando mov (Abkürzung für move, verschieben). Es dient dazu, Werte zu verschieben, also etwa konstante Werte in einem Register abzulegen oder Inhalte eines Registers in ein anderes zu übertragen. Um Werte aus Registern in den Hauptspeicher zu schieben oder von dort zu laden, kommen stattdessen die Kommandos str (store register) und ldr (load register) zum Einsatz.

Bei einem Register handelt es sich um einen Speicherplatz innerhalb der CPU. Diese arbeitet nur mit Werten, die sich in den Registern befinden. Eine Übersicht der Register beim RasPi zeigt die Tabelle “CPU-Register der ARM-Architektur”.

Register

Mnemonik

Funktion

r0 bis r10

allgemeine Register ohne Sonderfunktion

r11

fp

Frame-Pointer-Register

r12

ip

allgemeines Register ohne Sonderfunktion

r13

sp

Stack Pointer

r14

lr

Link-Register

r15

pc

Programm-Counter

Das Programm-Status-Register dient als internes Steuerregister der CPU. Die Zustände der einzelnen Bits geben Auskunft darüber, was bei bestimmten CPU-Register-Operationen als Ergebnis herauskam. Die Kommandos für bedingte Sprünge reagieren auf diese Bits. Ein bekanntes Steuerbit ist das sogenannte Zero-Bit (Bit 30), das signalisiert, dass der Wert der ALU 0 ist.

In unserem Beispiel aus Listing 1 speichert das Kommando mov den Wert 42 im CPU-Register r0. Endet das Programm, liest das Betriebssystem den Wert des Registers r0 aus und speichert ihn in der Shell-Variablen $?.

Das Kommando bx in Zeile 4 veranlasst die CPU dazu, das Programm an einer anderen Speicheradresse fortzuführen, im vorliegenden Fall an der Adresse, die im Register lr steht. In diesem Register findet sich bei Programmaufrufen die Adresse, die der Rechner nach Beenden des Programms aufrufen soll.

Jetzt bleibt nur noch die Frage zu klären, wie Sie aus dem Assembler-Code ein lauffähiges Programm erzeugen. Die dazu nötigen Schritte ähneln sehr dem Ablauf beim Kompilieren von C-Programmen. Listing 2 zeigt die Schritte auf der Kommandozeile. Dabei erzeugt Zeile 1 aus dem Assembler-Quellcode eine Objektdatei. Zeile 2 bindet die Objektdatei ans Betriebssystem, um eine ausführbare Datei zu erhalten. Diese trägt, sofern Sie nichts anderes angeben, den Namen a.out. Das Programm a.out können Sie wie gewohnt ausführen (Zeile 3). Um zu überprüfen, ob das Programm tatsächlich den Wert 42 zurückgibt, führen Sie das Kommando in Zeile 4 aus.

Listing 2

Programm kompilieren und ausführen

$ as -o 42.o 42.s
$ gcc 42.o
$ ./a.out
$ echo $?
42

Blink

Ein einfaches Assembler-Programm haben Sie jetzt schon geschrieben. Nun ist es an der Zeit für ein etwas anspruchsvolleres Projekt. Dazu eignet sich das gute alte Blink-Programm, das einfach eine einzelne LED zum Blinken bringt. Den Plus-Kontakt der LED schließen Sie mit einem 1 kOhm Vorwiderstand an GPIO21 (BCM-Notation) an. Der Anschluss ist auf dem Header-Pin 40 herausgeführt – praktisch, denn auf dem Header-Pin 39 direkt daneben liegt GND. Diesen Pin verbinden Sie mit dem Minus-Anschluss der LED.

Das Programm birgt allerdings gleich mehrere Herausforderungen, die man als Anwender von Hochsprachen gar nicht kennt. Die GPIOs des RasPi lassen sich extrem vielseitig anwenden. Daher ist es auch nicht ganz einfach, sie aus einem Assembler-Programm heraus richtig anzusprechen. Für das Ansteuern der 54 GPIO-Pins des auf dem RasPi verbauten Chips gibt es 41 Register mit einer Länge von je 32 Bit. Nicht alle dieser GPIOs sind über den Header des RasPi zugänglich, einige davon verwendet der RasPi für interne Zwecke. Es bleibt aber immer noch eine riesige Anzahl an Möglichkeiten, um die einzelnen GPIOs anzusteuern. Eine detaillierte Beschreibung der Funktionsweise der GPIOs findet sich im Peripherie-Datenblatt [2] des BCM2835-Chips auf den Seiten 89 bis 109.

Nachdem Sie wissen, wie Sie die GPIOs ansprechen, taucht ein weiteres Problem auf: Das Betriebssystem des Raspberry verhindert den direkten Zugriff auf alle Hardwareressourcen. Versuchen Sie trotzdem, direkt auf die Hardware zuzugreifen, erhalten Sie die Fehlermeldung Segmentation Fault. Das zeigt an, dass es zu einer Speicherschutzverletzung kam, gibt aber keine zusätzlichen Hinweise, wo genau der Fehler auftrat. Die Pi-OS-Entwickler haben zum Glück eine Möglichkeit geschaffen, die GPIOs anzusteuern, ohne direkt auf die entsprechenden Speicheradressen zuzugreifen. Das Betriebssystem stellt ein Gerät bereit, das den Speicherbereich der GPIO-Steuerregister repräsentiert. Eine gute Beschreibung dazu findet sich auf den Webseiten der Sonoma State University [3].

Das Programm in Listing 3 enthält im ersten Block des Programms die Definition von verschiedenen Konstanten und belegt diese mit Werten. Das bietet gleich mehrere Vorteile: Zum einen haben Sie so die Möglichkeit, im Programm sprechende Namen zu verwenden, und zum anderen können Sie so die Register mit 32 Bit langen Werten beladen.

Listing 3

blink.s

.globl main
.equ GPIOVAL, 0x200000 // Registerwert für die GPIO 21(BCM)
.equ GPFSEL2, 0x08     // Offset-Adresse zum Setzen des GPIO-Modus
.equ GPIO_OUTPUT,0x08  // GPIO als Ausgang definieren
.equ GPFSET0, 0x1c     // Offset Register Set
.equ GPFCLR0, 0x28     // Offset Register Clear
.equ TIME, 0x8000000   // Wert für die Wartezeit
main:
ldr r0,=gpiomem
ldr r1,=0x101002 // zum Lesen und Schreiben öffnen
mov r7, #5
svc #0
mov r4, r0
mov r0, #0
mov r1, #4096
mov r2, #3
mov r3, #1
mov r5, #0
mov r7, #192
svc #0
// r0 enthält die Basisadresse des gemappten Speicherbereichs der GPIO
ldr r1, =GPIO_OUTPUT // GPIO21
str r1, [r0,#GPFSEL2] // als Output setzen
ldr r2, =TIME    // Wartezeit in r2
ldr r1, =GPIOVAL // Registerwert in r1 für GPIO21
// Endlosschleife
loop:
  str r1, [r0,#GPFSET0] // LED anschalten
  mov r10, #0           // r10 auf 0 setzen
  wait_on:              // r10 bis TIME hochzählen
    add r10, r10, #1
    cmp r10, r2
    bne wait_on
  str r1, [r0,#GPFCLR0] // LED ausschalten
  mov r10, #0           // r10 auf 0 setzen
  wait_off: // r10 bis TIME hochzählen
    add r10, r10, #1
    cmp r10, r2
    bne wait_off
b loop
.data
gpiomem: .asciz "/dev/gpiomem"

Das Kommando mov kann nur Werte bis zu einer bestimmten Größe direkt in Register schieben. Der nächste große Block von Anweisungen öffnet das Gerät /dev/gpoimem und speichert die Basisadresse des gemappten Speicherbereichs im Register r0.

In diesem Block werden sogenannte Supervisor Calls verwendet (Kommando svc), vereinfacht ausgedrückt so etwas wie Aufrufe von vorhandenen Betriebssystemprogrammen. Der Kasten “Supervisor Calls” enthält zusätzliche Informationen dazu. Wichtig ist vorerst nur, dass Sie danach die Möglichkeit haben, über die Adresse in r0 auf die GPIO zuzugreifen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
RASPBERRY PI GEEK KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Raspberry Pi Geek bei Google Play Readly Logo
Nach oben