This article first appeared in Sixteen Bits, the journal of the PCUG inc.

Build a $1.75 Thermometer for Your PC

This article describes how your PC's games port may be used for a variety of real world tasks, including the measurement of temperature and humidity. Hardware issues relating to using the PC's hardware timers and joystick port(s) are discussed.

The first part describes in detail how the games port hardware works, how the Intel 8253/8254 Timer chip works, and how standard BIOS or custom software works with these hardware components. It is followed by outline instructions showing how to build an inexpensive temperature probe and a humidity probe.

Games Port Hardware

The games port is usually used for, but need not be limited to, interfacing a joystick to your PC. A standard PC joystick is a very simple device made up of two variable resistors and two normally open press button switches (so why do they cost so much? that's a marketing question). The variable resistors report the position of the joystick in the forward (X) and sideway (Y) positions. The original IBM joystick port used discrete components and allowed you to attach two joysticks, known as A and B.

The pinouts of the joystick port are shown below, a DB15P (male) plug is at the card end (the joystick uses a DB15S, a female socket). The table also shows the hardware port bits associated with each pin and the BASIC commands that can access data from each of the pins (Note: most languages can be used to access this hardware):

            G a m e   P o r t   P i n   U s a g e

     Pin        Usage             Port Bit    BASIC Command
     ------------------------------------------------------
      1         +5 Volts             -
      2         Switch A1            4        STRIG(0/1)
      3         A (x) axis           0        STICK(0)
      4         Ground               -
      5         Ground               -
      6         A (y) axis           1        STICK(1)
      7         Switch A2            5        STRIG(2/3)
      8         +5 Volts             -
      9         +5 Volts             -
     10         Switch B1            6        STRIG(4/5)
     11         B (x) axis           2        STICK(2)
     12         Ground               -
     13         B (y) axis           4        STICK(3)
     14         Switch B2            7        STRIG(6/7)
     15         +5 Volts             -
The IBM joystick is called an analogue device because some other joysticks (eg. the Atari) use a set of 4 switches to indicate the up/down and left/right positions whereas the IBM device reports all position between the X and Y limits by sensing the value of the X and Y resistors.

How is the Joystick Resistance Measured?

Rather than using an analogue-to-digital converter (ADC) chip to measure the current flowing through the resistance and hence arriving at the resistance using Ohms law (V=IR), an even simpler technique is used. We measure how long it takes a small capacitor to charge through the resistance. A 0.01 uF (micro Farad) capacitor is charged by the current flowing through the resistance of the joystick, this capacitor is also connected to a threshold sensing circuit. When the capacitor charges to a certain fraction of the +5 Volts supply rail, the sensing circuit toggles its output from a 0 to 1. A small resistance (ie. low Ohms) will charge the capacitor rapidly and a large resistance (high Ohms) passes only a small current and the capacitor charges slowly.

The rest of the sensing is done in software. The processor times how long it takes for the capacitor to charge by reading the time at the start of the charging process and again when the sense circuit reaches the threshold. The difference in times gives a measure of the resistance and hence the position of the joystick.

The threshold sensing and discharging of the capacitors at the start of a new read cycle is handled for each of the 4 axis inputs by just one chip. An NE558 (made by Signetics) was used by IBM on the original games board. This is decoded to the I/O address of 201 Hex (decimal 513). Whenever 201H is accessed with an out instruction (the data is not decoded) the NE558 is triggered and the capacitors are discharged. Inputting from address 201H establishes the state of the 4 threshold sense outputs as well as the open closed/closed status of the 4 buttons. The bits of the byte read from address 201H are shown in the above table under the "bit" column.

Position sense resistors are connected from the +5Volt rail to each threshold sense input. The switch inputs however are connected to ground to CLOSE the circuit and either left open or connected to +5Volts to OPEN the circuit. Switches are normally open which means they read as a 1 (high) bit when unconnected. The schematic of a standard joystick is shown in fig 1, the schematic of the original PC's "Game Control Adaptor" can be found in Ref 1.

Measuring Time differences with the 8253/8254

To understand how time differences are measured in a processor independent fashion, an explanation of the 8253/8254 Timer chip is required. The XT used an Intel 8253 to perform timing, sound generation, and memory refresh logic, the AT improved upon this by using the upwardly compatible 8254. I'll stick to the 8253 description so that any code I develop will work on an XT and an AT. The 8253 has 3 independent 16 bit counters that are accessed by the processor on two successive byte input and output cycles. These counters are dedicated to the following tasks:
   8 2 5 3   T i m e r   F u n c t i o n s

   Counter 0        generates the 18.2 Hz system clock tick
   Counter 1        generates Dynamic RAM refresh signals
   Counter 2        generates sound pulses for internal speaker
For more information on the 8253/8254 can be found here.

Counter 0 is the one we're interested in as it allows us to time intervals as short as 0.84 uSec. The input to this counter comes from a 1.19318 MHz signal generated by dividing the 4.77 MHz processor clock by 4. Interestingly, the PC master clock frequency was selected to be 4 times the NTSC (US colour TV system) colour burst frequency of approximately 3.58 MHz, presumably because it saved a dollar or two in the original PC's design. This results in the following derived frequency and timing periods:

   C o u n t e r   0   T i m e r   P e r i o d   C a l c u l a t i o n s

   Master crystal locked Clock frequency     14.31818 MHz  (NTS Colour Burst frequency)
   Processor Clock frequency                  4.77273 MHz  (Master Clock/3)
   8253 Timer Clock input frequency           1.19318 MHz  (Processor Clock/4)
   8353 Timer Clock period                    0.83810 uSec (period = 1 / frequency)
   PC Software clock period                  54.92549 mSec (65,536 x Clock period)
   PC Software clock frequency               18.2064  Hz   (about 18 times per second)
The important factor in the above figures is that counter 0 decrements every 0.83810 micro Seconds (let's call this 0.84 uSec). If we read the 16 bit counter when we first trigger the NE558 and then again when the threshold is reached, we can calculate how many multiples of 0.84 uSecs have elapsed. Obviously this gives us an interval that is totally independent of the speed of the processor doing the timing.

Counter 0 continually counts down and on reaching 0 its next count is 0FFFF Hex. The calculation of the count interval must take this wrap-around into account and after this is done an interval of up to 54.9 mSecs (= 0.838 x 65,536) may be reliably measured in increments of 0.84 uSec. Obviously longer intervals may be measured as multiples of the 54.9 mSec period but this is not as simple as it first seems (see ref 2).

GAMES PORT SOFTWARE

We know, from the explanation above, how the hardware determines the position of the joystick, but what standard software interfaces are there to use?

The IBM Rom BIOS manual by Ray Duncan (Ref 3) describes Int 15H, Function 84h as the Read Joystick function (available on AT and PS/2). This has two subfunctions; to read the joystick switch settings (with AH=84H, DX=00, return AL bits 4..7 = switches A1, A2, B1, B2); and to read the resistive inputs (call with AH=84H, DX=01, return AX=A(x), BX=A(y), CX=B(x), DX=B(y)). You can call these functions (at least on an AT) from any language that allows you to perform a software interrupt.

The following C function uses the BIOS Int 15H, function 84H, subfunction 01, and returns joystick readings for one of the joystick and emulates the BASIC STICK(n) function (note that care must be taken with functions that use the register pseudovariables as the registers are easily overwritten, a case statement will not work as expected if used instead of the if statements below):
  stick() function in C

  #include <dos.h>  // Use real INT instuctions, not a call

  unsigned int stick (unsigned int n)
  {
      if (n > 3)  return -1;     // Invalid n return
      _AH = 0x84;                // Function 84H
      _DX = 01;                  // Subfunction 1 - Read resistive input
      geninterrupt (0x15);       // Int 15H
      if (n == 0) return (_AX);  // for n = 0 read A(x)
      if (n == 1) return (_BX);  // for n = 1 read A(y)
      if (n == 2) return (_CX);  // for n = 2 read B(x)
      if (n == 3) return (_DX);  // for n = 3 read B(y)
  }
From GW and QBASIC you can use the STICK(n) and STRIG(m) functions. STICK(n) behaves just like the C function above. The value returned by STRIG(m) is either 0 or -1 depending on whether the trigger is pressed (-1) or not (0). The STRIG function has another form, STRIG ON and STRIG OFF. STRIG ON tells BASIC to record, before each statement is executed, the status of the switches, the intention being to catch a button being pressed before a call to STRIG(n) is executed. Refer to the Q-BASIC on-line documentation (with DOS 5) or the GW-BASIC manual for more information.

But what exactly are the values returned by int 15H, function 84H, subfunction 01. Ray Duncan (Ref 3) states that the values returned will be between 0 and 416 if a 250k* joystick is used, but more information can be had from a BIOS listing (ref 4). This listing shows that the time taken for the capacitor to charge is calculated as described above but the count of 0.84*Secs periods is rounded and truncated as follows (using C syntax):

    count = (count & 0x1FF0) >> 4;
This can be expressed in assembler code as follows (the count is in CX):
    AND CX,01FF0H   ;remove bits 13..15
    SHR CX,1        ;divide by 2
    SHR CX,1        ;       by 4
    SHR CX,1        ;       by 8
    SHR CX,1        ;       by 16
What this is saying mathematically is that the count is divided by 16 and truncated to 9 binary bits or a maximum decimal value of 512. A value of 1 represents a time interval of between 13.41 and 26.82 * Secs and the function measures a maximum value of 6.86 mSecs. In fact, if the time interval is greater than 6.86 mSecs then the integer returned will represent the time modulo 6.86 mSecs. This is due to the AND function removing bits 13, 14 and 15 from the raw count.

However, we are not limited to using the BIOS functions to measure the resistors. We can write our own function in assembler to give us back a 0.84*Sec resolution. This also allows us to write code that is sure to run on an XT as well as an AT as long as we have the games port hardware. Assuming we have done this and using the fact that the capacitor is nominally 0.01*F and knowing the threshold of the NE558, we can arrive at a mathematical formula relating the measured count to the resistance. Ref 5, a reasonable text on some of the PC's internal hardware, gives the relationship between the time and resistance as:

        T  =  24.2  +  (11.0  x  R)             (1)  time in *Sec and resistance in K*.
We can relate this to our measured count as 1 count equals 0.84*Sec:
        C  = (24.2  +  (11.0  x  R)) / 0.84     (2) calculated count for Resistance R
This formula says that if we have a 250k* resistance we'll get a 3310 count of 0.84*Sec periods and our STICK(n) function (either BASIC or C routine) will return 3310/16 or 207. The astute reader will notice that Ref 3 indicates the value will be 416 or twice as much for 250K*. The only way to resolve this is to actually measure the count for a real value of resistance. Well, I did this with a 270k* resistor on the A(x) input and measured a GW-BASIC STICK(0) value of between 207 and 213. I measured the same value from my C STICK(n) function. I can only conclude that Mr Ray Duncan is wrong, perhaps he was thinking of a 500k* resistor ?

It is worth pointing out that formula (1) and (2) above are only approximate because of the tolerance of the analogue components used. In particular, the capacitors probably have a manufacturing tolerance of * 5% (at worst they may be *20%). This will swamp out other smaller variations such differences in the threshold sensing circuit within the NE558 etc. This tolerance will mean that a given resistance will produce a variety of STICK(n) values for a given games port and for different games boards. Fortunately, most people are smart enough to write software that can be calibrated. This involves including a variable software factor that we can adjust with a known resistance.

MEASURING TEMPERATURE

Well, if you made it to here, you probably want to how to measure temperature with the games port. It's quite simple if you know you can purchase a device called a THERMISTOR quite cheaply ($1.75 from Dick Smith, see Ref 6). As its name implies, this device is a temperature sensitive resistor. The one I have is readily available and has the following properties:
NTC Thermistor, 100k* nominal @ 25 C, -5.2k*/ C, -55 C to +200 C.
This says that it is manufactured to have a resistance of about 100k*, will change resistance at the rate of 5.2k* per Celsius degree, and is rated to do this over the -55 C to +200 C temperature range. The NTC (Negative Temperature Coefficient) indicates that resistance decreases with increase in temperature.

All that we need to do is attach this thermistor to one of the resistance measuring inputs of the games port, write some software we can calibrate, and Bob's your uncle. By the time this article is in 16 bits I should have completed some software to run under DOS (XT and AT) and also WINDOWS to actually display the temperature. If you're interested then give me a call on (06) 251 5519.

MEASURING HUMIDITY

A number of electronic humidity sensors have been available for some time. Some work on the variable resistance principle and others work on the basis of changing capacitance. The variable capacitance variety were made until recently by Phillips, and because this requires a slightly different interfacing technique, I'll describe how to use these. The sensor has a Farnell part number of 2322-691-90001 and, if available, has a price of about $26.00. Its specifications are:
Range 10% to 90%, 122pF *15% at 25*C, 43%RH and 100kHz, typical sensitivity 0.4pF/%RH.
The main specification to note is that it will vary by about 0.4pF per % of Relative Humidity (%RH) and has a nominal value of 122pF (pica Farad) at 43%RH and 25*C. As you can see, this device seems a bit more complicated than the thermistor as the capacitance may also have a small thermal effect (although the specs don't mention this explicitly). The capacitance will vary from about 109pF to about 141pF over the range 10% to 90%RH. This is a variation of about 30% and should allow us a reasonable indication of humidity with a simple circuit.

The trick to using the humidity sensor is to be able to convert the capacitance into a time varying signal. An RC (Resistance-Capacitance) oscillator can be made very cheaply (about $2.00 worth of components) and this signal can be fed into one of the switch inputs (not the resistance inputs) of the games port. The idea is that the frequency of the oscillator will change as the humidity changes. Using our time interval measuring techniques described above (but not using the NE558) we can measure the period (equal to 1/frequency) of the oscillator. By again using a calibration technique, we can write some software to display relative humidity. If enough interest is generated I'll publish this simple circuit in the next issue. In the mean time, contact me if you want more details.

REFERENCES

  1. IBM PC Technical Reference, Appendix D, p D-55.
  2. Windows/DOS Developer's Journal, "Fast Times with the PC Clock", April '92, pp 37 to 48.
  3. IBM Rom BIOS, Ray Duncan, Microsoft Press (ISBN 1-55615-135-7), p 88.
  4. IBM AT Technical Reference, BIOS1 listing, pp 5-148 to 5-149.
  5. Interfacing to the IBM Personal Computer, Lewis Eggebrecht, SAMS (ISBN 0-672-22722-3), p233.
  6. NTC Thermistor, 100k* nominal, Dick Smith Cat R-1797 $1.75 ($12.30 for 10).
About the Author: Peter Gargano is director of Tech Edge Pty Ltd. He has a professional interest in making PC hardware perform real world measurements. In a recent project he used the games port, and some of the techniques discussed here, to measure the horsepower of go-Karts for tuning purposes.

Copyright (c) 1995, Peter Gargano
(converted to HTML 03 June 1999)