Das Terminal (PI5): Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
KKeine Bearbeitungszusammenfassung
 
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt)
Zeile 14: Zeile 14:
</syntaxhighlight>
</syntaxhighlight>
Um die aktuelle Position des Cursors (die Stelle, an der das nächste Zeichen erscheinen wird) zu speichern, definieren wir unter <syntaxhighlight lang="asm" inline>.section .data</syntaxhighlight> zwei Variablen. Diese setzen wir anfangs auf 0,0 (oben links):
Um die aktuelle Position des Cursors (die Stelle, an der das nächste Zeichen erscheinen wird) zu speichern, definieren wir unter <syntaxhighlight lang="asm" inline>.section .data</syntaxhighlight> zwei Variablen. Diese setzen wir anfangs auf 0,0 (oben links):
 
<syntaxhighlight lang="asm">
// Terminal
// Terminal
cursor_x:
cursor_x:
Zeile 20: Zeile 20:
cursor_y:
cursor_y:
.int 0
.int 0
</syntaxhighlight>


 
== Zeichen im Terminal zeichnen ==
 
Als erstes schreiben wir eine Funktion, die ein Zeichen im Terminal an der aktuellen Cursor-Position zeichnet. Die Funktion bekommt einfach das Zeichen übergeben, das gezeichnet werden soll:
 
<syntaxhighlight lang="asm">
 
 
 
//
// screen.S
//
 
#include "config.h"
#include "base.h"
#include "mailbox.h"
 
#define maxZeichen 240
#define maxZeilen 108
 
/* Dimension */
#define FONT_WIDTH  8
#define FONT_HEIGHT 8
#define FONT_MAX 256
 
.section .text
 
// bolean Init_Screen (void)
//
.globl Init_Screen
Init_Screen:
stp x29, x30, [sp, -16]!
mov x29, sp
 
ldr w0,=BCM_MAILBOX_PROP_OUT //Kanal
ldr x1,=pScreen //ScreenStruktur
1:
bl BcmMailBox_Write //Rufe die Mailbox auf
 
ldr x0,=m_nBufferPtr //lade die Speicheradresse
ldr w0,[x0]
cmp w0,#0
beq 1b
and w0,w0,#0x3FFFFFFF //Passe die Adresse an
ldr x1,=graphicsAddress //und sichere sie
str w0,[x1]
ldp x29, x30, [sp], 16
ret
 
// void DrawPixel(u32 x, u32 y)
// Zeichne einen Pixel auf den Bildschirm
.globl DrawPixel
DrawPixel:
stp x29, x30, [sp, -16]!
stp x10, x11, [sp, -16]!
mov x29, sp
 
    //Überprüfe, ob der Pixel in den Screen passt
cmp w0,#SCREEN_X
bge 1f
cmp w1,#SCREEN_Y
bge 1f
 
    //Berechne Position des Pixels: graphicsAddress+(((SCREEN_X*y)+x)*4)
mov w10,SCREEN_X
mul w10,w10,w1
add w10,w10,w0
lsl w10,w10,#2
ldr x11,=graphicsAddress
ldr w11,[x11]
add w10,w10,w11
 
    //Schreibe die Farbe in die Position
ldr x11,=DrawColor
ldr w11,[x11]
str w11,[x10]
1:
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ret
 
//u32 GetPixel(u32 x, u32 y)
//Abfrage, der Farbe des Pixels im Screen
.globl GetPixel
GetPixel:
stp x29, x30, [sp, -16]!
stp x10, x11, [sp, -16]!
mov x29, sp
   
    //Überprüfe, ob der Pixel im Screen ist
cmp w0,#SCREEN_X
bge 1f
cmp w1,#SCREEN_Y
bge 1f
 
    //Berechne Position des Pixels: graphicsAddress+(((SCREEN_X*y)+x)*4)
mov w10,SCREEN_X
mul w10,w10,w1
add w10,w10,w0
lsl w10,w10,#2
ldr x11,=graphicsAddress
ldr w11,[x11]
add w10,w10,w11
 
    //Lade die Farbe des Pixels nach w0
ldr w0,[x10]
b 2f
 
1:
    //Wenn Position des Pixel nicht im Screen ist, dann NULL zurück
mov w0,#0
 
2:
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ret
 
 
//void SetDrawColor(u8 r, u8 g, u8 b)
//definiert die Zeichenfarbe
.globl SetDrawColor
SetDrawColor:
stp x29, x30, [sp, -16]!
mov x29, sp
 
    //Berechne die Farbe mit: (0xFF << 24) | (r << 16) | (g << 8) | b;
mov w3,#0xFF
lsl w3,w3,#24
lsl w0,w0,#16
lsl w1,w1,#8
orr w3,w3,w0
orr w3,w3,w1
orr w3,w3,w2
    //Und lege sie in der Variablen "DrawColor" ab
ldr x0,=DrawColor
str w3,[x0]
 
ldp x29, x30, [sp], 16
ret
 
//void SetFrontColor(u8 r, u8 g, u8 b)
//definiere die Vordergrundfarbe, für zum Beispiel des Chars
.globl SetFrontColor
SetFrontColor:
stp x29, x30, [sp, -16]!
mov x29, sp
   
    //Berechne die Farbe mit: (0xFF << 24) | (r << 16) | (g << 8) | b;
