The Terminal in C (PI5): Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
KKeine Bearbeitungszusammenfassung
KKeine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
The Terminal
== The Terminal ==
After we have displayed characters on the screen, we will build a terminal. A terminal is an area on the screen where texts can be displayed and later also inputs can be made. An example of a terminal is the command prompt that appears when Linux starts.
After we have displayed characters on the screen, we will build a terminal. A terminal is an area on the screen where texts can be displayed and later also inputs can be made. An example of a terminal is the command prompt that appears when Linux starts.


What does a terminal look like?
== What does a terminal look like? ==
First, we need to consider what the terminal should look like. Since we know the screen resolution, we use it. Our font has a size of 8x8 pixels per character. To prevent the characters from being too close together, we use a resolution of 8x10 pixels per character in the terminal.
First, we need to consider what the terminal should look like. Since we know the screen resolution, we use it. Our font has a size of 8x8 pixels per character. To prevent the characters from being too close together, we use a resolution of 8x10 pixels per character in the terminal.


Copy
<syntaxhighlight lang="C">
#define CHAR_WIDTH 8
#define CHAR_WIDTH 8
#define CHAR_HEIGHT 10
#define CHAR_HEIGHT 10
</syntaxhighlight>
With this, we can determine how many characters can be displayed in a row and how many rows can be displayed on the screen:
With this, we can determine how many characters can be displayed in a row and how many rows can be displayed on the screen:


Copy
<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>
To store the current position of the cursor (the place where the next character will appear), we use two variables. We initially set these to 0,0 (top left):
To store the current position of the cursor (the place where the next character will appear), we use two variables. We initially set these to 0,0 (top left):


Copy
<syntaxhighlight lang="C">
u32 cursor_x = 0;
u32 cursor_x = 0;
u32 cursor_y = 0;
u32 cursor_y = 0;
Drawing characters in the terminal
</syntaxhighlight>
== Drawing characters in the terminal ==
First, we write a function that draws a character in the terminal at the current cursor position. The function simply receives the character to be drawn:
First, we write a function that draws a character in the terminal at the current cursor position. The function simply receives the character to be drawn:


Copy
<syntaxhighlight lang="C">
void DrawCharAtCursor(char c)
void DrawCharAtCursor(char c)
{
{
     // Function to draw a character
     // Function to draw a character
}
}
Considering control characters
</syntaxhighlight>
=== Considering control characters ===
When drawing texts, we need to consider various control characters, such as "new line" (Enter), "Tab", and "Backspace". We capture these control characters and react accordingly:
When drawing texts, we need to consider various control characters, such as "new line" (Enter), "Tab", and "Backspace". We capture these control characters and react accordingly:


New Line (\n): If the character is a newline (\n), we set the cursor to the beginning of the next line:
'''New Line (\n)''': If the character is a newline (\n), we set the cursor to the beginning of the next line:


Copy
<syntaxhighlight lang="C">
if (c == '\n') // New Line
if (c == '\n') // New Line
{
{
Zeile 37: Zeile 41:
     cursor_y++;
     cursor_y++;
}
}
Carriage Return (\r): If the character is a carriage return (\r), we set the cursor to the beginning of the current line:
</syntaxhighlight>
'''Carriage Return (\r)''': If the character is a carriage return (\r), we set the cursor to the beginning of the current line:


Copy
<syntaxhighlight lang="C">
else if (c == '\r') // Carriage Return
else if (c == '\r') // Carriage Return
{
{
     cursor_x = 0;
     cursor_x = 0;
}
}
Tab (\t): If the character is a tab (\t), we move the cursor four positions to the right:
</syntaxhighlight>
'''Tab (\t)''': If the character is a tab (\t), we move the cursor four positions to the right:


Copy
<syntaxhighlight lang="C">
else if (c == '\t') // Tab
else if (c == '\t') // Tab
{
{
     cursor_x += 4;
     cursor_x += 4;
}
}
Backspace (\b): If the character is a backspace (\b), we move the cursor one character to the left unless the cursor is already at the beginning of the line:
</syntaxhighlight>
'''Backspace (\b)''': If the character is a backspace (\b), we move the cursor one character to the left unless the cursor is already at the beginning of the line:


Copy
<syntaxhighlight lang="C">
else if (c == '\b') // Backspace
else if (c == '\b') // Backspace
{
{
Zeile 60: Zeile 67:
     }
     }
}
}
Form Feed (\f): If the character is a form feed (\f), we clear the screen. This character was historically used to start a new page on dot matrix printers. Here, we use it to clear the screen:
</syntaxhighlight>
'''Form Feed (\f)''': If the character is a form feed (\f), we clear the screen. This character was historically used to start a new page on dot matrix printers. Here, we use it to clear the screen:


