Chars (PI5)
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)) > |