OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 1:52 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 9 posts ] 
Author Message
 Post subject: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Wed Aug 09, 2017 7:45 am 
Offline
Member
Member

Joined: Wed Aug 09, 2017 7:37 am
Posts: 80
Hello.

I've made a simple kernel (or second sector booted by INT 13h if preferred) that prints Hello World! on the screen. I want to add color to every character that prints. I am using the 0xb800 VGA address.

Code:
BITS 16
org 0x7c00

jmp Main

Main:
mov dx, 0xb800
mov es, dx

mov si, msg
mov cx, 0

Print:
lodsb
cmp al, 0
je Done

mov di, cx
mov byte [es:di], 0x1f ; color that is expected to work.
mov byte [es:di], al ; this prints the actual character

inc cx
inc cx

jmp Print

Done:
ret

msg db 'Hello World!', 90, 0 ; I put in NOP operand because of a little problem... ignore this


By the way, when I print 13 (0x0D or carriage return), 10 (0x0A or line feed) to make a new line, it only shows characters. It doesn't actually do a new line. Why is this? I just see a music symbol (that's supposed to be 0x0E) and a triangle, not a new line.

Any help is appreciated.

Thanks
Steve.


Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Wed Aug 09, 2017 8:09 am 
Offline
Member
Member

Joined: Sat Mar 01, 2014 2:59 pm
Posts: 1146
The colour should be located at the memory location after the character that you want it to apply to. Your code is doing
Code:
mov di, cx
mov byte [es:di], 0x1f ; color that is expected to work.
mov byte [es:di], al ; this prints the actual character

inc cx
inc cx
which places the colour in memory at the location where the character is supposed to be, and then places the character instead where it's supposed to go. Your mistake is that you're putting both bytes (the character and the colour) at the same location.

What you actually need to do is
Code:
; place character
mov di, cx
mov byte [es:di], al
inc cx

; set colour
mov di, cx
mov byte [es:di], 0x1f
inc cx
Personally however I prefer to set them both at the same time.
Code:
mov di, cx
mov ah, 0x1f ; put the colour in the upper byte of ax
mov word [es:di], ax ; set the character and the colour at the same time
inc cx
inc cx
This uses the useful feature of the x86 architecture that the ah and al registers combine to form ax, so you can set them separately and then access them together. Also because the x86 architecture is little-endian, the lower byte of ax (which is al, the character) will be placed first in memory, followed by the upper byte which is the colour and is placed after the character in memory where it should be.

_________________
When you start writing an OS you do the minimum possible to get the x86 processor in a usable state, then you try to get as far away from it as possible.

Syntax checkup:
Wrong: OS's, IRQ's, zero'ing
Right: OSes, IRQs, zeroing


Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Wed Aug 09, 2017 8:22 am 
Offline
Member
Member
User avatar

