Das Terminal in C (PI5): Unterschied zwischen den Versionen
Die Seite wurde neu angelegt: „### 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 Bildsch…“ |
|||
| (4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
== 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. | 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. | 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. | ||
<syntaxhighlight lang="C"> | |||
#define CHAR_WIDTH 8 | #define CHAR_WIDTH 8 | ||
#define CHAR_HEIGHT 10 | #define CHAR_HEIGHT 10 | ||
</syntaxhighlight> | |||
Damit können wir festlegen, wie viele Zeichen in einer Zeile und wie viele Zeilen auf dem Bildschirm dargestellt werden können: | Damit können wir festlegen, wie viele Zeichen in einer Zeile und wie viele Zeilen auf dem Bildschirm dargestellt werden können: | ||
<syntaxhighlight lang="C"> | |||
#define NUM_COLS (SCREEN_X / CHAR_WIDTH) | #define NUM_COLS (SCREEN_X / CHAR_WIDTH) | ||
#define NUM_ROWS (SCREEN_Y / CHAR_HEIGHT) | #define NUM_ROWS (SCREEN_Y / CHAR_HEIGHT) | ||
</syntaxhighlight> | |||
Um die aktuelle Position des Cursors (die Stelle, an der das nächste Zeichen erscheinen wird) zu speichern, verwenden wir 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, verwenden wir zwei Variablen. Diese setzen wir anfangs auf 0,0 (oben links): | ||
<syntaxhighlight lang="C"> | |||
u32 cursor_x = 0; | u32 cursor_x = 0; | ||
u32 cursor_y = 0; | u32 cursor_y = 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: | 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="C"> | |||
void DrawCharAtCursor(char c) | void DrawCharAtCursor(char c) | ||
{ | { | ||
// Funktion zum Zeichnen eines Zeichens | // Funktion zum Zeichnen eines Zeichens | ||
} | } | ||
</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: | 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. | 1. '''Neue Zeile (`\n`)''': | ||
Wenn das Zeichen ein Zeilenumbruch (`\n`) ist, setzen wir den Cursor an den Anfang der nächsten Zeile: | |||
<syntaxhighlight lang="C"> | |||
if (c == '\n') // Neue Zeile | if (c == '\n') // Neue Zeile | ||
{ | { | ||
| Zeile 49: | Zeile 49: | ||
cursor_y++; | cursor_y++; | ||
} | } | ||
</syntaxhighlight> | |||
2. '''Zeilenanfang (`\r`)''': | |||
2. | Wenn das Zeichen ein Wagenrücklauf (`\r`) ist, setzen wir den Cursor an den Anfang der aktuellen Zeile: | ||
<syntaxhighlight lang="C"> | |||
else if (c == '\r') // Zeilenanfang | else if (c == '\r') // Zeilenanfang | ||
{ | { | ||
cursor_x = 0; | cursor_x = 0; | ||
} | } | ||
</syntaxhighlight> | |||
3. '''Tabulator (`\t`)''': | |||
3. | Wenn das Zeichen ein Tabulator (`\t`) ist, bewegen wir den Cursor um vier Positionen nach rechts: | ||
<syntaxhighlight lang="C"> | |||
else if (c == '\t') // Tabulator | else if (c == '\t') // Tabulator | ||
{ | { | ||
cursor_x += 4; | cursor_x += 4; | ||
} | } | ||
</syntaxhighlight> | |||
4. '''Rückschritt (`\b`)''': | |||
4. | 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="C"> | |||
else if (c == '\b') // Rückschritt | else if (c == '\b') // Rückschritt | ||
{ | { | ||
| Zeile 78: | Zeile 75: | ||
} | } | ||
} | } | ||
</syntaxhighlight> | |||
5. '''Seitenvorschub (`\f`)''': | |||
5. | 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="C"> | |||
else if (c == '\f') // Seitenvorschub (Bildschirm löschen) | else if (c == '\f') // Seitenvorschub (Bildschirm löschen) | ||
{ | { | ||
ClearScreen(); | ClearScreen(); | ||
} | } | ||
</syntaxhighlight> | |||
6. '''Normales Zeichen''': | |||
6. | 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="C"> | |||
else // Normales Zeichen | else // Normales Zeichen | ||
{ | { | ||
| Zeile 101: | Zeile 96: | ||
} | } | ||
} | } | ||
</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: | 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="C"> | |||
if (cursor_y >= NUM_ROWS) | if (cursor_y >= NUM_ROWS) | ||
{ | { | ||
| Zeile 113: | Zeile 105: | ||
cursor_y = NUM_ROWS - 1; | cursor_y = NUM_ROWS - 1; | ||
} | } | ||
</syntaxhighlight> | |||
== 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. | |||
<syntaxhighlight lang="C"> | |||
void ClearScreen() | void ClearScreen() | ||
{ | { | ||
// Setze die Zeichenfarbe auf die Hintergrundfarbe | |||
DrawColor = BackColor; | DrawColor = BackColor; | ||
// Lösche den gesamten Bildschirm | |||
for (u32 y = 0; y < SCREEN_Y; y++) { | for (u32 y = 0; y < SCREEN_Y; y++) { | ||
for (u32 x = 0; x < SCREEN_X; x++) { | for (u32 x = 0; x < SCREEN_X; x++) { | ||
| Zeile 128: | Zeile 122: | ||
} | } | ||
} | } | ||
// Setze den Cursor auf die obere linke Ecke | |||
cursor_x = 0; | cursor_x = 0; | ||
cursor_y = 0; | cursor_y = 0; | ||
} | } | ||
</syntaxhighlight> | |||
== 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 sie mit der Hintergrundfarbe füllen. | |||
<syntaxhighlight lang="C"> | |||
void ScrollScreen() | void ScrollScreen() | ||
{ | { | ||
// Verschiebe den Bildschirminhalt um eine Zeile nach oben | |||
for (u32 y = 0; y < SCREEN_Y - CHAR_HEIGHT; y++) { | for (u32 y = 0; y < SCREEN_Y - CHAR_HEIGHT; y++) { | ||
for (u32 x = 0; x < SCREEN_X; x++) { | for (u32 x = 0; x < SCREEN_X; x++) { | ||
| Zeile 146: | Zeile 141: | ||
} | } | ||
} | } | ||
// Lösche die letzte Zeile | |||
DrawColor = BackColor; | DrawColor = BackColor; | ||
for (u32 y = SCREEN_Y - CHAR_HEIGHT; y < SCREEN_Y; y++) { | for (u32 y = SCREEN_Y - CHAR_HEIGHT; y < SCREEN_Y; y++) { | ||
| Zeile 153: | Zeile 150: | ||
} | } | ||
} | } | ||
</syntaxhighlight> | |||
== 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="C"> | |||
Die | |||
u32 GetPixel(u32 x, u32 y) | u32 GetPixel(u32 x, u32 y) | ||
{ | { | ||
// Prüfe, ob die Pixelkoordinaten innerhalb des Bildschirms liegen | |||
if ((x < SCREEN_X) && (y < SCREEN_Y)) { | if ((x < SCREEN_X) && (y < SCREEN_Y)) { | ||
// Berechne die Speicheradresse des Pixels und lese den Farbwert | |||
return read32(graphicsAddress + (((SCREEN_X * y) + x) * 4)); | return read32(graphicsAddress + (((SCREEN_X * y) + x) * 4)); | ||
} else { | } else { | ||
// Pixel liegt außerhalb des Bildschirms, gib 0 zurück | |||
return 0; | return 0; | ||
} | } | ||
} | } | ||
</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: | Um ganze Texte (Strings) im Terminal anzuzeigen, verwenden wir die `DrawString`-Funktion. Diese Funktion durchläuft den String und zeichnet jedes Zeichen: | ||
<syntaxhighlight lang="C"> | |||
void DrawString(const char* str) { | void DrawString(const char* str) { | ||
while (*str) { | while (*str) { | ||
| Zeile 180: | Zeile 176: | ||
} | } | ||
} | } | ||
</syntaxhighlight> | |||
== Testen der Funktion == | |||
Zum Testen dieser Funktionen schreiben wir eine `main`-Funktion: | Zum Testen dieser Funktionen schreiben wir eine `main`-Funktion: | ||
<syntaxhighlight lang="C"> | |||
// main.c | // main.c | ||
| Zeile 202: | Zeile 195: | ||
LED_Error(2); | LED_Error(2); | ||
} | } | ||
</syntaxhighlight> | |||
== Zusammenfassung == | |||
Mit diesem Code haben wir ein einfaches Terminal programmiert. Der Code verwaltet die Anzeige der Zeichen und ermöglicht es uns, Meldungen wie Fehler besser und lesbar darzustellen. Im nächsten Kapitel werden wir die Funktion `printf` einbinden, um noch bessere Auswertungen der Daten zu bekommen und auch Variablen anzeigen zu lassen. | Mit diesem Code haben wir ein einfaches Terminal programmiert. Der Code verwaltet die Anzeige der Zeichen und ermöglicht es uns, Meldungen wie Fehler besser und lesbar darzustellen. Im nächsten Kapitel werden wir die Funktion `printf` einbinden, um noch bessere Auswertungen der Daten zu bekommen und auch Variablen anzeigen zu lassen. | ||
Du kannst den Source-Code als ZIP-Datei mit folgenden Link downloaden: https://www.satyria.de/arm/sources/C/terminal.zip | |||
----- | |||
{| style="width: 100%; | |||
| style="width: 33%;" | [[Chars in C (PI5)|< Zurück (Chars in C (PI5))]] | |||
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] | |||
| style="width: 33%; text-align:right;" | [[printf in BareMetal (PI5)|Weiter (printf in BareMetal (PI5)) >]] | |||
|} | |||
Aktuelle Version vom 23. August 2024, 10:56 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, verwenden wir zwei Variablen. Diese setzen wir anfangs auf 0,0 (oben links):
u32 cursor_x = 0;
u32 cursor_y = 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)
{
// Funktion zum Zeichnen eines Zeichens
}
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:
if (c == '\n') // Neue Zeile
{
cursor_x = 0;
cursor_y++;
}
2. Zeilenanfang (`\r`): Wenn das Zeichen ein Wagenrücklauf (`\r`) ist, setzen wir den Cursor an den Anfang der aktuellen Zeile:
else if (c == '\r') // Zeilenanfang
{
cursor_x = 0;
}
3. Tabulator (`\t`): Wenn das Zeichen ein Tabulator (`\t`) ist, bewegen wir den Cursor um vier Positionen nach rechts:
else if (c == '\t') // Tabulator
{
cursor_x += 4;
}
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:
else if (c == '\b') // Rückschritt
{
if (cursor_x > 0) {
cursor_x--;
}
}
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:
else if (c == '\f') // Seitenvorschub (Bildschirm löschen)
{
ClearScreen();
}
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:
else // Normales Zeichen
{
DrawChar(c, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT);
cursor_x++;
if (cursor_x >= NUM_COLS) {
cursor_x = 0;
cursor_y++;
}
}
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:
if (cursor_y >= NUM_ROWS)
{
ScrollScreen();
cursor_y = NUM_ROWS - 1;
}
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()
{
// Setze die Zeichenfarbe auf die Hintergrundfarbe
DrawColor = BackColor;
// Lösche den gesamten Bildschirm
for (u32 y = 0; y < SCREEN_Y; y++) {
for (u32 x = 0; x < SCREEN_X; x++) {
DrawPixel(x, y);
}
}
// Setze den Cursor auf die obere linke Ecke
cursor_x = 0;
cursor_y = 0;
}
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 sie mit der Hintergrundfarbe füllen.
void ScrollScreen()
{
// Verschiebe den Bildschirminhalt um eine Zeile nach oben
for (u32 y = 0; y < SCREEN_Y - CHAR_HEIGHT; y++) {
for (u32 x = 0; x < SCREEN_X; x++) {
DrawColor = GetPixel(x, y + CHAR_HEIGHT);
DrawPixel(x, y);
}
}
// Lösche die letzte Zeile
DrawColor = BackColor;
for (u32 y = SCREEN_Y - CHAR_HEIGHT; y < SCREEN_Y; y++) {
for (u32 x = 0; x < SCREEN_X; x++) {
DrawPixel(x, y);
}
}
}
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)
{
// Prüfe, ob die Pixelkoordinaten innerhalb des Bildschirms liegen
if ((x < SCREEN_X) && (y < SCREEN_Y)) {
// Berechne die Speicheradresse des Pixels und lese den Farbwert
return read32(graphicsAddress + (((SCREEN_X * y) + x) * 4));
} else {
// Pixel liegt außerhalb des Bildschirms, gib 0 zurück
return 0;
}
}
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) {
while (*str) {
DrawCharAtCursor(*str++);
}
}
Testen der Funktion
Zum Testen dieser Funktionen schreiben wir eine `main`-Funktion:
// main.c
#include "led.h"
#include "screen.h"
#include "types.h"
int main(void)
{
LED_off();
Init_Screen();
DrawString("Hallo Welt!");
LED_Error(2);
}
Zusammenfassung
Mit diesem Code haben wir ein einfaches Terminal programmiert. Der Code verwaltet die Anzeige der Zeichen und ermöglicht es uns, Meldungen wie Fehler besser und lesbar darzustellen. Im nächsten Kapitel werden wir die Funktion `printf` einbinden, um noch bessere Auswertungen der Daten zu bekommen und auch Variablen anzeigen zu lassen.
Du kannst den Source-Code als ZIP-Datei mit folgenden Link downloaden: https://www.satyria.de/arm/sources/C/terminal.zip
| < Zurück (Chars in C (PI5)) | < Hauptseite > | Weiter (printf in BareMetal (PI5)) > |