Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C Programming


AUG93: C PROGRAMMING

It's August, the annual C issue and my anniversary. I began writing this column five years ago this month. I'd regale you with the story of my rise to eminence, but at a recent conference a young reader took me to task for my occasional trips down memory lane. "Who cares about the good old days?" he wanted to know. I realized then that someone too young to have a past might very well have no appreciation or reverence for tradition and nostalgia.

 So, instead of an inconsequential reminiscence, I'll tell a simple story that leads into this month's project, in which I'll build a simple hardware device to emulate the PC keyboard, then develop the necessary driver software in C. First the story.

 My brother, Fred, is a consulting engineer who, among other things, designs hardware. One of his clients is an entrepreneur named Wolfie, and some of Wolfie's enterprises involve the application of color terminals in embedded systems. The color screens that announce flight arrivals and departures at some airports are Wolfie's. Another of Wolfie's systems is a script-driven automated driver's-license test that enables applicants to answer multiple-choice questions displayed on the color screens. At the time of this story, the system used a popular color terminal and a five-key keyboard with A, B, C, D, and Enter keys. Wolfie's prototype employed the terminal's standard QWERTY keyboard, but the final product had to have only the five keys. Building the custom keyboard was Fred's job. Fred called one day and asked me to help assemble some keyboards. They were due the next day, he was late getting started, and Wolfie was fretting. We stayed up all night building them. Each keyboard had five embossed keys, cable, and keyboard-controller electronics built into a nice professional-looking box. It was mindless work and we had a great time soldering, assembling, drinking coffee, and talking until dawn. Wolfie shall be, he said, eternally grateful for my assistance. That's how Wolfie talks. End of story; now on to the project.

 Those custom keyboards came to mind recently when I began to prepare a lecture for the Borland International Conference, held in San Diego in May. Usually I use overhead transparencies, but this particular lecture called for a number of slides interspersed with online demonstrations of compiles and executions of the demonstration program. The conference speaker liaison said that it might not be easy to switch projection systems during the speech. So I decided to prepare everything on a laptop, plug into their VGA projector, and proceed with one projection system.

 When speaking to programmers, I like to walk around the room with the briefing slides in the background and my attention on the attendees. The conference rooms usually come with a wireless microphone, which lets me be Phil Donahue, running all over the room, even going down into the audience. That has always worked, because I could stroll back to the podium to change slides. I figured on using the same technique with the automated slide show but found that in rehearsal I'd often hit the wrong laptop key. They don't lay those things out very well, and during a speech my attention is anywhere but on that cramped keyboard. That's when I remembered Wolfie's five-key keyboard. With one of those to carry around, I could control the slides from anywhere in the room and have fewer wrong keys to press. A keyboard like that would work in many embedded applications as well--arcade games, shopping-mall directories, electronic catalogs, computer-assisted instruction, and the like--so it seemed to be a project worth investigating.

 

The Hardware

