CLOSE

A little recap!

VGA, introduced by IBM in 1987, is a display standard that allows various screen resolutions and color depths. In bootloaders, VGA is often used because it is widely supported by PCs and offers a simple interface for setting graphics modes.

  • Text Mode: Displays characters using a grid, typically 80x25 characters with 16 colors.
  • Graphics Mode: Allows pixel-by-pixel control, enabling more complex images and interfaces.

Graphics Modes in VGA

VGA offers several graphics modes, but the most commonly used in bootloaders are:

  • 320x200 with 256 colors (Mode 13h): This is often preferred for its balance between resolution and color depth.
  • 640x480 with 16 colors (Mode 12h): Provides higher resolution but with limited color depth.
  • 640x480 with 256 colors (Mode X): An extension of standard VGA allowing for higher color depth at higher resolution.

Why Use Graphics Mode in Bootloaders?

While text mode is adequate for simple bootloaders, graphics mode provides several advantages:

  1. Enhanced User Interface: Create more visually appealing and user-friendly interfaces.
  2. Rich Graphics: Display logos, progress bars, and other graphical elements.
  3. Better Resolution: Higher resolutions allow more information to be displayed at once.

Setting Up VGA Graphics Mode

To set a graphics mode in VGA, we typically interact with the BIOS interrupt 0x10. Here’s a step-by-step guide to switch to 320x200 mode with 256 colors (Mode 13h):

Enter Assembly Mode: The bootloader code usually starts in assembly.

Invoke BIOS Interrupt 0x10: Use this interrupt to switch video modes.

mov ah, 0x00     ; Function: Set Video Mode
mov al, 0x13     ; Mode: 320x200 with 256 colors
int 0x10         ; Call BIOS Video Services

Setup Pixel Data: Once in the desired mode, you can manipulate pixel data directly in the VGA memory, starting at address 0xA0000.

Displaying Graphics

1 Drawing Pixels

Once in graphics mode, you can directly manipulate video memory to draw pixels on the screen.

mode13h.png

The framebuffer is essentially a linear array of bytes, where each byte represents the color of one pixel on the screen. The memory layout typically starts from the top-left corner of the screen and proceeds row by row until the bottom-right corner. This means that the byte at index 0 represents the color of the pixel at coordinates (0,0), the byte at index 1 represents the color of the pixel at coordinates (1,0), and so on, until the byte at index 63,999 represents the color of the pixel at coordinates (319,199).

Frame Buffer Layout:

  • The frame buffer for mode 13h starts at the segment address 0xA0000.
  • Each pixel is represented by a single byte.
  • The total memory required is 320 x 200 = 64,000 bytes or 64 KB.

To Plot a pixel at coordinates (x, y):

  • The memory address for a pixel is calculated as:

offset = (y * Screen_Width) + x

for mode 13h, the Screen_Width = 320

  • The video memory for the pixel is then:
    • memory address = 0xA0000 + offset

The address of a pixel at coordinates (x, y) can be calculated as:

Address = 0xA0000 + (y*320) + x

Color Representation:

  • Each byte in the framebuffer corresponds to a pixel color, which is an index into a palette of 256 colors.
  • The VGA palette defines the actual RGB values for these 256 indices, which can be modified to change the colors displayed on the screen.
ValueColor
0Black
1Blue
2Green
3Cyan
4Red
5Magenta
6Brown
7Light Gray
8Dark Gray
9Light Blue
10Light Green
11Light Cyan
12Light Red
13Light Magenta
14Yellow
15White

Table I. The first 16 VGA colors.

Palette Configuration:

  • The VGA palette consists of 256 entries, each representing an RGB color.
  • You can set the palette using VGA I/O ports to define which RGB color corresponds to each of the 256 possible pixel values.

Let's modify our barebone bootloader code to switch to graphics mode and then plot a pixel on the specified place on the screen.

; Set VGA Mode 13h (320x200, 256 colors)
mov ax, 0x13      ; Set AX to 0x13 (mode 13h)
int 0x10          ; Call BIOS interrupt to set the video mode


GRAPHICS_MODE_13_SCREEN_WIDTH equ 320

; Draw red pixels using the draw_pixel function
    mov ax, 100  ; y coordinate
    mov bx, 100  ; x coordinate
    mov cl, 4    ; red color
    call draw_pixel

; Function to draw a pixel at (x, y) with color index
; ax = y coordinate
; bx = x coordinate
; cl = color
draw_pixel:
    push ax
    push bx
    push cx
    push dx
    push di

    mov dx, 0xA000  ; Set segment to video memory
    mov es, dx      ; es now points to video memory

    mov dx, GRAPHICS_MODE_13_SCREEN_WIDTH  ; set dx to screen width

    mul dx          ; ax = y * 320 (multiplying ax by dx)

    add ax, bx      ; ax = y * 320 + bx

    mov di, ax      ; move offset to di

    mov al, cl      ; set al to the color for the pixel
    stosb           ; store al to ES:DI and increment di

    pop di
    pop dx
    pop cx
    pop bx
    pop ax

ret

section .text
    ; Example usage: draw a red pixel at (100, 100)
    mov cx, 100    ; x coordinate
    mov dx, 100    ; y coordinate
    mov bx, 4      ; color index (red)
    call draw_pixel
image-140.png

2 Drawing Text in Graphics Mode

It involves rendering each character as a set of pixels on the screen. Each character can be represented by a bitmap, which is binary representation of the pixels that form the character. The simplest way is to create a bitmap [matrix of size 8x8] in which we have 0's and 1's of where 0 means draw pixel of background color while 1 means drawing pixel of foreground color. Thus making an text character.

Bitmap: also known as a raster graphic, is a type of digital image composed of a matrix of dots called pixels. Each pixel in a bitmap corresponds to a specific color and is stored as a binary value. Bitmaps are commonly used for images, icons, and graphical representations in computer systems.

In memory, a bitmap is typically stored as a series of bytes, where each byte (or group of bytes) represents the color of a pixel or group of pixels. For simplicity, we'll focus on monochrome (1-bit per pixel) and 8-bit color bitmaps.

Monochrome Bitmap (1-bit per Pixel)

In a monochrome bitmap, each bit represents one pixel. A 1 might represent a white pixel, and a 0 might represent a black pixel (or vice versa).

Example: 8x8 Monochrome Bitmap

0b00011000
0b00111100
0b01111110
0b11011011
0b11111111
0b01111110
0b00111100
0b00011000

This pattern could represent a simple 8x8 image, where each bit corresponds to one pixel.

8-bit Color Bitmap (256 Colors)

In an 8-bit color bitmap, each pixel is represented by one byte, allowing for 256 different colors.

Example: 4x4 8-bit Color Bitmap

0x01 0x02 0x03 0x04
0x05 0x06 0x07 0x08
0x09 0x0A 0x0B 0x0C
0x0D 0x0E 0x0F 0x10

Each value corresponds to a color in a color palette.

Steps to Implement Drawing Text

  • Define Font Data: A simple 8x8 font can be defined using an array where each byte represents a row of the character. Each byte in the bitmap represents a row of 8 pixels. For example, the character A might be represented by an 8-byte array.

For example, let's represent the character A:

.XX.
X..X
XXXX
X..X
X..X


