Chars (PI5)

Aus C und Assembler mit Raspberry

DrawChar 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".

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".

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.

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 ± ÷ ° · ²    

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. Zur Einfachheit verwenden wir eine 8x8 Matrix, um die Zeichen darzustellen.

Dazu erstellen wir eine Datei, die folgendermaßen aussieht:

  • 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:

/* 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" 
.byte 0b11111100
.byte 0b01100110
.byte 0b01100110
.byte 0b01111100
.byte 0b01100110
.byte 0b01100110
.byte 0b11111100
.byte 0b00000000

...

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:

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 der entsprechenden Binärcode ausgelöst werden. Hier steht jedes Bit, das gesetzt ist, für ein Pixel, welches gezeichnet werden soll. Umgesetzt in Code, sieht es dann so aus:

/* 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" 
...
...

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.

.section .data

font:
.include "ms8x8font.fon"

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.

//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

Unseren Font haben wir im Datenbereich abgelegt und ihm das Label "font" gegeben. Diesen Zeiger legen wir in x10 ab.

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

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.

Da wir wissen, wie der Font aufgebaut ist, also 8x8 Pixel, verwenden wir zwei ineinander verschachtelte Schleifen:

    //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

Nun müssen wir jedes Bit dieser Font-Matrix auswerten. Dazu verwenden wir folgende Formel: (Zeile >> (7 - BitPos)) & 1;

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.

   mov r10,#8

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.

Zunächst laden wir das gesammte Byte aus dem Speicher nach r0.

DrawCharLoop1:
   ldrb r0,[r7]   
   lsr r0,r10     
   and r0,#1      
   cmp r0,#1

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.

   bne DrawCharSkip1
      mov r0,r5   
      mov r1,r6
      bl DrawPixel
   DrawCharSkip1:

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.

Wenn wir dies soweit haben, müssen wir unsere Werte, die wir für die Auswertung benutzen, entsprechend anpassen.

   add r5,#1      
   sub r10,#1     
   cmp r10,#-1    
   bne DrawCharSkip2
      add r7,#1   
      mov r10,#8  
      sub r5,#9   
      add r6,#1   
DrawCharSkip2:

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. 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.

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.

   cmp r6,r9     
   popcs {r4,r5,r6,r7,r8,r9,r10,pc}
   b DrawCharLoop1

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.

Juhu, wir haben es geschafft. Nun wollen wir aber diese Funktion testen. Also schreiben wir unser Hauptprogramm um.

main:             
   mov sp,#0x8000 

   bl FB_Init   
   
   mov r0,#0xff  
   mov r1,#0x00
   mov r2,#0xff
   bl SetColor

Zunächst erstellen wir unseren Screen und setzen die Farbe auf Pink.

Unser Ziel wird sein, dass wir alle Zeichen anzeigen lassen, die wir in der Font-Liberay erstellt haben.

   mov r4,#0     
   mov r5,#16
   mov r6,#16
   mov r7,#350
   mov r8,#500

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. 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. In r7 und r8 sind die Startkoordinaten für unser Feld abgelegt.

loop:             
   mov r0,r4
   mov r1,r7
   mov r2,r8
   bl DrawChar

Mit einem einfache Übertrag vom Zeichen und der Position übergeben wir unserer neuen "DrawChar"-Funktion das Zeichen und lassen es uns anzeigen.

   add r4,#1
   add r7,#10
   sub r5,#1
   cmp r5,#0
   bne loop

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.

Wenn nicht:

   add r8,#10
   mov r7,#350
   mov r5,#16
   sub r6,#1
   cmp r6,#0
   beq skip
   b loop
skip:

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.