Hardware Ports

Atrevida Game Programming Tutorial #6
Copyright 1997, Kevin Matz, All Rights Reserved.

"Prerequisites":

  • Chapter 1: Introduction to Binary and Hexadecimal
  • Chapter 2: Binary Operations
  • Chapter 3: Binary Manipulations

       

What are hardware ports?

Hardware ports are the primary method of communication with external devices and the PC's support chips. (Reading and writing to specific memory locations, or memory-mapped I/O, is the other method; this method is applicable to very few devices, such as the video card. BIOS and DOS services that access hardware devices make use of either hardware ports or memory.)

We can read and write values to and from hardware ports. I like to think of ports as "portholes" or "doorways" through which information can be passed to or retrieved from "the outside world". The term "outside world" is actually quite commonly used, and it usually refers to everything outside of the processor (so the support chips inside the computer are often deemed part of the outside world). When we read from or write to a port, we are actually reading from or writing to memory locations that are not part of the main memory, but are a part of the device that we are programming.

There are 65536 possible hardware ports, although most of them are unused. They are always numbered using hexadecimal, so the range is 0 hex to FFFF hex.

Writing to hardware ports using C/C++

In Turbo/Borland C and C++, we can use the port input/output functions from either dos.h or conio.h.

If we "#include <dos.h>", then we can use the functions (or macros) outportb(), for writing a byte, and outport(), for writing a word.

If we "#include <conio.h>", we can use outp() for writing bytes and outpw() for writing words to hardware ports.

The prototypes for each are listed below:

int outportb (unsigned int port, int value);
unsigned int outport (unsigned int port, unsigned int value);
int outp (unsigned int port, int value);
unsigned int outportw (unsigned int port, unsigned int value);

Notice that outport() and outp() use int types in the places where I think that char types (bytes) would have been more appropriate.

So, as an example, say we wish to write the byte-sized value 85 hex to port number 9D4 hex. We could use either of the following statements:

outportb (0x9D4, 0x85);
outp (0x9D4, 0x85);

What happens if we write a word? Writing a word to a port results in writing a word to a particular device's memory, and that memory must use the little endian format for storing multiple-byte numbers. The least-significant (rightmost) byte of the word is stored in the first memory location, and the most-significant (leftmost) byte of the word is stored in the next memory location. The same effect can be achieved by writing the least-significant byte to a port number "x", and then writing the most-significant byte to the port "x + 1". So, the following code fragments for writing words to ports are equivalent:

outpw (0x9EB, 0x4D3C);

and

outp (0x9EB, 0x3C);
outp (0x9EC, 0x4D);             /* 0x9EB + 1 = 0x9EC */

(Of course, you could replace "outpw" with "outport" and "outp" with "outportb"; just be sure to include the correct header file.)

By writing values to ports, we can send commands or data to devices.

Reading from hardware ports in C/C++

Again, using Turbo/Borland C and C++, we have the choice of using the dos.h or the conio.h routines for accessing the hardware ports.

With dos.h, we can use the inportb() and inport() functions or macros; with conio.h, we can use the inp() and inpw() functions or macros. The syntaxes are listed below:

int inportb (unsigned int port);
unsigned int inport (unsigned int port);
int inp (unsigned int port);
unsigned int inpw (unsigned int port);

Suppose we wish to read a byte from port 76A hex, and store the result in a variable called value. We could use either of the following two statements:

value = inportb(0x76A);
value = inp(0x76A);

Reading a word from a port is similar to writing a word to a port. We must take into account the fact that little endian number storage is used. Reading a word from a port means that the word that is returned is constructed from two bytes: the least-significant byte will come from the first port address "x", and the most-significant byte will come from the next port address, "x + 1". So, the following two examples should be equivalent:

value = inpw(0x4000);

and

value = inp(0x4000) + (inp(0x4001) << 8);

An example of the usage of ports

For an interesting example of how ports can be used, we will construct a short program that accesses some of the VGA's registers. (If you do not have a VGA card (in this case, an EGA should work as well), this example will not work.) The example is fairly involved, but it demonstrates a fairly common situation that is encountered when programming devices through ports.

