Chars (PI5): Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
KKeine Bearbeitungszusammenfassung
 
(7 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
=== DrawChar auf dem Raspberry Pi ===
== Zeichnen von Zeichen auf dem Raspberry Pi ==
Wir haben nun eine Pixel-Funktion geschrieben, aber möchten wir nicht auch, dass der Raspberry Pi Informationen direkt auf dem Bildschirm anzeigen kann? Das wollen wir jetzt versuchen. In der Regel kommuniziert der Computer mittels Schriftzeichen. Dies geschieht nicht einfach so; ähnlich wie ein Mensch muss der Computer zuerst lernen, wie jeder einzelne Buchstabe aussieht. Außerdem muss er wissen, wann er das entsprechende Zeichen verwenden soll. Bestimmte Bytewerte stehen für bestimmte Zeichen oder Steuercodes. Es gibt einen Standard, der diese Kommunikation festlegt. Dieser Code nennt sich "ANSI-Zeichencode".
Nachdem wir eine Funktion zum Setzen einzelner Pixel implementiert haben, möchten wir nun dem Raspberry Pi beibringen, Text auf dem Bildschirm anzuzeigen. Texte bestehen aus Schriftzeichen, die der Computer nicht von Natur aus kennt. Ähnlich wie ein Mensch muss der Computer zunächst "lernen", wie jedes einzelne Zeichen aussieht, bevor er es darstellen kann. Diese Zeichen sind in einem bestimmten Code definiert, den der Computer versteht. Einer der am weitesten verbreiteten Codes ist der ANSI-Zeichencode.


=== Der ANSI-Zeichencode ===
== Der ANSI-Zeichencode ==
Der ANSI-Zeichencode, den wir hier verwenden, ist eine Erweiterung des ASCII-Codes, bei dem von 7-Bit pro Zeichen auf 8-Bit pro Zeichen umgestellt wurde, was einem Byte entspricht. Der ANSI-Zeichencode wurde eingeführt, als Computeranwender über Netzwerke kommunizieren wollten. Dafür war es notwendig, dass beide Computer mit dem gleichen Code "sprechen".
Der ANSI-Zeichencode, den wir hier verwenden, ist eine Erweiterung des ursprünglichen ASCII-Codes. Während ASCII ursprünglich nur 7-Bit pro Zeichen verwendete, erweitert ANSI dies auf 8-Bit, was einem Byte entspricht. Der ANSI-Code wurde entwickelt, als Computer über Netzwerke kommunizieren mussten und sicherstellen wollten, dass beide Seiten dieselben Zeichen verwenden. Die ersten 128 Zeichen im ANSI-Code stimmen mit dem ASCII-Code überein, aber die Zeichen 128 bis 255 können je nach verwendeter Codepage variieren.


Innerhalb des ASCII-Codes sind die Zahlenwerte für die Zeichen in der Regel identisch, aber in der Erweiterung zu 8-Bit nicht unbedingt gleich. Jede Computerfirma hat eigene Codepages entwickelt und veröffentlicht. Gerade in der Anfangszeit dominierten IBM-Rechner mit dem Betriebssystem MS-DOS. Wir werden hier genau diesen Zeichencode verwenden, der dort eingesetzt wurde und auch unter Windows in der Konsole verwendet wird. Dieser Code wird als "Codepage 437" bezeichnet.
Wir verwenden hier die "Codepage 437", die ursprünglich von IBM für MS-DOS-Rechner entwickelt wurde und auch heute noch in der Windows-Kommandozeile verwendet wird.


{| class="wikitable" style="text-align: center;"
{| class="wikitable" style="text-align: center;"
Zeile 11: Zeile 11:
! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!
! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !! !!
|-
|-
! !! 00 !! 01 !! 02 !! 03 !! 04 !! 05 !! 06 !! 07 !! 08 !! 09 !! 0A !! 0B !! 0C !! 0D !! 0E !! 0F
! !! 0x0 !! 0x1 !! 0x2 !! 0x3 !! 0x4 !! 0x5 !! 0x6 !! 0x7 !! 0x8 !! 0x9 !! 0xA !! 0xB !! 0xC !! 0xD !! 0xE !! 0xF
|-
|-
! 0x0
! 0x0
Zeile 62: Zeile 62:
|}
|}


=== Font-Erstellen ===
== Erstellen einer Schriftart (Font) ==
Nun kümmern wir uns darum, dem Computer beizubringen, wie die einzelnen Zeichen aussehen. Dazu erstellen wir eine Art Datenbank, in der jedes Zeichen beschrieben wird. Zur Einfachheit verwenden wir eine 8x8 Matrix, um die Zeichen darzustellen.
Um den Computer in die Lage zu versetzen, Zeichen darzustellen, müssen wir ihm zeigen, wie jedes Zeichen aussieht. Dazu erstellen wir eine Art Datenbank, in der jedes Zeichen als ein 8x8-Pixel-Muster beschrieben wird.


Dazu erstellen wir eine Datei, die folgendermaßen aussieht:
Beispielsweise könnte die Datei, die diese Zeichen beschreibt, folgendermaßen aussehen:
 
*Dimension
**INT X
**INT Y
 
*Data
**Binärcode
 
Zunächst geben wir die Größe der Zeichen in X und Y an. Daraus können wir zur Berechnung der einzelnen Zeichen den entsprechenden Binärcode ableiten. Hier steht jedes Bit, das gesetzt ist, für ein Pixel, welches gezeichnet werden soll. Umgesetzt in Code sieht es dann so aus:


<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
/* 8x8 FONT by Satyria */
/* 8x8 FONT by Satyria */


@ Dimension
@ Dimension, noch verzichten wir auf die Dimensionen
.int 8 @ x
@.int 8 @ x  
.int 8 @ y
@.int 8 @ y


@ Data
@ Data
Zeile 118: Zeile 109:
...
...
</syntaxhighlight>
</syntaxhighlight>
Dieses Beispiel zeigt, wie wir die Datenbank für die Zeichendarstellung aufbauen können. Jeder Buchstabe wird als 8x8 Pixel großes Muster beschrieben, wobei jedes Byte eine Zeile des Musters darstellt. Das Bitmuster gibt an, welche Pixel gesetzt werden und somit sichtbar sind.
Mein Beispiel für den Font habe ich als "ms8x8font.fon" im Includeverzeichnis abgelegt.
=== Font-Erstellen ===
Nun kümmern wir uns darum, dem Computer beizubringen, wie die einzelnen Zeichen aussehen. Dazu erstellen wir eine Art Datenbank, in der jedes Zeichen beschrieben wird, wie es aussieht.Wie bereits zuvor erwähnt, müssen wir selbst einen Zeichentabelle erstellen, in der wir festlegen, wie jedes einzelne Zeichen aussieht. Zur Einfachheit werden wir nur eine 8x8 Matrix verwenden.


Dazu erstellen wir nun eine Datei, die dem folgendem Aussehen entspricht:
In diesem Beispiel wird das Zeichen "A" als ein 8x8-Pixel großes Muster beschrieben, wobei jedes Byte eine Zeile des Musters darstellt. Jedes Bit in diesem Byte steht für einen Pixel: Ein gesetztes Bit (1) bedeutet, dass der Pixel in der Vordergrundfarbe gezeichnet wird, während ein nicht gesetztes Bit (0) bedeutet, dass der Pixel in der Hintergrundfarbe gezeichnet wird.


<syntaxhighlight>
Mein Beispiel für die Schriftart habe ich als "ms8x8font.fon" im Includeverzeichnis abgelegt.
Dimension
  INT X
  INT Y
Data
  Binärcode
</syntaxhighlight>


Zunächst geben wir die Größe der Zeichen in X und Y an. Daraus können wir zur Berechnung der einzelnen Zeichen der entsprechenden Binärcode ausgelöst werden. Hier steht jedes Bit, das gesetzt ist, für ein Pixel, welches gezeichnet werden soll.
== Zeichnen von Zeichen auf dem Bildschirm ==
Umgesetzt in Code, sieht es dann so aus:
Nachdem wir definiert haben, wie jedes Zeichen aussieht, müssen wir eine Funktion schreiben, um diese Zeichen auf dem Bildschirm anzuzeigen. Wir nennen diese Funktion „DrawChar“. Wir werden das zu zeichnende Zeichen sowie die Position (x, y) auf dem Bildschirm übergeben.
 
<syntaxhighlight lang="asm">
/* 8x8 FONT by Satyria */
 
@ Dimension
.int 8 @ x
.int 8 @ y
 
@ Data
@ 0: NULL
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
 
...
...
@ 65: "A"
.byte 0b00011000
.byte 0b00111100
.byte 0b00111100
.byte 0b01100110
.byte 0b01111110
.byte 0b11000011
.byte 0b11000011
.byte 0b00000000
 
@ 66: "B"
...
...
</syntaxhighlight>
 
=== Font-Anzeigen lassen ===
Nachdem wir nun einen Font beschrieben haben, also definiert haben, wie ein Zeichen aussieht, müssen wir diesen noch auf den Bildschirm zeichnen.
 
Bisher können wir nicht auf externe Medien, während unser Programm läuft, zugreifen. Da dies bisher nicht geht, müssen wir den Font in unseren Sourcecode integrieren. Dazu verwenden wir die Include-Funktion des Compilers.
 
<syntaxhighlight lang="asm">
.section .data
 
font:
.include "ms8x8font.fon"
</syntaxhighlight>
 
Unserer Funktion werden wir den Namen "DrawChar" geben. Ihr wird das Zeichen selbst, welches angezeigt werden soll und die Position (x,y) übergeben.
 
Zunächst sichern wir diese Angaben, damit diese nicht gelöscht werden, wenn wir eine andere Funktion verwenden.


Zunächst speichern wir die übergebenen Werte, damit diese nicht durch andere Funktionen überschrieben werden:
<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
//void DrawChar(char c, u32 x0, u32 y0)
//void DrawChar(char c, u32 x0, u32 y0)
Zeile 206: Zeile 135:
</syntaxhighlight>
</syntaxhighlight>


Unseren Font haben wir im Datenbereich abgelegt und ihm das Label "font" gegeben. Diesen Zeiger legen wir in x10 ab.
Als nächstes berechnen wir den Zeiger auf das Zeichen, das wir zeichnen wollen. Der Font ist im Datenbereich abgelegt und hat das Label "font". Um die genaue Position des gewünschten Zeichens zu bestimmen, verschieben wir den Wert des Zeichens um 3 Bit nach links (entspricht einer Multiplikation mit 8, da jedes Zeichen 8 Zeilen hat) und addieren ihn zum Basiszeiger:


<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
     //Berechne den Zeiger auf das richtige Zeichen  
     // Berechne den Zeiger auf das richtige Zeichen  
ldr x10,=font
ldr x10, =font
lsl w0,w0,#3
lsl w0, w0, #3
add x10,x10,x0
add x10, x10, x0
</syntaxhighlight>
</syntaxhighlight>


Da wir die Dimension des Fonts kennen, überspringen wir diese Angabe. Danach multiplizieren wir die Übergabe des Zeichen, welche wir in w0 übergeben bekommen haben, mit acht. Ein Shift um 3 Positionen nach links, entspricht einer Multiplikation von acht. Und addieren das Ergebnis auf den Zeiger. Damit haben wir die Position des entsprechenden Fonts, den wir anzeigen lassen wollen.
Jetzt, da wir den Zeiger auf das Zeichen haben, verwenden wir zwei ineinander verschachtelte Schleifen, um durch das 8x8-Pixel-Muster zu iterieren:
 
Da wir wissen, wie der Font aufgebaut ist, also 8x8 Pixel, verwenden wir zwei ineinander verschachtelte Schleifen:


<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
Zeile 243: Zeile 170:
</syntaxhighlight>
</syntaxhighlight>


Nun müssen wir jedes Bit dieser Font-Matrix auswerten. Dazu verwenden wir folgende Formel: (Zeile >> (7 - BitPos)) & 1;
Innerhalb der inneren Schleife wird das aktuelle Byte der Zeile, die wir gerade zeichnen wollen, in w13 geladen. Um das relevante Bit zu überprüfen, wird das Byte nach rechts verschoben (lsr), sodass das zu überprüfende Bit an der Position 0 steht. Dann wird mit einem and-Befehl überprüft, ob dieses Bit gesetzt ist:
 
Wir lesen zunächst das Byte heraus, was wir mit w11 (Zeile) definiert haben. Dies machen wir mit dem Befehl ldrb w13,[x10,x11]. Dies bedeutet das wir aus dem Speicher das Byte lesen, welches im Speicher an x10 steht und addieren dort den Offset von x11 dazu. Den Wert schreiben wir nach w13. Da wir das Ergebnis falsch herum bekomme, ziehen wir von der 7 (1. Bit = 0) die BitPos ab, die wir in der Schleife mit w12 definiert haben. Verschieben damit dann das Byte
 
 
 
Wir müssen, um den Font zu zeichnen, eine Schleife bilden, in der wir die Daten auswerten. Um eine Ende der Schleife zu definieren, verwenden wir die Y-Position. Da der Font 8 Pixel hoch ist, addieren wir einfach acht hinzu. Wenn wir später diesen Wert erreicht haben, haben wir auch das gesamte Zeichen gemalt.
 
Unser Font haben wir in Binärform erstellt. Das Bedeutet, wenn wir nun die Daten Auswerten, müssen wir hier auch in Binär denken.


<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
  mov r10,#8
    // Überprüfe das Bit nach folgender Formel:
    // (Zeile >> (7 - BitPos)) & 1;
ldrb w13, [x10, x11]
mov w14, #7
sub w14, w14, w12
lsr w13, w13, w14
and w13, w13, #1
</syntaxhighlight>
</syntaxhighlight>
Wenn das Ergebnis 0 ist, zeichnen wir einen Pixel in der Hintergrundfarbe, ansonsten in der Vordergrundfarbe:


In r10 legen wir die Position des Pixels ab, den wir überprüfen und eventuell dann zeichnen werden. Damit wir einen einzelnen Bit prüfen können, verwenden wir r10 und eine Shift-Operation.
<syntaxhighlight lang="asm">
 
    // Wenn Ergebnis 0 ist, zeichne in der Hintergrundfarbe
Zunächst laden wir das gesammte Byte aus dem Speicher nach r0.
cbz w13, 5f


    // Ansonsten in der Vordergrundfarbe
ldr x13, =FrontColor
ldr w13, [x13]
ldr x14, =DrawColor
str w13, [x14]
b 6f
5:
ldr x13, =BackColor
ldr w13, [x13]
ldr x14, =DrawColor
str w13, [x14]
</syntaxhighlight>
Zum Schluss zeichnen wir den Pixel an der berechneten Position auf dem Bildschirm:
<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
DrawCharLoop1:
6:
  ldrb r0,[r7] 
    // Zeichne den Pixel an die Position
  lsr r0,r10   
mov w0, w12
  and r0,#1     
add w0, w0, w20
  cmp r0,#1     
mov w1, w11
</syntaxhighlight>
add w1, w1, w21
bl DrawPixel


Da wir in r10 die Position gespeichert haben, um die Position des entsprechenden Pixel auszuwerten, schieben wir einfach die Bits mit der Anzahl aus r10 nach rechts. Damit steht das eigentliche Bit, welches wir auswerten wollen, an der Position "0" im Byte. Damit der Wert dann noch auswertbar wird, verwenden wir ein "and" und löschen damit alle Bits oberhalb des unteren Bits. Nun können wir den Wert einfach mit #1 vergleichen.
    // end_for w12
 
add w12, w12, #1
<syntaxhighlight lang="asm">
b 3b
  bne DrawCharSkip1
4:
      mov r0,r5 
    // end_for w11
      mov r1,r6
add w11, w11, #1
      bl DrawPixel
b 1b
  DrawCharSkip1:
2:
ldp x14, x15, [sp], 16
ldp x12, x13, [sp], 16
ldp x10, x11, [sp], 16
ldp x20, x21, [sp], 16
ldp x29, x30, [sp], 16
ret
</syntaxhighlight>
</syntaxhighlight>


Wenn hier nun keine #1 steht, überspringen wir einfach die "DrawPixel"-Funktion. Ansonsten übergeben wir die Positionen von x und y und zeichnen einen Punkt.
Hier der gesamte Code für die DrawChar-Funktion:
 
Wenn wir dies soweit haben, müssen wir unsere Werte, die wir für die Auswertung benutzen, entsprechend anpassen.


<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
  add r5,#1     
//void DrawChar(char c, u32 x0, u32 y0)
  sub r10,#1   
//Zeichnet einen Char an die Position x0, y0 auf den Screen
  cmp r10,#-1   
.globl DrawChar
  bne DrawCharSkip2
DrawChar:
      add r7,#1 
stp x29, x30, [sp, -16]!
      mov r10,#8 
stp x20, x21, [sp, -16]!
      sub r5,#9 
stp x10, x11, [sp, -16]!
      add r6,#1 
stp x12, x13, [sp, -16]!
DrawCharSkip2:
stp x14, x15, [sp, -16]!
</syntaxhighlight>
mov x29, sp
    Sichere zunächst die Position
mov w20,w1 // x0
mov w21,w2 // y0


Zunächst erhöhen wir die Position X um eins, für das nächste Pixel. Subtrahieren die Shiftposition um eins und vergleichen dann diesen Wert. Wenn dieser nicht negativ wurde, überspringen wir den nächsten Teil. Sollte er negativ sein, so haben wir eine Zeile komplett ausgewertet.
    //Berechne den Zeiger auf das richtige Zeichen
In diesem Fall, müssen wir einige Werte anpassen, damit wir dann die nächste Zeile anzeigen können. Dazu erhöhen wir zunächst den Zeiger auf den Font um eins, unseren Shiftwert setzen wir wieder auf acht und setzen X wieder zurück zum Ausgangswert. Y erhöhen wir um eins, für die nächste Zeile.
ldr x10,=font
lsl w0,w0,#3
add x10,x10,x0


Das nächste, was wir prüfen müssen, ist, wenn wir nun das Ende des Fonts erreicht haben. Dies Bedeutet, dass das komplette Zeichen angezeigt wurde.
    //for (w11 = 0; w11 < FONT_HEIGHT; w11++)
mov w11,#0
1:
cmp w11,#FONT_HEIGHT
bge 2f


<syntaxhighlight lang="asm">
    //for (w12 = 0; w12 < FONT_WIDTH; w12++)
  cmp r6,r9   
mov w12,#0
  popcs {r4,r5,r6,r7,r8,r9,r10,pc}
3:
  b DrawCharLoop1 
cmp w12,#FONT_WIDTH
</syntaxhighlight>
bge 4f


In r9 hatten wir das Ende des Fonts abgelegt und wir vergleichen nun einfach diese Position mit der aktuellen Y Position. Wenn dieser Unterschied Größer oder gleich ist, wird die Funktion beendet. Wenn nicht, wird einfach die Schleife wiederholt.
    //Überprüfe das Bit nach folgender Formel:
    //(Zeile >> (7 - BitPos)) & 1;
ldrb w13,[x10,x11]
mov w14,#7
sub w14,w14,w12
lsr w13,w13,w14
and w13,w13,#1
    //Wenn Ergebniss 0 ist, dann Hintergrund zeichnen
cbz w13,5f


Juhu, wir haben es geschafft. Nun wollen wir aber diese Funktion testen. Also schreiben wir unser Hauptprogramm um.
    //Ansonsten in der Vordergrundfarbe
ldr x13,=FrontColor
ldr w13,[x13]
ldr x14,=DrawColor
str w13,[x14]
b 6f
5:
ldr x13,=BackColor
ldr w13,[x13]
ldr x14,=DrawColor
str w13,[x14]


<syntaxhighlight lang="asm">
6:
main:           
    //Zeichne den Pixel an die Position
  mov sp,#0x8000
mov w0,w12
add w0,w0,w20
mov w1,w11
add w1,w1,w21
bl DrawPixel


  bl FB_Init 
    //end_for w12
 
add w12,w12,#1
  mov r0,#0xff 
b 3b
  mov r1,#0x00
4:
  mov r2,#0xff
    //end_for w11
  bl SetColor
add w11,w11,#1
b 1b
2:
ldp x14, x15, [sp], 16
ldp x12, x13, [sp], 16
ldp x10, x11, [sp], 16
ldp x20, x21, [sp], 16
ldp x29, x30, [sp], 16
ret
</syntaxhighlight>
</syntaxhighlight>


Zunächst erstellen wir unseren Screen und setzen die Farbe auf Pink.
== Testen der Funktion ==
 
Um die Funktion zu testen, ändern wir unser Hauptprogramm so, dass es eine Schleife enthält, die eine Reihe von Zeichen auf den Bildschirm schreibt:
Unser Ziel wird sein, dass wir alle Zeichen anzeigen lassen, die wir in der Font-Liberay erstellt haben.


<syntaxhighlight lang="asm">
<syntaxhighlight lang="asm">
  mov r4,#0   
//
  mov r5,#16
// kernel.S
  mov r6,#16
//
  mov r7,#350
  mov r8,#500
</syntaxhighlight>


In r4 legen wir einen Zähler an, der jedes Zeichen repräsentiert. Zunächst wird der Wert auf null gesetzt, was dem ersten Zeichen entspricht.
.section .text
Um nicht aus dem Bereich der Anzeige zu kommen, werden wir die Zeichen in einem 16 x 16 Gitter setzen. Die Positionen sind in r5 und r6 abgelegt.
.globl main
In r7 und r8 sind die Startkoordinaten für unser Feld abgelegt.
main:


<syntaxhighlight lang="asm">
    bl LED_off
loop:           
bl Init_Screen
  mov r0,r4
  mov r1,r7
  mov r2,r8
  bl DrawChar
</syntaxhighlight>


Mit einem einfache Übertrag vom Zeichen und der Position übergeben wir unserer neuen "DrawChar"-Funktion das Zeichen und lassen es uns anzeigen.
mov w10,#33
mov w11,#100
2:
cmp w10,#90
bge 1f
mov w0,w10
mov w1,#100
mov w2,w11
bl DrawChar
add w11,w11,#10
add w10,w10,#1


<syntaxhighlight lang="asm">
b 2b
  add r4,#1
1:
  add r7,#10
mov w0,#2
  sub r5,#1
bl LED_Error
  cmp r5,#0
999:
   bne loop
   b 999b
</syntaxhighlight>
</syntaxhighlight>


Im Anschluss erhöhen wir den Zeichenzähler (r4) um eins, Setzen die X Position um 10 hoch. Und reduzieren die X-Position unseres Gitters um eins. Solange dieser Wert nicht die Null erreicht hat, wird nun die Schleife ausgeführt.
In diesem Beispiel schalten wir die LED aus, initialisieren den Bildschirm und zeichnen dann eine Reihe von Zeichen auf den Bildschirm, beginnend bei Position (100, 100). Mit jeder Iteration der Schleife wird das Zeichen um 10 Pixel nach unten verschoben. Wenn das Programm erfolgreich war, wird der Fehlercode 2 ausgegeben.


Wenn nicht:
-----


<syntaxhighlight lang="asm">
{| style="width: 100%;
  add r8,#10
| style="width: 33%;" | [[Grafik (PI5)|< Zurück (Grafik (PI5))]]
  mov r7,#350
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]]
  mov r5,#16
