Chars (PI5): Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
Zeile 346: Zeile 346:


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.
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.
{|
 
|-
-----
| style="width: 33%; text-align:left;" | [[Grafik (PI5)|< Zurück]] || style="width: 33%" | [[Hauptseite|^ Hauptseite]] || style="width: 33%; text-align:right;" | weiter >
 
{| style="width: 100%;
| style="width: 33%;" | [[Grafik (PI5)|< Zurück (Grafik (PI5))]]
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]]
| style="width: 33%; text-align:right;" | [[Das Terminal (PI5)|Weiter (Das Terminal (PI5)) >]]
|}
|}

Version vom 23. August 2024, 10:46 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.

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
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)) >