If you have done any programming in text mode (or drawing "ANSI art") using different colors and attributes, you are aware that each character can have one of sixteen foreground colors, one of eight background colors, and as well, that character's foreground can blink or remain solid. But by manipulating some of the VGA's registers by using ports, we can change this setup: we will be able to select from sixteen colors for the foreground (as usual), but we will be able to select from sixteen background colors instead of eight (but we give up blinking). We will gain access to the "high-intensity" colors, 8 dec through 15 dec, for use as background colors.

First, I'll give some brief background information for using colors in text mode. (Admittedly, this part is unrelated to ports.)

In Borland and Turbo C/C++, with conio.h, there are facilities for changing the foreground and background colors. The function textcolor() accepts an integer in the range 0..15 dec as a parameter, and sets the current color to that value. The function textbackground() accepts an integer in the range 0..7 dec as a paramter, and sets the current background color to that value. For example, to use a bright magenta (pink) foreground on a cyan background, we can use:

textcolor (13);                 /* 13 is the "code" for bright magenta */
textbackground (3);             /* 3 is the "code" for cyan */

Now, to use this tacky color combination, you use the cprintf() function, which works just like printf():

cprintf ("This text is bright magenta, with a cyan background.\n");

To use blinking, you can use a function called textattr(), which takes as its parameter an int in the range 0..255. Here is a bit diagram showing how to construct the color codes:

          7     6     5     4     3     2     1     0
       +-----+-----+-----+-----+-----+-----+-----+-----+
       |BLINK|  R  |  G  |  B  |  I  |  R  |  G  |  B  |
       +-----+-----+-----+-----+-----+-----+-----+-----+
             |<-- Background ->|<---- Foreground ----->|

       Bit 7:  1 = blinking on; 0 = blinking off
       Bit 6:  Background red component on/off
       Bit 5:  Background green component on/off
       Bit 4:  Background blue component on/off
       Bit 3:  High-intensity foreground on/off
       Bit 2:  Foreground red component on/off
       Bit 1:  Foreground green component on/off
       Bit 0:  Foreground blue component on/off

If we wanted blinking bright green text (blink bit + high intensity foreground + green foreground) on a purple background (green background + blue background), our attribute code would be 2^7 + 2^3 + 2^1 + 2^5 + 2^4, or 128 + 8 + 2 + 32 + 16 (decimal), which equals 186 dec. The faster method is to create the pattern 10111010 bin, which is BA hex:

textattr (0xBA);
cprintf ("This is bright green blinking text on a purple background.\n");

Note that when high-intensity backgrounds are enabled, bit 7, the "blinking on/off" bit, will instead be the "high-intensity background on/off" bit. This will allow 16 possible colors for the background.

Now we can return to our discussion of ports.

There is one bit in one of the VGA's registers which, if set, permits blinking, and if cleared, permits high-insensity backgrounds. The register that we need to modify is the "Attribute Controller Mode Control Register". Unfortunately, we cannot write directly to this register in one easy step.

On the VGA, and often with other devices, there are many registers available, but the VGA (or other device) only uses a few ports. The registers need to share the limited number of ports. To allow access to multiple registers, the following scheme is common: an "array" of registers exists that is accessible through an "address" or "index" port and read and/or write ports. You write a number to the index port, which tells the device which register you want to access. If there were, say, ten registers available, and you wanted to write a value to register number 8, you would usually write the number 8 to the index port. Then, you would write the value that you wish to put in register 8 to the write port. Or, if a read port was available, you could read a value from that read port, and you would get returned the current value of register number 8.

This is the way that you access most registers on the VGA. The VGA has several "families" of registers, such as the Sequencer Registers, the CRT Controller Registers, and so on. Each family of registers has an index port and a read and/or write port.

The Attribute Controller family of registers is slightly different, however. There is no real index port, just a write port and a separate read port. Here's how you use it: you first need to "reset" the circuitry controlling the write port. You do this by reading a byte from another VGA port, the "Input Status #1" register (you can throw away the returned byte). Then, you write a specially coded address byte (described shortly) to the write port to indicate which register you wish to access. In the Attribute Controller family, there are twenty registers available (0 hex through to 14 hex). Then, you can either write a value to the selected register through the write port, or you can read a value from that selected register from the read port.