Building a PC keyboard is easy enough if you know how the keys interact with the electronics and you have the necessary parts and cabling. I didn't want to do anything that fancy in hardware, though, because I prefer using software to solve problems. Besides, I wanted to build the keyboard with inexpensive parts off the Radio Shack rack.

 Not using a keyboard-controller chip means I can't use the keyboard port itself. The mouse has the serial port tied up, so that leaves the printer port. The next problem is figuring out how to use it.

 A PC's printer port has 3 input/output addresses and about 13 lines that could be used for data. Eight lines carry the eight bits of printer data, but those are write-only signals on some machines. The other lines are control signals. Whether those lines are readable or not, and how you read them differs from machine to machine. The Centronics protocol and the standard PC printer specification leave a lot of room for unique implementations. Devices exist that use the printer port for bidirectional data exchange. I have a Xircom network adaptor that uses the printer port as an Ethernet port. The infamous dongle sends an encoded value from the printer port to serialize protected software. Many laptops use the printer port for an external disk-drive port. So, it must be possible to connect something other than a printer to that port and read data from it.

 The printer connector has no voltage-supply pin, which means that there's no power supply for any digital circuitry in a device. The Xircom adaptor uses an external AC adaptor power supply of its own. I didn't want to do that. Instead, I decided to try to read meaningful signals by grounding the data and control pins at the printer connector, which is a mechanical solution that involves no electronics. You can do this, but differences in printer-port implementations means there's no common denominator. Not even one pin. So, before you begin, you must determine how your particular printer port works.

 Listing One, page 148, is port.c. It reads and displays the hexadecimal values of the two sets of three printer I/O ports. To use the program, you need a 25-pin cable with a male end to plug into the computer's printer port and a female end for your test. You also need two metal paper clips and a test lead with alligator clamps. Plug the cable's male end into the printer port. Bend out one end of each paper clip, and insert one of them into the hole for pin 25 of the female end of the cable. Connect one end of the test lead to this paper clip and the other end to the other, loose, paper clip.

 Compile and run port.c. When you run it, type a 0 as the only command-line parameter. The 0 is the value that the program will write to the printer ports before reading them. Many printer cards leave the signals "floating," which means they're neither +5 volts nor grounded, but depend on the printer to pull them up and ground them. Without a +5 line to pull these floating lines up, their values at any particular time are unreliable. You can, however, condition the initial value by writing all 0s or all 1s to the port before you read it. The idea is to pull the port's lines up to +5 volts and then ground pins one at a time to see if you can read the effect by reading the ports. Whether you write 0s or 1s depends on which value sets the port lines "high." You have to experiment to find out what works with your system. There are many unconventional implementations, particularly in laptops, that manage to look conventional at the BIOS level and also work at higher levels such as DOS and Windows.

 The port.c program continuously displays the output in Figure 1(a). The display shows the two sets of three ports for LPT1 and LPT2 with their port addresses across the top and the data values read from each port under its address. In Figure 1(a), the first three addresses, which address LPT1, have values; the second three, which address LPT2, are all 0xff, which means that the second printer port probably isn't installed. The association between port addresses and the logical LPT1 and LPT2 devices is made by DOS at startup time. If DOS finds only one set of active ports, it sets those ports up as LPT1, regardless of which port addresses it finds.

 While port.c is running, insert the free paper clip into the hole for pin 1 in the cable's female connector, then the hole for pin 2, and so on, all the time watching the program's output. When a grounded pin produces a readable change in one of the ports, the hexadecimal data value under the port's address changes. Only one bit position of one port changes. If you don't get enough satisfactory results, rerun the program with 255 as a command-line parameter to write all 1s to the ports before you read them. Some signals are asserted high, others low. Record the results of the program for all the pins that change the display when you short them. The signals that change can be used for keystroke values on your remote keyboard. The output will tell you which pins to connect to the buttons on the keyboard and how to translate the port values into simulated keystrokes. Press Ctrl-Break to exit from the program.

 Next you must build the keyboard. A trip to Radio Shack serves up a hobby project box (#270-230), some momentary, normally open SPST push buttons (#275-1556), and a 100-foot spool of six-conductor telephone wire (#278-874) with enough conductors for five buttons. The only part I couldn't get at Radio Shack was the 25-pin male EIA connector and hood, which I found at a local electronics supply house. Drill holes in the hobby box for the buttons and cable to go in. Mount the buttons in the hobby box, run the cable into the box, tie a knot in the cable on the inside of the box to keep it from pulling out, and connect one end of the the black wire from the telephone cable to one side of all of the buttons. This wire will be the ground wire. Connect the other end of the black wire to pin 25 of the EIA connector. Connect the other five wires to the other sides of the push buttons, one wire per connection. Connect the other ends of the five wires to other pins on the EIA connector choosing those pins that provided readable signals when you grounded them in your test. Make a diagram of which buttons are connected to which pins.

 After the keyboard is built, you must decide which buttons correspond to which simulated keystrokes. Plan this part carefully. You'll want to arrange the buttons in intuitive positions with respect to one another, depending on how you use them. I use the five buttons to simulate F1, PgUp, PgDn, and the up and down arrow keys.

 

The Software

So much for the hardware. Now you must determine which PC keys to simulate. That depends on the application that uses the keyboard. To get the keyboard scan codes for the selected keys, compile and run kb.c (Listing Two, page 148) which reads the keyboard and displays the scan codes of the keys you type. Type the keys you want to simulate and write down their scan codes. Type Esc to exit from the program.

 Next build an ASCII file with a table that describes the keys to simulate; I use the table in Figure 1(b). The first line specifies how many keys to simulate, five in this example. Each of the remaining lines defines a key. The first four-digit value is the hexadecimal keyboard scan code, as reported by kb.c. The second value on the line is the hexadecimal printer port that will deliver a changed value when the corresponding button grounds a pin. The third value is a hexadecimal value that identifies the bit position that will change. The program that uses this table will AND this value with the value read from the printer port. The fourth value is either the same as the third or 0, depending on whether the bit changes from 0 to 1 or 1 to 0, respectively, when you push the button. The last value is either 00 or ff, to specify the value that conditions the port before the program reads it.

 Listing Three, page 148 (pb.c), converts the push buttons to keystrokes. It is a TSR, and it intercepts the BIOS 0x16 keyboard interrupt vector and the system-timer interrupt vector. To run the program, enter its name and specify the table file as standard input like this:

 

C:>pb< keytable
When the program loads, it reads the table into memory. Then it hooks and chains the two interrupt vectors and becomes resident.

 Application programs call interrupt 0x16 to read the keyboard. kb.c intercepts that interrupt and uses the button-definition table to test the printer ports to see if one of the programmed buttons has been pressed. If so, the programmed scan code is returned. Otherwise, the program passes control to the chained 0x16 interrupt handler so that the real keyboard works as usual.

 The program uses the timer interrupt to regulate the typematic repeat rate of the keyboard. As with the standard keyboard, an initial delay is followed by a shorter one for subsequent repeats for the same, prolonged key press.

 The keyboard and pb.c program are not perfect. For example, the program does not work with Windows. You'd need to write a specific Windows-keyboard device driver program for that. Your meanderings around the lecture hall are limited to the length of the cable, too, which tends to get tangled if you go too far or if you stuff the box into your briefcase. Wireless devices that use radio frequencies or infrared technology (like a TV's remote control) can emulate more keys and the mouse, but they're more expensive than our home-built device. Even with its shortcomings, though, the keyboard simulator solves a problem for me and is an inexpensive way to add custom keyboards to other embedded systems.

 