mov w3,#0xFF
lsl w3,w3,#24
lsl w0,w0,#16
lsl w1,w1,#8
orr w3,w3,w0
orr w3,w3,w1
orr w3,w3,w2
    //Und lege sie in der Variablen "FrontColor" ab
ldr x0,=FrontColor
str w3,[x0]
 
ldp x29, x30, [sp], 16
ret
 
//void SetBackColor(u8 r, u8 g, u8 b)
//definiere die Hintergrundfarbe, für zum Beispiel des Chars
.globl SetBackColor
SetBackColor:
stp x29, x30, [sp, -16]!
mov x29, sp
   
    //Berechne die Farbe mit: (0xFF << 24) | (r << 16) | (g << 8) | b;
mov w3,#0xFF
lsl w3,w3,#24
lsl w0,w0,#16
lsl w1,w1,#8
orr w3,w3,w0
orr w3,w3,w1
orr w3,w3,w2
    //Und lege sie in der Variablen "BackColor" ab
ldr x0,=BackColor
str w3,[x0]
 
ldp x29, x30, [sp], 16
ret
 
//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
 
//**********************************************************
// Konsole
//**********************************************************
 
// Dimensionen für die Konsole
#define CHAR_WIDTH 8
#define CHAR_HEIGHT 10
#define NUM_COLS (SCREEN_X / CHAR_WIDTH)
#define NUM_ROWS (SCREEN_Y / CHAR_HEIGHT)
 
//void ClearScreen()
//Löscht den Inhalt des Screens in der Hintergrundfarbe
.globl ClearScreen
ClearScreen:
stp x29, x30, [sp, -16]!
stp x10, x11, [sp, -16]!
mov x29, sp
 
    //Setze die Zeichenfarbe auf die Hintergrundfarbe
    ldr x10,=BackColor
ldr w10,[x10]
ldr x11,=DrawColor
str w10,[x11]
 
    //for (w11 = 0; w11 < SCREEN_Y; w11++)
mov w11,#0
1:
cmp w11,#SCREEN_Y
bge 2f
 
    //for (w10 = 0; w10 < SCREEN_X; w10++)
mov w10,#0
3:
cmp w10,#SCREEN_X
bge 4f
 
    //DrawPixel(x, y);
mov w0,w10
mov w1,w11
bl DrawPixel
 
    //end_for w10
add w10,w10,#1
b 3b
4:
    //end_for w11
add w11,w11,#1
b 1b
2:
    //Setze cursor_x auf 0
    mov x10,#0
    ldr x11,=cursor_x
    str w10,[x11]
    //Setze cursor_y auf 0
    ldr x11,=cursor_y
    str w10,[x11]
 
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ret
 
//void MoveCursor(u32 x, u32 y)
//Setze den Cursor auf eine neue Position
.globl MoveCursor
MoveCursor:
stp x29, x30, [sp, -16]!
stp x10, x11, [sp, -16]!
mov x29, sp
 
    //Setze cursor_x auf x
ldr x10,=cursor_x
str w0,[x10]
    //Setze cursor_y auf y
ldr x10,=cursor_y
str w1,[x10]
 
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ret
 
//void DrawCharAtCursor(char c)
//void DrawCharAtCursor(char c)
//Zeichne den Char an die aktuelle Position in der Konsole
//Zeichne den Char an die aktuelle Position in der Konsole
Zeile 373: Zeile 32:
stp x29, x30, [sp, -16]!
stp x29, x30, [sp, -16]!
mov x29, sp
mov x29, sp
...
...
...
ldp x29, x30, [sp], 16
ret
</syntaxhighlight>


== Steuerzeichen beachten ==
Beim Zeichnen von Texten müssen wir verschiedene Steuerzeichen berücksichtigen, wie zum Beispiel "neue Zeile" (Enter), "Tab" und "Backspace". Diese Steuerzeichen fangen wir ab und reagieren entsprechend darauf:
1. Neue Zeile (`\n`):
Wenn das Zeichen ein Zeilenumbruch (`\n`) ist, setzen wir den Cursor an den Anfang der nächsten Zeile:
<syntaxhighlight lang="asm">
     //Vergleiche auf Zeilenumbruch
     //Vergleiche auf Zeilenumbruch
cmp w0,#'\n'
cmp w0,#'\n'
Zeile 391: Zeile 63:


b 7f
b 7f
</syntaxhighlight>
2. Zeilenanfang (`\r`):
Wenn das Zeichen ein Wagenrücklauf (`\r`) ist, setzen wir den Cursor an den Anfang der aktuellen Zeile:
<syntaxhighlight lang="asm">
1:
1:
     //Vergleiche auf Carriage Return
     //Vergleiche auf Carriage Return
Zeile 401: Zeile 78:


b 7f
b 7f
</syntaxhighlight>
3. Tabulator (`\t`):
Wenn das Zeichen ein Tabulator (`\t`) ist, bewegen wir den Cursor um vier Positionen nach rechts:
<syntaxhighlight lang="asm">
2:
2:
     //Vergleiche, ob TAB angezeigt werden soll
     //Vergleiche, ob TAB angezeigt werden soll
Zeile 414: Zeile 96:


