UART: Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
Die Seite wurde neu angelegt: „UART“
 
KKeine Bearbeitungszusammenfassung
 
Zeile 1: Zeile 1:
UART
UART wird verwendet, um serielle Daten zu übertragen. Dies werden wir nun verwenden, um bei der Entwicklung von unserer Software eine Kommunikation zwischen Raspberry Pi und dem Host-System herzustellen. Damit können wird in Klarschrift Informationen anzeigen lassen oder auch Informationen an den Raspberry Pi übertragen.
 
== USB über TTL Kabel anschließen ==
 
Um dies auch zu nutzen benötigen wir ein USB zu TTL Serielles Kabel. Dieses verbinden wir mit unserem PC und dem Raspberry PI. Auf dem Raspberry PI stecken wir die Kabel in die PINs 6, 8 und 10, wie hier auf dem Bild zu erkennen ist.
 
[[Datei:TTL Kabel.png|rand|700x700px]]
 
Um nun die Informationen zu erhalten oder zu senden, benötigen wir ein Terminal-Programm, welches serielle Daten verarbeiten kann. Ich verwende dafür PuTTY. Dieses Terminal-Programm ist für Windows und Linux verfügbar und kann unter [https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html] geladen werden.
 
=== Verwendung von Windows ===
 
Lade zunächst PuTTY von der Home-Page herunter und installiere es. Es bedarf keinerlei Änderungen der Installation.
 
Um nun PuTTY selbst einzurichten, benötigen wir noch eine Informationen, wie unser Schnittstelle (COM) heißt. Dies können wir über den Geräte-Manager von Windows erfahren.
 
Zunächst wird der USB-Stecker des Kabels in den PC gesteckt und der Geräte-Manager gestartet. Im Unterordner für “Anschlüsse (COM & LPT) steht die entsprechende Schnittstelle, die wir benötigen.
 
[[Datei:SystemManager.png|rand|500x500px]]
 
In diesem Beispiel ist es COM3.
 
PuTTY kann nun gestartet werden.
Unter der "Category/Session" wird zunächst der "Connection type" auf "Serial" umgestellt. Anschließend wird unter "Serial line" "COM3" eingetragen und den "Speed" auf "115200" gesetzt.
 
[[Datei:Putty1.png|rand|500x500px]]
 
Nach wechsel der "Category" auf "Serial" wird überprüft, ob die Daten dem folgendem Scrennshot entsprechen. Sollte hier Unstimmigkeiten sein, werden diese entsprechend geändert.
 
[[Datei:Putty2.png|rand|500x500px]]
 
Unter dem Punkt "Category/Session" kann diese Konfiguration gespeichert werden.
 
Mit "Open" wird das Terminal gestartet.
 