Copy
<syntaxhighlight lang="C">
else if (c == '\f') // Form Feed (Clear Screen)
else if (c == '\f') // Form Feed (Clear Screen)
{
{
     ClearScreen();
     ClearScreen();
}
}
Normal character: If the character is not a control character, we draw it at the current cursor position and move the cursor one position to the right. If the end of the line is reached, we set the cursor to the beginning of the next line:
</syntaxhighlight>
'''Normal character''': If the character is not a control character, we draw it at the current cursor position and move the cursor one position to the right. If the end of the line is reached, we set the cursor to the beginning of the next line:


Copy
<syntaxhighlight lang="C">
else // Normal character
else // Normal character
{
{
Zeile 79: Zeile 88:
     }
     }
}
}
Reaching the end of the screen
</syntaxhighlight>
=== Reaching the end of the screen ===
If the cursor reaches the end of the screen, we need to scroll the text up to make room for new lines:
If the cursor reaches the end of the screen, we need to scroll the text up to make room for new lines:


Copy
<syntaxhighlight lang="C">
if (cursor_y >= NUM_ROWS)  
if (cursor_y >= NUM_ROWS)  
{
{
Zeile 88: Zeile 98:
     cursor_y = NUM_ROWS - 1;
     cursor_y = NUM_ROWS - 1;
}
}
ClearScreen function
</syntaxhighlight>
== ClearScreen function ==
The ClearScreen function clears the entire screen and resets the cursor to the starting position. First, we set the drawing color to the background color. Then, we loop through the entire screen and draw the background color at each position. Finally, we reset the cursor to the top left corner.
The ClearScreen function clears the entire screen and resets the cursor to the starting position. First, we set the drawing color to the background color. Then, we loop through the entire screen and draw the background color at each position. Finally, we reset the cursor to the top left corner.


Copy
<syntaxhighlight lang="C">
void ClearScreen()
void ClearScreen()
{
{
Zeile 108: Zeile 119:
     cursor_y = 0;
     cursor_y = 0;
}
}
ScrollScreen function
</syntaxhighlight>
== ScrollScreen function ==
The ScrollScreen function moves the text up by one line and clears the bottom line. First, we loop through all the lines until 10 pixels before the end of the screen and copy the color value of the pixel that is 10 pixels below the current line into the current line. Then, we clear the bottom line by filling it with the background color.
The ScrollScreen function moves the text up by one line and clears the bottom line. First, we loop through all the lines until 10 pixels before the end of the screen and copy the color value of the pixel that is 10 pixels below the current line into the current line. Then, we clear the bottom line by filling it with the background color.


Copy
<syntaxhighlight lang="C">
void ScrollScreen()
void ScrollScreen()
{
{
Zeile 130: Zeile 142:
     }
     }
}
}
GetPixel function
</syntaxhighlight>
== GetPixel function ==
The GetPixel function reads the color value of a pixel at the specified position. First, we check if the pixel being queried is actually on the screen. If not, we return 0. Otherwise, we calculate the position of the pixel in memory and read the value.
The GetPixel function reads the color value of a pixel at the specified position. First, we check if the pixel being queried is actually on the screen. If not, we return 0. Otherwise, we calculate the position of the pixel in memory and read the value.


Copy
<syntaxhighlight lang="C">
u32 GetPixel(u32 x, u32 y)
u32 GetPixel(u32 x, u32 y)
{
{
Zeile 145: Zeile 158:
     }
     }
}
}
DrawString function
</syntaxhighlight>
== DrawString function ==
To display entire texts (strings) in the terminal, we use the DrawString function. This function iterates through the string and draws each character:
To display entire texts (strings) in the terminal, we use the DrawString function. This function iterates through the string and draws each character:


Copy
<syntaxhighlight lang="C">
void DrawString(const char* str) {
void DrawString(const char* str) {
     while (*str) {
     while (*str) {
Zeile 154: Zeile 168:
     }
     }
}
}
Testing the function
</syntaxhighlight>
== Testing the function ==
To test these functions, we write a main function:
To test these functions, we write a main function:


Copy
<syntaxhighlight lang="C">
// main.c
// main.c


Zeile 173: Zeile 188:
     LED_Error(2);
     LED_Error(2);
}
}
Summary
</syntaxhighlight>
== Summary ==
With this code, we have programmed a simple terminal. The code manages the display of characters and allows us to display messages such as errors more clearly and legibly. In the next chapter, we will integrate the printf function to get even better data evaluations and also display variables.
With this code, we have programmed a simple terminal. The code manages the display of characters and allows us to display messages such as errors more clearly and legibly. In the next chapter, we will integrate the printf function to get even better data evaluations and also display variables.


You can download the source code as a ZIP file from the following link: https://www.satyria.de/arm/sources/C/terminal.zip.
You can download the source code as a ZIP file from the following link: https://www.satyria.de/arm/sources/C/terminal.zip.

Version vom 22. August 2024, 07:46 Uhr

The Terminal

After we have displayed characters on the screen, we will build a terminal. A terminal is an area on the screen where texts can be displayed and later also inputs can be made. An example of a terminal is the command prompt that appears when Linux starts.

What does a terminal look like?