b 7f
b 7f
</syntaxhighlight>
4. Rückschritt (`\b`):
Wenn das Zeichen ein Rückschritt (`\b`) ist, bewegen wir den Cursor um ein Zeichen nach links, es sei denn, der Cursor steht bereits am Anfang der Zeile:
<syntaxhighlight lang="asm">
3:
3:
     //Vergleich auf Backspace
     //Vergleich auf Backspace
Zeile 431: Zeile 118:
8:
8:
b 7f
b 7f
</syntaxhighlight>
5. Seitenvorschub (`\f`):
Wenn das Zeichen ein Seitenvorschub (`\f`) ist, löschen wir den Bildschirm. Früher wurde dieses Zeichen verwendet, um zum Beispiel auf Nadeldruckern eine neue Seite zu beginnen. Hier verwenden wir es, um den Bildschirm zu löschen:
<syntaxhighlight lang="asm">
4:
4:
     //Vergleich auf Form Feed)
     //Vergleich auf Form Feed)
Zeile 440: Zeile 132:


b 7f
b 7f
5:
</syntaxhighlight>
    //Vergleich auf ESC-Zeichen
6. Normales Zeichen:
cmp w0,#27
bne 6f
 
    // ESC-Zeichen erkannt, muss aber zuvor Abgefangen werden.


b 7f
Wenn das Zeichen kein Steuerzeichen ist, zeichnen wir es an der aktuellen Cursor-Position und bewegen den Cursor um eine Position nach rechts. Wenn das Ende der Zeile erreicht ist, setzen wir den Cursor an den Anfang der nächsten Zeile:
<syntaxhighlight lang="asm">
6:
6:
     //Wenn hier angelangt, müsste es sich um ein druckbares Zeichen handeln
     //Wenn hier angelangt, müsste es sich um ein druckbares Zeichen handeln
     // Char in w0
     // Char in w0
mov w0,w0
mov w0,w0
     // Berechnung der x-Pos (cursor_x * CHAR_WIDTH)
     // Berechnung der x-Pos (cursor_x * CHAR_WIDTH)
ldr x1,=cursor_x
ldr x1,=cursor_x
Zeile 458: Zeile 147:
mov w2,#CHAR_WIDTH
mov w2,#CHAR_WIDTH
mul w1,w1,w2
mul w1,w1,w2
     // Berechnung der y-Pos (cursor_y * CHAR_HEIGHT)
     // Berechnung der y-Pos (cursor_y * CHAR_HEIGHT)
ldr x2,=cursor_y
ldr x2,=cursor_y
Zeile 464: Zeile 154:
mul w2,w2,w3
mul w2,w2,w3
bl DrawChar
bl DrawChar
     //Addiere cursor_x um 1
     //Addiere cursor_x um 1
ldr x0,=cursor_x
ldr x0,=cursor_x
Zeile 469: Zeile 160:
add w1,w1,#1
add w1,w1,#1
str w1,[x0]
str w1,[x0]
     // Wenn cursor_x >= NUM_COLS ist
     // Wenn cursor_x >= NUM_COLS ist
cmp w1,NUM_COLS
cmp w1,NUM_COLS
blt 9f
blt 9f
     // dann cursor_x = 0
     // dann cursor_x = 0
ldr x0,=cursor_x
ldr x0,=cursor_x
Zeile 483: Zeile 176:
str w1,[x0]
str w1,[x0]
9:
9:
</syntaxhighlight>


== Bildschirmende erreichen ==
Wenn der Cursor das Ende des Bildschirms erreicht, müssen wir den Text nach oben scrollen, um Platz für neue Zeilen zu schaffen:
<syntaxhighlight lang="asm">
7:  //Für alle:
7:  //Für alle:
     //Wenn cursor_y >= max Anzahl Zeilen
     //Wenn cursor_y >= max Anzahl Zeilen
Zeile 500: Zeile 197:
str w0,[x1]
str w0,[x1]
10:
10:
ldp x29, x30, [sp], 16
</syntaxhighlight>
ret
Damit haben wir bereits unser Terminalprogramm beschrieben. In diesem SourceCode sind ein paar Funktionsaufrufe, die wir nun beschreiben:


// void ProcessEscSequence(const char* seq)
== ClearScreen-Funktion ==
// Bearbeite ESC-Funktionen
Die ClearScreen-Funktion löscht den gesamten Bildschirm und setzt den Cursor wieder auf die Anfangsposition. Zuerst setzen wir die Zeichenfarbe auf die Hintergrundfarbe. Dann durchlaufen wir den gesamten Bildschirm mit einer Schleife und zeichnen an jeder Position die Hintergrundfarbe. Schließlich setzen wir den Cursor auf die obere linke Ecke.
.globl ProcessEscSequence
<syntaxhighlight lang="asm">
ProcessEscSequence:
//void ClearScreen()
//Löscht den Inhalt des Screens in der Hintergrundfarbe
.globl ClearScreen
ClearScreen:
stp x29, x30, [sp, -16]!
stp x29, x30, [sp, -16]!
stp x10, x11, [sp, -16]!
mov x29, sp
mov x29, sp


// Beispiel: "\033[1m" -> Setzt die Vordergrundfarbe auf Rot
    //Setze die Zeichenfarbe auf die Hintergrundfarbe
    ldr x10,=BackColor