This character can be stored as the following bitmap:

  01111110  ; Row 0
  10000001  ; Row 1
  10000001  ; Row 2
  11111111  ; Row 3
  10000001  ; Row 4
  10000001  ; Row 5
  10000001  ; Row 6
  00000000  ; Row 7

Here, 0 means black and 1 means white so when it is drawn on a black screen, it is printed as below:

   111111   ; Row 0
  1      1  ; Row 1
  1      1  ; Row 2
  11111111  ; Row 3
  1      1  ; Row 4
  1      1  ; Row 5
  1      1  ; Row 6
            ; Row 7
isnt' it awesome!
  • Draw Each Character: Convert each character to pixels and draw them on the screen.

We have the bitmap font mapping, which will store our all printable characters. Now we need a way to use that mapping decode them in a way that it prints as a character. We will need to write a function that can, Find the bitmap for the given character from the font table and a function Draw the bitmap on the screen at the desired coordinates.

Here are the steps to draw character on the screen:

1 Calculate Pixel Offset: Calculate the starting address for the character in video memory on the coordinates. For example: (x = 100, y = 50)

  1. Screen width in mode 13h: 320 pixels
  2. Starting Y coordinate (top row): 50
  3. Starting X coordinate (left column): 100
  4. Formula for offset:
offset = (y * screen_width) + x
       = (50 * 320) + 100
       = 16000 + 100
       = 16100

2 Draw the Character: Loop through each row of the character bitmap and set the corresponding pixels in video memory.

– Drawing Each Row

Row 0: 01111110

The starting address for the row: 16100

Bitmap for row 0 = 01111110

Pixel Calculation: For each bit in the row, determine if it's set (1) or not (0).

Bit 7 (most significant): 0 -> Skip
Bit 6: 1 -> Set pixel at address 16100
Bit 5: 1 -> Set pixel at address 16101
Bit 4: 1 -> Set pixel at address 16102
Bit 3: 1 -> Set pixel at address 16103
Bit 2: 1 -> Set pixel at address 16104
Bit 1: 1 -> Set pixel at address 16105
Bit 0 (least significant): 0 -> Skip

Row 1: 10000001

Start address for row 1: 16100 (base address for row 0) + 320 (offset for each new row, given the screen width is 320 pixels).

The starting address is = 16100 + 320 = 16420

Bitmap for row 1 = 10000001

Pixel Calculation: For each bit in the row, determine if it's set (1) or not (0).

Bit 7 (most significant): 1 -> Set pixel at address 16420
Bit 6: 0 -> Skip
Bit 5: 0 -> Skip
Bit 4: 0 -> Skip
Bit 3: 0 -> Skip
Bit 2: 0 -> Skip
Bit 1: 0 -> Skip
Bit 0 (least significant): 1 -> Set pixel at address 16427

Similarly we traverse the complete bitmap for the particular character. Thus effectively draw character on the screen.

Our bitmap mapping would from 32 to 126 which is ASCII 32 (space) to ASCII 126 (tilde ~). So, how do we get the offset of particular character in the bitmap table.

font_table:
	db row1 of ASCII 32
	db row2 of ASCII 32
	db row3 of ASCII 32
	db row4 of ASCII 32
	db row5 of ASCII 32
	db row6 of ASCII 32
	db row7 of ASCII 32
	db row8 of ASCII 32
	;; end of character bitmap
	
	;; start of bitmap of next ASCII character
	db row1 of ASCII 33
	db row2 of ASCII 33
	db row3 of ASCII 33
	db row4 of ASCII 33
	db row5 of ASCII 33
	db row6 of ASCII 33
	db row7 of ASCII 33
	db row8 of ASCII 33
	;; end of character bitmap
	
	;; similarly upto ASCII 126

Computer stores characters using the ASCII (American Standard for Information Interchange) encoding, where each character is represented by  a unique numerical value. In this encoding, for instance, the character A is represented by the number 65. Since each bitmap is 8 bytes long then we can use this to get the effective offset of character into the font table.

Calculate Offset in font table:

Since out table start with the bitmap for character 32 (space), the offset for character A is (A - 32) * 8.

  • 8 = one character bitmap is 8 bytes long.

Let's write the code:

Now we understood the basics, time to implement it.

This completes the 8x8 font table for all printable ASCII characters from 32 to 126. Each db line represents the 8x8 bitmap of a character in binary form.

Implement the Character Drawing Function:

Implement the function to draw a character. The function draw_char will use the font table to print characters on the screen.

section .text
    global _start

_start:
    ; Set video mode to 13h
    mov ax, 0x0013
    int 0x10

    ; Print character 'A' at (10, 10)
    mov al, 'A'      ; Character to print
    mov bx, 10       ; X position
    mov cx, 10       ; Y position
    call draw_char

    ; Hang the system
    hlt

; Function: draw_char
; Inputs: AL = character, BX = X position, CX = Y position
draw_char:
    pusha
    mov ah, 0          ; Video page number (always 0 in mode 13h)
    sub al, 32         ; Convert ASCII to font table index
    shl ax, 3          ; Multiply index by 8 (each character is 8 bytes)
    add ax, font_table ; Point AX to the start of the character bitmap

    mov di, ax         ; ES:DI -> character bitmap
    mov dx, 320        ; Screen width in mode 13h (320 pixels)
    mov si, cx         ; SI = Y position
    mul dx             ; AX = Y * 320
    add ax, bx         ; AX += X position
    mov di, ax         ; DI = Screen position (Y * 320 + X)
    mov es, 0xA000     ; Segment address of video memory

    mov cx, 8          ; 8 rows per character
next_row:
    lodsb              ; Load next byte from font_table to AL
    mov ah, al         ; Copy to AH
    mov al, 0          ; AL = 0
    mov dx, 8          ; 8 bits per row
next_pixel:
    shl ah, 1          ; Shift AH left
    jnc skip_pixel     ; If carry (bit 0) is 0, skip
    mov byte [es:di], 0x0F ; Set pixel color to white (0x0F)
skip_pixel:
    inc di             ; Move to next pixel on screen
    dec dx
    jnz next_pixel     ; Repeat for 8 bits (pixels)

    add di, 320-8      ; Move to the start of the next row
    loop next_row      ; Repeat for 8 rows

    popa
    ret

Explanation:

1 Data Section:

The font_table defines the 8x8 bitmap for the character A. Each byte represents one row of the character.

The bitmap for A is defined as follows:

section .data
font_table:
    ; Character 'A' in binary
    db 0b01111110  ; Row 1: 0x7E
    db 0b11000011  ; Row 2: 0xC3
    db 0b11000011  ; Row 3: 0xC3
    db 0b11000011  ; Row 4: 0xC3
    db 0b11111111  ; Row 5: 0xFF
    db 0b11000011  ; Row 6: 0xC3
    db 0b11000011  ; Row 7: 0xC3
    db 0b11000011  ; Row 8: 0xC3

Each byte represents one row of the character, with bits set to 1 representing pixels to be turned on.

Each byte in font_table corresponds to a row in the 8x8 grid for the character 'A'. For example, the first byte 0b01111110 represents the top row of the character 'A'.

Draw Function:

The draw_A function is responsible for drawing the character 'A' on the screen at the specified position.