| style="width: 33%; text-align:right;" | [[Das Terminal (PI5)|Weiter (Das Terminal (PI5)) >]]
  sub r6,#1
|}
  cmp r6,#0
  beq skip
  b loop
skip:
</syntaxhighlight>
Die Position von Y wir um 10 erhöht und die Position X auf den Ausgangswert (350) gesetzt. In unserem Gitter fangen wir für die X-Position wieder von vorne an und die Y-Position wird um eins reduziert. Wenn nun die Y-Position null erreicht haben wir 256 Zeichen dargestellt und sind fertig. Wenn noch nicht, wird die Schleife einfach wiederholt.

Aktuelle Version vom 22. Februar 2025, 11:33 Uhr

Zeichnen von Zeichen auf dem Raspberry Pi

Nachdem wir eine Funktion zum Setzen einzelner Pixel implementiert haben, möchten wir nun dem Raspberry Pi beibringen, Text auf dem Bildschirm anzuzeigen. Texte bestehen aus Schriftzeichen, die der Computer nicht von Natur aus kennt. Ähnlich wie ein Mensch muss der Computer zunächst "lernen", wie jedes einzelne Zeichen aussieht, bevor er es darstellen kann. Diese Zeichen sind in einem bestimmten Code definiert, den der Computer versteht. Einer der am weitesten verbreiteten Codes ist der ANSI-Zeichencode.

