Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Part V : VGA Text Mode

In this chapter we are making a convenient way to output text to the screen using the VGA text buffer we wrote to when we first reached C. This task is a lot more like regular programming, so I will allow some creative freedom. Here is the header file that I have created:

#ifndef VGA_TEXT_H
#define VGA_TEXT_H

#include <stddef.h>
#include <stdint.h>

typedef enum {
    VGA_COLOR_BLACK = 0,
    VGA_COLOR_BLUE,
    VGA_COLOR_GREEN,
    VGA_COLOR_CYAN,
    VGA_COLOR_RED,
    VGA_COLOR_MAGENTA,
    VGA_COLOR_BROWN,
    VGA_COLOR_LIGHT_GREY,
    VGA_COLOR_DARK_GREY,
    VGA_COLOR_LIGHT_BLUE,
    VGA_COLOR_LIGHT_GREEN,
    VGA_COLOR_LIGHT_CYAN,
    VGA_COLOR_LIGHT_RED,
    VGA_COLOR_LIGHT_MAGENTA,
    VGA_COLOR_LIGHT_BROWN,
    VGA_COLOR_WHITE
} vga_color;

typedef struct {
    size_t row;
    size_t column;

    size_t width;
    size_t height;

    uint8_t color;

    uint16_t* buffer;
} vga_text;

// init
void vga_text_init(vga_text* terminal);

//screen ops
//
void vga_text_clear(vga_text* terminal);

/* cursor ops */
void vga_text_set_cursor(
    vga_text* terminal,
    size_t row,
    size_t column
);

/* Color ops */
void vga_text_set_color(
    vga_text* terminal,
    vga_color f,
    vga_color b
);

/* char out */
void vga_text_putchar(
    vga_text* terminal,
    char c
);

/* string out */
void vga_text_write(
    vga_text* terminal,
    const char* string
);

void vga_text_writeline(
    vga_text* terminal,
    const char* string
);

/* num out */
void vga_text_write_dec(
    vga_text* terminal,
    uint32_t value 
);

void vga_text_write_hex(
    vga_text* terminal,
    uint32_t value
);

/* low level writing */

void vga_text_put_entry_at(
    vga_text* terminal,
    char character,
    uint8_t fcolor,
    uint8_t bcolor,
    size_t row,
    size_t column
);

#endif

All these definitions are pretty self-explanatory, other than the final function. Which just writes a char without considering our main struct. For this part of the guide I’ll just give you all the implementations for my functions. All this code is simple C with no OS-specific content. Try and use my implementations as a rough guide and not as law. You can even make your own functions for writing text entries instead of just characters as I did.

#include "vga_text.h"

void vga_text_init(vga_text* terminal) {
    terminal->row = 0;
    terminal->column = 0;

    vga_text_set_color(terminal, VGA_COLOR_WHITE, VGA_COLOR_RED);

    terminal->width = 80;
    terminal->height = 25;

    terminal->buffer = (uint16_t*)0xB8000;
    vga_text_clear(terminal);
}

void vga_text_clear(vga_text* terminal) {
    uint8_t color = terminal->color;

    uint16_t blank = ((uint16_t)color << 8) | ' ';

    for (size_t row = 0; row < terminal->height; row++) {
        for (size_t col = 0; col < terminal->width; col++) {
            size_t index = row * terminal->width + col;
            terminal->buffer[index] = blank;
        }
    }
    
    terminal->row = 0;
    terminal->column = 0;
    
}

void vga_text_set_color(vga_text* terminal, vga_color f, vga_color b) {
    terminal->color = ((uint8_t)b << 4) | (uint8_t)f; 
}

void vga_text_set_cursor(vga_text* terminal, size_t row, size_t column) {
    terminal->row = row;
    terminal->column = column;
}

void vga_text_putchar(vga_text* terminal, char c) {
    uint8_t color = terminal->color;
    size_t index = terminal->row * terminal->width + terminal->column;
    uint16_t entry = ((uint16_t)color << 8) | (uint16_t)c;
    terminal->buffer[index] = entry;
}

void vga_text_write(vga_text* terminal, const char* string) {
    size_t pos = terminal->row * terminal->width + terminal->column;

    for (size_t i = 0; string[i] != '\0'; i++) {
        vga_text_putchar(terminal, string[i]);
        pos++;
        terminal->row = pos / terminal->width;
        terminal->column = pos % terminal->width;
        terminal->row = terminal->row % terminal->height;
    }
}

void vga_text_writeline(vga_text* terminal, const char* string) {
    vga_text_write(terminal, string);
    terminal->column = 0;
    terminal->row++;
    terminal->row = terminal->row % terminal->height;
}

void vga_text_write_dec(vga_text* terminal, uint32_t value) {
    char buffer[10];
    size_t digits = 0;
    if (value == 0) {
        vga_text_putchar(terminal, '0');
        return;
    }

    while (value > 0) {
        buffer[digits++] = '0' + (value % 10);
        value /= 10;
    }
    
    size_t index = 0;
    while (index < digits) {
        vga_text_putchar(terminal, buffer[--digits]);
        terminal->column++;
    }
}

void vga_text_write_hex(vga_text* terminal, uint32_t value) {
    char buffer[8];
    size_t digits = 0;

    if (value == 0) {
        vga_text_putchar(terminal, '0');
        return;
    }

    while(value > 0) {
        uint32_t digit = value % 16;

        if (digit < 10) {
            buffer[digits++] = '0' + digit;
        } else {
            buffer[digits++] = 'A' + (digit - 10);
        }
        value /= 16;
    }

    size_t index = 0;
    while (index < digits) {
        vga_text_putchar(terminal, buffer[--digits]);
        terminal->column++;
    }
}

void vga_text_put_entry_at(
    vga_text* terminal,
    char character,
    uint8_t fcolor,
    uint8_t bcolor,
    size_t row,
    size_t column
) {
    uint8_t color = ((uint8_t)bcolor << 4) | (uint8_t)fcolor; 
    size_t index = row * terminal->width + column;
    uint16_t entry = ((uint16_t)color << 8) | (uint16_t)character;
    terminal->buffer[index] = entry; 
}

vga_text_putchar() writes a character at the current cursor position, but it does not move the cursor. The higher-level writing functions are responsible for moving the cursor. This is the inverse to what you might expect for putfoo() functions, so I just thought I’d point that out.

The decimal and hexadecimal functions currently assume that there is enough space remaining on the current row. Unlike vga_text_write(), they do not handle reaching the end of a row, so just keep mind of that if you ever wish to use this near the end of a row.

And that’s essentially this section complete.

Found this guide useful?

Consider starring lytlnyblOS on GitHub. It helps others discover the guide.

Star lytlnyblOS on GitHub →