The Tech-support Blues

Some of you are responsible for technical support in your companies. That's where programmers do penance for having written programs that other people want to use. It's called, "paying your dues with the tech-support blues." Besides providing tech support, most of you, as users of compilers and such, will at one time or another be on the receiving end of a tech-support service, and that experience can give you another version of the blues.

 I've had those tech-support blues three times in the recent past, as a consumer rather than a supporter, and I have some observations that might help you the next time you consider buying a product or the next time your own tech-support phone rings.

 There are several ways to provide tech support. You can provide a phone number, preferably an 800 number so your users don't have to pay to be on hold. You can use a fax. You can put a sysop on a CompuServe forum, a method that I like because other users can chime in to help, and the phone is never busy. But regardless of which method you use, if you offer tech support, you ought to be prepared to deliver on your promise. Here's how well all three methods worked for me recently.

 I bought a new computer from Gateway 2000. They're one of the high-visibility clone makers with multipage, full-color, centerfold ads in magazines. They have an 800 number for taking orders. You never have any trouble getting through to that number. There's always an eager sales person ready to take your order. My new computer is a belch-fire, neck-snapper 66-MHz 486, a programmer's dream because it bangs out long makefiles quicker than you can watch for warning messages. Gateway has a 30-day return policy. If you don't like it, send it back. I liked it, but it had a problem: It wouldn't run Brief 3.1. Every time I saved a file and exited, the computer displayed a divide-by-zero error message and locked up. Surely, says I, they've heard about this one. They have an 800 number for tech support. Trouble is, it's always busy. That's odd, the sales line is always available. It seems to me that a company should have more sales than problems to stay in business. If a line's going to be busy, it ought to be the one getting the most calls. But for two weeks I dialed tech support, and for two weeks I got a busy signal. Finally I called the company's reception line and they promised me a "guaranteed call-back." Two more weeks went by. No call-back. I kept trying the tech-support line, which was always busy, and my 30 days were running out. So I called reception again and talked to Heather. Right off, she wanted to give me a guaranteed call-back. No soap, I said, I've tried that and it doesn't work. Let me talk to someone right now or I'm sending this turkey back. Enter the Catch-22. You can't get a return authorization, said Heather, until you've discussed your problem with tech support, and their line is busy. Let me talk to your supervisor. He's busy, too. Let me talk to the president of the company. We don't have his number. Where does he live? Don't know. Well, I'm not hanging up until I talk to someone who can resolve this problem. Please hold. After a while, Heather, who has the patience of a saint, returned to report that her supervisor had authorized her to interrupt a very busy tech-support person, putting me ahead of other poor souls who were also waiting, obviously more patiently than I. After about ten minutes of unbearable elevator soft-rock music, the tech-support person came on line. I described the problem and two minutes later had a Debug patch script to fix the Brief 3.1 executable so that it wouldn't do whatever bad thing it was doing. The problem is fixed and the computer is fine, but it took me a month to get a two-minute solution to a show-stopping, wall-hitting, dead-in-the-water problem. And I would not have gotten it if I had not obstinately insisted on the support that was my due.

 The lesson? Before you buy an expensive product from anyone, make a test run through their tech-support service. Call them to see how easy it is to get to talk to someone. If you're satisfied, proceed. If not, buy elsewhere.

 Second case in point: Recently I revised a C++ book to include templates. To make sure that my programs were not compiler specific, I ran them through Borland C++ 3.1, Zortech C++ 3.1, and Comeau C++ 3.0. The Zortech compiler wouldn't compile some of the exercises. In one case, it reported an internal error with nothing more than an error number. The other cases were perfectly good C++ template code, which the other compilers accepted. I reduced the four problems to four very short code samples and posted a message on CompuServe in the Symantec developers' forum. When you join that forum, a cheery message greets you and promises 48-hour turnaround time on problem reports. I was on deadline and needed an answer. My message was ignored. After about two weeks I posted an inquiry and was greeted by a new sysop, who asked me to repost the problems. I did. It's three weeks later now, and there's no response. A final message asked them to comment. No response. My book will go to press with caveats for Zortech users. But worse, I am left with a bad taste that compromises my objectivity about Symantec products. I don't like having my problems ignored, and I don't trust a company that does that.

 The lesson? Don't ignore someone who has a tech-support problem. At best you'll promote ill will. At worst, you'll be reading about yourself in the press, probably in letters to the editor, but sometimes in a column. By the way, the best tech support often comes from small companies. You may even get to talk to the programmer who built the product. Comeau C++ is an example. Greg Comeau is always available on CompuServe or by phone.

 This final tale could have had an unhappy ending. I could be writing this column from the confines of a Federal country club in the good company of lawyers, S&L executives, drug runners, and high-ranking government officials. For years I used TurboTax from ChipSoft to prepare my income-tax returns. This year I did not upgrade the venerable DOS version because that new 486 computer included a bundled copy of TurboTax for Windows. Not wanting to wait until the last minute, I cranked it up fully one week before the April 15 filing deadline. I've never seen a released software product less ready for release. The user interface is a joke, unlike any CUA standard Windows application I have seen, and the program just doesn't work. I was able to get enough out of it to file for an extension, but I cannot file that return with our favorite uncle. It's no surprise that ChipSoft's tech-support line was always busy. So was their fax line. There are so many problems that I wrote them a three-page report. I couldn't get through to their fax until a week after April 15th. Finally, the report went out, and I waited to hear from them. Nothing. Not even an acknowledgment. So how does one get their attention? Perhaps this will work. If all of you fax a copy of this column to ChipSoft maybe someone there will take notice. Their fax number is 1-800-766-8890.

 

