See also the code snippets below.
The putch() and cputs() functions may be good choices for text output in a 16-bit real-mode OS. Both the DJGPP and Turbo C versions use BIOS interrupts but not DOS interrupts.
Which is more difficult: writing a VMM, or writing pmode driver code to replace 16-bit BIOS functions? Opinions vary.
emulation | framebuffer linear address | framebuffer real-mode address | I/O address of CRTC |
---|---|---|---|
color | B8000h | B800h:0000 | 3D4h |
monochrome | B0000h | B000h:0000 | 3B4h |
Usually, the board is set for color emulation. The CRTC is a functional unit of the VGA, and is discussed below.
Each character in the framebuffer takes 2 bytes: an ASCII value at address N, and an attribute byte at address N+1:
bit in attribute byte | usage |
---|---|
b7 | |
b6:b4 | background color (0-7) |
b3:b0 | foreground color (0-15) |
color value | color | color value | color |
---|---|---|---|
0 | black | 8 | dark gray |
1 | blue | 9 | bright blue |
2 | green | 10 | bright green |
3 | cyan | 11 | bright cyan |
4 | red | 12 | pink |
5 | magenta | 13 | bright magenta |
6 | brown | 14 | yellow |
7 | white | 15 | bright white |
For both DJGPP and Turbo C, software scrolling of the display can be done with the movedata() function. This function works like memcpy() but uses far pointers for both source and destination.
movedata() may not work properly if src < dst, i.e. for making the display scroll down. If you use near pointers, memmove() can be used instead of movedata() or memcpy(). memmove() is guaranteed to work properly if src and dst overlap.
For hardware scrolling, you instruct the VGA to use a different memory location for the framebuffer. CRTC registers 12 and 13 contain the MSB and LSB of the framebuffer offset, relative to B0000h, B8000h, or A0000h. Hardware scrolling can not continue indefinitely. Eventually the framebuffer will extend beyond the end of video memory.
The framebuffer memory can also be divided into virtual consoles (VCs). 32K of video memory is enough for eight 80x25 VCs. The VCs can be spaced 4000 bytes apart (80x25x2), and the hardware scrolling code snippet below can be used to select which VC is currently displayed. (Linux VCs use a different method.)
If you do not want to use the BIOS: CRTC registers 14 and 15 contain the MSB and LSB of the cursor position, relative to B8000h or B0000h, in units of characters. The cursor can be moved with a code snippet similar to the one for scrolling (see below).
Code to detect monochrome/color emulation.
Turbo C code (16-bit real mode) to put white on blue 'H' in upper left corner of screen:
#include <dos.h> /* pokeb() */ pokeb(0xB800, 0, 'H'); pokeb(0xB800, 1, 0x1F);NASM code (16-bit real mode) to put white on blue 'H' in upper left corner of screen:
mov bx,0B800h mov es,bx mov byte [es:0],'H' mov byte [es:1],1FhDJGPP code (32-bit pmode) to put yellow on red '*' in upper right corner of 80x25 screen, using far pointers:
#include <sys/farptr.h> /* _farpokeb() */ #include <go32.h> /* _dos_ds */ _farpokeb(_dos_ds, 0xB8000 + 79 * 2 + 0, '*'); _farpokeb(_dos_ds, 0xB8000 + 79 * 2 + 1, 0x4E);DJGPP code (32-bit pmode) to put yellow on red '*' in upper right corner of 80x25 screen, using near pointers:
#include <sys/nearptr.h> #include <crt0.h> #include <stdio.h> unsigned char *fb; if(!(_crt0_startup_flags & _CRT0_FLAG_NEARPTR)) { if(!__djgpp_nearptr_enable()) { printf("Could not enable nearptr access\n"); return -1; } } /* probably Windows NT DOS box */ fb = (unsigned char *)0xB8000 + __djgpp_conventional_base; fb[79 * 2 + 0] = '*'; fb[79 * 2 + 1] = 0x4E;Turbo C code to scroll 80x25 display up one line (color emulation):
#include <string.h> /* movedata() */ movedata(0xB800, 80 * 2, 0xB800, 0, 80 * (25 - 1) * 2);DJGPP code to scroll 80x25 display up one line (color emulation):
#include <string.h> /* movedata() */ #include <go32.h> /* _dos_ds */ movedata(_dos_ds, 0xB8000L + 80 * 2, _dos_ds, 0xB8000L, 80 * (25 - 1) * 2);Hardware scrolling by changing the framebuffer base address:
/* scroll up one line */ #include <dos.h> /* outportb() */ unsigned short crtc_adr = 0x3D4; /* 0x3B4 for monochrome */ unsigned short offset = 80; /* the CRTC index is at crtc_adr + 0 select register 12 */ outportb(crtc_adr + 0, 12); /* the selected CRTC register appears at crtc_adr + 1 */ outportb(crtc_adr + 1, offset >> 8); outportb(crtc_adr + 0, 13); outportb(crtc_adr + 1, offset & 0xFF);Move hardware cursor using INT 10h AH=02h:
mov ah,2 mov bh,0 ; video page mov dh,1 ; row (y; 0 is top) mov dl,10 ; col (x; 0 is left) int 10hMove hardware cursor by changing VGA registers; without using the BIOS:
#include <dos.h> /* outportb() */ unsigned short crtc_adr = 0x3D4; /* 0x3B4 for monochrome */ unsigned short offset; unsigned short x = 20, y = 3; offset = x + y * 80; /* 80 characters per line */ outportb(crtc_adr + 0, 14); /* MSB of offset to CRTC reg 14 */ outportb(crtc_adr + 1, offset >> 8); outportb(crtc_adr + 0, 15); /* LSB of offset to CRTC reg 15 */ outportb(crtc_adr + 1, offset);
Unicode? UTF-8?