ldr w10,[x10]
ldr x11,=DrawColor
str w10,[x11]


//DOTO: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
    //for (w11 = 0; w11 < SCREEN_Y; w11++)
//Color Name Foreground Color Code Background Color Code
mov w11,#0
//Black 30 40
1:
//Red 31 41
cmp w11,#SCREEN_Y
//Green 32 42
bge 2f
//Yellow 33 43
//Blue 34 44
//Magenta 35 45
//Cyan 36 46
//White 37 47
//Default 39 49
//Reset 0 0
//
//# Set style to bold, red foreground.
//\033[1;31mHello
//# Set style to dimmed white foreground with red background.
//\033[2;37;41mWorld


// Unterstützung nur für Farben: ESC[xm -> x = Farbe
    //for (w10 = 0; w10 < SCREEN_X; w10++)
    // Nur wenn seq[0] = '[' und seq[2] = 'm' entspricht
mov w10,#0
ldrb w1,[x0]
3:
cmp w1,'['
cmp w10,#SCREEN_X
bne 1f
bge 4f
ldrb w1,[x0,2]
cmp w1,'m'
bne 1f


     //Werte seq[1] aus:
     //DrawPixel(x, y);
ldrb w1,[x0,1]
mov w0,w10
mov w1,w11
bl DrawPixel


     // Wenn 0
     //end_for w10
cmp w1,'0'
add w10,w10,#1
bne 2f
b 3b
     //dann FrontColor = Schwarz
4:
mov w0,#0
     //end_for w11
mov w1,#0
add w11,w11,#1
mov w2,#0
b 1b
bl SetFrontColor
b 1f
2:
2:
     // Wenn 1
     //Setze cursor_x auf 0
cmp w1,'1'
     mov x10,#0
bne 3f
     ldr x11,=cursor_x
    //dann FrontColor = Rot
     str w10,[x11]
mov w0,#0xFF
     //Setze cursor_y auf 0
mov w1,#0
     ldr x11,=cursor_y
mov w2,#0
     str w10,[x11]
bl SetFrontColor
b 1f
3:
     // Wenn 2
cmp w1,'2'
bne 4f
    //dann FrontColor = Grün
mov w0,#0
mov w1,#0xFF
mov w2,#0
bl SetFrontColor
b 1f
4:
     // Wenn 3
cmp w1,'3'
bne 5f
    //dann FrontColor = Gelb
mov w0,#0xFF
mov w1,#0xFF
mov w2,#0
bl SetFrontColor
b 1f
5:
     // Wenn 4
cmp w1,'4'
bne 6f
     //dann FrontColor = Blau
mov w0,#0
mov w1,#0
mov w2,#0xFF
bl SetFrontColor
b 1f
6:
    // Wenn 5
cmp w1,'5'
bne 7f
    //dann FrontColor = Magenta
mov w0,#0xFF
mov w1,#0
mov w2,#0xFF
bl SetFrontColor
b 1f
7:
     // Wenn 6
cmp w1,'6'
bne 8f
    //dann FrontColor = Cyan
mov w0,#0
mov w1,#0xFF
mov w2,#0xFF
bl SetFrontColor
b 1f
8:
    // Wenn 7
cmp w1,'7'
bne 1f
     //dann FrontColor = Weiß
mov w0,#0xFF
mov w1,#0xFF
mov w2,#0xFF
bl SetFrontColor
// b 1f


1:
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ldp x29, x30, [sp], 16
ret
ret
</syntaxhighlight>


//void ScrollScreen()
== ScrollScreen-Funktion ==
Die ScrollScreen-Funktion verschiebt den Text um eine Zeile nach oben und löscht die unterste Zeile. Zuerst durchlaufen wir alle Zeilen bis 10 Pixel vor dem Ende des Bildschirms und kopieren den Farbwert des Pixels, das 10 Pixel unter der aktuellen Zeile liegt, in die aktuelle Zeile. Danach löschen wir die letzte Zeile, indem wir diese Zeile mit "Leerzeichen" füllen.
<syntaxhighlight lang="asm">
// void ScrollScreen()
// Schiebe den Inhalt des Screens um eine Zeichenzeile nach oben
// Schiebe den Inhalt des Screens um eine Zeichenzeile nach oben
.globl ScrollScreen
.globl ScrollScreen
Zeile 633: Zeile 265:
stp x10, x11, [sp, -16]!
stp x10, x11, [sp, -16]!
mov x29, sp
mov x29, sp
//{
 
//   for (u32 y = 1; y < NUM_ROWS; y++)
// for (u32 y = 1; y < NUM_ROWS; y++)
mov x10,#1
mov x10,#1
1:
1:
cmp x10,#NUM_ROWS
cmp x10,#NUM_ROWS
bge 2f
bge 2f
//    {
 
//       for (u32 x = 0; x < NUM_COLS; x++)
// for (u32 x = 0; x < NUM_COLS; x++)
mov x11,#0
mov x11,#0
3:
3:
cmp x11,#NUM_COLS
cmp x11,#NUM_COLS
bge 4f
bge 4f
//        {
 
