The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twenty Four (Part 1)

Table of Content

Chapter Twenty Four (Part 3)

CHAPTER TWENTY FOUR:
THE PC GAME ADAPTER (Part 2)
24.5.13 - An SGDI Driver for the Standard Game Adapter Card

24.5.13 An SGDI Driver for the Standard Game Adapter Card

If you write your program to make SGDI calls you will discover that the TestPresence call will probably return "not present" when your program searches for a resident SGDI driver in memory. This is because few manufacturers provide SGDI drivers at this point and even fewer standard game adapter companies ship any software at all with their products much less an SGDI driver. Gee what kind of standard is this if no one uses it? Well the purpose of this section is to rectify that problem.

The assembly code that appears at the end of this section provides a fully functional public domain SGDI driver for the standard game adapter card (the next section present an SGDI driver for the CH Products' Flightstick Pro). This allows you to write your application making only SGDI calls. By supplying the SGDI TSR with your product your customers can use your software with all standard joysticks. Later if they purchase a specialized device with its own SGDI driver your software will automatically work with that driver with no changes to your software.

If you do not like the idea of having a user run a TSR before your application you can always include the following code within your program's code space and activate it if the SGDI TestPresence call determines that no other SGDI driver is present in memory when you start your program.

Here's the complete code for the standard game adapter SGDI driver:

                .286
page    58
132
name    SGDI
title   SGDI Driver for Standard Game Adapter Card
subttl  This Program is Public Domain Material.

; SGDI.EXE
;
;       Usage:
;               SDGI
;
; This program loads a TSR which patches INT 15 so arbitrary game programs
; can read the joystick in a portable fashion.
;
;
; We need to load cseg in memory before any other segments!

cseg            segment para public 'code'
cseg            ends


; Initialization code
which we do not need except upon initial load

; goes in the following segment:

Initialize      segment para public 'INIT'
Initialize      ends

; UCR Standard Library routines which get dumped later on.

.xlist
include         stdlib.a
includelib      stdlib.lib
.list

sseg            segment para stack 'stack'
sseg            ends

zzzzzzseg       segment para public 'zzzzzzseg'
zzzzzzseg       ends



CSEG            segment para public 'CODE'
assume  cs:cseg
ds:nothing

wp              equ     <word ptr>
byp             equ     <byte ptr>

Int15Vect       dword   0

PSP             word    ?

; Port addresses for a typical joystick card:

JoyPort         equ     201h
JoyTrigger      equ     201h

; Data structure to hold information about each pot.
; (mainly for calibration and normalization purposes).

Pot             struc
PotMask         byte    0       ;Pot mask for hardware.
DidCal          byte    0       ;Is this pot calibrated?
min             word    5000    ;Minimum pot value
max             word    0       ;Max pot value
center          word    0       ;Pot value in the middle
Pot             ends

; Variables for each of the pots. Must initialize the masks so they
; mask out all the bits except the incomming bit for each pot.

Pot0            Pot     <1>
Pot1            Pot     <2>
Pot2            Pot     <4>
Pot3            Pot     <8>



; The IDstring address gets passed back to the caller on a testpresence
; call. The four bytes before the IDstring must contain the serial number
; and current driver number.

SerialNumber    byte    0
0
0
IDNumber        byte    0
IDString        byte    "Standard SGDI Driver"
0
byte    "Public Domain Driver Written by Randall L. Hyde"
0


;============================================================================
;
; ReadPots-     AH contains a bit mask to determine which pots we should read.
;               Bit 0 is one if we should read pot 0
bit 1 is one if we should
;               read pot 1
bit 2 is one if we should read pot 2
bit 3 is one
;               if we should read pot 3. All other bits will be zero.
;
;       This code returns the pot values in SI
BX
BP
and DI for Pot 0
1

;       2
& 3.
;

ReadPots        proc    near
sub     bp
bp
mov     si
bp
mov     di
bp
mov     bx
bp

; Wait for any previous signals to finish up before trying to read this
; guy. It is possible that the last pot we read was very short. However

; the trigger signal starts timers running for all four pots. This code
; terminates as soon as the current pot times out. If the user immediately
; reads another pot
it is quite possible that the new pot's timer has
; not yet expired from the previous read. The following loop makes sure we
; aren't measuring the time from the previous read.

mov     dx
JoyPort
mov     cx
400h
Wait4Clean:     in      al
dx
and     al
0Fh
loopnz  Wait4Clean

; Okay
read the pots. The following code triggers the 558 timer chip
; and then sits in a loop until all four pot bits (masked with the pot mask
; in AL) become zero. Each time through this loop that one or more of these
; bits contain zero
this loop increments the corresponding register(s).

mov     dx
JoyTrigger
out     dx
al          ;Trigger pots
mov     dx
JoyPort
mov     cx
1000h       ;Don't let this go on forever.
PotReadLoop:    in      al
dx
and     al
ah
jz      PotReadDone
shr     al
1
adc     si
0           ;Increment SI if pot 0 still active.
shr     al
1
adc     bx
0           ;Increment BX if pot 1 still active.
shr     al
1
adc     bp
0           ;Increment BP if pot 2 still active.
shr     al
1
adc     di
0           ;Increment DI if pot 3 still active.
loop    PotReadLoop     ;Stop
eventually
if funny hardware.

and     si
0FFFh       ;If we drop through to this point

and     bx
0FFFh       ; one or more pots timed out (usually
and     bp
0FFFh       ; because they are not connected).
and     di
0FFFh       ; The reg contains 4000h
set it to 0.
PotReadDone:    ret
ReadPots        endp



;----------------------------------------------------------------------------
;
; Normalize-    BX contains a pointer to a pot structure
AX contains
;               a pot value. Normalize that value according to the
;               calibrated pot.
;
; Note: DS must point at cseg before calling this routine.


assume  ds:cseg
Normalize       proc    near
push    cx

; Sanity check to make sure the calibration process went okay.

cmp     [bx].Pot.DidCal
0      ;Is this pot calibrated?
je      BadNorm                 ;If not
quit.

mov     dx
[bx].Pot.Center     ;Do a sanity check on the
cmp     dx
[bx].Pot.Min        ; min
center
and max
jbe     BadNorm                 ; values to make sure
cmp     dx
[bx].Pot.Max        ; min < center < max.
jae     BadNorm

; Clip the value if it is out of range.

cmp     ax
[bx].Pot.Min        ;If the value is less than
ja      MinOkay                 ; the minimum value
set it
mov     ax
[bx].Pot.Min        ; to the minimum value.
MinOkay:

cmp     ax
[bx].Pot.Max        ;If the value is greater than
jb      MaxOkay                 ; the maximum value
set it
mov     ax
[bx].Pot.Max        ; to the maximum value.
MaxOkay:

; Scale this guy around the center:

cmp     ax
[bx].Pot.Center     ;See if less than or greater
jb      Lower128                ; than centered value.

; Okay
current reading is greater than the centered value
scale the reading
; into the range 128..255 here:

sub     ax
[bx].Pot.Center
mov     dl
ah                  ;Multiply by 128
mov     ah
al
mov     dh
0
mov     al
dh
shr     dl
1
rcr     ax
1
mov     cx
[bx].Pot.Max
sub     cx
[bx].Pot.Center
jz      BadNorm                 ;Prevent division by zero.
div     cx                      ;Compute normalized value.
add     ax
128                 ;Scale to range 128..255.
cmp     ah
0
je      NormDone
mov     ax
0ffh                ;Result must fit in 8 bits!
jmp     NormDone

; If the reading is below the centered value
scale it into the range
; 0..127 here:

Lower128:       sub     ax
[bx].Pot.Min
mov     dl
ah
mov     ah
al
mov     dh
0
mov     al
dh
shr     dl
1
rcr     ax
1
mov     cx
[bx].Pot.Center
sub     cx
[bx].Pot.Min
jz      BadNorm
div     cx
cmp     ah
0
je      NormDone
mov     ax
0ffh
jmp     NormDone

; If something went wrong
return zero as the normalized value.

BadNorm:        sub     ax
ax

NormDone:       pop     cx
ret
Normalize       endp
assume  ds:nothing

;============================================================================
; INT 15h handler functions.
;============================================================================
;
; Although these are defined as near procs
they are not really procedures.
; The MyInt15 code jumps to each of these with BX
a far return address
and
; the flags sitting on the stack. Each of these routines must handle the
; stack appropriately.
;
;----------------------------------------------------------------------------
; BIOS- Handles the two BIOS calls
DL=0 to read the switches
DL=1 to
;       read the pots. For the BIOS routines
we'll ignore the cooley
;       switch (the hat) and simply read the other four switches.

BIOS            proc    near
cmp     dl
1           ;See if switch or pot routine.
jb      Read4Sw
je      ReadBIOSPots

; If not a valid BIOS call
jump to the original INT 15 handler and
; let it take care of this call.

pop     bx
jmp     cs:Int15Vect    ;Let someone else handle it!

; BIOS read switches function.

Read4Sw:        push    dx
mov     dx
JoyPort
in      al
dx
and     al
0F0h        ;Return only switch values.
pop     dx
pop     bx
iret

; BIOS read pots function.

ReadBIOSPots:   pop     bx              ;Return a value in BX!
push    si
push    di
push    bp
mov     ah
0Fh         ;Read all four pots.
call    ReadPots
mov     ax
si
mov     cx
bp          ;BX already contains pot 1 reading.
mov     dx
di
pop     bp
pop     di
pop     si
iret
BIOS            endp

;----------------------------------------------------------------------------
;
; ReadPot-              On entry
DL contains a pot number to read.
;               Read and normalize that pot and return the result in AL.

assume  ds:cseg
ReadPot         proc    near
;;;;;;;;;;      push    bx                      ;Already on stack.
push    ds
push    cx
push    dx
push    si
push    di
push    bp

mov     bx
cseg
mov     ds
bx

; If dl = 0
read and normalize the value for pot 0
if not
try some
; other pot.

cmp     dl
0
jne     Try1
mov     ah
Pot0.PotMask        ;Get bit for this pot.
call    ReadPots                ;Read pot 0.
lea     bx
Pot0                ;Pointer to pot data.
mov     ax
si                  ;Get pot 0 reading.
call    Normalize               ;Normalize to 0..FFh.
jmp     GotPot                  ;Return to caller.

; Test for DL=1 here (read and normalize pot 1).

Try1:           cmp     dl
1
jne     Try2
mov     ah
Pot1.PotMask
call    ReadPots
mov     ax
bx
lea     bx
Pot1
call    Normalize
jmp     GotPot

; Test for DL=2 here (read and normalize pot 2).

Try2:           cmp     dl
2
jne     Try3
mov     ah
Pot2.PotMask
call    ReadPots
lea     bx
Pot2
mov     ax
bp
call    Normalize
jmp     GotPot

; Test for DL=3 here (read and normalize pot 3).

Try3:           cmp     dl
3
jne     BadPot
mov     ah
Pot3.PotMask
call    ReadPots
lea     bx
Pot3
mov     ax
di
call    Normalize
jmp     GotPot

; Bad value in DL if we drop to this point. The standard game card
; only supports four pots.

BadPot:         sub     ax
ax          ;Pot not available
return zero.
GotPot:         pop     bp
pop     di
pop     si
pop     dx
pop     cx
pop     ds
pop     bx
iret
ReadPot         endp
assume  ds:nothing


;----------------------------------------------------------------------------
;
; ReadRaw-              On entry
DL contains a pot number to read.
;               Read that pot and return the unnormalized result in AX.

assume  ds:cseg
ReadRaw         proc    near
;;;;;;;;;;      push    bx              ;Already on stack.
push    ds
push    cx
push    dx
push    si
push    di
push    bp

mov     bx
cseg
mov     ds
bx

; This code is almost identical to the ReadPot code. The only difference
; is that we don't bother normalizing the result and (of course) we return
; the value in AX rather than AL.

cmp     dl
0
jne     Try1
mov     ah
Pot0.PotMask
call    ReadPots
mov     ax
si
jmp     GotPot

Try1:           cmp     dl
1
jne     Try2
mov     ah
Pot1.PotMask
call    ReadPots
mov     ax
bx
jmp     GotPot

Try2:           cmp     dl
2
jne     Try3
mov     ah
Pot2.PotMask
call    ReadPots
mov     ax
bp
jmp     GotPot

Try3:           cmp     dl
3
jne     BadPot
mov     ah
Pot3.PotMask
call    ReadPots
mov     ax
di
jmp     GotPot

BadPot:         sub     ax
ax          ;Pot not available
return zero.
GotPot:         pop     bp
pop     di
pop     si
pop     dx
pop     cx
pop     ds
pop     bx
iret
ReadRaw         endp
assume  ds:nothing


;----------------------------------------------------------------------------
; Read4Pots-    Reads pots zero
one
two
and three returning their
;               values in AL
AH
DL
and DH.
;
;               On entry
AL contains the pot mask to select which pots
;               we should read (bit 0=1 for pot 0
bit 1=1 for pot 1
etc).

Read4Pots       proc    near
;;;;;;;;;;;     push    bx                      ;Already on stack
push    ds
push    cx
push    si
push    di
push    bp

mov     dx
cseg
mov     ds
dx

mov     ah
al
call    ReadPots

push    bx                      ;Save pot 1 reading.
mov     ax
si                  ;Get pot 0 reading.
lea     bx
Pot0                ;Point bx at pot0 vars.
call    Normalize               ;Normalize.
mov     cl
al                  ;Save for later.

pop     ax                      ;Retreive pot 1 reading.
lea     bx
Pot1
call    Normalize
mov     ch
al                  ;Save normalized value.

mov     ax
bp
lea     bx
Pot2
call    Normalize
mov     dl
al                  ;Pot 2 value.

mov     ax
di
lea     bx
Pot3
call    Normalize
mov     dh
al                  ;Pot 3 value.
mov     ax
cx                  ;Pots 0 and 1.

pop     bp
pop     di
pop     si
pop     cx
pop     ds
pop     bx
iret
Read4Pots       endp




;----------------------------------------------------------------------------
; CalPot-       Calibrate the pot specified by DL. On entry
AL contains
;               the minimum pot value (it better be less than 256!)
BX
;               contains the maximum pot value
and CX contains the centered
;               pot value.

assume  ds:cseg
CalPot          proc    near
pop     bx                      ;Retrieve maximum value
push    ds
push    si
mov     si
cseg
mov     ds
si

; Sanity check on parameters
sort them in ascending order:

mov     ah
0
cmp     bx
cx                  ;Make sure center < max
ja      GoodMax
xchg    bx
cx
GoodMax:        cmp     ax
cx                  ;Make sure min < center.
jb      GoodMin                 ; (note: may make center<max).
xchg    ax
cx
GoodMin:        cmp     cx
bx                  ;Again
be sure center < max.
jb      GoodCenter
xchg    cx
bx
GoodCenter:


; Okay
figure out who were supposed to calibrate:

lea     si
Pot0
cmp     dl
1
jb      DoCal                   ;Branch if this is pot 0
lea     si
Pot1
je      DoCal                   ;Branch if this is pot 1
lea     si
Pot2
cmp     dl
3
jb      DoCal                   ;Branch if this is pot 2
jne     CalDone                 ;Branch if not pot 3
lea     si
Pot3

DoCal:          mov     [si].Pot.min
ax        ;Store away the minimum

mov     [si].Pot.max
bx        ; maximum
and
mov     [si].Pot.center
cx     ; centered values.
mov     [si].Pot.DidCal
1      ;Note we've cal'd this pot.
CalDone:        pop     si
pop     ds
iret
CalPot          endp
assume  ds:nothing


;----------------------------------------------------------------------------
; TestCal-      Just checks to see if the pot specified by DL has already
;               been calibrated.

assume  ds:cseg
TestCal         proc    near
;;;;;;;;        push    bx              ;Already on stack
push    ds
mov     bx
cseg
mov     ds
bx

sub     ax
ax          ;Assume no calibration (also zeros AH)
lea     bx
Pot0        ;Get the address of the specified
cmp     dl
1           ; pot's data structure into the
jb      GetCal          ; BX register.
lea     bx
Pot1
je      GetCal
lea     bx
Pot2
cmp     dl
3
jb      GetCal
jne     BadCal
lea     bx
Pot3

GetCal:         mov     al
[bx].Pot.DidCal
BadCal:         pop     ds
pop     bx
iret
TestCal         endp
assume  ds:nothing


;----------------------------------------------------------------------------
;
; ReadSw-       Reads the switch whose switch number appears in DL.

ReadSw          proc    near
;;;;;;;         push    bx              ;Already on stack
push    cx

sub     ax
ax          ;Assume no such switch.
cmp     dl
3           ;Return if the switch number is
ja      NotDown         ; greater than three.

mov     cl
dl          ;Save switch to read.
add     cl
4           ;Move from position four down to zero.
mov     dx
JoyPort
in      al
dx          ;Read the switches.
shr     al
cl          ;Move desired switch bit into bit 0.
xor     al
1           ;Invert so sw down=1.
and     ax
1           ;Remove other junk bits.
NotDown:        pop     cx
pop     bx
iret
ReadSw          endp


;----------------------------------------------------------------------------
;
; Read16Sw-     Reads all four switches and returns their values in AX.

Read16Sw        proc    near
;;;;;;;;        push    bx                      ;Already on stack
mov     dx
JoyPort
in      al
dx
shr     al
4
xor     al
0Fh                 ;Invert all switches.
and     ax
0Fh                 ;Set other bits to zero.
pop     bx
iret
Read16Sw        endp


;****************************************************************************
;
; MyInt15-      Patch for the BIOS INT 15 routine to control reading the
;               joystick.

MyInt15         proc    far
push    bx
cmp     ah
84h                 ;Joystick code?
je      DoJoystick
OtherInt15:     pop     bx
jmp     cs:Int15Vect

DoJoystick:     mov     bh
0
mov     bl
dh
cmp     bl
80h
jae     VendorCalls
cmp     bx
JmpSize
jae     OtherInt15
shl     bx
1
jmp     wp cs:jmptable[bx]

jmptable        word    BIOS
word    ReadPot
Read4Pots
CalPot
TestCal
word    ReadRaw
OtherInt15
OtherInt15
word    ReadSw
Read16Sw
JmpSize         =       ($-jmptable)/2


; Handle vendor specific calls here.

VendorCalls:    je      RemoveDriver
cmp     bl
81h
je      TestPresence
pop     bx
jmp     cs:Int15Vect


; TestPresence- Returns zero in AX and a pointer to the ID string in ES:BX

TestPresence:   pop     bx                      ;Get old value off stack.
sub     ax
ax
mov     bx
cseg
mov     es
bx
lea     bx
IDString
iret

; RemoveDriver- If there are no other drivers loaded after this one in
;                memory
disconnect it and remove it from memory.

RemoveDriver:
push    ds
push    es
push    ax
push    dx

mov     dx
cseg
mov     ds
dx

; See if we're the last routine patched into INT 15h

mov     ax
3515h
int     21h
cmp     bx
offset MyInt15
jne     CantRemove
mov     bx
es
cmp     bx
wp seg MyInt15
jne     CantRemove

mov     ax
PSP                 ;Free the memory we're in
mov     es
ax
push    es
mov     ax
es:[2ch]            ;First
free env block.
mov     es
ax
mov     ah
49h
int     21h

pop     es                      ;Now free program space.
mov     ah
49h
int     21h

lds     dx
Int15Vect           ;Restore previous int vect.
mov     ax
2515h
int     21h

CantRemove:     pop     dx
pop     ax
pop     es
pop     ds
pop     bx
iret
MyInt15         endp
cseg            ends



Initialize      segment para public 'INIT'
assume  cs:Initialize
ds:cseg
Main            proc
mov     ax
cseg                ;Get ptr to vars segment
mov     es
ax
mov     es:PSP
ds              ;Save PSP value away
mov     ds
ax

mov     ax
zzzzzzseg
mov     es
ax
mov     cx
100h
meminit2

print
byte    " Standard Game Device Interface driver"
cr
lf
byte    " PC Compatible Game Adapter Cards"
cr
lf
byte    " Written by Randall Hyde"
cr
lf
byte    cr
lf
byte    cr
lf
byte    "'SGDI REMOVE' removes the driver from memory"
cr
lf
byte    lf
byte    0

mov     ax
1
argv                            ;If no parameters
empty str.
stricmpl
byte    "REMOVE"
0
jne     NoRmv

mov     dh
81h                 ;Remove opcode.
mov     ax
84ffh
int     15h                     ;See if we're already loaded.
test    ax
ax                  ;Get a zero back?
jz      Installed
print
byte    "SGDI driver is not present in memory
REMOVE "
byte    "command ignored."
cr
lf
0
mov     ax
4c01h               ;Exit to DOS.
int     21h

Installed:              mov     ax
8400h
mov     dh
80h                 ;Remove call
int     15h
mov     ax
8400h
mov     dh
81h                 ;TestPresence call
int     15h
cmp     ax
0
je      NotRemoved
print
byte    "Successfully removed SGDI driver from memory."
byte    cr
lf
0
mov     ax
4c01h               ;Exit to DOS.
int     21h

NotRemoved:     print
byte    "SGDI driver is still present in memory."
cr
lf
0
mov     ax
4c01h               ;Exit to DOS.
int     21h





; Okay
Patch INT 15 and go TSR at this point.

NoRmv:
mov     ax
3515h
int     21h
mov     wp Int15Vect
bx
mov     wp Int15Vect+2
es

mov     dx
cseg
mov     ds
dx
mov     dx
offset MyInt15
mov     ax
2515h
int     21h

mov     dx
cseg
mov     ds
dx
mov     dx
seg Initialize
sub     dx
ds:psp
add     dx
2
mov     ax
3100h               ;Do TSR
int     21h
Main            endp

Initialize      ends


sseg            segment para stack 'stack'
word    128 dup (0)
endstk          word    ?
sseg            ends


zzzzzzseg       segment para public 'zzzzzzseg'
byte    16 dup (0)
zzzzzzseg       ends
end     Main

The following program makes several different types of calls to an SGDI driver. You can use this code to test out an SGDI TSR:

                .xlist
include         stdlib.a
includelib      stdlib.lib
.list


cseg            segment para public 'code'
assume  cs:cseg
ds:nothing

MinVal0         word    ?
MinVal1         word    ?
MaxVal0         word    ?
MaxVal1         word    ?


; Wait4Button-  Waits until the user presses and releases a button.

Wait4Button     proc    near
push    ax
push    dx
push    cx

W4BLp:          mov     ah
84h
mov     dx
900h        ;Read the L.O. 16 buttons.
int     15h
cmp     ax
0           ;Any button down? If not

je      W4BLp           ; loop until this is so.

xor     cx
cx          ;Debouncing delay loop.
Delay:          loop    Delay

W4nBLp:         mov     ah
84h         ;Now wait until the user releases
mov     dx
900h        ; all buttons
int     15h
cmp     ax
0
jne     W4nBLp

Delay2:         loop    Delay2

pop     cx
pop     dx
pop     ax
ret
Wait4Button     endp

Main            proc

print
byte    "SGDI Test Program."
cr
lf
byte    "Written by Randall Hyde"
cr
lf
lf
byte    "Press any key to continue"
cr
lf
0

getc

mov     ah
84h
mov     dh
4           ;Test presence call.
int     15h
cmp     ax
0           ;See if there
je      MainLoop0
print
byte    "No SGDI driver present in memory."
cr
lf
0
jmp     Quit

MainLoop0:      print
byte    "BIOS: "
0

; Okay
read the switches and raw pot values using the BIOS compatible calls.

mov     ah
84h
mov     dx
0           ;BIOS compat. read switches.
int     15h
puth                    ;Output switch values.
mov     al
' '
putc

mov     ah
84h         ;BIOS compat. read pots.
mov     dx
1
int     15h
putw
mov     al
' '
putc
mov     ax
bx
putw
mov     al
' '
putc
mov     ax
cx
putw
mov     al
' '
putc
mov     ax
dx
putw

putcr
mov     ah
1           ;Repeat until key press.
int     16h
je      MainLoop0
getc


; Read the minimum and maximum values for each pot from the user so we
; can calibrate the pots.

print
byte    cr
lf
lf
lf
byte    "Move joystick to upper left corner and press "
byte    "any button."
cr
lf
0

call    Wait4Button
mov     ah
84h
mov     dx
1           ;Read Raw Values
int     15h
mov     MinVal0
ax
mov     MinVal1
bx

print
byte    cr
lf
byte    "Move the joystick to the lower right corner "
byte    "and press any button"
cr
lf
0

call    Wait4Button
mov     ah
84h
mov     dx
1           ;Read Raw Values
int     15h
mov     MaxVal0
ax
mov     MaxVal1
bx

; Calibrate the pots.

mov     ax
MinVal0     ;Will be eight bits or less.
mov     bx
MaxVal0
mov     cx
bx          ;Compute centered value as the
add     cx
ax          ; average of these two (this is
shr     cx
1           ; dangerous
but usually works!)
mov     ah
84h
mov     dx
300h        ;Calibrate pot 0
int     15h

mov     ax
MinVal1     ;Will be eight bits or less.
mov     bx
MaxVal1
mov     cx
bx          ;Compute centered value as the
add     cx
ax          ; average of these two (this is
shr     cx
1           ; dangerous
but usually works!)
mov     ah
84h
mov     dx
301h        ;Calibrate pot 1
int     15h

MainLoop1:      print
byte    "ReadSw: "
0

; Okay
read the switches and raw pot values using the BIOS compatible calls.

mov     ah
84h
mov     dx
800h        ;Read switch zero.
int     15h
or      al
'0'
putc

mov     ah
84h
mov     dx
801h        ;Read switch one.
int     15h
or      al
'0'
putc

mov     ah
84h
mov     dx
802h        ;Read switch two.
int     15h
or      al
'0'
putc

mov     ah
84h
mov     dx
803h        ;Read switch three.
int     15h
or      al
'0'
putc

mov     ah
84h
mov     dx
804h        ;Read switch four
int     15h
or      al
'0'
putc

mov     ah
84h
mov     dx
805h        ;Read switch five.
int     15h
or      al
'0'
putc

mov     ah
84h
mov     dx
806h        ;Read switch six.
int     15h
or      al
'0'
putc

mov     ah
84h
mov     dx
807h        ;Read switch seven.
int     15h             ;We won't bother with
or      al
'0'         ; any more switches.
putc
mov     al
' '
putc

mov     ah
84h
mov     dh
9           ;Read all 16 switches.
int     15h
putw

print
byte    " Pots: "
0
mov     ax
8403h       ;Read joystick pots.
mov     dx
200h        ;Read four pots.
int     15h
puth
mov     al
' '
putc
mov     al
ah
puth
mov     al
' '
putc

mov     ah
84h
mov     dx
503h        ;Raw read
pot 3.
int     15h
putw

putcr
mov     ah
1           ;Repeat until key press.
int     16h
je      MainLoop1
getc




Quit:           ExitPgm                 ;DOS macro to quit program.
Main            endp

cseg            ends

sseg            segment para stack 'stack'
stk             byte    1024 dup ("stack ")
sseg            ends

zzzzzzseg       segment para public 'zzzzzz'
LastBytes       byte    16 dup (?)
zzzzzzseg       ends
end     Main

Chapter Twenty Four (Part 1)

Table of Content

Chapter Twenty Four (Part 3)

Chapter Twenty Four: The PC Game Adapter (Part 2)
29 SEP 1996