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