First, we need to consider what the terminal should look like. Since we know the screen resolution, we use it. Our font has a size of 8x8 pixels per character. To prevent the characters from being too close together, we use a resolution of 8x10 pixels per character in the terminal.

#define CHAR_WIDTH 8
#define CHAR_HEIGHT 10

With this, we can determine how many characters can be displayed in a row and how many rows can be displayed on the screen:

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

To store the current position of the cursor (the place where the next character will appear), we use two variables. We initially set these to 0,0 (top left):

u32 cursor_x = 0;
u32 cursor_y = 0;

Drawing characters in the terminal

First, we write a function that draws a character in the terminal at the current cursor position. The function simply receives the character to be drawn:

void DrawCharAtCursor(char c)
{
    // Function to draw a character
}

Considering control characters

When drawing texts, we need to consider various control characters, such as "new line" (Enter), "Tab", and "Backspace". We capture these control characters and react accordingly:

New Line (\n): If the character is a newline (\n), we set the cursor to the beginning of the next line:

if (c == '\n') // New Line
{
    cursor_x = 0;
    cursor_y++;
}

Carriage Return (\r): If the character is a carriage return (\r), we set the cursor to the beginning of the current line:

else if (c == '\r') // Carriage Return
{
    cursor_x = 0;
}

Tab (\t): If the character is a tab (\t), we move the cursor four positions to the right:

else if (c == '\t') // Tab
{
    cursor_x += 4;
}

Backspace (\b): If the character is a backspace (\b), we move the cursor one character to the left unless the cursor is already at the beginning of the line:

else if (c == '\b') // Backspace
{
    if (cursor_x > 0) {
        cursor_x--;
    }
}

Form Feed (\f): If the character is a form feed (\f), we clear the screen. This character was historically used to start a new page on dot matrix printers. Here, we use it to clear the screen:

else if (c == '\f') // Form Feed (Clear Screen)
{
    ClearScreen();
}

Normal character: If the character is not a control character, we draw it at the current cursor position and move the cursor one position to the right. If the end of the line is reached, we set the cursor to the beginning of the next line:

else // Normal character
{
    DrawChar(c, cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT);
    cursor_x++;
    if (cursor_x >= NUM_COLS) {
        cursor_x = 0;
        cursor_y++;
    }
}

Reaching the end of the screen

If the cursor reaches the end of the screen, we need to scroll the text up to make room for new lines:

if (cursor_y >= NUM_ROWS) 
{
    ScrollScreen();
    cursor_y = NUM_ROWS - 1;
}

ClearScreen function

The ClearScreen function clears the entire screen and resets the cursor to the starting position. First, we set the drawing color to the background color. Then, we loop through the entire screen and draw the background color at each position. Finally, we reset the cursor to the top left corner.

void ClearScreen()
{
    // Set the drawing color to the background color
    DrawColor = BackColor;
    
    // Clear the entire screen
    for (u32 y = 0; y < SCREEN_Y; y++) {
        for (u32 x = 0; x < SCREEN_X; x++) {
            DrawPixel(x, y);
        }
    }
    
    // Reset the cursor to the top left corner
    cursor_x = 0;
    cursor_y = 0;
}

ScrollScreen function

The ScrollScreen function moves the text up by one line and clears the bottom line. First, we loop through all the lines until 10 pixels before the end of the screen and copy the color value of the pixel that is 10 pixels below the current line into the current line. Then, we clear the bottom line by filling it with the background color.

void ScrollScreen()
{
    // Move the screen content up by one line
    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);
        }
    }
    
    // Clear the bottom line
    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 function

The GetPixel function reads the color value of a pixel at the specified position. First, we check if the pixel being queried is actually on the screen. If not, we return 0. Otherwise, we calculate the position of the pixel in memory and read the value.

u32 GetPixel(u32 x, u32 y)
{
    // Check if the pixel coordinates are within the screen
    if ((x < SCREEN_X) && (y < SCREEN_Y)) {
        // Calculate the memory address of the pixel and read the color value
        return read32(graphicsAddress + (((SCREEN_X * y) + x) * 4));
    } else {
        // Pixel is outside the screen, return 0
        return 0;
    }
}

DrawString function

To display entire texts (strings) in the terminal, we use the DrawString function. This function iterates through the string and draws each character:

void DrawString(const char* str) {
    while (*str) {
        DrawCharAtCursor(*str++);
    }
}

Testing the function

To test these functions, we write a main function:

// main.c

#include "led.h"
#include "screen.h"
#include "types.h"

int main(void)
{
    LED_off();
    Init_Screen();
    
    DrawString("Hello World!");
    
    LED_Error(2);
}

Summary

With this code, we have programmed a simple terminal. The code manages the display of characters and allows us to display messages such as errors more clearly and legibly. In the next chapter, we will integrate the printf function to get even better data evaluations and also display variables.

You can download the source code as a ZIP file from the following link: https://www.satyria.de/arm/sources/C/terminal.zip.