The Input Status #1 register is located at port 3DA hex. The Attribute Controller family's write port is at port 3C0 hex, and its read port is at 3C1 hex.

Here is the format of the address byte for the Attribute Controller family of registers:

          7     6     5     4     3     2     1     0
       +-----+-----+-----+-----+-----+-----+-----+-----+
       | XXXXXXXXX | PAS |             ADR             |
       +-----+-----+-----+-----+-----+-----+-----+-----+

       Bits 6..7:  Unused
           Bit 5:  Palette Address Source bit.  (This should be left as
                   1; if it is 0, the screen goes blank, because the
                   VGA is not allowed to access the memory containing
                   palette information).
       Bits 0..4:  Address index (in the range 0 hex to 14 hex).

The Attribute Controller register we want to access, the Mode Control register, has the index 10 hex. If you're curious about the other nineteen registers in this family, I'd suggest taking a look at the books listed in the "references" section at the end of this article. Here is the format for the Mode Control register:

          7     6     5     4     3     2     1     0
       +-----+-----+-----+-----+-----+-----+-----+-----+
       | IPS | PCS | PPC | XXX | B/I | ELG | DT  | G/A |
       +-----+-----+-----+-----+-----+-----+-----+-----+

       Bit 3:  Blink/Intensity bit.  0 = permit high-intensity
                                         backgrounds
                                     1 = permit blinking (default)

You're probably curious about all of those other bits. I'd like to list them all, but that would take up too much space, and I'd probably be violating some copyrights. Again, if you're interested, check out the "references" section at the end of this article.

So, finally, here's our plan:

  1. Read in a byte from the Input Status #1 register (port 3DA hex).
  2. Construct an index or address byte. The register we want to access has the index 10 hex, and we want to turn on bit 5, which we can do by OR'ing the index number with 20 hex, like this: "index_byte = 0x10 | 0x20;"
  3. Output index_byte to the Attribute Controller family's write port (port 3C0 hex).
  4. Read in the value from the register we have selected by inputting a byte from the read port (port 3C1 hex). Store this value in a temporary variable such as x.
  5. Set (to permit blinking) or clear (to permit high-intensity background colors) the Blink/Intensity bit (bit 3) in x. To turn that bit on, we could use "x |= 0x80;". To turn that bit off, we could use "x &= 0xF7;".
  6. We want to write the value in x back to the Mode Control register, so to "reset" the write port's circuitry, read in a byte from the Input Status #1 register again (port 3DA hex).
  7. Output our index byte to the write port (3C0 hex) again.
  8. Write out the new value of x to the write port (3C0 hex).

Let's construct two functions in C using the above algorithm. EnableBlinking() will enable blinking, and EnableHighIntensityBackgrounds() will enable high-intensity backgrounds. While we're at it, let's write a short function to draw characters on the screen, using all of the different attribute codes from 0 to 255 dec. This will let us see the results of the change. In our main() function, we will draw the colored characters, wait for a keypress, switch to high-intensity background mode, wait for a keypress, and then return to blinking-enabled mode:

/* VGAREGS1.C: Quick VGA registers test program
   Copyright 1997, Kevin Matz, All Rights Reserved.

   This program displays some text-mode color patterns on the screen, and
   then switches between blinking and high-intensity modes.

   References used:
   - "Programmer's Guide to the EGA, VGA, and Super VGA Cards, Third
	 Edition", Copyright 1994, Richard F. Ferraro.  Addison-Wesley
	 Publishing Co., Inc., Reading, Massachusetts.  ISBN 0-201-62490-7.
   ------------------------------------------------------------------------*/

#include <stdio.h>
#include <conio.h>
#include <dos.h>

void DrawColorPatterns ();
void EnableBlinking ();
void EnableHighIntensityBackgrounds ();