Joined: Sat Mar 31, 2012 3:07 am
Posts: 4591
Location: Chichester, UK
CR and LF are not characters (well, they are - the symbols that you see, but that's not what you want) but logical concepts. If you want a new line you just start writing characters to the appropriate address (0xb8000 for the first line, 0xb80a0 for the next line, etc.).


Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Wed Aug 09, 2017 8:26 am 
Offline
Member
Member

Joined: Sat Mar 01, 2014 2:59 pm
Posts: 1146
The reason why your carriage return and line feed characters are displaying wrong is because the VGA doesn't understand text flow, it's just a grid of characters. Think of it like this (ignoring colour):
Code:
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
When you put the text "hello world" on the screen, it becomes this:
Code:
[h][e][l][l][o][ ][w][o][r][l]
[d][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
The text wraps because each "box" is a memory location and the first line of boxes comes first in memory, followed by the second line of boxes, and so on.

Suppose you change the text to "hello\nworld" ("\n" refers to the line feed character). Your character grid will become:
Code:
[h][e][l][l][o][*][w][o][r][l]
[d][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
where "*" is the line feed character. The VGA doesn't put the text on the next line, because the characters are in the memory locations for the previous line. Control characters (most commonly carriage return, line feed, tab, and delete) don't mean anything to it. This means that you have to handle these characters yourself, by changing the memory address at which you put the characters to "skip" to the next line:
Code:
[h][e][l][l][o][ ][ ][ ][ ][ ]
[w][o][r][l][d][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
Notice that there is a row of blank "boxes" after the word "hello" continuing to the end of the line, before the word "world" appears. In the computer's memory, which is a single straight line, this appears as:
Code:
[h][e][l][l][o][ ][ ][ ][ ][ ][w][o][r][l][d][ ][ ][ ][ ][ ]...
The reason why you get music notes and triangles is because, since the VGA doesn't understand these control characters, they're used to display extra symbols. All of the character codes from 0x00 to 0x1F (0x20 is a space) are used like this. A similar thing is done with the characters from 0x7F (delete) upwards, and you can find a table of all of the available characters here.

Also note that some platforms use both a carriage return and a line feed to represent a newline, and others use just a line feed. When you display this, you want to display only a single new line. My preferred way to handle this is to output a new line whenever the line feed character occurs in the source string and to ignore the carriage return without either outputting it to the screen or changing the address where the next character will be located. This will handle both platforms that use a carriage return and a line feed and those that use just a line feed, and you don't need to detect the sequence of a carriage return followed by a line feed and can simply handle each character on its own.

_________________
When you start writing an OS you do the minimum possible to get the x86 processor in a usable state, then you try to get as far away from it as possible.

Syntax checkup:
Wrong: OS's, IRQ's, zero'ing
Right: OSes, IRQs, zeroing


Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Wed Aug 09, 2017 9:27 am 
Offline
Member
Member
User avatar

Joined: Fri Aug 07, 2015 6:13 am
Posts: 1134
onlyonemac wrote:
The reason why you get music notes and triangles is because, since the VGA doesn't understand these control characters, they're used to display extra symbols. All of the character codes from 0x00 to 0x1F (0x20 is a space) are used like this. A similar thing is done with the characters from 0x7F (delete) upwards, and you can find a table of all of the available characters here.


Here is a more "detailed" version of it: https://en.wikipedia.org/wiki/Code_page_437
I use this one all the time.

_________________
OS: Basic OS
About: 32 Bit Monolithic Kernel Written in C++ and Assembly, Custom FAT 32 Bootloader


Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Wed Aug 09, 2017 10:11 am 
Offline
Member
Member

Joined: Wed Aug 09, 2017 7:37 am
Posts: 80
onlyonemac wrote:
The colour should be located at the memory location after the character that you want it to apply to. Your code is doing
Code:
mov di, cx
mov byte [es:di], 0x1f ; color that is expected to work.
mov byte [es:di], al ; this prints the actual character

inc cx
inc cx
which places the colour in memory at the location where the character is supposed to be, and then places the character instead where it's supposed to go. Your mistake is that you're putting both bytes (the character and the colour) at the same location.

What you actually need to do is
Code:
; place character
mov di, cx
mov byte [es:di], al
inc cx

; set colour
mov di, cx
mov byte [es:di], 0x1f
inc cx
Personally however I prefer to set them both at the same time.
Code:
mov di, cx
mov ah, 0x1f ; put the colour in the upper byte of ax
mov word [es:di], ax ; set the character and the colour at the same time
inc cx
inc cx
This uses the useful feature of the x86 architecture that the ah and al registers combine to form ax, so you can set them separately and then access them together. Also because the x86 architecture is little-endian, the lower byte of ax (which is al, the character) will be placed first in memory, followed by the upper byte which is the colour and is placed after the character in memory where it should be.


Thank you for solving my problem. I tried putting it after and before but I didn't do the other instructions. 0x0741 is 0x4107 in memory because of little endian! (see Section 1.3.1 of https://software.intel.com/sites/default/files/managed/a4/60/253665-sdm-vol-1.pdf).

I also checked out http://wiki.osdev.org/Printing_to_Screen to check if I was doing everything right.

Anyway, this solved my problem! Thank you so much, onlyonemac.


Last edited by stevewoods1986 on Wed Aug 09, 2017 10:19 am, edited 3 times in total.

Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Wed Aug 09, 2017 10:16 am 
Offline
Member
Member

Joined: Wed Aug 09, 2017 7:37 am
Posts: 80
iansjack wrote:
CR and LF are not characters (well, they are - the symbols that you see, but that's not what you want) but logical concepts. If you want a new line you just start writing characters to the appropriate address (0xb8000 for the first line, 0xb80a0 for the next line, etc.).


OK. Thank you. By the way, is that to do with 80*25 text mode?

Hello World!.................................................................... (thanks to Python for the multiple dots).
Goodbye World!


Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Thu Aug 10, 2017 10:09 am 
Offline
Member
Member
User avatar

Joined: Fri Oct 27, 2006 9:42 am
Posts: 1925
Location: Athens, GA, USA
It applies to any text mode, actually. The text video buffer is basically a single-dimensional array of character-attribute pairs (shown here as a FASM struc):

Code:
struc text_cell char attrib
{
    . db char,
    . db attrib
}


but each text mode's row size overlays it to effectively form a two-dimensional array.

This means that the best way to handle the insertion point - and the cursor in general - is to have a pair of integer variables for the x and y positions in that grid. For now, these could be globals, but you will probably want to make a struc for those which can be set up for a given text mode as needed.

EDIT: Sorry for cutting it off so abruptly, I had to leave for a medical appointment. I have a moment to post this now, but I can make further suggestions later if you like. I will need to read up on the virtual and rept directives to give a deeper answer.

_________________
Rev. First Speaker Schol-R-LEA;2 LCF ELF JAM POEE KoR KCO PPWMTF
Ordo OS Project
Lisp programmers tend to seem very odd to outsiders, just like anyone else who has had a religious experience they can't quite explain to others.


Top
 Profile  
 
 Post subject: Re: How to print with color using 0xb800 in Assembly? (VGA)
PostPosted: Fri Aug 11, 2017 1:33 pm 
Offline
Member
Member

Joined: Sat Mar 01, 2014 2:59 pm
Posts: 1146
Schol-R-LEA wrote:
This means that the best way to handle the insertion point - and the cursor in general - is to have a pair of integer variables for the x and y positions in that grid.
I second this. You'll probably want to write a module like this:
Code:
#define VGA_BASE_ADDRESS (0xB8000)
#define ROWS (25)
#define COLS (80)

int cursor_x = 0;
int cursor_y = 0;

// main function to put characters on the screen
void console_print_character(char character, unsigned char colour)
{
    if (character >= 0x20 && character <= 0x7E)    // printable characters
    {
        // put character on screen
        ((unsigned char*) VGA_BASE_ADDRESS)[(cursor_x + (cursor_y * COLS)) * 2] = character;
        ((unsigned char*) VGA_BASE_ADDRESS)[(cursor_x + (cursor_y * COLS)) * 2 + 1] = colour;

        // move cursor
        cursor_x++;
        if (cursor_x == COLS)
        {
            cursor_x = 0;
            cursor_y++;
            if (cursor_y == ROWS)
            {
                console_clear_screen();    // TODO: make this scroll the screen upwards rather than clear it completely
            }
        }
    }
    else if (character == 0x0A)    // line feed
    {
        // move cursor to next line
        cursor_x = 0;
        cursor_y++;
        if (cursor_y == ROWS)
        {
            console_clear_screen();    // TODO: make this scroll the screen upwards rather than clear it completely
        }
    }
   else if (character == 0x7F)   // delete
    {
        // move cursor back
        cursor_x--;
        if (cursor_x < 0)
        {
            cursor_y--;
            if (cursor_y < 0)
            {
                cursor_y == 0;
            }
        }

        // delete character
        ((unsigned char*) VGA_BASE_ADDRESS)[(cursor_x + (cursor_y * COLS)) * 2] = 0x20;    // space, which is also used to produce a "blank" character
        ((unsigned char*) VGA_BASE_ADDRESS)[(cursor_x + (cursor_y * COLS)) * 2 + 1] = 0;
    }
}

// print a string
void console_print_string(char* string)
{
    int index = 0;
    while (string[index] != 0)
    {
        console_print_character(string[index]);
    }
}

// clear the screen
void console_clear_screen()
{
    for (int y = 0; y < ROWS; y++)
    {
        for (int x = 0; x < COLS; x++)
        {
            ((unsigned char*) VGA_BASE_ADDRESS)[(cursor_x + (cursor_y * COLS)) * 2] = 0x20;    // space, which is also used to produce a "blank" character
            ((unsigned char*) VGA_BASE_ADDRESS)[(cursor_x + (cursor_y * COLS)) * 2 + 1] = 0;
        }
    }
    cursor_x = 0;
    cursor_y = 0;
}
This will give you basic console output, with new lines and deleting behaving as expected. You can later add support for tabs and scrolling the screen. You can also write another function that turns a number into text and prints it to the screen.

You could adapt this module to assembly, but for tasks like this it's usually better to use a higher-level language like C.

_________________
When you start writing an OS you do the minimum possible to get the x86 processor in a usable state, then you try to get as far away from it as possible.

Syntax checkup:
Wrong: OS's, IRQ's, zero'ing
Right: OSes, IRQs, zeroing


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 9 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 58 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group