; Function: draw_A
; Inputs: BX = X position, CX = Y position
draw_A:
    pusha                    ; Save all general-purpose registers

    mov si, font_table       ; SI = Pointer to the beginning of the font_table
    mov dx, 0                ; Offset in the font table for character 'A'
    add si, dx               ; Add DX to SI to point to the correct character bitmap
    mov dx, 320              ; Screen width in mode 13h (320 pixels)
    mov di, cx               ; DI = Y position
    mul dx                   ; AX = Y * 320
    add ax, bx               ; AX += X position
    mov di, ax               ; DI = Screen position (Y * 320 + X)
    mov es, 0xA000           ; Segment address of video memory

    mov cx, 8                ; 8 rows per character
next_row:
    lodsb                    ; Load next byte from font_table to AL
    mov ah, al               ; Copy to AH
    mov al, 0                ; Clear AL
    mov dx, 8                ; 8 bits per row
next_pixel:
    shr ah, 1                ; Shift AH right
    jnc skip_pixel           ; If carry (bit 0) is 0, skip
    mov byte [es:di], 0x0F   ; Set pixel color to white (0x0F)
skip_pixel:
    inc di                   ; Move to next pixel on screen
    dec dx
    jnz next_pixel           ; Repeat for 8 bits (pixels)

    add di, 320 - 8          ; Move to the start of the next row
    loop next_row            ; Repeat for 8 rows

    popa                     ; Restore all general-purpose registers
    ret

  • Save Registers: pusha saves all general-purpose registers.
  • Set Source Index: mov si, font_table loads the address of the font table into SI.
  • Offset: mov dx, 0 and add si, dx set the offset to zero for character A (no offset needed here, since we only have a single character as of now).
  • Set Screen Width: mov dx, 320 sets the screen width in pixels.
  • Calculate Screen Position:
    • mov di, cx Loads the Y position into DI.
    • mul dx multiplies cx by 320 to calculate the offset for the Y position.
    • add ax, bx adds the X position to the offset.
    • mov di, ax sets DI to the final screen position.
  • Set Video Memory Segment: mov es, 0xA000 sets the segment address of the video memory.
  • Draw Character:
    • mov cx, 8 sets up a loop to handle 8 rows.
    • next_row: label marks the start of the row loop.
    • lodsb loads the next byte from the font table into AL.
    • mov ah, al copies the byte to AH
    • mov al, 0 clears AL.
    • mov dx, 8 sets up a loop to handle 8 bits (pixels) per row.
    • next_pixel: label marks the start of the pixel loop.
    • shl ah, 1 Shifts AH left, and it sets the carry flag with the leftmost shifted bit.
    • jnc skip_pixel skips setting pixels if the carry flag is not set.
    • mov byte [es:di], 0x0F sets the pixel color to white.
    • skip_pixel: label marks the skip position.
    • inc di moves di to the next pixel on the screen.
    • dec dx decrements the pixel counter.
    • jnz next_pixel jumps back to next_pixel if there are more pixels to process.
    • add di, 320 -8 moves to the start of the next row.
    • loop next_row jumps back to next_row if there are more to process.
  • Restore Registers: popa restores all general-purpose registers.
  • Return: ret returns from the function.

Note: This code is just for the character A only. we have to create bitmap for all ASCII characters (from 32 to 127), we will need to define the bitmaps for each character in a similar 8x8 format as we did for the character A.

ASCII Character Bitmap Definition:

Below is a bitmap representation for each character. This example includes a few characters to illustrate the idea.