//           DrawColor = GetPixel(x * CHAR_WIDTH, y * CHAR_HEIGHT);
// DrawColor = GetPixel(x * CHAR_WIDTH, y * CHAR_HEIGHT);
mov w0,w11
mov w0,w11
mov w2,#CHAR_WIDTH
mov w2,#CHAR_WIDTH
Zeile 656: Zeile 288:
ldr x1,=DrawColor
ldr x1,=DrawColor
str w0,[x1]
str w0,[x1]
//           DrawPixel(x * CHAR_WIDTH, (y - 1) * CHAR_HEIGHT);
 
// DrawPixel(x * CHAR_WIDTH, (y - 1) * CHAR_HEIGHT);
mov w0,w11
mov w0,w11
mov w2,#CHAR_WIDTH
mov w2,#CHAR_WIDTH
Zeile 665: Zeile 298:
mul w1,w1,w2
mul w1,w1,w2
bl DrawPixel
bl DrawPixel
//       }
//
add x11,x11,#1
add x11,x11,#1
b 3b
b 3b
4:
4:
//   }
//  
add x10,x10,#1
add x10,x10,#1
b 1b
b 1b
2:
2:
//    // Clear the last line
 
//   for (u32 x = 0; x < NUM_COLS; x++)
// Clear the last line
// for (u32 x = 0; x < NUM_COLS; x++)
mov w10,#0
mov w10,#0
5:
5:
cmp w10,NUM_COLS
cmp w10,NUM_COLS
bge 6f  
bge 6f  
//    {
 
//       DrawChar(' ', x * CHAR_WIDTH, (NUM_ROWS - 1) * CHAR_HEIGHT);
// DrawChar(' ', x * CHAR_WIDTH, (NUM_ROWS - 1) * CHAR_HEIGHT);
mov w0,#' '
mov w0,#' '
mov w1,w10
mov w1,w10
Zeile 690: Zeile 324:
mul w2,w2,w3
mul w2,w2,w3
bl DrawChar
bl DrawChar
//   }
//  
add w10,w10,#1
add w10,w10,#1
b 5b
b 5b
6:
6:
//}
//  
ldp x10, x11, [sp], 16
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ldp x29, x30, [sp], 16
ret
ret
</syntaxhighlight>
Bemerkung: Diese Routine ist extremst Zeitaufwendig. Wir werden hier später eine neue Methode (DMA) verwenden, die dies viel schneller durchführen kann.


//void DrawString(const char* str) {
== GetPixel-Funktion ==
Die GetPixel-Funktion liest den Farbwert eines Pixels an der angegebenen Position. Zuerst prüfen wir, ob der Pixel, der abgefragt wird, auch tatsächlich auf dem Bildschirm ist. Falls nicht, geben wir 0 zurück. Ansonsten berechnen wir die Position des Pixels im Speicher und lesen den Wert aus.
<syntaxhighlight lang="asm">
//u32 GetPixel(u32 x, u32 y)
//Abfrage, der Farbe des Pixels im Screen
.globl GetPixel
GetPixel:
stp x29, x30, [sp, -16]!
stp x10, x11, [sp, -16]!
mov x29, sp
   
    //Überprüfe, ob der Pixel im Screen ist
cmp w0,#SCREEN_X
bge 1f
cmp w1,#SCREEN_Y
bge 1f
 
    //Berechne Position des Pixels: graphicsAddress+(((SCREEN_X*y)+x)*4)
mov w10,SCREEN_X
mul w10,w10,w1
add w10,w10,w0
lsl w10,w10,#2
ldr x11,=graphicsAddress
ldr w11,[x11]
add w10,w10,w11
 
    //Lade die Farbe des Pixels nach w0
ldr w0,[x10]
b 2f
1:
    //Wenn Position des Pixel nicht im Screen ist, dann NULL zurück
mov w0,#0
2:
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ret
</syntaxhighlight>
 
== DrawString-Funktion ==
Um ganze Texte (Strings) im Terminal anzuzeigen, verwenden wir die `DrawString`-Funktion. Diese Funktion durchläuft den String und zeichnet jedes Zeichen:
<syntaxhighlight lang="asm">
//void DrawString(const char* str)
.globl DrawString
.globl DrawString
DrawString:
DrawString:
Zeile 712: Zeile 389:
cmp w0,#0
cmp w0,#0
beq 1f
beq 1f
//    {
//        if (*str == '\033') ToDo: Komplett nach DrawCharAtCursor verlegen (Flexibler)
cmp w0,#27
bne 3f
//        { // ESC-Zeichen erkannt
//            const char* esc_seq = ++str;
add x10,x10,#1
mov x0,x10
//            ProcessEscSequence(esc_seq);
bl ProcessEscSequence
//            str += 3; // Überspringen der ESC-Sequenz
add x10,x10,#3
b 4f
//        }
3:
//        else
//        {
//            DrawCharAtCursor(*str++);
//ldr w0,[x10]
bl DrawCharAtCursor
bl DrawCharAtCursor
add x10,x10,#1
add x10,x10,#1
//        }
//    }
4:
b 2b
b 2b
//}
1:
1:
ldp x10, x11, [sp], 16
ldp x10, x11, [sp], 16
ldp x29, x30, [sp], 16
ldp x29, x30, [sp], 16
ret
ret
</syntaxhighlight>


