The Terminal in C (PI5)

Aus C und Assembler mit Raspberry

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.


< Back (Chars in C (PI5)) < Home > Next (Using printf in C (PI5)) >