Figure 1: (a) port.c output; (b) table that describes the keys to simulate.

(a)

0378 0379 037a 03bc 03bd 03be

000b 007f 00C0 00ff 00ff 00ff

(b)

5

5100 379 40 00 00

4900 379 80 80 00

4800 379 20 00 00

5000 379 10 00 00

3b00 37a 01 01 00
[LISTING ONE]


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

int ports[] = {
    0x378,
    0x379,
    0x37a,
    0x3bc,
    0x3bd,
    0x3be
};
void main(int argc, char *argv[])
{
    int i;
    for (i = 0; i < 6; i++)
        printf("%04x ", ports[i]);
    putchar(æ\n');
    while (1)    {
        putchar(æ\r');
        for (i = 0; i < 6; i++)    {
            if (argc > 1)
                outportb(ports[i], atoi(argv[1]));
            printf("%04x ", inportb(ports[i]));
        }
    }
}

[LISTING TWO]





#include <stdio.h>
#include <bios.h>

void main()
{
    int c = 0;
    while ((c & 255) != 27)    {
        c = bioskey(0);
        printf("\n%04x", c);
    }
}

[LISTING THREE]





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

// -------- typematic values
#define DELAY    10
#define TYPEMATIC 1
/* ------- the interrupt function registers -------- */
typedef struct {
    int bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl;
} IREGS;
// ---- map signals to keystrokes
struct pmap {
    int KeyCode;
    int Port;
    int bit;
    int mask;
    int prime;
};
int keycount;                // number of keys simulated
#define MAXKEYS 20           // maximum keys to simulate
struct pmap PTbl[MAXKEYS];   // table of simulated keys
// ---------- interrupt vectors
#define KEYBOARD 0x16
#define TIMER    0x1c
#define ZEROFLAG 0x40
// -------- 0x16 BIOS functions
#define READKEY 0
#define KEYSTATUS 1
// ------ interrupt vectors
static void (interrupt *oldtimer)(void);
static void (interrupt *old16)(void);
static void interrupt newtimer(void);
static void interrupt int16(IREGS);
// ------- TSR stuff
static unsigned highmemory;
static unsigned sizeprogram;
extern unsigned _heaplen = 1;
extern unsigned _stklen = 1024;

void main()
{
    int i;
    // ---- read in the number of keys to simulate
    scanf("%d", &keycount);
    if (keycount <= MAXKEYS)    {
        // ---- read the key simulation table
        for (i = 0; i < keycount; i++)
            scanf("%X %X %X %X %X",
                &PTbl[i].KeyCode,
                &PTbl[i].Port,
                &PTbl[i].bit,
                &PTbl[i].mask,
                &PTbl[i].prime);
        /* ----- attach interrupt vectors ------ */
        old16 = getvect(KEYBOARD);
        oldtimer = getvect(TIMER);
        setvect(KEYBOARD, int16);
        setvect(TIMER, newtimer);
        /* ------ compute program size ------- */
        highmemory = _SS + ((_SP+8) / 16);
        sizeprogram = highmemory - _psp + 1;
        /* ----- terminate and stay resident ------- */
        keep(0, sizeprogram);
    }
}
static int Timer = 0;
/* ----- timer interrupt service routine ------- */
static void interrupt newtimer(void)
{
    if (Timer > 0)
    --Timer;
    (*oldtimer)();
}
// ---- test for simulated key pushed
static int pushbutton(void)
{
    int i, b, key = 0;
    static int TimerValue = DELAY;
    if (Timer == 0)     {
        for (i = 0; i < MAXKEYS; i++) {
            struct pmap pm = PTbl[i];
            outportb(pm.Port, pm.prime);
            b = inportb(pm.Port);
            if ((b & pm.bit) == pm.mask)  {
                Timer = TimerValue;
                TimerValue = TYPEMATIC;
                key = pm.KeyCode;
                break;
            }
        }
        if (i == 5)     {
            Timer = 0;
            TimerValue = DELAY;
        }
    }
    return key;
}
/* ----- Keyboard BIOS ISR ------- */
static void interrupt int16(IREGS ir)
{
    int func = (ir.ax >> 8) & 0xff;
    static int newkey = 0;
    static int rdflags = 0;
    static int stflags = 0;
    /* -- for read key bios call, loop until key pressed -- */
    if (func == READKEY)    {
        int flg = ZEROFLAG;
        while (flg & ZEROFLAG)    {
            _AH = KEYSTATUS;
            geninterrupt(KEYBOARD); /* this will call myself */
            flg = _FLAGS;
        }
    }
    if (func == READKEY || func == KEYSTATUS)    {
        if (!newkey)
            newkey = pushbutton();

        if (newkey)     {
            ir.ax = newkey;
            if (func == READKEY)    {
                newkey = 0;
                ir.fl = rdflags;
            }
            else
                ir.fl = stflags & ~ZEROFLAG;
            return;
        }
    }
    _BX = ir.bx;
    _CX = ir.cx;
    _AX = ir.ax;
    (*old16)();
    ir.ax = _AX;
    ir.fl = _FLAGS;
    if (func == READKEY)
        rdflags = ir.fl;
    if (func == KEYSTATUS)
        stflags = ir.fl;
}
End Listings


Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.