main ()
{
    DrawColorPatterns ();

    printf ("This is the normal (blinking) state.\n");
    getch ();

    EnableHighIntensityBackgrounds ();
    printf ("This is the high-intensity background state.\n");
    getch ();

    EnableBlinking ();
    printf ("This is the blinking state again.\n");
    getch ();

    return 0;
}

void DrawColorPatterns ()
{
    int x, y, attribute;

    /* Draw the asterisk character with every possible text-mode
       attribute: */
    attribute = 0;
    for (x = 0; x <= 15; x++)
    {
        for (y = 0; y <= 15; y++)
        {
            textattr (attribute++);
            cprintf ("*");
        }

        textattr (0);
        printf ("\n");
    }

    /* Revert to the standard text colors: */
    textcolor (7);
    textbackground (0);
}


void EnableBlinking ()
{
    unsigned char old_mode_ctrl_reg;

    /* Read from the Input Status #1 register and throw away the returned
       value.  This is done to tell the Attribute Controller's write port
       to accept an index byte next. */
    inportb (0x3DA);
    /* Tell the VGA that we want to access the Attribute Controller's Mode
       Control Register (AC Index 10h); also, prevent the clearing of the
       screen, by setting bit 5 on: */
    outportb (0x3C0, 0x10 | 0x20);
    /* Get the current value: */
    old_mode_ctrl_reg = inportb(0x3C1);

    inportb (0x3DA);               /* Reset circuitry again. */
    outportb (0x3C0, 0x10 | 0x20); /* Indicate that we want AC Index 10h. */

    /* Turn on bit 4 (the Enable Blink or Intensity bit): */
    outportb (0x3C0, old_mode_ctrl_reg | 0x08);
}


void EnableHighIntensityBackgrounds ()
{
    unsigned char old_mode_ctrl_reg;

    /* Read from the Input Status #1 register and throw away the returned
       value.  This is done to tell the Attribute Controller's write port
       to accept an index byte next. */
    inportb (0x3DA);
    /* Tell the VGA that we want to access the Attribute Controller's Mode
       Control Register (AC Index 10h); also, prevent the clearing of the
       screen, by setting bit 5 on: */
    outportb (0x3C0, 0x10 | 0x20);
    /* Get the current value: */
    old_mode_ctrl_reg = inportb(0x3C1);

    inportb (0x3DA);               /* Reset circuitry again. */
    outportb (0x3C0, 0x10 | 0x20); /* Indicate that we want AC Index 10h. */

    /* Turn off bit 4 (the Enable Blink or Intensity bit): */
    outportb (0x3C0, old_mode_ctrl_reg & 0xF7);
}

/* End of listing */

Remember that you need to use the textattr() function to use blinking or high-intensity background characters. The bit diagram for determining color and attribute codes was described previously.

This example program shows that, by using ports (and systems programming in general), we can do many interesting and "amazing" things. For another program, I was able to shift the image on the screen back and forth (distorting some of the colors in the process) by changing the timing settings for a horizontal retrace counter register. Admittedly, this doesn't have any practical uses, but I think it does fall into the "cool" category.

Summary

Hardware ports allow us to access registers and memory locations on certain hardware devices. In C/C++, we can use functions such as inport(), inportb(), inp(), and inpw() to read from ports. To write to ports, we can use function such as outport(), outportb(), out(), and outw(). With some devices, you may need to set up an index through one port and read or write through another (or, in the example presented in this chapter, index and read/write ports may be shared).

References to material (sources used)

Ferraro, Richard F. "Programmer's Guide to the EGA, VGA, and Super VGA Cards, Third Edition". Reading, MA, USA: Addison-Wesley Publishing Co., Inc., 1994. ISBN: 0-201-62490-7.

(This rather thick book contains descriptions of all of the VGA's registers, as well as information on all of the major Super VGA chipsets. One of the chapters contains a decent selection of sample code. This book has been criticized as being full of minor errors, and while I have spotted a number of errors, I don't think the problem is too serious.)

There is an older book about the VGA and its registers that I am aware of. I believe the author's name is Wilton. I haven't seen this book, but when I find more information, I'll put it here.

  

Copyright 1997, Kevin Matz, All Rights Reserved. Last revision date: Wed. Jun. 04, 1997.

Go back

A project