//**********************************************************
== Testen ==
// Data
Um nun das alles zu testen, schreiben wir unseren kernel.S Code um:
//**********************************************************
<syntaxhighlight lang="asm">
.section .data
//
.align 16
// kernel.S
pScreen:
//
.int pScreen_end - pScreen
 
.int CODE_REQUEST
.section .text
.globl main
main:


.int PROPTAG_ALLOCATE_BUFFER
    bl LED_off
.int 8
bl Init_Screen
.int 4
m_nBufferPtr:
.int 0
m_nBufferSize:
.int 0
.int PROPTAG_END
pScreen_end:


.align 2
ldr x0,=message
graphicsAddress:
bl DrawString
.int 0
DrawColor:
.int 0xFFFFFFFF
FrontColor:
.int 0xFFFFFFFF
BackColor:
.int 0


// Terminal
mov w0,#2
cursor_x:
bl LED_Error
.int 0
cursor_y:
999:
.int 0
  b 999b


.ltorg
.section .data
font:
message:
.include "ms8x8font.fon"
.asciz "Hallo, Welt!\nDies ist eine neue Zeile.\tMit Tab.\nUnd noch mehr Text."
</syntaxhighlight>

Aktuelle Version vom 29. November 2024, 13:09 Uhr

Das Terminal

Nachdem wir nun Zeichen auf den Bildschirm anzeigen können, werden wir ein Terminal bauen. Ein Terminal ist ein Bereich auf dem Bildschirm, in dem Texte angezeigt werden können und später auch Eingaben erfolgen können. Ein Beispiel für ein Terminal ist die Eingabeaufforderung, die beim Starten von Linux erscheint.

Wie sieht ein Terminal aus?

Zuerst müssen wir uns überlegen, wie das Terminal aussehen soll. Da wir die Bildschirmauflösung kennen, verwenden wir diese. Unsere Schriftart (Font) hat eine Größe von 8x8 Pixeln pro Zeichen. Damit die Zeichen nicht zu eng beieinander stehen, nutzen wir im Terminal eine Auflösung von 8x10 Pixeln pro Zeichen.

#define CHAR_WIDTH 8
#define CHAR_HEIGHT 10

Damit können wir festlegen, wie viele Zeichen in einer Zeile und wie viele Zeilen auf dem Bildschirm dargestellt werden können:

#define NUM_COLS (SCREEN_X / CHAR_WIDTH)
#define NUM_ROWS (SCREEN_Y / CHAR_HEIGHT)

Um die aktuelle Position des Cursors (die Stelle, an der das nächste Zeichen erscheinen wird) zu speichern, definieren wir unter .section .data zwei Variablen. Diese setzen wir anfangs auf 0,0 (oben links):

// Terminal
cursor_x:
	.int 0
cursor_y:
	.int 0

Zeichen im Terminal zeichnen

Als erstes schreiben wir eine Funktion, die ein Zeichen im Terminal an der aktuellen Cursor-Position zeichnet. Die Funktion bekommt einfach das Zeichen übergeben, das gezeichnet werden soll:

//void DrawCharAtCursor(char c)
//Zeichne den Char an die aktuelle Position in der Konsole
//Berücksichtige auch Sonderzeichen
.globl DrawCharAtCursor
DrawCharAtCursor:
	stp x29, x30, [sp, -16]!
	mov x29, sp
...
...
...
	ldp x29, x30, [sp], 16
	ret

Steuerzeichen beachten

Beim Zeichnen von Texten müssen wir verschiedene Steuerzeichen berücksichtigen, wie zum Beispiel "neue Zeile" (Enter), "Tab" und "Backspace". Diese Steuerzeichen fangen wir ab und reagieren entsprechend darauf:

1. Neue Zeile (`\n`):

Wenn das Zeichen ein Zeilenumbruch (`\n`) ist, setzen wir den Cursor an den Anfang der nächsten Zeile:

    //Vergleiche auf Zeilenumbruch
	cmp w0,#'\n'
    //Wenn nicht, weitere Abfrage
	bne 1f

    //Setze den cursor_x auf 0
	ldr x1,=cursor_x
	mov w2,#0
	str w2,[x1]

    //Und den cursor_y um eins höher, für die Zeile
	ldr x1,=cursor_y
	ldr w2,[x1]
	add w2,w2,#1
	str w2,[x1]

	b 7f

2. Zeilenanfang (`\r`):

Wenn das Zeichen ein Wagenrücklauf (`\r`) ist, setzen wir den Cursor an den Anfang der aktuellen Zeile:

1:
    //Vergleiche auf Carriage Return
	cmp w0,#'\r'
	bne 2f
    //Setzt den Cursor an den Anfang der aktuellen Zeile.
	ldr x1,=cursor_x
	mov w2,#0
	str w2,[x1]

	b 7f

3. Tabulator (`\t`):

Wenn das Zeichen ein Tabulator (`\t`) ist, bewegen wir den Cursor um vier Positionen nach rechts:

2:
    //Vergleiche, ob TAB angezeigt werden soll
	cmp w0,#'\t'
	bne 3f

    //Erhöht den cursor_x um 4
    //DOTO: Fester Wert in allen Zeilen
	ldr x1,=cursor_x
	ldr w2,[x1]
	add w2,w2,#4
	str w2,[x1]

	b 7f