font_table:
    ; Offset 0x00 (ASCII 32: Space ' ')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00000000  ; Row 2: 0x00
    db 0b00000000  ; Row 3: 0x00
    db 0b00000000  ; Row 4: 0x00
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x08 (ASCII 33: Exclamation Mark '!')
    db 0b00011000  ; Row 0: 0x18
    db 0b00011000  ; Row 1: 0x18
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00011000  ; Row 4: 0x18
    db 0b00000000  ; Row 5: 0x00
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x10 (ASCII 34: Double Quote '"')
    db 0b01100110  ; Row 0: 0x66
    db 0b01100110  ; Row 1: 0x66
    db 0b00100100  ; Row 2: 0x24
    db 0b00000000  ; Row 3: 0x00
    db 0b00000000  ; Row 4: 0x00
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x18 (ASCII 35: Hash '#')
    db 0b01100110  ; Row 0: 0x66
    db 0b01100110  ; Row 1: 0x66
    db 0b11111111  ; Row 2: 0xFF
    db 0b01100110  ; Row 3: 0x66
    db 0b11111111  ; Row 4: 0xFF
    db 0b01100110  ; Row 5: 0x66
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x20 (ASCII 36: Dollar Sign '$')
    db 0b00011000  ; Row 0: 0x18
    db 0b00111111  ; Row 1: 0x3F
    db 0b01100000  ; Row 2: 0x60
    db 0b00111110  ; Row 3: 0x3E
    db 0b00000111  ; Row 4: 0x07
    db 0b01111110  ; Row 5: 0x7E
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x28 (ASCII 37: Percent '%')
    db 0b11000001  ; Row 0: 0xC1
    db 0b11000110  ; Row 1: 0xC6
    db 0b00001100  ; Row 2: 0x0C
    db 0b00011000  ; Row 3: 0x18
    db 0b00110000  ; Row 4: 0x30
    db 0b01100011  ; Row 5: 0x63
    db 0b11000011  ; Row 6: 0xC3
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x30 (ASCII 38: Ampersand '&')
    db 0b00110000  ; Row 0: 0x30
    db 0b01101100  ; Row 1: 0x6C
    db 0b01100100  ; Row 2: 0x64
    db 0b00111000  ; Row 3: 0x38
    db 0b01101110  ; Row 4: 0x6E
    db 0b11001100  ; Row 5: 0xCC
    db 0b01110110  ; Row 6: 0x76
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x38 (ASCII 39: Single Quote ''')
    db 0b00011000  ; Row 0: 0x18
    db 0b00011000  ; Row 1: 0x18
    db 0b00110000  ; Row 2: 0x30
    db 0b00000000  ; Row 3: 0x00
    db 0b00000000  ; Row 4: 0x00
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x40 (ASCII 40: Left Parenthesis '(')
    db 0b00001100  ; Row 0: 0x0C
    db 0b00011000  ; Row 1: 0x18
    db 0b00110000  ; Row 2: 0x30
    db 0b00110000  ; Row 3: 0x30
    db 0b00110000  ; Row 4: 0x30
    db 0b00011000  ; Row 5: 0x18
    db 0b00001100  ; Row 6: 0x0C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x48 (ASCII 41: Right Parenthesis ')')
    db 0b00110000  ; Row 0: 0x30
    db 0b00011000  ; Row 1: 0x18
    db 0b00001100  ; Row 2: 0x0C
    db 0b00001100  ; Row 3: 0x0C
    db 0b00001100  ; Row 4: 0x0C
    db 0b00011000  ; Row 5: 0x18
    db 0b00110000  ; Row 6: 0x30
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x50 (ASCII 42: Asterisk '*')
    db 0b00011000  ; Row 0: 0x18
    db 0b01101100  ; Row 1: 0x6C
    db 0b00111100  ; Row 2: 0x3C
    db 0b11111111  ; Row 3: 0xFF
    db 0b00111100  ; Row 4: 0x3C
    db 0b01101100  ; Row 5: 0x6C
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x58 (ASCII 43: Plus '+')
    db 0b00011000  ; Row 0: 0x18
    db 0b00011000  ; Row 1: 0x18
    db 0b00011000  ; Row 2: 0x18
    db 0b11111111  ; Row 3: 0xFF
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x60 (ASCII 44: Comma ',')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00000000  ; Row 2: 0x00
    db 0b00000000  ; Row 3: 0x00
    db 0b00000000  ; Row 4: 0x00
    db 0b00011000  ; Row 5: 0x18
    db 0b00011000  ; Row 6: 0x18
    db 0b00110000  ; Row 7: 0x30

    ; Offset 0x68 (ASCII 45: Hyphen '-')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00000000  ; Row 2: 0x00
    db 0b11111111  ; Row 3: 0xFF
    db 0b00000000  ; Row 4: 0x00
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x70 (ASCII 46: Period '.')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00000000  ; Row 2: 0x00
    db 0b00000000  ; Row 3: 0x00
    db 0b00000000  ; Row 4: 0x00
    db 0b00011000  ; Row 5: 0x18
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x78 (ASCII 47: Slash '/')
    db 0b00000001  ; Row 0: 0x01
    db 0b00000010  ; Row 1: 0x02
    db 0b00000110  ; Row 2: 0x06
    db 0b00001100  ; Row 3: 0x0C
    db 0b00011000  ; Row 4: 0x18
    db 0b00110000  ; Row 5: 0x30
    db 0b01100000  ; Row 6: 0x60
    db 0b11000000  ; Row 7: 0xC0

    ; Offset 0x80 (ASCII 48: Zero '0')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01101110  ; Row 2: 0x6E
    db 0b01110110  ; Row 3: 0x76
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x88 (ASCII 49: One '1')
    db 0b00011000  ; Row 0: 0x18
    db 0b00111000  ; Row 1: 0x38
    db 0b01111000  ; Row 2: 0x78
    db 0b00011000  ; Row 3: 0x18
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b01111110  ; Row 6: 0x7E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x90 (ASCII 50: Two '2')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b00000110  ; Row 2: 0x06
    db 0b00001100  ; Row 3: 0x0C
    db 0b00110000  ; Row 4: 0x30
    db 0b01100000  ; Row 5: 0x60
    db 0b01111110  ; Row 6: 0x7E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x98 (ASCII 51: Three '3')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b00000110  ; Row 2: 0x06
    db 0b00011100  ; Row 3: 0x1C
    db 0b00000110  ; Row 4: 0x06
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xA0 (ASCII 52: Four '4')
    db 0b00001100  ; Row 0: 0x0C
    db 0b00011100  ; Row 1: 0x1C
    db 0b00111100  ; Row 2: 0x3C
    db 0b01101100  ; Row 3: 0x6C
    db 0b11111110  ; Row 4: 0xFE
    db 0b00001100  ; Row 5: 0x0C
    db 0b00001100  ; Row 6: 0x0C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xA8 (ASCII 53: Five '5')
    db 0b01111110  ; Row 0: 0x7E
    db 0b01100000  ; Row 1: 0x60
    db 0b01111100  ; Row 2: 0x7C
    db 0b00000110  ; Row 3: 0x06
    db 0b00000110  ; Row 4: 0x06
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xB0 (ASCII 54: Six '6')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100000  ; Row 1: 0x60
    db 0b01111100  ; Row 2: 0x7C
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xB8 (ASCII 55: Seven '7')
    db 0b01111110  ; Row 0: 0x7E
    db 0b01100110  ; Row 1: 0x66
    db 0b00000110  ; Row 2: 0x06
    db 0b00001100  ; Row 3: 0x0C
    db 0b00001100  ; Row 4: 0x0C
    db 0b00001100  ; Row 5: 0x0C
    db 0b00001100  ; Row 6: 0x0C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xC0 (ASCII 56: Eight '8')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b00111100  ; Row 3: 0x3C
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xC8 (ASCII 57: Nine '9')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b00111110  ; Row 3: 0x3E
    db 0b00000110  ; Row 4: 0x06
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xD0 (ASCII 58: Colon ':')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00000000  ; Row 4: 0x00
    db 0b00011000  ; Row 5: 0x18
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xD8 (ASCII 59: Semicolon ';')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00000000  ; Row 4: 0x00
    db 0b00011000  ; Row 5: 0x18
    db 0b00011000  ; Row 6: 0x18
    db 0b00110000  ; Row 7: 0x30

    ; Offset 0xE0 (ASCII 60: Less Than '<')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000110  ; Row 1: 0x06
    db 0b00011000  ; Row 2: 0x18
    db 0b01100000  ; Row 3: 0x60
    db 0b11000000  ; Row 4: 0xC0
    db 0b01100000  ; Row 5: 0x60
    db 0b00011000  ; Row 6: 0x18
    db 0b00000110  ; Row 7: 0x06

    ; Offset 0xE8 (ASCII 61: Equals '=')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b11111111  ; Row 2: 0xFF
    db 0b00000000  ; Row 3: 0x00
    db 0b11111111  ; Row 4: 0xFF
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0xF0 (ASCII 62: Greater Than '>')
    db 0b00000000  ; Row 0: 0x00
    db 0b01100000  ; Row 1: 0x60
    db 0b00011000  ; Row 2: 0x18
    db 0b00000110  ; Row 3: 0x06
    db 0b00000011  ; Row 4: 0x03
    db 0b00000110  ; Row 5: 0x06
    db 0b00011000  ; Row 6: 0x18
    db 0b01100000  ; Row 7: 0x60

    ; Offset 0xF8 (ASCII 63: Question Mark '?')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b00000110  ; Row 2: 0x06
    db 0b00001100  ; Row 3: 0x0C
    db 0b00011000  ; Row 4: 0x18
    db 0b00000000  ; Row 5: 0x00
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x100 (ASCII 64: At Sign '@')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01000010  ; Row 1: 0x42
    db 0b01011010  ; Row 2: 0x5A
    db 0b01011110  ; Row 3: 0x5E
    db 0b01011110  ; Row 4: 0x5E
    db 0b01000000  ; Row 5: 0x40
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x108 (ASCII 65: Capital A 'A')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01111110  ; Row 3: 0x7E
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x110 (ASCII 66: Capital B 'B')
    db 0b01111100  ; Row 0: 0x7C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01111100  ; Row 3: 0x7C
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b01111100  ; Row 6: 0x7C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x118 (ASCII 67: Capital C 'C')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100000  ; Row 2: 0x60
    db 0b01100000  ; Row 3: 0x60
    db 0b01100000  ; Row 4: 0x60
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x120 (ASCII 68: Capital D 'D')
    db 0b01111100  ; Row 0: 0x7C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100010  ; Row 2: 0x62
    db 0b01100010  ; Row 3: 0x62
    db 0b01100010  ; Row 4: 0x62
    db 0b01100110  ; Row 5: 0x66
    db 0b01111100  ; Row 6: 0x7C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x128 (ASCII 69: Capital E 'E')
    db 0b01111110  ; Row 0: 0x7E
    db 0b01100000  ; Row 1: 0x60
    db 0b01100000  ; Row 2: 0x60
    db 0b01111100  ; Row 3: 0x7C
    db 0b01100000  ; Row 4: 0x60
    db 0b01100000  ; Row 5: 0x60
    db 0b01111110  ; Row 6: 0x7E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x130 (ASCII 70: Capital F 'F')
    db 0b01111110  ; Row 0: 0x7E
    db 0b01100000  ; Row 1: 0x60
    db 0b01100000  ; Row 2: 0x60
    db 0b01111100  ; Row 3: 0x7C
    db 0b01100000  ; Row 4: 0x60
    db 0b01100000  ; Row 5: 0x60
    db 0b01100000  ; Row 6: 0x60
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x138 (ASCII 71: Capital G 'G')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100000  ; Row 2: 0x60
    db 0b01100000  ; Row 3: 0x60
    db 0b01101110  ; Row 4: 0x6E
    db 0b01100110  ; Row 5: 0x66
    db 0b00111110  ; Row 6: 0x3E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x140 (ASCII 72: Capital H 'H')
    db 0b01100110  ; Row 0: 0x66
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01111110  ; Row 3: 0x7E
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x148 (ASCII 73: Capital I 'I')
    db 0b00111100  ; Row 0: 0x3C
    db 0b00011000  ; Row 1: 0x18
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x150 (ASCII 74: Capital J 'J')
    db 0b00011110  ; Row 0: 0x1E
    db 0b00001100  ; Row 1: 0x0C
    db 0b00001100  ; Row 2: 0x0C
    db 0b00001100  ; Row 3: 0x0C
    db 0b01101100  ; Row 4: 0x6C
    db 0b01101100  ; Row 5: 0x6C
    db 0b00111000  ; Row 6: 0x38
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x158 (ASCII 75: Capital K 'K')
    db 0b01100110  ; Row 0: 0x66
    db 0b01101100  ; Row 1: 0x6C
    db 0b01111000  ; Row 2: 0x78
    db 0b01110000  ; Row 3: 0x70
    db 0b01111000  ; Row 4: 0x78
    db 0b01101100  ; Row 5: 0x6C
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x160 (ASCII 76: Capital L 'L')
    db 0b01100000  ; Row 0: 0x60
    db 0b01100000  ; Row 1: 0x60
    db 0b01100000  ; Row 2: 0x60
    db 0b01100000  ; Row 3: 0x60
    db 0b01100000  ; Row 4: 0x60
    db 0b01100000  ; Row 5: 0x60
    db 0b01111110  ; Row 6: 0x7E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x168 (ASCII 77: Capital M 'M')
    db 0b01100011  ; Row 0: 0x63
    db 0b01110111  ; Row 1: 0x77
    db 0b01111111  ; Row 2: 0x7F
    db 0b01101010  ; Row 3: 0x6A
    db 0b01100011  ; Row 4: 0x63
    db 0b01100011  ; Row 5: 0x63
    db 0b01100011  ; Row 6: 0x63
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x170 (ASCII 78: Capital N 'N')
    db 0b01100011  ; Row 0: 0x63
    db 0b01110011  ; Row 1: 0x73
    db 0b01111011  ; Row 2: 0x7B
    db 0b01111111  ; Row 3: 0x7F
    db 0b01101111  ; Row 4: 0x6F
    db 0b01100111  ; Row 5: 0x67
    db 0b01100011  ; Row 6: 0x63
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x178 (ASCII 79: Capital O 'O')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x180 (ASCII 80: Capital P 'P')
    db 0b01111100  ; Row 0: 0x7C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01111100  ; Row 3: 0x7C
    db 0b01100000  ; Row 4: 0x60
    db 0b01100000  ; Row 5: 0x60
    db 0b01100000  ; Row 6: 0x60
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1B8 (ASCII 81: Capital Q 'Q')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x190 (ASCII 82: Capital R 'R')
    db 0b01111100  ; Row 0: 0x7C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01111100  ; Row 3: 0x7C
    db 0b01101100  ; Row 4: 0x6C
    db 0b01100110  ; Row 5: 0x66
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x198 (ASCII 83: Capital S 'S')
    db 0b00111100  ; Row 0: 0x3C
    db 0b01100110  ; Row 1: 0x66
    db 0b01100000  ; Row 2: 0x60
    db 0b00111100  ; Row 3: 0x3C
    db 0b00000110  ; Row 4: 0x06
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1A0 (ASCII 84: Capital T 'T')
    db 0b01111110  ; Row 0: 0x7E
    db 0b01011010  ; Row 1: 0x5A
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1A8 (ASCII 85: Capital U 'U')
    db 0b01100110  ; Row 0: 0x66
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1B0 (ASCII 86: Capital V 'V')
    db 0b01100110  ; Row 0: 0x66
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b01100110  ; Row 3: 0x66
    db 0b00100100  ; Row 4: 0x24
    db 0b00100100  ; Row 5: 0x24
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1B8 (ASCII 87: Capital W 'W')
    db 0b01100011  ; Row 0: 0x63
    db 0b01100011  ; Row 1: 0x63
    db 0b01100011  ; Row 2: 0x63
    db 0b01101011  ; Row 3: 0x6B
    db 0b01101011  ; Row 4: 0x6B
    db 0b01111111  ; Row 5: 0x7F
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1C0 (ASCII 88: Capital X 'X')
    db 0b01100110  ; Row 0: 0x66
    db 0b01100110  ; Row 1: 0x66
    db 0b00111100  ; Row 2: 0x3C
    db 0b00011000  ; Row 3: 0x18
    db 0b00111100  ; Row 4: 0x3C
    db 0b01100110  ; Row 5: 0x66
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1C8 (ASCII 89: Capital Y 'Y')
    db 0b01100110  ; Row 0: 0x66
    db 0b01100110  ; Row 1: 0x66
    db 0b01100110  ; Row 2: 0x66
    db 0b00111100  ; Row 3: 0x3C
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1D0 (ASCII 90: Capital Z 'Z')
    db 0b01111110  ; Row 0: 0x7E
    db 0b00000110  ; Row 1: 0x06
    db 0b00001100  ; Row 2: 0x0C
    db 0b00011000  ; Row 3: 0x18
    db 0b00110000  ; Row 4: 0x30
    db 0b01100000  ; Row 5: 0x60
    db 0b01111110  ; Row 6: 0x7E
    db 0b00000000  ; Row 7: 0x00
    
    ; Offset 0x1D8 (ASCII 91: Left Square Bracket '[')
    db 0b00011110  ; Row 0: 0x1E
    db 0b00011000  ; Row 1: 0x18
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00011110  ; Row 6: 0x1E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1E0 (ASCII 92: Backslash '\')
    db 0b01100000  ; Row 0: 0x60
    db 0b01100000  ; Row 1: 0x60
    db 0b00110000  ; Row 2: 0x30
    db 0b00110000  ; Row 3: 0x30
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00001100  ; Row 6: 0x0C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1E8 (ASCII 93: Right Square Bracket ']')
    db 0b00011110  ; Row 0: 0x1E
    db 0b00001100  ; Row 1: 0x0C
    db 0b00001100  ; Row 2: 0x0C
    db 0b00001100  ; Row 3: 0x0C
    db 0b00001100  ; Row 4: 0x0C
    db 0b00001100  ; Row 5: 0x0C
    db 0b00011110  ; Row 6: 0x1E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1F0 (ASCII 94: Caret '^')
    db 0b00001000  ; Row 0: 0x08
    db 0b00011100  ; Row 1: 0x1C
    db 0b00110110  ; Row 2: 0x36
    db 0b01100011  ; Row 3: 0x63
    db 0b01100011  ; Row 4: 0x63
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x1F8 (ASCII 95: Underscore '_')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00000000  ; Row 2: 0x00
    db 0b00000000  ; Row 3: 0x00
    db 0b00000000  ; Row 4: 0x00
    db 0b00000000  ; Row 5: 0x00
    db 0b01111111  ; Row 6: 0x7F
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x200 (ASCII 96: Grave Accent '`')
    db 0b00011000  ; Row 0: 0x18
    db 0b00001100  ; Row 1: 0x0C
    db 0b00000110  ; Row 2: 0x06
    db 0b00000000  ; Row 3: 0x00
    db 0b00000000  ; Row 4: 0x00
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x208 (ASCII 97: Lowercase a 'a')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011100  ; Row 2: 0x1C
    db 0b00100010  ; Row 3: 0x22
    db 0b00111110  ; Row 4: 0x3E
    db 0b01000010  ; Row 5: 0x42
    db 0b00111110  ; Row 6: 0x3E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x210 (ASCII 98: Lowercase b 'b')
    db 0b01100000  ; Row 0: 0x60
    db 0b01100000  ; Row 1: 0x60
    db 0b01111100  ; Row 2: 0x7C
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01111100  ; Row 5: 0x7C
    db 0b01100000  ; Row 6: 0x60
    db 0b01100000  ; Row 7: 0x60

    ; Offset 0x218 (ASCII 99: Lowercase c 'c')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011100  ; Row 2: 0x1C
    db 0b00100010  ; Row 3: 0x22
    db 0b00100000  ; Row 4: 0x20
    db 0b00111100  ; Row 5: 0x3C
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x220 (ASCII 100: Lowercase d 'd')
    db 0b00000110  ; Row 0: 0x06
    db 0b00000110  ; Row 1: 0x06
    db 0b00011110  ; Row 2: 0x1E
    db 0b00110110  ; Row 3: 0x36
    db 0b00100110  ; Row 4: 0x26
    db 0b00011110  ; Row 5: 0x1E
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x228 (ASCII 101: Lowercase e 'e')
    db 0b00000000  ; Row 0: 0x00
    db 0b00011100  ; Row 1: 0x1C
    db 0b00100010  ; Row 2: 0x22
    db 0b00111110  ; Row 3: 0x3E
    db 0b00111110  ; Row 4: 0x3E
    db 0b00100000  ; Row 5: 0x20
    db 0b00011110  ; Row 6: 0x1E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x230 (ASCII 102: Lowercase f 'f')
    db 0b00011100  ; Row 0: 0x1C
    db 0b00100010  ; Row 1: 0x22
    db 0b00111110  ; Row 2: 0x3E
    db 0b00100010  ; Row 3: 0x22
    db 0b00100000  ; Row 4: 0x20
    db 0b00100000  ; Row 5: 0x20
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x238 (ASCII 103: Lowercase g 'g')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011110  ; Row 2: 0x1E
    db 0b00100110  ; Row 3: 0x26
    db 0b00111110  ; Row 4: 0x3E
    db 0b00000110  ; Row 5: 0x06
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x240 (ASCII 104: Lowercase h 'h')
    db 0b01100000  ; Row 0: 0x60
    db 0b01100000  ; Row 1: 0x60
    db 0b01111100  ; Row 2: 0x7C
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x248 (ASCII 105: Lowercase i 'i')
    db 0b00011000  ; Row 0: 0x18
    db 0b00000000  ; Row 1: 0x00
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00011110  ; Row 6: 0x1E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x250 (ASCII 106: Lowercase j 'j')
    db 0b00000110  ; Row 0: 0x06
    db 0b00000000  ; Row 1: 0x00
    db 0b00000110  ; Row 2: 0x06
    db 0b00000110  ; Row 3: 0x06
    db 0b00000110  ; Row 4: 0x06
    db 0b00000110  ; Row 5: 0x06
    db 0b00000110  ; Row 6: 0x06
    db 0b00111100  ; Row 7: 0x3C

    ; Offset 0x258 (ASCII 107: Lowercase k 'k')
    db 0b01100000  ; Row 0: 0x60
    db 0b01100000  ; Row 1: 0x60
    db 0b01100110  ; Row 2: 0x66
    db 0b01101100  ; Row 3: 0x6C
    db 0b01111000  ; Row 4: 0x78
    db 0b01101100  ; Row 5: 0x6C
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x260 (ASCII 108: Lowercase l 'l')
    db 0b00011110  ; Row 0: 0x1E
    db 0b00000110  ; Row 1: 0x06
    db 0b00000110  ; Row 2: 0x06
    db 0b00000110  ; Row 3: 0x06
    db 0b00000110  ; Row 4: 0x06
    db 0b00000110  ; Row 5: 0x06
    db 0b00000110  ; Row 6: 0x06
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x268 (ASCII 109: Lowercase m 'm')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01101010  ; Row 2: 0x6A
    db 0b01110101  ; Row 3: 0x75
    db 0b01110101  ; Row 4: 0x75
    db 0b01101010  ; Row 5: 0x6A
    db 0b01100010  ; Row 6: 0x62
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x270 (ASCII 110: Lowercase n 'n')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01111100  ; Row 2: 0x7C
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01100110  ; Row 5: 0x66
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x278 (ASCII 111: Lowercase o 'o')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011100  ; Row 2: 0x1C
    db 0b00100010  ; Row 3: 0x22
    db 0b00100010  ; Row 4: 0x22
    db 0b00011100  ; Row 5: 0x1C
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x280 (ASCII 112: Lowercase p 'p')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01111100  ; Row 2: 0x7C
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b01111100  ; Row 5: 0x7C
    db 0b01100000  ; Row 6: 0x60
    db 0b01100000  ; Row 7: 0x60

    ; Offset 0x288 (ASCII 113: Lowercase q 'q')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011110  ; Row 2: 0x1E
    db 0b00110110  ; Row 3: 0x36
    db 0b00100110  ; Row 4: 0x26
    db 0b00011110  ; Row 5: 0x1E
    db 0b00000110  ; Row 6: 0x06
    db 0b00000110  ; Row 7: 0x06

    ; Offset 0x290 (ASCII 114: Lowercase r 'r')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01111100  ; Row 2: 0x7C
    db 0b01100110  ; Row 3: 0x66
    db 0b01100000  ; Row 4: 0x60
    db 0b01100000  ; Row 5: 0x60
    db 0b01100000  ; Row 6: 0x60
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x298 (ASCII 115: Lowercase s 's')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00011100  ; Row 2: 0x1C
    db 0b00100010  ; Row 3: 0x22
    db 0b00011100  ; Row 4: 0x1C
    db 0b00000110  ; Row 5: 0x06
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2A0 (ASCII 116: Lowercase t 't')
    db 0b00000000  ; Row 0: 0x00
    db 0b00001000  ; Row 1: 0x08
    db 0b00011100  ; Row 2: 0x1C
    db 0b00001000  ; Row 3: 0x08
    db 0b00001000  ; Row 4: 0x08
    db 0b00001000  ; Row 5: 0x08
    db 0b00000110  ; Row 6: 0x06
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2A8 (ASCII 117: Lowercase u 'u')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01100110  ; Row 2: 0x66
    db 0b01100110  ; Row 3: 0x66
    db 0b01100110  ; Row 4: 0x66
    db 0b00111110  ; Row 5: 0x3E
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2B0 (ASCII 118: Lowercase v 'v')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01100110  ; Row 2: 0x66
    db 0b01100110  ; Row 3: 0x66
    db 0b00111100  ; Row 4: 0x3C
    db 0b00111100  ; Row 5: 0x3C
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2B8 (ASCII 119: Lowercase w 'w')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01100011  ; Row 2: 0x63
    db 0b01101011  ; Row 3: 0x6B
    db 0b01101011  ; Row 4: 0x6B
    db 0b00111110  ; Row 5: 0x3E
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2C0 (ASCII 120: Lowercase x 'x')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01100110  ; Row 2: 0x66
    db 0b00111100  ; Row 3: 0x3C
    db 0b00011000  ; Row 4: 0x18
    db 0b00111100  ; Row 5: 0x3C
    db 0b01100110  ; Row 6: 0x66
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2C8 (ASCII 121: Lowercase y 'y')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01100110  ; Row 2: 0x66
    db 0b01100110  ; Row 3: 0x66
    db 0b00111110  ; Row 4: 0x3E
    db 0b00000110  ; Row 5: 0x06
    db 0b00111100  ; Row 6: 0x3C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2D0 (ASCII 122: Lowercase z 'z')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b01111110  ; Row 2: 0x7E
    db 0b00000110  ; Row 3: 0x06
    db 0b00001100  ; Row 4: 0x0C
    db 0b00011000  ; Row 5: 0x18
    db 0b01111110  ; Row 6: 0x7E
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2D8 (ASCII 123: Left Curly Brace '{')
    db 0b00011100  ; Row 0: 0x1C
    db 0b00011000  ; Row 1: 0x18
    db 0b00011000  ; Row 2: 0x18
    db 0b00110000  ; Row 3: 0x30
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00011100  ; Row 6: 0x1C
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2E0 (ASCII 124: Vertical Bar '|')
    db 0b00011000  ; Row 0: 0x18
    db 0b00011000  ; Row 1: 0x18
    db 0b00011000  ; Row 2: 0x18
    db 0b00011000  ; Row 3: 0x18
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b00011000  ; Row 6: 0x18
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2E8 (ASCII 125: Right Curly Brace '}')
    db 0b01110000  ; Row 0: 0x70
    db 0b00011000  ; Row 1: 0x18
    db 0b00011000  ; Row 2: 0x18
    db 0b00001100  ; Row 3: 0x0C
    db 0b00011000  ; Row 4: 0x18
    db 0b00011000  ; Row 5: 0x18
    db 0b01110000  ; Row 6: 0x70
    db 0b00000000  ; Row 7: 0x00

    ; Offset 0x2F0 (ASCII 126: Tilde '~')
    db 0b00000000  ; Row 0: 0x00
    db 0b00000000  ; Row 1: 0x00
    db 0b00110010  ; Row 2: 0x32
    db 0b01001100  ; Row 3: 0x4C
    db 0b00000000  ; Row 4: 0x00
    db 0b00000000  ; Row 5: 0x00
    db 0b00000000  ; Row 6: 0x00
    db 0b00000000  ; Row 7: 0x00

Now, we have our ASCII characters bitmap table from ASCII (32 to 126).

Now we need a way to get the offset in the table for able to read the particular character bitmap.

To get the offset in the table to read the particular character bitmap, you can calculate the offset based on the ASCII value of the character. Here's a simple formula to calculate the offset:

offset = (ASCII_value - 32) * bitmap_size

Where:

  • ASCII_value is the ASCII value of the character.
  • bitmap_size is the size of each bitmap in bytes.
  • 32 is the starting ASCII character bitmap.

For example, if the ASCII value of the character is 65 ('A') and each bitmap is 8 bytes in size, the offset would be:

offset = (65 - 32) * 8
       = 33 * 8
       = 264

Note: Each bitmap is 8 bytes (one byte per row) that's why we multiplied by 8

This means the bitmap for the character 'A' would be located at offset 264 in your bitmap table. You can then use this offset to access the bitmap in your table.

Calculating the Offset in Code:

; Suppose ax carries the character to print
; let ax = A, which is ASCII 65

sub ax, 32 	; ASCII_value - 32 (first printable character)
mov bx, 8	; Each character bitmap is 8 bytes
mul bx		; AX = (ASCII_value - 32) * 8
mov bx, ax	; BX = offset in bitmap table

;; We can do the multiplication by 8 with shifting
; Since each character bitmap is 8 bytes, we can multiply by 8 using left shift by 3 (equivalent to multiplying by 2^3)
; Here is the code
sub ax, 32 	; ASCII_value - 32 (first printable character)
shl ax, 3	; Multiply by 8 using left shift
mov bx, ax	; BX = offset in bitmap table
; Now bx contains the offset of the charactet in bitmap table
; Just add the bx to bitmap_table starting address to reach to character offset.

mov esi, font_table
add esi, ebx

Here is the complete code:

; Draw the character on the screen
; eax = charaacter to print
; ebx = x position
; ecx = y position
draw_Char:
    pusha                    ; Save all general-purpose registers
	; Finding the offset of character in font table
	sub eax, 32	; ASCII_value - 32 (First Printable character)
	shl eax, 3	; Multiply by 8 using left shift (each character is 8 bytes long)

; pointing SI to the bitmap of character in the font_table by adding offset
	mov esi, font_table           ; SI point to the starting address of font_table
	add esi, eax	; Add the offset, SI now points to the character's bitmap in the font_table

	mov eax, ecx	; set Y position to ax
	mov edx, MODE_13_SCREEN_WIDTH              ; Screen width in mode 13h (320 pixels)

	mul edx                   ; AX = Y * 320
	add eax, ebx               ; AX += X position
	mov edi, eax               ; DI = Screen position (Y * 320 + X)

	add edi, FRAMEBUFFER_ADDRESS_MODE_13

	mov cx, 8		; 8 rows per character
next_row32:
	lodsb                    ; Load next byte from font_A to AL
	mov ah, al               ; Copy to AH
	mov al, 0                ; Clear AL
	mov edx, 8                ; 8 bits per row
next_pixel32:
	shl ah, 1                ; Shift AH right
	jnc skip_pixel32           ; If carry (bit 0) is 0, skip
	mov byte [edi], 0x0F   ; Set pixel color to white (0x0F)
skip_pixel32:
	inc edi		; Move to next pixel on screen
	dec edx		; Move to next bit
	jnz next_pixel32           ; Repeat for 8 bits (pixels)

	add edi, MODE_13_SCREEN_WIDTH - 8          ; Move to the start of the next row
	loop next_row32            ; Repeat for 8 rows

	popa                     ; Restore all general-purpose registers
ret

3 Drawing String

Since we can draw a single character onto the screen. Now, we can use that function to draw the string of characters.

We will do it as follows:

  • print_string Function:
    • Loads each character form the string.
    • Checks for the end of the string (null terminator).
    • Calls draw_Char for each character.
    • Moves the x position to the right by 8 pixels for the next character.
  • draw_Char Function:
    • Calculates the offset for the character in the font table.
    • Points esi to the character's bitmap.
    • Calculates the screen position.
    • Draws the character on the screen by setting pixels based on the bitmap.
section .data
font_table:
    ; Space (ASCII 32)
    db 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000
    ; 'A' (ASCII 65)
    db 0b00011000, 0b00100100, 0b00100100, 0b00111100, 0b00100100, 0b00100100, 0b00100100, 0b00000000
    ; 'B' (ASCII 66)
    db 0b00111000, 0b00100100, 0b00100100, 0b00111000, 0b00100100, 0b00100100, 0b00111000, 0b00000000
    ; ... additional characters as needed

MODE_13_SCREEN_WIDTH equ 320
FRAMEBUFFER_ADDRESS_MODE_13 equ 0xA0000

section .bss

section .text
extern _start

_start:
    ; Set up GDT and enter protected mode as before

    ; Call print_string with the string to print, x position, y position, and color
    mov esi, string_to_print  ; ESI points to the string
    mov ebx, 50               ; X position
    mov ecx, 50               ; Y position
    mov edx, 15               ; Color
    call print_string

    ; Infinite loop
    cli
    hlt

string_to_print db 'AB', 0   ; String to print, null-terminated

print_string:
    pusha                    ; Save all general-purpose registers

.next_char:
    lodsb                    ; Load the next byte (character) from string into AL
    test al, al              ; Check if AL is zero (end of string)
    jz .done                 ; If zero, we're done

    ; Draw the character at the current position
    mov eax, al              ; Character to print
    push ebx                 ; Save X position
    push ecx                 ; Save Y position
    call draw_Char
    pop ecx                  ; Restore Y position
    pop ebx                  ; Restore X position

    ; Advance to the next character position
    add ebx, 8               ; Move to the next character position (8 pixels to the right)
    jmp .next_char           ; Repeat for the next character

.done:
    popa                     ; Restore all general-purpose registers
    ret

; Draw the character on the screen
; eax = character to print
; ebx = x position
; ecx = y position
draw_Char:
    pusha                    ; Save all general-purpose registers
    ; Finding the offset of character in font table
    sub eax, 32              ; ASCII_value - 32 (First Printable character)
    shl eax, 3               ; Multiply by 8 using left shift (each character is 8 bytes long)

    ; pointing SI to the bitmap of character in the font_table by adding offset
    mov esi, font_table      ; SI points to the starting address of font_table
    add esi, eax             ; Add the offset, SI now points to the character's bitmap in the font_table

    mov eax, ecx             ; set Y position to ax
    mov edx, MODE_13_SCREEN_WIDTH ; Screen width in mode 13h (320 pixels)
    mul edx                  ; AX = Y * 320
    add eax, ebx             ; AX += X position
    mov edi, eax             ; DI = Screen position (Y * 320 + X)

    add edi, FRAMEBUFFER_ADDRESS_MODE_13

    mov cx, 8                ; 8 rows per character
next_row32:
    lodsb                    ; Load next byte from font_table to AL
    mov ah, al               ; Copy to AH
    mov al, 0                ; Clear AL
    mov edx, 8               ; 8 bits per row
next_pixel32:
    shl ah, 1                ; Shift AH left
    jnc skip_pixel32         ; If carry (bit 0) is 0, skip
    mov byte [edi], 0x0F     ; Set pixel color to white (0x0F)
skip_pixel32:
    inc edi                  ; Move to next pixel on screen
    dec edx                  ; Move to next bit
    jnz next_pixel32         ; Repeat for 8 bits (pixels)

    add edi, MODE_13_SCREEN_WIDTH - 8 ; Move to the start of the next row
    loop next_row32          ; Repeat for 8 rows

    popa                     ; Restore all general-purpose registers
    ret

4 Drawing Line

%define FRAMEBUFFER_ADDRESS 0xA0000
%define SCREEN_WIDTH 320
%define SCREEN_HEIGHT 200

section .data
font_table: db 0 ; Placeholder for font table

section .text
extern _start

; set_pixel:
; Inputs:
; eax = x position
; ebx = y position
; dl = color
set_pixel:
    push ebp
    mov ebp, esp

    ; Calculate the framebuffer offset
    mov ecx, SCREEN_WIDTH
    mul ecx               ; EAX = y * SCREEN_WIDTH
    add eax, ebx          ; EAX += x
    add eax, FRAMEBUFFER_ADDRESS ; Add base address of framebuffer

    mov [eax], dl         ; Set the pixel color

    pop ebp
    ret

; draw_line:
; Inputs:
; eax = x1
; ebx = y1
; ecx = x2
; edx = y2
; [esp + 4] = color
draw_line:
    pusha                 ; Save all general-purpose registers
    push ebp
    mov ebp, esp
    sub esp, 4            ; Reserve space for local variables

    ; Load the color from the stack
    mov dl, [ebp + 12]

    ; Calculate dx and dy
    mov esi, ecx          ; esi = x2
    sub esi, eax          ; esi = dx = x2 - x1
    mov edi, edx          ; edi = y2
    sub edi, ebx          ; edi = dy = y2 - y1

    ; Determine the direction of the line
    mov ebp, esi
    sar ebp, 31           ; Sign extend dx
    mov ecx, edi
    sar ecx, 31           ; Sign extend dy

    ; Absolute values of dx and dy
    xor esi, ebp
    sub esi, ebp
    xor edi, ecx
    sub edi, ecx

    ; Initialize decision parameter
    cmp esi, edi
    jge .not_steep        ; If |dx| >= |dy|, the line is not steep

    ; Steep line
    xchg eax, ebx         ; Swap x1 and y1
    xchg ecx, edx         ; Swap x2 and y2
    xchg esi, edi         ; Swap dx and dy

.not_steep:
    mov ebp, eax          ; ebp = x
    mov ecx, ebx          ; ecx = y
    mov edi, esi          ; edi = dx
    shr edi, 1            ; edi = dx / 2

.loop:
    ; Draw the pixel
    push ecx              ; Save y
    push ebp              ; Save x
    call set_pixel
    pop ebp               ; Restore x
    pop ecx               ; Restore y

    ; Update the decision parameter
    sub edi, edi          ; edi -= dy
    jge .no_step_y
    add ecx, 1            ; y += 1
    add edi, esi          ; p += dx

.no_step_y:
    add ebp, 1            ; x += 1

    ; Loop until we reach the end point
    cmp ebp, eax          ; Compare x1 with x2
    jle .loop

.end:
    add esp, 4            ; Deallocate local variable space
    pop ebp
    popa                  ; Restore all general-purpose registers
    ret

section .bss