In case you do not know how to print a character, read the following.
In case you already know how to print a character, still read the following.
0xB8000 | character at (0, 0) |
0xB8001 | color of character at (0, 0) |
0xB8002 | character at (1, 0) |
0xB8003 | color of character at (1, 0) |
0xB8004 | character at (2, 0) |
0xB8005 | color of character at (2, 0) |
0xB8006 | character at (3, 0) |
0xB8007 | color of character at (3, 0) |
.
.
.
To improve performance, you should store the cursor's position in two variables and update the cursor whenever necessary. By doing so, we will avoid reading from the controller. Also when you are OS starts, you would want to start printing text from (0, 0) right?
If you observe the memory layout I posted at the start, you can derive the following formula which tells you the memory location where the character at (x, y) is stored:
location = 0xB8000 + (y*80 + x)*2
Why 80? There are 80 characters per line.
Why 2? The first byte is used to store the character and the next byte is used to store the color/attribute. So each character takes up two bytes.
You would want to have a common color for all characters, so you store the color information it in a variable.
To display a character on the screen, all you have to do is write to the correct memory location. Nothing to do with the hardware.
Code: Select all
static void putc(unsigned char c)
{
uint16_t *where = 0xB8000 + (y *80 + x);
*where = c | (attribute << 8);
x++;
if(x == 80)
{
x = 0;
y++;
}
}
x, y always has the position of the next character to be printed.
Where should the cursor appear?
"Hiiiiiiiii_"
..........^ the cursor should appear in the location of the next character to be printed
so that means that x, y are essentially storing the cursor's new position.
Unlike displaying characters on screen, the cursor has to be updated by talking to the CRT controller using the in/out instruction.
The controller accepts the position as:
cursor position = y*80 + x
25*80 = 2000, this would mean the cursor position cannot be stored in a byte. You'd need at least two bytes.
Unfortunately, you cannot send two bytes of data in a single out instruction to the CRT controller. You have to split it into two bytes and send separately.
Code: Select all
unsigned int temp = y * 80 + x;
outportb(0x3D4, 0xE);
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 0xF);
outportb(0x3D5, temp);
So everytime you print a character, you must update the cursor.
The final code would look something like this:
Code: Select all
static void putc(unsigned char c)
{
uint16_t *where = 0xB8000 + (y *80 + x);
*where = c | (attribute << 8);
x++;
if(x == 80)
{
x = 0;
y++;
}
unsigned int temp = y * 80 + x;
outportb(0x3D4, 0xE);
outportb(0x3D5, temp >> 8);
outportb(0x3D4, 0xF);
outportb(0x3D5, temp);
}
You'll never need to read the location from the controller because you already have it
You might also want to update the cursor when your OS starts so that it moves to zero (in case it had been moved somewhere else by the bootloader or BIOS or whatever). Might also want to clear the screen? That is for you to write after reading this post (fill the screen with spaces).
The code I gave does not handle null character, carriage return, backspace, etc. Here is a better (more complete) version of the same.
Code: Select all
static void putc(unsigned char c)
{
switch(c)
{
case '\0': return;
case '\b':
if(curPos_x != 0) curPos_x--;
break;
case ' ':
curPos_x++;
break;
case '\t':
curPos_x = (curPos_x + 8) & ~(8 - 1);
break;
case '\r':
curPos_x = 0;
break;
case '\n':
curPos_x = 0;
curPos_y++;
break;
default:
{
if(isprint(c))
{
uint16_t *where = vmemptr + (curPos_y * TEXTMODE_MAX_X + curPos_x);
*where = c | (attribute << 8);
curPos_x++;
}
break;
}
}
if(curPos_x >= TEXTMODE_MAX_X)
{
curPos_x = 0;
curPos_y++;
}
if(curPos_y >= TEXTMODE_MAX_Y)
{
uint16_t blank = 0x20 | (attribute << 8);
uint16_t temp = curPos_y - TEXTMODE_MAX_Y + 1;
memcpy (vmemptr, vmemptr + temp * TEXTMODE_MAX_X, (TEXTMODE_MAX_Y - temp) * TEXTMODE_MAX_X * 2);
memsetw (vmemptr + (TEXTMODE_MAX_Y - temp) * TEXTMODE_MAX_X, blank, TEXTMODE_MAX_X);
curPos_y = TEXTMODE_MAX_Y - 1;
}
updatecursor();
}
When you are printing strings, you should update the cursor only once. Not every time you print a character. So instead of having update cursor in putc, add it in your puts.