4. Rückschritt (`\b`):

Wenn das Zeichen ein Rückschritt (`\b`) ist, bewegen wir den Cursor um ein Zeichen nach links, es sei denn, der Cursor steht bereits am Anfang der Zeile:

3:
    //Vergleich auf Backspace
	cmp w0,#'\b'
	bne 4f

    //Bewegt den Cursor um ein Zeichen nach links
    //nur wenn cursor_x > 0 ist
	ldr x1,=cursor_x
	ldr w2,[x1]
	cmp w2,#0
	ble 8f

    // cursor_x -1
	sub w2,w2,#1
	str w2,[x1]
8:
	b 7f

5. Seitenvorschub (`\f`):

Wenn das Zeichen ein Seitenvorschub (`\f`) ist, löschen wir den Bildschirm. Früher wurde dieses Zeichen verwendet, um zum Beispiel auf Nadeldruckern eine neue Seite zu beginnen. Hier verwenden wir es, um den Bildschirm zu löschen:

4:
    //Vergleich auf Form Feed)
	cmp w0,#'\f'
	bne 5f

    // Neue Seite
	bl ClearScreen

	b 7f

6. Normales Zeichen:

Wenn das Zeichen kein Steuerzeichen ist, zeichnen wir es an der aktuellen Cursor-Position und bewegen den Cursor um eine Position nach rechts. Wenn das Ende der Zeile erreicht ist, setzen wir den Cursor an den Anfang der nächsten Zeile:

6:
    //Wenn hier angelangt, müsste es sich um ein druckbares Zeichen handeln
    // Char in w0
	mov w0,w0

    // Berechnung der x-Pos (cursor_x * CHAR_WIDTH)
	ldr x1,=cursor_x
	ldr w1,[x1]
	mov w2,#CHAR_WIDTH
	mul w1,w1,w2

    // Berechnung der y-Pos (cursor_y * CHAR_HEIGHT)
	ldr x2,=cursor_y
	ldr w2,[x2]
	mov w3,#CHAR_HEIGHT
	mul w2,w2,w3
	bl DrawChar

    //Addiere cursor_x um 1
	ldr x0,=cursor_x
	ldr w1,[x0]
	add w1,w1,#1
	str w1,[x0]

    // Wenn cursor_x >= NUM_COLS ist
	cmp w1,NUM_COLS
	blt 9f

    // dann cursor_x = 0
	ldr x0,=cursor_x
	mov w1,#0
	str w1,[x0]
    
    // und neue Zeile (cursor_y + 1)
	ldr x0,=cursor_y
	ldr w1,[x0]
	add w1,w1,#1
	str w1,[x0]
9:

Bildschirmende erreichen

Wenn der Cursor das Ende des Bildschirms erreicht, müssen wir den Text nach oben scrollen, um Platz für neue Zeilen zu schaffen:

7:  //Für alle:
    //Wenn cursor_y >= max Anzahl Zeilen
	ldr x0,=cursor_y
	ldr w1,[x0]
	cmp w1,NUM_ROWS
	blt 10f

    //Dann Scrolle den Bildscirm
	bl ScrollScreen

    //Setze die Cursorposition auf Vorletzte Zeile (cursor_y = NUM_ROWS - 1)
	mov w0,NUM_ROWS
	sub w0,w0,#1
	ldr x1,=cursor_y
	str w0,[x1]
10:

Damit haben wir bereits unser Terminalprogramm beschrieben. In diesem SourceCode sind ein paar Funktionsaufrufe, die wir nun beschreiben:

ClearScreen-Funktion

Die ClearScreen-Funktion löscht den gesamten Bildschirm und setzt den Cursor wieder auf die Anfangsposition. Zuerst setzen wir die Zeichenfarbe auf die Hintergrundfarbe. Dann durchlaufen wir den gesamten Bildschirm mit einer Schleife und zeichnen an jeder Position die Hintergrundfarbe. Schließlich setzen wir den Cursor auf die obere linke Ecke.

//void ClearScreen()
//Löscht den Inhalt des Screens in der Hintergrundfarbe
.globl ClearScreen
ClearScreen:
	stp x29, x30, [sp, -16]!
	stp x10, x11, [sp, -16]!
	mov x29, sp

    //Setze die Zeichenfarbe auf die Hintergrundfarbe
    ldr x10,=BackColor
	ldr w10,[x10]
	ldr x11,=DrawColor
	str w10,[x11]

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

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

    //DrawPixel(x, y);
	mov w0,w10
	mov w1,w11
	bl DrawPixel

    //end_for w10
	add w10,w10,#1
	b 3b
4:
    //end_for w11
	add w11,w11,#1
	b 1b
2:
    //Setze cursor_x auf 0
    mov x10,#0
    ldr x11,=cursor_x
    str w10,[x11]
    //Setze cursor_y auf 0
    ldr x11,=cursor_y
    str w10,[x11]

	ldp x10, x11, [sp], 16
	ldp x29, x30, [sp], 16
	ret

ScrollScreen-Funktion