Um nun zu Testen, ob alles korrekt eingestellt wurde, wird der vorbereitete [https://www.satyria.de/arm/source/kurs/UARTtest/kernel7l.img kernel7l.img] heruntergeladen und auf der SD-Karte gespeichert und der Raspberry Pi gestartet.
 
=== Verwendung von Linux ===
 
PuTTY ist auch für Linux verfügbar. In diesem Beispiel wird Linux Mint verwendet.
 
Zum installieren von PuTTY wird in der Konsole folgender Befehl eingegeben:
 
<syntaxhighlight lang="shell">
sudo apt-get install putty
</syntaxhighlight>
 
Anschließend kann das USB-Kabel angeschloßen werden und mit <syntaxhighlight lang="shell">dmesg|grep tty</syntaxhighlight> wird angezeigt, wie die Schnittstelle heißt:
 
[[Datei:Dmesg.png|rand]]
 
Als Beispiel hier bekomme wird die Schnittstelle ttyUSB0 angezeigt. Dies wird beim Start von PuTTY übergeben:
 
<syntaxhighlight lang="shell">
sudo putty /dev/ttyUSB0 -serial -sercfg 115200,8,n,1,N
</syntaxhighlight>
 
Die Desktop-Anwendung kann erst gestartet werden, wenn der angemeldete User entsprechende Rechte bekommen hat. Dies erfolgt mit folgender Anweisung in der Konsole:
 
<syntaxhighlight lang="shell">
sudo adduser $USER dialout
</syntaxhighlight>
 
Erst nach einem Reboot sind dann die Rechte vorhanden und PuTTY kann auf dem Desktop gestartet werden. Die Einstellungen entsprechen hier dann die gleichen, wie zuvor unter "Windows" beschrieben wurde.
Allerdings hat Linux eine andere Bezeichnung für die Schnittstelle, die zuvor mit "dmesg" festgestellt wurde.
 
[[Datei:Putty3.png|rand|500x500px]]
 
[[Datei:Putty4.png|rand|500x500px]]
 
Unter dem Punkt "Category/Session" kann diese Konfiguration gespeichert werden.
 
Mit "Open" wird das Terminal gestartet.
 
Um nun zu Testen, ob alles korrekt eingestellt wurde, wird der vorbereitete [https://www.satyria.de/arm/source/kurs/UARTtest/kernel7l.img kernel7l.img] heruntergeladen und auf der SD-Karte gespeichert und der Raspberry Pi gestartet.
 
== UART auf dem Raspberry PI ==
Nachdem unsere Schnittstelle vom Windows oder Linux (Host-System) zum Raspberry erstellt wurde, wird nun beschrieben, wie diese Schnittstelle  Programmiert wird. Das Testprogramm, welches zuvor verwendet wurde, wir nun beschrieben, wie es programmiert wird.
 
Das UART wird in den Unterlagen im Kapitel 11 erläutert. Der Raspberry Pi 4 besitzt einen Mini UART (UART1) und fünf “PL011”-UARTs (UART0, UART2, UART3, UART4 und UART5). Alle UARTs werden mit Alternativen Belegungen über die GPIOs gesteuert. Wir benötigen für die Steuerung den primären Input RXD und als primären Output TXD. Welche das sind, verrät uns folgende Tabelle:
 
[[Datei:AlternativeGPIO.png|rand]]
 
Zur Programmierung wird der UART0 verwendet. Über die Zuordnung der GPIOs (Kapitel 2 der Dokumentation) ist zu erkennen, welche Pins Verwendung finden. Das Seriale-Kabel wurde an die PINs 6, 8 und 10 angeschloßen:
 
[[Datei:GPIO-Zuordnung.png|rand|600x600px]]
 
Das erste Programm, welches hier beschrieben wird, wird über die Schnittstelle UART zum Host-System einen Text übermitteln. Für den UART wird eine neue Include Datei erzeugt, die den Namen "uart.h" bekommt. Dort werden alle Funktionen abgelegt, damit das Hauptprogramm recht einfach erstellt werden kann.
 
== Den UART Initialisieren ==
 
Um das UART funktionsfähig zu machen, wird der UART zunächst initialisiert. Hierzu werden einige Informationen benötigt, was von der Schnittstelle erwartet wird, um später überhaupt mit dem Host zu kommunizieren. Die Schnittstelle muss dazu synchron eingestellt werden. Hier wird eine alternative zur offizielen Dokumentation verwendet, damit diese Beispiele auch auf älteren Modellen des Raspberry Pis funktioniert. Zunächst erweitern wir unsere "base.inc" Datei um weitere Werte, die den UART betreffen.
 
<syntaxhighlight lang="asm">
@UART0
.equ UART0_BASE, RPI_BASE + 0x201000    @Baseadresse UART0
.equ UART0_DATA, UART0_BASE + 0x00
.equ UART0_STATUS, UART0_BASE + 0x18
  .equ TX_FIFO_FULL, 0b100000
.equ UART0_IBRD, UART0_BASE + 0x24
.equ UART0_FBRD, UART0_BASE + 0x28
.equ UART0_LINE_CTRL, UART0_BASE + 0x2C
  .equ ENABLE_FIFO, 0b10000
  .equ BYTE_WORD_LENGTH, 0b1100000
.equ UART0_CONTROL, UART0_BASE + 0x30
  .equ ENABLE, 0b1
  .equ TX_ENABLE, 0b100000000
  .equ RX_ENABLE, 0b1000000000
</syntaxhighlight>
 
Diese Werte können in der Dokumentations nachgelesen werden.
 
Wie bereits geschrieben, wird eine neue Datei "uart.h" erstellt. Die erste Funktion, die dort erstellt wird, ist die Funktion "UartInit". Sie benötigt keine Daten und wird keine Daten zurückgeben.
 
<syntaxhighlight lang="asm">
/* void UartInit (void)
  Initialisierung des UART-Ports
*/
UartInit:
  push {r1,lr}
</syntaxhighlight>
 
Der Raspberry Pi arbeitet im Hindergrund mit den UARTs. Dies kann zu Komplikationen führen. Damit dies nicht geschieht, muss Line_CTRL und CONTROL bei der ersten initalisierung deaktiviert werden.
 
<syntaxhighlight lang="asm">
  ldr r0,=UART0_LINE_CTRL      @Line_CTRL
  mov r1,#0                    @All to NULL
  str r1,[r0]                  @and store
 
  ldr r0,=UART0_CONTROL        @CONTROL
  mov r1,#0                    @All to NULL
  str r1,[r0]                  @and store
</syntaxhighlight>
 
Laut Dokumentation muss etwas gewartet werden, bis alles soweit fertig ist. Da diese "freie" Zeit nicht unnötig gewartet wird, werden andere Register bearbeitet. In dieser Zeit, werden den GPIO-PINs (14 und 15) die entsprechende Funktion (Alt0) zugeordnet.
 
<syntaxhighlight lang="asm">
/* GPIO 14 und 15 */
 
  mov r0,#14          @TXD0
  mov r1,#GPIO_alt0    @ALT 0
  bl SetGPIOFunction
 
  mov r0,#15          @RXD0
  mov r1,#GPIO_alt0    @ALT 0
  bl SetGPIOFunction
</syntaxhighlight>
 
Da die Schnittstelle Zeitgesteuert sein muss (sonst verstehen sich die zwei Seiten nicht), wird den GPIO-PINs dies mitgeteilt. Laut Dokumentation für den RPI 1, spielen hierbei zwei Funktionen eine Rolle. Dies sind die Funktionen "GPPUD" und "GPPUDCLKn". Dazu wird das pull-Up/down mit GPPUD abgeschaltet, ca. 150 Zyklen gewartet werden, damit die Änderung durchgeführt wurde und dann wird die Uhr auf die PINs mit GPPUDCLKn gesetzt.
 
<syntaxhighlight lang="asm">
  ldr r0,=GPPUD
  mov r1,#GPPUD_OFF
  str r1,[r0]
 
  mov r0,#MICROS_PER_MILLISECOND
  bl wait
 
  ldr r0,=GPPUDCLK0
  mov r1,#0b11                  @two Pins
  lsl r1,#14                    @From Bit 14
  str r1,[r0]
</syntaxhighlight>
 
Nun werden die Einstellungen für die Schnittstelle übermittel. Zunächst wird der Schnittstelle die Baudrate übergeben. Hierfür ist das Register IBRD und FBRD zuständig. Um die richtigen Werte herauszubekommen, müssen die Werte zunächst berechnet werden. Laut Handbuch wird folgende Formel verwendet:
 
<syntaxhighlight lang="shell">
BAUDDIV = (FUARTCLK/(16 * BAUD rate)
</syntaxhighlight>
 
FUARTCLK ist die Interne Frequenze, mit der der Raspberry arbeitet. Für dem Raspberry Pi 4 sind das 48MHZ.
 
<syntaxhighlight lang="shell">
48.000.000 / ( 16 * 115200 ) = 26 Rest 0,0417
</syntaxhighlight>
 
Für IBRD wird nun der Wert 26 verwendet. Für FBRD muss der Wert noch zu einem Integer Wert umgewandelt werden:
 
<syntaxhighlight lang="shell">
0,0417 * 64 = 2,667 Gerundet 3
</syntaxhighlight>
 
Der Wert für FBRD ist damit 3.
 
<syntaxhighlight lang="asm">
  ldr r0,=UART0_IBRD
  mov r1,#26
  str r1,[r0]
 
  ldr r0,=UART0_FBRD
  mov r1,#3
  str r1,[r0]
</syntaxhighlight>
 
Über LINE_CTRL wird nun der Transfer auf 8 Bit (BYTE_WORD_LENGTH) und FIFO (ENABLE_FIFO) angeschaltet.
 
<syntaxhighlight lang="asm">
  ldr r0,=UART0_LINE_CTRL
  mov r1,#BYTE_WORD_LENGTH | ENABLE_FIFO
  str r1,[r0]
</syntaxhighlight>
 
Mit dem Controlregister wird der UART mit TX und RX gestartet.
 
<syntaxhighlight lang="asm">
  ldr r0,=UART0_CONTROL
  mov r1,#ENABLE | TX_ENABLE | RX_ENABLE
  str r1,[r0]
</syntaxhighlight>
 
Damit ist nun der UART bereit und wir können diese Funktion beenden.
 
<syntaxhighlight lang="asm">
  pop {r1,pc}
</syntaxhighlight>
 
== Zeichen in den UART schreiben ==
Um nun in den UART zu schreiben, wird die Funktion UART0_DATA verwendet. Zunächst muss überprüft werden, ob die Schnittstelle für diese Aufgabe bereit ist. Dazu wird der Status der Schnittstelle abgefragt. Sollange im FIFO-Buffer kein Platz ist, muss gewartet werden, bis dieser für unsere Übergabe bereit ist.
 
<syntaxhighlight lang="asm">
/* void UartPutc (int Char)
  Write char in the UART
*/
UartPutc:                         
  push {r1,r2,lr}
  mov r2,r0                    @save Char to r2
 
  UartPutc_loop1$:              @Wait until UART is empty ...
      ldr r0,=UART0_STATUS      @address UART0_STATUS
      ldr r1,[r0]                @Load status to r1
      ands r1,r1,#TX_FIFO_FULL  @If status full...
      bne UartPutc_loop1$        @wait
</syntaxhighlight>
 
Dies Funktion wird ein Zeichen (Char) in die Schnittstelle schreiben. Dazu wird ihr ein Zeichen übergeben und als Ergebnis wird nichts übergeben.
Das übergebene Zeichenwird zunächst nach '''r2''' gesichert. Mit der Schleife "UartPutc_loop1$" wird zunächst der Status des UARTs geladen und mit dem Wert "TX_FIFO_FULL" verglichen. Dazu wird in diesem Fall "ands" verwendet. Dies führt ein logisches UND durch und übergibt die Bedingungscodes weiter. Sollte es entsprechen, wird die Schleife wiederholt.
 
Sobald die Bedingungen stimmen, wird einfach das Zeichen nach UART0_DATA kopiert und damit an die Schnittstelle übertragen.
 
<syntaxhighlight lang="asm">
  ldr r0,=UART0_DATA            @addresse for data to r0
  strb r2,[r0]                  @store char to data
   
  pop {r1,r2,pc}      @End at the function
</syntaxhighlight>
 
== Einen String (Zeichenfolge) in den UART schreiben ==
Mit der zuvor erstellten Funktion ist es möglich, ein Zeichen anzuzeigen. In der Regel sind einzelne Zeichen nicht der Standard. Es mach Sinn, ganze Wörter oder sogar ganze Sätze auf einmal anzeigen zu können. Dazu wird die Funktion UartPuts erstellt, die dies übernimmt.
 
<syntaxhighlight lang="asm">
/* void UartPuts (int String, int len)
  write string of length (len) to the UART
*/
 
UartPuts:
  push {r1,r2,lr}
  mov r2,r0                  @Store address of the string to r2
 
  UartPuts_loop1:
      subs r1,#1              @reduce length at 1
      blt UartPuts_loopend    @Jump as soon as length is zero
      ldrb r0,[r2]            @Load Char from string to r0
      add r2,#1              @Increment the pointer of the string by 1
      bl UartPutc            @Show Char
      b UartPuts_loop1        @Display the next char
  UartPuts_loopend:
</syntaxhighlight>
 
Die Funktion erwartet in '''r0''' den String und in '''r1''' die Länge des Strings. Mit einer Schleife wir jedes einzelne Zeichen gelesen und der zuvor erstellten Funktion (UartPutc) übergeben.
Zunächst wird die Adresse des Strings nach '''r2''' kopiert, da r0 für die Übergabe des Zeichens an die Unterfunktion benötigt wird. In der Schleife wird zunächst die länge um eins reduziert. Sollte der Wert kleiner NULL werden, wird die Schleife beendet. Danach wird das Zeichen, in diesem Fall ein Byte, nach '''r0''' kopiert und die Adresse um eins erhöht, damit beim nächsten Aufruf das nächste Zeichen verfügbar ist. Da in '''r0''' das Zeichen liegt, wird einfach die UartPutc-Funktion aufgerufen und damit das Zeichen angezeigt. Diese ganze Schleife wird nun solange wiederholt, bis die Länge kleiner als NULL ist.
 
Bisher wird der String 1:1 angezeigt. Im Terminalprogramm würde er nun mit seinem Cursor an der aktuellen Stelle stehen und falls noch etwas Angezeigt werden soll, direkt nach der letzen Anzeige fortgeführt werden. Das sieht natürlich nicht schön aus. Dazu wird der Funktion noch ein Zeilenvorlauf und ein Rücksprung übergeben, so dass die nächste Anzeige in der neuen Zeile begint.
 
<syntaxhighlight lang="asm">
  mov r0,#'\n'              @New line
  bl UartPutc                @and display
 
  mov r0,#'\r'              @return
  bl UartPutc                @and display
 
  pop {r1,r2,pc}
</syntaxhighlight>
 
Mit dieser einfachen Routine, wird nun ein ganzer String angezeigt.
 
== Hauptprogramm UART 4.1 ==
Im Hauptprogramm können nun diese neue Funktionen verwenden werden:
 
<syntaxhighlight lang="asm">
  bl UartInit            @Initiate UART first so that I can send messages
 
  ldr r0,=Text1          @Load pointer of the text to r0
  mov r1,#Text1End-Text1  @Length to r1
  bl UartPuts
</syntaxhighlight>
 
Zunächst wird der UART Initialisiert. Diese Funktion wird nur einmal verwendet, auch dann, wenn mehr als eine Zeile angezeigt werden sollte. Die UartPuts-Funktion benötigt einen Zeiger auf den String. Dies wird mit ldr r0,=Text1 übergeben. In r1 benötigt die Funktion die Länge des Textes. Hier werden einfach zwei Labels, die den Text umschließen miteinander subtrahiert.
 
Damit später beim Kompilieren nichts durcheinander kommt, wird eine Datensektion (.section .data) erstellt, in der die Daten abgelegt werden. Hier ist dann auch zu sehen, was mit zwei Labels, die den Text umschließen, gemeint war.
 
<syntaxhighlight lang="asm">
.section .data
Text1:
  .ascii "Hello World!"
Text1End:
</syntaxhighlight>
 
Das Label "Text1End" zeigt auf das Ende des Textes, wobei das Label "Text1" den Anfang des Textes definiert. Im oberen Code werden einfach diese zwei Label voneinander abgezogen und das Ergebnis ist die Länge des Textes. .ascii wird im nächsten Unterkapitel beschrieben.
 
Der Sourcecode, für dieses Beispiel ist hier zum laden bereit: [https://www.satyria.de/arm/source/kurs/4.1.zip 4.1.zip]
 
== Neue Befehle (1)==
 
=== ands ===
 
Den Befehl "and" hatten wir bereits. Mit der Angabe des Suffix "s" wird hier mitgeteilt, das die CPU zusätzlich vom der Art des Ergebnisses das Statusregister aktualisiert. Dieses Statusregister wird für die weitere bedingte Ausführung verwendet.
 
=== subs ===
 
Auch der Befehl "sub" kann mit dem Suffix "s" definiert werden und kann damit genauso verwendet werden, wie zuvor der Befehl "ands".
 
=== strb, ldrb ===
 
Bei str und oder ldr kann das Suffix "b" verwendet werden. Dieser Suffix bedeutet, dass die Operation nur mit einem Byte verwendet wird. Mit "strb" wird ein Byte gespeichert und mit "ldrb" ein Byte geladen.
 
=== .ascii ===
 
Mit .ascii wird im Datenbereich ein String gekennzeichnet. Erläuterung erfolgt im nächsten Abschnitt “Richtlinien zur Datendefinition”.
 
== Richtlinien zur Datendefinition ==
Im Kapitel 4 (UART) wurde das erstemal für Daten im Speicher Platz erzeugt. In diesem Fall wurde ein String im Speicher abgelegt, der über den UART angezeigt wurde. In diesem Speicher können sehr unterschiedliche Daten abgelgt werden. Der Assembler erwartet für bestimmte Arten von Daten entsprechende Schlüßelwörter, die hier erläutert werden.
 
=== Zahlenwerte ===
 
Folgende Zahlenwerte sind möglich:
 
{| class="wikitable"
|-
! Syntax !! Aliase !! Größe<br>in Bytes !! Bereich
|-
| .byte || .1byte, .dc.b || 1 || -128 bis 255
|-
| .hword || .2byte, .dc, .dc.w, .short, .value || 2 || -0x8000 bis 0xffff
|-
| .word || .4byte, -long, .int, .dc.l, .dc.a (nur ARM32) || 4 || -2<sup>31</sup> bis 2<sup>32</sup>-1
|-
| .quad || .8byte, .xword (nur ARM64), .dc.a (nur ARM64) || 8 || -2<sup>63</sup> bis 2<sup>64</sup>-1
|-
| .octa ||  || 16 || 0 bis 2<sup>128</sup>-1
|}
 
Da es viele Möglichkeiten gibt, die gleiche Aussage mit verschiedenen Aliase zu bewirken, kann dies sehr verwirrend sein. Viele Programmierer verwenden die Aliase, da diese näher an andere Programmiersprachen, wie "C", erinnern. Auch ich verwende ausschließlich ".int", statt ".word".
 
Ascii-Zeichen sind nichts anderes als “.byte”-Bereiche. Diese können wie folgt deklariert werden:
 
<syntaxhighlight lang="asm">
.byte 'H','e','l','l','o'
</syntaxhighlight>
 
Escape-Befehle sind mit dieser Anweisung auch möglich.
 
=== Strings (Zeichenketten) ===
 
Unter dem Absatz "Zahlenwerten" wurde indirekt mit ".byte" eine Art String erzeugt. Der Assembler erlaubt noch zwei zusätzliche Parameter, um einen Sring zu erzeugen.
 
{| class="wikitable"
|-
! Syntax !! Aliase !! Beschreibung
|-
| .ascii || || Erzeugt einen String, der '''nicht''' mit NULL abgeschlossen ist.
|-
| .asciz || .string || Erzeugt einen String, der mit NULL abgeschlossen ist.
|}
 
Im ersten Programm zu UART wurde .ascii verwendet und die länge des Strings in eine Funktion übermittelt. Mit ".asciz" wäre es auch möglich, dass eine Unterroutine diesen String anzeigt, bis dieser mit NULL abgeschloßen ist. Unter zum Beispiel "C" wird dies auch sehr häufig verwendet. Dort müssen in der Regel alle Strings mit "NULL" abgeschloßen sein. Der Assembler bietet aus diesem Grund auch diese Methode an. Welche der Programmierer bevorzugt ist ihm überlassen.
 
Innerhalb eines Strings können zusätzlich Escape-Sequenzen verwendet werden:
 
{| class="wikitable"
|-
! Escapezeichen !! Bedeutung
|-
| \b || Rücktaste
|-
| \f || Zeilenvorschub
|-
| \n || Neue Zeile
|-
| \r || Wagenrücklauf
|-
| \t || Tabulator
|-
| \" || Das Zeichen " selbst
|-
| \\ || Backslash (\)
|-
| \Zahlencode || Escapecode für jedes ASCII-Zeichen
|}
 
=== Gleitkommazahlen (Rationale Zahlen) ===
 
Gleitkommazahlen, sind Zahlen, die es in einem Binären System so nicht geben kann. Da sie dennoch für komplexe Berechnungen erforderlich sind, wurden dazu zwei Arten von Typen dieser Zahlen als Datentypen definiert. In der Regel verwendet ein Coprozessor diese Daten und wandelt diese für sich lesbaren Code um. Allerdings muss hier auch darauf hingewiesen werden, dass die Daten, die hier erzeugt werden nur eine Annäherung zu der angegebenen rationalen Zahl ist.
 
Wer die genaue Funktion dieser Zahlensysteme verstehen möchte, so bitte ich den Leser dazu auf, dies z.B. im Internet selbst nachzuschlagen, da dies zur Zeit den Rahmen dieser Beschreibung sprengen würde.
 
{| class="wikitable"
|-
! Syntax !! Aliase !! Größe<br>in Bytes !! Beschreibung
|-
| .float || .single, .dc.s || 4 || Speichert die Werte im IEEE754-Format mit einfacher Genauigkeit ab
|-
| .double || .dc.d || 8 || Speichert die Werte im IEEE754-Format mit doppelter Genauigkeit ab
|}
 
== Interagieren mit dem Raspberry Pi (4.2) ==
 
Die UART-Schnittstelle ist keine Einbahnstraße. Es ist durchaus möglich Daten vom Hostsystem auch an den Raspberry Pi zu senden und ihn auf diese Eingaben reagieren zu lassen. Dazu wird nun über die Eingabe die LED entsprechend gesteuert. Den Source gibt es hier: [https://www.satyria.de/arm/source/kurs/4.2.zip 4.2.zip]
 
=== Eingaberoutine (UART) ===
In der uart.h wird eine neue Funktion erzeugt, die UartGetc genannt wird. Mit dieser Funktion werden einzelne Zeichen vom UART gelesen.
Zunächst wird geprüft, ob Daten verfügbar sind.
 
<syntaxhighlight lang="asm">
/* char UartGetc (void)
  Read Char from UART */
UartGetc:
  push {r4,lr}
 
  UartGetc_loop1$:              @Wait for UART to contain data ...
      ldr r0,=UART0_STATUS      @address UART0_STATUS
      ldr r1,[r0]                @Load Status to r4
      ands r1,r1,#RX_FIFO_EMPTY  @If Status empty
      bne UartGetc_loop1$        @Wait
</syntaxhighlight>
 
Zunächst wird der Status aus dem UART geladen und überprüft, ob dort Daten sind. Wenn nicht, wird die Schleife einfach wiederholt, bis ein Ergebnis vorhanden ist. Sobald Daten verfügbar sind, werden diese abgefragt.
 
<syntaxhighlight lang="asm">
  ldr r0,=UART0_DATA            @address for data
  ldrb r0,[r0]                  @Write Char to r0
</syntaxhighlight>
 
Auf diese weise, ist bereits ein Zeichen in r0 verfügbar. Zu diesem Zeitpunkt, wenn es so stehen bleibt, wird im Terminal-Programm nichts angezeigt. Das Terminalprogramm übergibt zwar das Zeichen in die Schnittstelle, zeigt es aber selbst nicht an. Dies ist etwas blöd, da der Anwender nicht weiß, was den nun an den Raspberry übergeben wurde. Aus diesem Grund, wird der Raspberry Pi dazu gebracht, einfach das Zeichen zurück zu schicken. Indirekt wird darüber überprüft, ob der richtige Wert dort angekommen ist. Dazu wird die Funktion UartPutc verwendet.
 
<syntaxhighlight lang="asm">
  mov r4,r0
  bl UartPutc
  mov r0,r4
</syntaxhighlight>
 
Damit das Zeichen nicht verloren geht, wird es in r4 zwischen gespeichert. Die Funktion UartPutc aufgerufen und anschließend das Zeichen wieder nach r0 kopiert.
 
=== Eine ganze Zeile einlesen ===
 
Auch beim Einlesen von Daten wird selten nur ein Zeichen benötigt. Wie bereits bei der Ausgabe von Strings, wird nun eine Funktion erzeugt, die einen ganzen String zurück gibt. Zunächst wird festgelegt, welche Eigenschaften ein String besitzt. Bei der Eingabe eines Strings, wird in der Regel die Return-Taste betätigt, die das Ende der Eingabe definiert. Genau auf diesen Wert hin wird geprüft und die Funktion wird beendet. Zusätzlich wird eine Struktur benötigt, in der der eigentliche String abgelegt wurde, damit dieser für andere interaktionen verfügbar ist. In diesem Fall funktioniert die alte Struktur nicht mehr, da nicht bekannt ist, wie lang der String wird. Natürlich wäre es nun möglich, eine Stringstrucktur zu erzeugen, wie sie in andere Hochsprachen verwendet werden, allerdings soll dieses Beispiel zeigen, wie unter Assembler mit Strukturen gearbeitet werden kann.
 
Die Struktur, die hier nun verwendet wird, hat folgendes Aussehen:
 
<syntaxhighlight lang="asm">
GetLineStruct:
  EingabeLen:
      .int 0
  Eingabe:
      .space 256
</syntaxhighlight>
 
Die Struktur beinhaltet zunächst die Länge der Eingabe und folgend mit den eingegebenen Zeichen. In diesem Fall wurde eine Speicherplatz für 256 Zeichen bereit gestellt.
Die Funktion, die erstellt wird, wird den Zeiger auf diese Struktur in r0 zurückgeben.
 
Die Funktion wird den Namen "UartGetLine" erhalten und zunächst als Eingabeaufforderung ein ">" ausgeben.
 
<syntaxhighlight lang="asm">
UartGetLine:
  push {r4,r5,lr}
  mov r0,#'>'             
  bl UartPutc
</syntaxhighlight>
 
Während die Funktion läuft, wird in r4 die Länge der Eingabe gespeichert und in r5 die Adresse des Strings.
 
<syntaxhighlight lang="asm">
  mov r4,#0
  ldr r5,=Eingabe
</syntaxhighlight>
 
Beim Einstieg in die Funktion ist zunächst die Länge 0 und die Adresse des Strings der Anfang (Eingabe) unseres Speichers.
 
<syntaxhighlight lang="asm">
UartGetLineLoop: 
  bl UartGetc
  cmp r0, #13
  beq UartGetLineEnd
</syntaxhighlight>
 
Damit alle Zeichen gelesen werden, wird eine Schleife erzeugt. Zunächst wird ein Zeichen geholt und überprüft, ob es die Eingabetaste (#13) war, wenn ja, wird die Schleife beendet.
 
<syntaxhighlight lang="asm">
  strb r0,[r5]
  add r5,#1
  add r4,#1
  b UartGetLineLoop
</syntaxhighlight>
 
Ansonsten wird das Zeichen in den String abgelegt, die Adresse des Strings um ein Byte erhöht und die Länge um eins erhöht. Die gesammte Schleife wird wiederholt.
 
Wird die Schleife beendet, wird im Terminal noch eine neue Zeile ausgegeben und die Strucktur des Strings angepasst.
 
<syntaxhighlight lang="asm">
UartGetLineEnd:
  mov r0,#'\n'              @New line
  bl UartPutc                @display
 
  mov r0,#'\r'              @Go back
  bl UartPutc                @and displayed
 
  ldr r0,=EingabeLen
  str r4,[r0]
 
  pop {r4,r5,pc}
</syntaxhighlight>
 
Die Adresse der Stringstrucktur wird nach r0 geladen und die Länge, welche in r4 steht, dort abgelegt. Als Rückgabe wird r0 zurück gegeben.
 
=== Neues Hauptprogramm (4.2) ===
 
Inzwischen ist die Eingabemethode soweit fertig, dass sie zu einer Aktion (interagieren) des Raspberry Pi verwendbar ist. Mit ein wenig Zeilen im Hauptprgramm wird nun diese Interaktion angewendet und der Raspberry Pi dazu gebracht, seine interne LED an oder abzuschalten.
 
<syntaxhighlight lang="asm">
MainLoop:                  @Infinite loop
 
  ldr r0,=Text1          @Load pointer from Text1 to r0
  mov r1,#Text1End-Text1  @length to r1
  bl UartPuts
</syntaxhighlight>
 
Und im Datensegment:
 
<syntaxhighlight lang="asm">
.section .data
Text1:
.ascii "0 = LEDOFF, 1 or other = LEDON"
Text1End:
</syntaxhighlight>
 
Zunächst wird ein Text angezeigt, der erzählt, was erwartet wird.
 
<syntaxhighlight lang="asm">
  bl UartGetLine
  ldr r1,[r0]
  add r0,#4
  ldrb r0,[r0]
  cmp r0,#48
  beq LEDOFF
</syntaxhighlight>
 
Als nächstes wird die Eingabe aus dem Host geholt. Zunächst wird die Länge der Eingabe nach r1 gesichert, anschließend die Adresse um 4 Bytes erhöht (Anfang des Strings) und das erste Zeichen nach r0 kopiert. Mit dem cmp Befehl wird dieses Zeichen mit 48 verglichen, welches dem ASCII-Zeichen "0" entspricht. Sollte es dieses gewesen sein, verzweigt es zum Label "LEDOFF". Wenn es nicht der Fall war, wird der Code hier weiter geführt.
 
<syntaxhighlight lang="asm">
  mov r0,#42
  bl LED_on
  b MainLoop
</syntaxhighlight>
 
r0 wird die PIN-Nr. der LED abgelegt und die Funktion LED_on ausgeführt. Die Funktion LED_on wurde im Kapitel General Purpose I/O (GPIO) entwickelt und kommt hier wieder zum Zuge.
Danach springt das Programm wieder zum Anfang der Endlosschleife.
 
<syntaxhighlight lang="asm">
LEDOFF:
      mov r0,#42
      bl LED_off
      b MainLoop
</syntaxhighlight>
 
Sollte bei der Abfrage eine "0" festgestellt worden sein, springt er zu LEDOFF und die LED wird abgeschaltet. Dieser Code kann unter [http://assem.satyria.org/source/kurs/4.2.zip 4.2.zip] heruntergeladen werden.
 
== Neue Befehle (Teil 2) ==
 
Inzwischen wird man feststellen, dass es nicht mehr viele neue Befehle gab. Mit den bereits Vorgestellen Befehlen ist bereits eine interaktion mit dem Raspberry möglich. Allerdings wurden noch nicht alle benötigt. Wer nun noch mehr Befehle kennen lernen möchte, kann hier im Anhang "[[Zusammenfassung des Befehlssatzes]]" mehr erfahren. Sicherlich wird man auch Dinge finden, wie der Code, der bisher vorgestellt wurde verbessert werden könnte. Gerne kann hier jeder seine Ideen hineinbringen, für sich behalten oder aber auch gerne an mich weiter geben.
 
In diesem Teil "neue Befehle" wird diesmal kein neuer Befehl aufgelistet, den noch haben wir eine Assembleranweisung neu verwendet.
 
=== .space ===
Mit .space wird ein Speicherbereich reserviert und mit NULLen gefüllt. Die zweite Angabe des Befehls zeigt die Größe des Bereichs in Bytes an.
 
<syntaxhighlight lang="asm">
.space 256
</syntaxhighlight>
 
Reserviert einen 256 Byte großen Speicherbereich.
 
 
-----
 
{| style="width: 100%;
| style="width: 33%;" | [[System Timer|< Zurück (System Timer)]]
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]]
| style="width: 33%; text-align:right;" | [[Die Anzeige|Weiter (Die Anzeige) >]]
|}

Aktuelle Version vom 22. August 2024, 10:10 Uhr

UART wird verwendet, um serielle Daten zu übertragen. Dies werden wir nun verwenden, um bei der Entwicklung von unserer Software eine Kommunikation zwischen Raspberry Pi und dem Host-System herzustellen. Damit können wird in Klarschrift Informationen anzeigen lassen oder auch Informationen an den Raspberry Pi übertragen.

USB über TTL Kabel anschließen

Um dies auch zu nutzen benötigen wir ein USB zu TTL Serielles Kabel. Dieses verbinden wir mit unserem PC und dem Raspberry PI. Auf dem Raspberry PI stecken wir die Kabel in die PINs 6, 8 und 10, wie hier auf dem Bild zu erkennen ist.

Um nun die Informationen zu erhalten oder zu senden, benötigen wir ein Terminal-Programm, welches serielle Daten verarbeiten kann. Ich verwende dafür PuTTY. Dieses Terminal-Programm ist für Windows und Linux verfügbar und kann unter [1] geladen werden.

Verwendung von Windows

Lade zunächst PuTTY von der Home-Page herunter und installiere es. Es bedarf keinerlei Änderungen der Installation.

Um nun PuTTY selbst einzurichten, benötigen wir noch eine Informationen, wie unser Schnittstelle (COM) heißt. Dies können wir über den Geräte-Manager von Windows erfahren.

Zunächst wird der USB-Stecker des Kabels in den PC gesteckt und der Geräte-Manager gestartet. Im Unterordner für “Anschlüsse (COM & LPT) steht die entsprechende Schnittstelle, die wir benötigen.

In diesem Beispiel ist es COM3.

PuTTY kann nun gestartet werden. Unter der "Category/Session" wird zunächst der "Connection type" auf "Serial" umgestellt. Anschließend wird unter "Serial line" "COM3" eingetragen und den "Speed" auf "115200" gesetzt.

Nach wechsel der "Category" auf "Serial" wird überprüft, ob die Daten dem folgendem Scrennshot entsprechen. Sollte hier Unstimmigkeiten sein, werden diese entsprechend geändert.

Unter dem Punkt "Category/Session" kann diese Konfiguration gespeichert werden.

Mit "Open" wird das Terminal gestartet.

Um nun zu Testen, ob alles korrekt eingestellt wurde, wird der vorbereitete kernel7l.img heruntergeladen und auf der SD-Karte gespeichert und der Raspberry Pi gestartet.

Verwendung von Linux

PuTTY ist auch für Linux verfügbar. In diesem Beispiel wird Linux Mint verwendet.

Zum installieren von PuTTY wird in der Konsole folgender Befehl eingegeben:

sudo apt-get install putty

Anschließend kann das USB-Kabel angeschloßen werden und mit

dmesg|grep tty

wird angezeigt, wie die Schnittstelle heißt:

Als Beispiel hier bekomme wird die Schnittstelle ttyUSB0 angezeigt. Dies wird beim Start von PuTTY übergeben:

sudo putty /dev/ttyUSB0 -serial -sercfg 115200,8,n,1,N

Die Desktop-Anwendung kann erst gestartet werden, wenn der angemeldete User entsprechende Rechte bekommen hat. Dies erfolgt mit folgender Anweisung in der Konsole:

sudo adduser $USER dialout

Erst nach einem Reboot sind dann die Rechte vorhanden und PuTTY kann auf dem Desktop gestartet werden. Die Einstellungen entsprechen hier dann die gleichen, wie zuvor unter "Windows" beschrieben wurde. Allerdings hat Linux eine andere Bezeichnung für die Schnittstelle, die zuvor mit "dmesg" festgestellt wurde.

Unter dem Punkt "Category/Session" kann diese Konfiguration gespeichert werden.

Mit "Open" wird das Terminal gestartet.

Um nun zu Testen, ob alles korrekt eingestellt wurde, wird der vorbereitete kernel7l.img heruntergeladen und auf der SD-Karte gespeichert und der Raspberry Pi gestartet.

UART auf dem Raspberry PI

Nachdem unsere Schnittstelle vom Windows oder Linux (Host-System) zum Raspberry erstellt wurde, wird nun beschrieben, wie diese Schnittstelle Programmiert wird. Das Testprogramm, welches zuvor verwendet wurde, wir nun beschrieben, wie es programmiert wird.

Das UART wird in den Unterlagen im Kapitel 11 erläutert. Der Raspberry Pi 4 besitzt einen Mini UART (UART1) und fünf “PL011”-UARTs (UART0, UART2, UART3, UART4 und UART5). Alle UARTs werden mit Alternativen Belegungen über die GPIOs gesteuert. Wir benötigen für die Steuerung den primären Input RXD und als primären Output TXD. Welche das sind, verrät uns folgende Tabelle:

Zur Programmierung wird der UART0 verwendet. Über die Zuordnung der GPIOs (Kapitel 2 der Dokumentation) ist zu erkennen, welche Pins Verwendung finden. Das Seriale-Kabel wurde an die PINs 6, 8 und 10 angeschloßen:

Das erste Programm, welches hier beschrieben wird, wird über die Schnittstelle UART zum Host-System einen Text übermitteln. Für den UART wird eine neue Include Datei erzeugt, die den Namen "uart.h" bekommt. Dort werden alle Funktionen abgelegt, damit das Hauptprogramm recht einfach erstellt werden kann.

Den UART Initialisieren

Um das UART funktionsfähig zu machen, wird der UART zunächst initialisiert. Hierzu werden einige Informationen benötigt, was von der Schnittstelle erwartet wird, um später überhaupt mit dem Host zu kommunizieren. Die Schnittstelle muss dazu synchron eingestellt werden. Hier wird eine alternative zur offizielen Dokumentation verwendet, damit diese Beispiele auch auf älteren Modellen des Raspberry Pis funktioniert. Zunächst erweitern wir unsere "base.inc" Datei um weitere Werte, die den UART betreffen.

@UART0
.equ UART0_BASE, RPI_BASE + 0x201000    @Baseadresse UART0
.equ UART0_DATA, UART0_BASE + 0x00
.equ UART0_STATUS, UART0_BASE + 0x18
   .equ TX_FIFO_FULL, 0b100000
.equ UART0_IBRD, UART0_BASE + 0x24
.equ UART0_FBRD, UART0_BASE + 0x28
.equ UART0_LINE_CTRL, UART0_BASE + 0x2C
   .equ ENABLE_FIFO, 0b10000
   .equ BYTE_WORD_LENGTH, 0b1100000
.equ UART0_CONTROL, UART0_BASE + 0x30
   .equ ENABLE, 0b1
   .equ TX_ENABLE, 0b100000000
   .equ RX_ENABLE, 0b1000000000

Diese Werte können in der Dokumentations nachgelesen werden.

Wie bereits geschrieben, wird eine neue Datei "uart.h" erstellt. Die erste Funktion, die dort erstellt wird, ist die Funktion "UartInit". Sie benötigt keine Daten und wird keine Daten zurückgeben.

/* void UartInit (void)
   Initialisierung des UART-Ports
*/
UartInit:
   push {r1,lr}

Der Raspberry Pi arbeitet im Hindergrund mit den UARTs. Dies kann zu Komplikationen führen. Damit dies nicht geschieht, muss Line_CTRL und CONTROL bei der ersten initalisierung deaktiviert werden.

   ldr r0,=UART0_LINE_CTRL       @Line_CTRL
   mov r1,#0                     @All to NULL
   str r1,[r0]                   @and store

   ldr r0,=UART0_CONTROL         @CONTROL
   mov r1,#0                     @All to NULL
   str r1,[r0]                   @and store

Laut Dokumentation muss etwas gewartet werden, bis alles soweit fertig ist. Da diese "freie" Zeit nicht unnötig gewartet wird, werden andere Register bearbeitet. In dieser Zeit, werden den GPIO-PINs (14 und 15) die entsprechende Funktion (Alt0) zugeordnet.

/* GPIO 14 und 15 */

   mov r0,#14           @TXD0
   mov r1,#GPIO_alt0    @ALT 0
   bl SetGPIOFunction
   
   mov r0,#15           @RXD0
   mov r1,#GPIO_alt0    @ALT 0
   bl SetGPIOFunction

Da die Schnittstelle Zeitgesteuert sein muss (sonst verstehen sich die zwei Seiten nicht), wird den GPIO-PINs dies mitgeteilt. Laut Dokumentation für den RPI 1, spielen hierbei zwei Funktionen eine Rolle. Dies sind die Funktionen "GPPUD" und "GPPUDCLKn". Dazu wird das pull-Up/down mit GPPUD abgeschaltet, ca. 150 Zyklen gewartet werden, damit die Änderung durchgeführt wurde und dann wird die Uhr auf die PINs mit GPPUDCLKn gesetzt.

   ldr r0,=GPPUD
   mov r1,#GPPUD_OFF
   str r1,[r0]
   
   mov r0,#MICROS_PER_MILLISECOND
   bl wait

   ldr r0,=GPPUDCLK0
   mov r1,#0b11                  @two Pins
   lsl r1,#14                    @From Bit 14
   str r1,[r0]

Nun werden die Einstellungen für die Schnittstelle übermittel. Zunächst wird der Schnittstelle die Baudrate übergeben. Hierfür ist das Register IBRD und FBRD zuständig. Um die richtigen Werte herauszubekommen, müssen die Werte zunächst berechnet werden. Laut Handbuch wird folgende Formel verwendet:

BAUDDIV = (FUARTCLK/(16 * BAUD rate)

FUARTCLK ist die Interne Frequenze, mit der der Raspberry arbeitet. Für dem Raspberry Pi 4 sind das 48MHZ.

48.000.000 / ( 16 * 115200 ) = 26 Rest 0,0417

Für IBRD wird nun der Wert 26 verwendet. Für FBRD muss der Wert noch zu einem Integer Wert umgewandelt werden:

0,0417 * 64 = 2,667 Gerundet 3

Der Wert für FBRD ist damit 3.

   ldr r0,=UART0_IBRD
   mov r1,#26
   str r1,[r0]

   ldr r0,=UART0_FBRD
   mov r1,#3
   str r1,[r0]

Über LINE_CTRL wird nun der Transfer auf 8 Bit (BYTE_WORD_LENGTH) und FIFO (ENABLE_FIFO) angeschaltet.

   ldr r0,=UART0_LINE_CTRL
   mov r1,#BYTE_WORD_LENGTH | ENABLE_FIFO
   str r1,[r0]

Mit dem Controlregister wird der UART mit TX und RX gestartet.

   ldr r0,=UART0_CONTROL
   mov r1,#ENABLE | TX_ENABLE | RX_ENABLE
   str r1,[r0]

Damit ist nun der UART bereit und wir können diese Funktion beenden.

   pop {r1,pc}

Zeichen in den UART schreiben

Um nun in den UART zu schreiben, wird die Funktion UART0_DATA verwendet. Zunächst muss überprüft werden, ob die Schnittstelle für diese Aufgabe bereit ist. Dazu wird der Status der Schnittstelle abgefragt. Sollange im FIFO-Buffer kein Platz ist, muss gewartet werden, bis dieser für unsere Übergabe bereit ist.

/* void UartPutc (int Char)
   Write char in the UART
*/
UartPutc:                           
   push {r1,r2,lr}
   mov r2,r0                     @save Char to r2

   UartPutc_loop1$:              @Wait until UART is empty ...
      ldr r0,=UART0_STATUS       @address UART0_STATUS 
      ldr r1,[r0]                @Load status to r1
      ands r1,r1,#TX_FIFO_FULL   @If status full...
      bne UartPutc_loop1$        @wait

Dies Funktion wird ein Zeichen (Char) in die Schnittstelle schreiben. Dazu wird ihr ein Zeichen übergeben und als Ergebnis wird nichts übergeben. Das übergebene Zeichenwird zunächst nach r2 gesichert. Mit der Schleife "UartPutc_loop1$" wird zunächst der Status des UARTs geladen und mit dem Wert "TX_FIFO_FULL" verglichen. Dazu wird in diesem Fall "ands" verwendet. Dies führt ein logisches UND durch und übergibt die Bedingungscodes weiter. Sollte es entsprechen, wird die Schleife wiederholt.

Sobald die Bedingungen stimmen, wird einfach das Zeichen nach UART0_DATA kopiert und damit an die Schnittstelle übertragen.

   ldr r0,=UART0_DATA            @addresse for data to r0
   strb r2,[r0]                  @store char to data
    
   pop {r1,r2,pc}      @End at the function

Einen String (Zeichenfolge) in den UART schreiben

Mit der zuvor erstellten Funktion ist es möglich, ein Zeichen anzuzeigen. In der Regel sind einzelne Zeichen nicht der Standard. Es mach Sinn, ganze Wörter oder sogar ganze Sätze auf einmal anzeigen zu können. Dazu wird die Funktion UartPuts erstellt, die dies übernimmt.

/* void UartPuts (int String, int len)
   write string of length (len) to the UART
*/

UartPuts:
   push {r1,r2,lr}
   mov r2,r0                  @Store address of the string to r2

   UartPuts_loop1:
      subs r1,#1              @reduce length at 1
      blt UartPuts_loopend    @Jump as soon as length is zero
      ldrb r0,[r2]            @Load Char from string to r0
      add r2,#1               @Increment the pointer of the string by 1
      bl UartPutc             @Show Char
      b UartPuts_loop1        @Display the next char
   UartPuts_loopend:

Die Funktion erwartet in r0 den String und in r1 die Länge des Strings. Mit einer Schleife wir jedes einzelne Zeichen gelesen und der zuvor erstellten Funktion (UartPutc) übergeben. Zunächst wird die Adresse des Strings nach r2 kopiert, da r0 für die Übergabe des Zeichens an die Unterfunktion benötigt wird. In der Schleife wird zunächst die länge um eins reduziert. Sollte der Wert kleiner NULL werden, wird die Schleife beendet. Danach wird das Zeichen, in diesem Fall ein Byte, nach r0 kopiert und die Adresse um eins erhöht, damit beim nächsten Aufruf das nächste Zeichen verfügbar ist. Da in r0 das Zeichen liegt, wird einfach die UartPutc-Funktion aufgerufen und damit das Zeichen angezeigt. Diese ganze Schleife wird nun solange wiederholt, bis die Länge kleiner als NULL ist.

Bisher wird der String 1:1 angezeigt. Im Terminalprogramm würde er nun mit seinem Cursor an der aktuellen Stelle stehen und falls noch etwas Angezeigt werden soll, direkt nach der letzen Anzeige fortgeführt werden. Das sieht natürlich nicht schön aus. Dazu wird der Funktion noch ein Zeilenvorlauf und ein Rücksprung übergeben, so dass die nächste Anzeige in der neuen Zeile begint.

   mov r0,#'\n'               @New line
   bl UartPutc                @and display
   
   mov r0,#'\r'               @return
   bl UartPutc                @and display
   
   pop {r1,r2,pc}

Mit dieser einfachen Routine, wird nun ein ganzer String angezeigt.

Hauptprogramm UART 4.1

Im Hauptprogramm können nun diese neue Funktionen verwenden werden:

   bl UartInit             @Initiate UART first so that I can send messages
   
   ldr r0,=Text1           @Load pointer of the text to r0
   mov r1,#Text1End-Text1  @Length to r1
   bl UartPuts

Zunächst wird der UART Initialisiert. Diese Funktion wird nur einmal verwendet, auch dann, wenn mehr als eine Zeile angezeigt werden sollte. Die UartPuts-Funktion benötigt einen Zeiger auf den String. Dies wird mit ldr r0,=Text1 übergeben. In r1 benötigt die Funktion die Länge des Textes. Hier werden einfach zwei Labels, die den Text umschließen miteinander subtrahiert.

Damit später beim Kompilieren nichts durcheinander kommt, wird eine Datensektion (.section .data) erstellt, in der die Daten abgelegt werden. Hier ist dann auch zu sehen, was mit zwei Labels, die den Text umschließen, gemeint war.

.section .data
Text1:
   .ascii "Hello World!"
Text1End:

Das Label "Text1End" zeigt auf das Ende des Textes, wobei das Label "Text1" den Anfang des Textes definiert. Im oberen Code werden einfach diese zwei Label voneinander abgezogen und das Ergebnis ist die Länge des Textes. .ascii wird im nächsten Unterkapitel beschrieben.

Der Sourcecode, für dieses Beispiel ist hier zum laden bereit: 4.1.zip

Neue Befehle (1)

ands

Den Befehl "and" hatten wir bereits. Mit der Angabe des Suffix "s" wird hier mitgeteilt, das die CPU zusätzlich vom der Art des Ergebnisses das Statusregister aktualisiert. Dieses Statusregister wird für die weitere bedingte Ausführung verwendet.

subs

Auch der Befehl "sub" kann mit dem Suffix "s" definiert werden und kann damit genauso verwendet werden, wie zuvor der Befehl "ands".

strb, ldrb

Bei str und oder ldr kann das Suffix "b" verwendet werden. Dieser Suffix bedeutet, dass die Operation nur mit einem Byte verwendet wird. Mit "strb" wird ein Byte gespeichert und mit "ldrb" ein Byte geladen.

.ascii

Mit .ascii wird im Datenbereich ein String gekennzeichnet. Erläuterung erfolgt im nächsten Abschnitt “Richtlinien zur Datendefinition”.

Richtlinien zur Datendefinition

Im Kapitel 4 (UART) wurde das erstemal für Daten im Speicher Platz erzeugt. In diesem Fall wurde ein String im Speicher abgelegt, der über den UART angezeigt wurde. In diesem Speicher können sehr unterschiedliche Daten abgelgt werden. Der Assembler erwartet für bestimmte Arten von Daten entsprechende Schlüßelwörter, die hier erläutert werden.

Zahlenwerte

Folgende Zahlenwerte sind möglich:

Syntax Aliase Größe
in Bytes
Bereich
.byte .1byte, .dc.b 1 -128 bis 255
.hword .2byte, .dc, .dc.w, .short, .value 2 -0x8000 bis 0xffff
.word .4byte, -long, .int, .dc.l, .dc.a (nur ARM32) 4 -231 bis 232-1
.quad .8byte, .xword (nur ARM64), .dc.a (nur ARM64) 8 -263 bis 264-1
.octa 16 0 bis 2128-1

Da es viele Möglichkeiten gibt, die gleiche Aussage mit verschiedenen Aliase zu bewirken, kann dies sehr verwirrend sein. Viele Programmierer verwenden die Aliase, da diese näher an andere Programmiersprachen, wie "C", erinnern. Auch ich verwende ausschließlich ".int", statt ".word".

Ascii-Zeichen sind nichts anderes als “.byte”-Bereiche. Diese können wie folgt deklariert werden:

.byte 'H','e','l','l','o'

Escape-Befehle sind mit dieser Anweisung auch möglich.

Strings (Zeichenketten)

Unter dem Absatz "Zahlenwerten" wurde indirekt mit ".byte" eine Art String erzeugt. Der Assembler erlaubt noch zwei zusätzliche Parameter, um einen Sring zu erzeugen.

Syntax Aliase Beschreibung
.ascii Erzeugt einen String, der nicht mit NULL abgeschlossen ist.
.asciz .string Erzeugt einen String, der mit NULL abgeschlossen ist.

Im ersten Programm zu UART wurde .ascii verwendet und die länge des Strings in eine Funktion übermittelt. Mit ".asciz" wäre es auch möglich, dass eine Unterroutine diesen String anzeigt, bis dieser mit NULL abgeschloßen ist. Unter zum Beispiel "C" wird dies auch sehr häufig verwendet. Dort müssen in der Regel alle Strings mit "NULL" abgeschloßen sein. Der Assembler bietet aus diesem Grund auch diese Methode an. Welche der Programmierer bevorzugt ist ihm überlassen.

Innerhalb eines Strings können zusätzlich Escape-Sequenzen verwendet werden:

Escapezeichen Bedeutung
\b Rücktaste
\f Zeilenvorschub
\n Neue Zeile
\r Wagenrücklauf
\t Tabulator
\" Das Zeichen " selbst
\\ Backslash (\)
\Zahlencode Escapecode für jedes ASCII-Zeichen

Gleitkommazahlen (Rationale Zahlen)

Gleitkommazahlen, sind Zahlen, die es in einem Binären System so nicht geben kann. Da sie dennoch für komplexe Berechnungen erforderlich sind, wurden dazu zwei Arten von Typen dieser Zahlen als Datentypen definiert. In der Regel verwendet ein Coprozessor diese Daten und wandelt diese für sich lesbaren Code um. Allerdings muss hier auch darauf hingewiesen werden, dass die Daten, die hier erzeugt werden nur eine Annäherung zu der angegebenen rationalen Zahl ist.

Wer die genaue Funktion dieser Zahlensysteme verstehen möchte, so bitte ich den Leser dazu auf, dies z.B. im Internet selbst nachzuschlagen, da dies zur Zeit den Rahmen dieser Beschreibung sprengen würde.

Syntax Aliase Größe
in Bytes
Beschreibung
.float .single, .dc.s 4 Speichert die Werte im IEEE754-Format mit einfacher Genauigkeit ab
.double .dc.d 8 Speichert die Werte im IEEE754-Format mit doppelter Genauigkeit ab

Interagieren mit dem Raspberry Pi (4.2)

Die UART-Schnittstelle ist keine Einbahnstraße. Es ist durchaus möglich Daten vom Hostsystem auch an den Raspberry Pi zu senden und ihn auf diese Eingaben reagieren zu lassen. Dazu wird nun über die Eingabe die LED entsprechend gesteuert. Den Source gibt es hier: 4.2.zip

Eingaberoutine (UART)

In der uart.h wird eine neue Funktion erzeugt, die UartGetc genannt wird. Mit dieser Funktion werden einzelne Zeichen vom UART gelesen. Zunächst wird geprüft, ob Daten verfügbar sind.

/* char UartGetc (void)
   Read Char from UART */
UartGetc:
   push {r4,lr}

   UartGetc_loop1$:              @Wait for UART to contain data ...
      ldr r0,=UART0_STATUS       @address UART0_STATUS 
      ldr r1,[r0]                @Load Status to r4
      ands r1,r1,#RX_FIFO_EMPTY  @If Status empty
      bne UartGetc_loop1$        @Wait

Zunächst wird der Status aus dem UART geladen und überprüft, ob dort Daten sind. Wenn nicht, wird die Schleife einfach wiederholt, bis ein Ergebnis vorhanden ist. Sobald Daten verfügbar sind, werden diese abgefragt.

   ldr r0,=UART0_DATA            @address for data
   ldrb r0,[r0]                  @Write Char to r0

Auf diese weise, ist bereits ein Zeichen in r0 verfügbar. Zu diesem Zeitpunkt, wenn es so stehen bleibt, wird im Terminal-Programm nichts angezeigt. Das Terminalprogramm übergibt zwar das Zeichen in die Schnittstelle, zeigt es aber selbst nicht an. Dies ist etwas blöd, da der Anwender nicht weiß, was den nun an den Raspberry übergeben wurde. Aus diesem Grund, wird der Raspberry Pi dazu gebracht, einfach das Zeichen zurück zu schicken. Indirekt wird darüber überprüft, ob der richtige Wert dort angekommen ist. Dazu wird die Funktion UartPutc verwendet.

   mov r4,r0
   bl UartPutc
   mov r0,r4

Damit das Zeichen nicht verloren geht, wird es in r4 zwischen gespeichert. Die Funktion UartPutc aufgerufen und anschließend das Zeichen wieder nach r0 kopiert.

Eine ganze Zeile einlesen

Auch beim Einlesen von Daten wird selten nur ein Zeichen benötigt. Wie bereits bei der Ausgabe von Strings, wird nun eine Funktion erzeugt, die einen ganzen String zurück gibt. Zunächst wird festgelegt, welche Eigenschaften ein String besitzt. Bei der Eingabe eines Strings, wird in der Regel die Return-Taste betätigt, die das Ende der Eingabe definiert. Genau auf diesen Wert hin wird geprüft und die Funktion wird beendet. Zusätzlich wird eine Struktur benötigt, in der der eigentliche String abgelegt wurde, damit dieser für andere interaktionen verfügbar ist. In diesem Fall funktioniert die alte Struktur nicht mehr, da nicht bekannt ist, wie lang der String wird. Natürlich wäre es nun möglich, eine Stringstrucktur zu erzeugen, wie sie in andere Hochsprachen verwendet werden, allerdings soll dieses Beispiel zeigen, wie unter Assembler mit Strukturen gearbeitet werden kann.

Die Struktur, die hier nun verwendet wird, hat folgendes Aussehen:

GetLineStruct:
   EingabeLen:
      .int 0
   Eingabe:
      .space 256

Die Struktur beinhaltet zunächst die Länge der Eingabe und folgend mit den eingegebenen Zeichen. In diesem Fall wurde eine Speicherplatz für 256 Zeichen bereit gestellt. Die Funktion, die erstellt wird, wird den Zeiger auf diese Struktur in r0 zurückgeben.

Die Funktion wird den Namen "UartGetLine" erhalten und zunächst als Eingabeaufforderung ein ">" ausgeben.

UartGetLine:
   push {r4,r5,lr}
   mov r0,#'>'               
   bl UartPutc

Während die Funktion läuft, wird in r4 die Länge der Eingabe gespeichert und in r5 die Adresse des Strings.

   mov r4,#0
   ldr r5,=Eingabe

Beim Einstieg in die Funktion ist zunächst die Länge 0 und die Adresse des Strings der Anfang (Eingabe) unseres Speichers.

 UartGetLineLoop:  
   bl UartGetc
   cmp	r0, #13
   beq UartGetLineEnd

Damit alle Zeichen gelesen werden, wird eine Schleife erzeugt. Zunächst wird ein Zeichen geholt und überprüft, ob es die Eingabetaste (#13) war, wenn ja, wird die Schleife beendet.

   strb r0,[r5]
   add r5,#1
   add r4,#1
   b UartGetLineLoop

Ansonsten wird das Zeichen in den String abgelegt, die Adresse des Strings um ein Byte erhöht und die Länge um eins erhöht. Die gesammte Schleife wird wiederholt.

Wird die Schleife beendet, wird im Terminal noch eine neue Zeile ausgegeben und die Strucktur des Strings angepasst.

UartGetLineEnd:
   mov r0,#'\n'               @New line
   bl UartPutc                @display
   
   mov r0,#'\r'               @Go back
   bl UartPutc                @and displayed
   
   ldr r0,=EingabeLen
   str r4,[r0]
   
   pop {r4,r5,pc}

Die Adresse der Stringstrucktur wird nach r0 geladen und die Länge, welche in r4 steht, dort abgelegt. Als Rückgabe wird r0 zurück gegeben.

Neues Hauptprogramm (4.2)

Inzwischen ist die Eingabemethode soweit fertig, dass sie zu einer Aktion (interagieren) des Raspberry Pi verwendbar ist. Mit ein wenig Zeilen im Hauptprgramm wird nun diese Interaktion angewendet und der Raspberry Pi dazu gebracht, seine interne LED an oder abzuschalten.

MainLoop:                  @Infinite loop 
   
   ldr r0,=Text1           @Load pointer from Text1 to r0
   mov r1,#Text1End-Text1  @length to r1
   bl UartPuts

Und im Datensegment:

.section .data
Text1:
.ascii "0 = LEDOFF, 1 or other = LEDON"
Text1End:

Zunächst wird ein Text angezeigt, der erzählt, was erwartet wird.

   bl UartGetLine
   ldr r1,[r0]
   add r0,#4
   ldrb r0,[r0]
   cmp r0,#48
   beq LEDOFF

Als nächstes wird die Eingabe aus dem Host geholt. Zunächst wird die Länge der Eingabe nach r1 gesichert, anschließend die Adresse um 4 Bytes erhöht (Anfang des Strings) und das erste Zeichen nach r0 kopiert. Mit dem cmp Befehl wird dieses Zeichen mit 48 verglichen, welches dem ASCII-Zeichen "0" entspricht. Sollte es dieses gewesen sein, verzweigt es zum Label "LEDOFF". Wenn es nicht der Fall war, wird der Code hier weiter geführt.

   mov r0,#42
   bl LED_on
   b MainLoop

r0 wird die PIN-Nr. der LED abgelegt und die Funktion LED_on ausgeführt. Die Funktion LED_on wurde im Kapitel General Purpose I/O (GPIO) entwickelt und kommt hier wieder zum Zuge. Danach springt das Programm wieder zum Anfang der Endlosschleife.

LEDOFF:
      mov r0,#42
      bl LED_off
      b MainLoop

Sollte bei der Abfrage eine "0" festgestellt worden sein, springt er zu LEDOFF und die LED wird abgeschaltet. Dieser Code kann unter 4.2.zip heruntergeladen werden.

Neue Befehle (Teil 2)

Inzwischen wird man feststellen, dass es nicht mehr viele neue Befehle gab. Mit den bereits Vorgestellen Befehlen ist bereits eine interaktion mit dem Raspberry möglich. Allerdings wurden noch nicht alle benötigt. Wer nun noch mehr Befehle kennen lernen möchte, kann hier im Anhang "Zusammenfassung des Befehlssatzes" mehr erfahren. Sicherlich wird man auch Dinge finden, wie der Code, der bisher vorgestellt wurde verbessert werden könnte. Gerne kann hier jeder seine Ideen hineinbringen, für sich behalten oder aber auch gerne an mich weiter geben.

In diesem Teil "neue Befehle" wird diesmal kein neuer Befehl aufgelistet, den noch haben wir eine Assembleranweisung neu verwendet.

.space

Mit .space wird ein Speicherbereich reserviert und mit NULLen gefüllt. Die zweite Angabe des Befehls zeigt die Größe des Bereichs in Bytes an.

.space 256

Reserviert einen 256 Byte großen Speicherbereich.



< Zurück (System Timer) < Hauptseite > Weiter (Die Anzeige) >