Der ANSI-Zeichencode

Der ANSI-Zeichencode, den wir hier verwenden, ist eine Erweiterung des ursprünglichen ASCII-Codes. Während ASCII ursprünglich nur 7-Bit pro Zeichen verwendete, erweitert ANSI dies auf 8-Bit, was einem Byte entspricht. Der ANSI-Code wurde entwickelt, als Computer über Netzwerke kommunizieren mussten und sicherstellen wollten, dass beide Seiten dieselben Zeichen verwenden. Die ersten 128 Zeichen im ANSI-Code stimmen mit dem ASCII-Code überein, aber die Zeichen 128 bis 255 können je nach verwendeter Codepage variieren.

Wir verwenden hier die "Codepage 437", die ursprünglich von IBM für MS-DOS-Rechner entwickelt wurde und auch heute noch in der Windows-Kommandozeile verwendet wird.

0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xA 0xB 0xC 0xD 0xE 0xF
0x0 NULL
0x1 §
0x2 Space ! " # $ % & ' ( ) * + , - . /
0x3 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0x4 @ A B C D E F G H I J K L M N O
0x5 P Q R S T U V W X Y Z [ \ ] ^ _
0x6 ` a b c d e f g h i j k l m n o
0x7 p q r s t u v w x y z { } ~ DEL
0x8 Ç ü é â ä à å ç ê ë è ï î ì Ä Å
0x9 É æ Æ ô ö ò û ù ÿ Ö Ü ¢ £ ¥ ƒ
0xA á í ó ú ñ Ñ ª º ¿ ¬ ½ ¼ ¡ « »
0xB
0xC
0xD
0xE α ß Γ π Σ σ µ τ Φ Θ Ω δ φ ε
0xF ± ÷ ° · ²    

Erstellen einer Schriftart (Font)

Um den Computer in die Lage zu versetzen, Zeichen darzustellen, müssen wir ihm zeigen, wie jedes Zeichen aussieht. Dazu erstellen wir eine Art Datenbank, in der jedes Zeichen als ein 8x8-Pixel-Muster beschrieben wird.

Beispielsweise könnte die Datei, die diese Zeichen beschreibt, folgendermaßen aussehen:

/* 8x8 FONT by Satyria */

@ Dimension, noch verzichten wir auf die Dimensionen
@.int 8 @ x 
@.int 8 @ y

@ Data
@ 0: NULL 
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000
.byte 0b00000000

...

@ 65: "A" 
.byte 0b00011000
.byte 0b00111100
.byte 0b00111100
.byte 0b01100110
.byte 0b01111110
.byte 0b11000011
.byte 0b11000011
.byte 0b00000000

@ 66: "B" 
.byte 0b11111100
.byte 0b01100110
.byte 0b01100110
.byte 0b01111100
.byte 0b01100110
.byte 0b01100110
.byte 0b11111100
.byte 0b00000000

...

In diesem Beispiel wird das Zeichen "A" als ein 8x8-Pixel großes Muster beschrieben, wobei jedes Byte eine Zeile des Musters darstellt. Jedes Bit in diesem Byte steht für einen Pixel: Ein gesetztes Bit (1) bedeutet, dass der Pixel in der Vordergrundfarbe gezeichnet wird, während ein nicht gesetztes Bit (0) bedeutet, dass der Pixel in der Hintergrundfarbe gezeichnet wird.

Mein Beispiel für die Schriftart habe ich als "ms8x8font.fon" im Includeverzeichnis abgelegt.

Zeichnen von Zeichen auf dem Bildschirm

Nachdem wir definiert haben, wie jedes Zeichen aussieht, müssen wir eine Funktion schreiben, um diese Zeichen auf dem Bildschirm anzuzeigen. Wir nennen diese Funktion „DrawChar“. Wir werden das zu zeichnende Zeichen sowie die Position (x, y) auf dem Bildschirm übergeben.

Zunächst speichern wir die übergebenen Werte, damit diese nicht durch andere Funktionen überschrieben werden:

//void DrawChar(char c, u32 x0, u32 y0)
//Zeichnet einen Char an die Position x0, y0 auf den Screen
.globl DrawChar
DrawChar:
	stp x29, x30, [sp, -16]!
	stp x20, x21, [sp, -16]!
	stp x10, x11, [sp, -16]!
	stp x12, x13, [sp, -16]!
	stp x14, x15, [sp, -16]!
	mov x29, sp
	
    Sichere zunächst die Position
	mov w20,w1	// x0
	mov w21,w2	// y0

Als nächstes berechnen wir den Zeiger auf das Zeichen, das wir zeichnen wollen. Der Font ist im Datenbereich abgelegt und hat das Label "font". Um die genaue Position des gewünschten Zeichens zu bestimmen, verschieben wir den Wert des Zeichens um 3 Bit nach links (entspricht einer Multiplikation mit 8, da jedes Zeichen 8 Zeilen hat) und addieren ihn zum Basiszeiger:

    // Berechne den Zeiger auf das richtige Zeichen 
	ldr x10, =font
	lsl w0, w0, #3
	add x10, x10, x0

Jetzt, da wir den Zeiger auf das Zeichen haben, verwenden wir zwei ineinander verschachtelte Schleifen, um durch das 8x8-Pixel-Muster zu iterieren:

    //for (w11 = 0; w11 < FONT_HEIGHT; w11++)
	mov w11,#0
1:
	cmp w11,#FONT_HEIGHT
	bge 2f

    //for (w12 = 0; w12 < FONT_WIDTH; w12++)
	mov w12,#0
3:
	cmp w12,#FONT_WIDTH
	bge 4f

...

    //end_for w12
	add w12,w12,#1
	b 3b
4:
    //end_for w11
	add w11,w11,#1
	b 1b

Innerhalb der inneren Schleife wird das aktuelle Byte der Zeile, die wir gerade zeichnen wollen, in w13 geladen. Um das relevante Bit zu überprüfen, wird das Byte nach rechts verschoben (lsr), sodass das zu überprüfende Bit an der Position 0 steht. Dann wird mit einem and-Befehl überprüft, ob dieses Bit gesetzt ist:

    // Überprüfe das Bit nach folgender Formel:
    // (Zeile >> (7 - BitPos)) & 1;
	ldrb w13, [x10, x11]
	mov w14, #7
	sub w14, w14, w12
	lsr w13, w13, w14
	and w13, w13, #1

Wenn das Ergebnis 0 ist, zeichnen wir einen Pixel in der Hintergrundfarbe, ansonsten in der Vordergrundfarbe:

    // Wenn Ergebnis 0 ist, zeichne in der Hintergrundfarbe
	cbz w13, 5f

    // Ansonsten in der Vordergrundfarbe
	ldr x13, =FrontColor
	ldr w13, [x13]
	ldr x14, =DrawColor
	str w13, [x14]
	b 6f
	
5:
	ldr x13, =BackColor
	ldr w13, [x13]
	ldr x14, =DrawColor
	str w13, [x14]

Zum Schluss zeichnen wir den Pixel an der berechneten Position auf dem Bildschirm:

6:
    // Zeichne den Pixel an die Position
	mov w0, w12
	add w0, w0, w20
	mov w1, w11
	add w1, w1, w21
	bl DrawPixel

    // end_for w12
	add w12, w12, #1
	b 3b
4:
    // end_for w11
	add w11, w11, #1
	b 1b
2:
	ldp x14, x15, [sp], 16
	ldp x12, x13, [sp], 16
	ldp x10, x11, [sp], 16
	ldp x20, x21, [sp], 16
	ldp x29, x30, [sp], 16
	ret

Hier der gesamte Code für die DrawChar-Funktion:

//void DrawChar(char c, u32 x0, u32 y0)
//Zeichnet einen Char an die Position x0, y0 auf den Screen
.globl DrawChar
DrawChar:
	stp x29, x30, [sp, -16]!
	stp x20, x21, [sp, -16]!
	stp x10, x11, [sp, -16]!
	stp x12, x13, [sp, -16]!
	stp x14, x15, [sp, -16]!
	mov x29, sp
	
    Sichere zunächst die Position
	mov w20,w1	// x0
	mov w21,w2	// y0

    //Berechne den Zeiger auf das richtige Zeichen 
	ldr x10,=font
	lsl w0,w0,#3
	add x10,x10,x0

    //for (w11 = 0; w11 < FONT_HEIGHT; w11++)
	mov w11,#0
1:
	cmp w11,#FONT_HEIGHT
	bge 2f

    //for (w12 = 0; w12 < FONT_WIDTH; w12++)
	mov w12,#0
3:
	cmp w12,#FONT_WIDTH
	bge 4f
	

    //Überprüfe das Bit nach folgender Formel:
    //(Zeile >> (7 - BitPos)) & 1;
	ldrb w13,[x10,x11]
	mov w14,#7
	sub w14,w14,w12
	lsr w13,w13,w14
	and w13,w13,#1
    //Wenn Ergebniss 0 ist, dann Hintergrund zeichnen
	cbz w13,5f

    //Ansonsten in der Vordergrundfarbe
	ldr x13,=FrontColor
	ldr w13,[x13]
	ldr x14,=DrawColor
	str w13,[x14]
	b 6f
	
5:
	ldr x13,=BackColor
	ldr w13,[x13]
	ldr x14,=DrawColor
	str w13,[x14]

6:
    //Zeichne den Pixel an die Position
	mov w0,w12
	add w0,w0,w20
	mov w1,w11
	add w1,w1,w21
	bl DrawPixel

    //end_for w12
	add w12,w12,#1
	b 3b
4:
    //end_for w11
	add w11,w11,#1
	b 1b
2:
	ldp x14, x15, [sp], 16
	ldp x12, x13, [sp], 16
	ldp x10, x11, [sp], 16
	ldp x20, x21, [sp], 16
	ldp x29, x30, [sp], 16
	ret

Testen der Funktion

Um die Funktion zu testen, ändern wir unser Hauptprogramm so, dass es eine Schleife enthält, die eine Reihe von Zeichen auf den Bildschirm schreibt:

//
// kernel.S
//

.section .text
.globl main
main:

    bl LED_off
	bl Init_Screen

	mov w10,#33
	mov w11,#100
2:
	cmp w10,#90
	bge 1f
	
	mov w0,w10
	mov w1,#100
	mov w2,w11
	bl DrawChar
	add w11,w11,#10
	add w10,w10,#1

	b 2b
1:	 
	mov w0,#2
	bl LED_Error
999:
   b 999b

In diesem Beispiel schalten wir die LED aus, initialisieren den Bildschirm und zeichnen dann eine Reihe von Zeichen auf den Bildschirm, beginnend bei Position (100, 100). Mit jeder Iteration der Schleife wird das Zeichen um 10 Pixel nach unten verschoben. Wenn das Programm erfolgreich war, wird der Fehlercode 2 ausgegeben.


< Zurück (Grafik (PI5)) < Hauptseite > Weiter (Das Terminal (PI5)) >