Die ScrollScreen-Funktion verschiebt den Text um eine Zeile nach oben und löscht die unterste Zeile. Zuerst durchlaufen wir alle Zeilen bis 10 Pixel vor dem Ende des Bildschirms und kopieren den Farbwert des Pixels, das 10 Pixel unter der aktuellen Zeile liegt, in die aktuelle Zeile. Danach löschen wir die letzte Zeile, indem wir diese Zeile mit "Leerzeichen" füllen.

// void ScrollScreen()
// Schiebe den Inhalt des Screens um eine Zeichenzeile nach oben
.globl ScrollScreen
ScrollScreen:
	stp x29, x30, [sp, -16]!
	stp x10, x11, [sp, -16]!
	mov x29, sp

// for (u32 y = 1; y < NUM_ROWS; y++)
	mov x10,#1
1:
	cmp x10,#NUM_ROWS
	bge 2f

// for (u32 x = 0; x < NUM_COLS; x++)
	mov x11,#0
3:
	cmp x11,#NUM_COLS
	bge 4f

// DrawColor = GetPixel(x * CHAR_WIDTH, y * CHAR_HEIGHT);
	mov w0,w11
	mov w2,#CHAR_WIDTH
	mul w0,w0,w2
	mov w1,w10
	mov w2,#CHAR_HEIGHT
	mul w1,w1,w2
	bl GetPixel
	ldr x1,=DrawColor
	str w0,[x1]

// DrawPixel(x * CHAR_WIDTH, (y - 1) * CHAR_HEIGHT);
	mov w0,w11
	mov w2,#CHAR_WIDTH
	mul w0,w0,w2
	mov w1,w10
	sub w1,w1,#1
	mov w2,#CHAR_HEIGHT
	mul w1,w1,w2
	bl DrawPixel
//
	add x11,x11,#1
	b 3b
4:
// 
	add x10,x10,#1
	b 1b
2:

// Clear the last line
// for (u32 x = 0; x < NUM_COLS; x++)
	mov w10,#0
5:
	cmp w10,NUM_COLS
	bge 6f 

// DrawChar(' ', x * CHAR_WIDTH, (NUM_ROWS - 1) * CHAR_HEIGHT);
	mov w0,#' '
	mov w1,w10
	mov w3,#CHAR_WIDTH
	mul w1,w1,w3
	mov w2,#NUM_ROWS
	sub w2,w2,#1
	mov w3,#CHAR_HEIGHT
	mul w2,w2,w3
	bl DrawChar
// 
	add w10,w10,#1
	b 5b
6:
// 
	ldp x10, x11, [sp], 16
	ldp x29, x30, [sp], 16
	ret

Bemerkung: Diese Routine ist extremst Zeitaufwendig. Wir werden hier später eine neue Methode (DMA) verwenden, die dies viel schneller durchführen kann.

GetPixel-Funktion

Die GetPixel-Funktion liest den Farbwert eines Pixels an der angegebenen Position. Zuerst prüfen wir, ob der Pixel, der abgefragt wird, auch tatsächlich auf dem Bildschirm ist. Falls nicht, geben wir 0 zurück. Ansonsten berechnen wir die Position des Pixels im Speicher und lesen den Wert aus.

//u32 GetPixel(u32 x, u32 y)
//Abfrage, der Farbe des Pixels im Screen
.globl GetPixel
GetPixel:
	stp x29, x30, [sp, -16]!
	stp x10, x11, [sp, -16]!
	mov x29, sp
    
    //Überprüfe, ob der Pixel im Screen ist
	cmp w0,#SCREEN_X
	bge 1f
	cmp w1,#SCREEN_Y
	bge 1f

    //Berechne Position des Pixels: graphicsAddress+(((SCREEN_X*y)+x)*4)
	mov w10,SCREEN_X
	mul w10,w10,w1
	add w10,w10,w0
	lsl w10,w10,#2
	ldr x11,=graphicsAddress
	ldr w11,[x11]
	add w10,w10,w11

    //Lade die Farbe des Pixels nach w0
	ldr w0,[x10]
	b 2f
1:
    //Wenn Position des Pixel nicht im Screen ist, dann NULL zurück 
	mov w0,#0
2:
	ldp x10, x11, [sp], 16
	ldp x29, x30, [sp], 16
	ret

DrawString-Funktion

Um ganze Texte (Strings) im Terminal anzuzeigen, verwenden wir die `DrawString`-Funktion. Diese Funktion durchläuft den String und zeichnet jedes Zeichen:

//void DrawString(const char* str)
.globl DrawString
DrawString:
	stp x29, x30, [sp, -16]!
	stp x10, x11, [sp, -16]!
	mov x29, sp

	mov x10,x0
//    while (*str)
2:
	ldrb w0,[x10]
	cmp w0,#0
	beq 1f
	bl DrawCharAtCursor
	add x10,x10,#1
	b 2b
1:
	ldp x10, x11, [sp], 16
	ldp x29, x30, [sp], 16
	ret

Testen

Um nun das alles zu testen, schreiben wir unseren kernel.S Code um:

//
// kernel.S
//

.section .text
.globl main
main:

    bl LED_off
	bl Init_Screen

	ldr x0,=message
	bl DrawString

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

.section .data
message:
.asciz "Hallo, Welt!\nDies ist eine neue Zeile.\tMit Tab.\nUnd noch mehr Text."