|
Table of Content | Chapter Twenty Four (Part 4) |
| CHAPTER TWENTY
FOUR: THE PC GAME ADAPTER (Part 3) |
| 24.6 - An SGDI Driver for the CH Products' Flight Stick Pro' |
| 24.6 An SGDI Driver for the CH Products' Flight Stick Pro' |
The CH Product's FlightStick Pro joystick is a good example of a specialized product for which the SGDI driver is a perfect solution. The FlightStick Pro provides three pots and five switches the fifth switch being a special five-position cooley switch. Although the pots on the FlightStick Pro map to three of the analog inputs on the standard game adapter card (pots zero one and three) there are insufficient digital inputs to handle the eight inputs necessary for the FlightStick Pro's four buttons and cooley switch.
The FlightStick Pro (FSP) uses some electronic circuitry to map these eight switch positions to four input bits. To do so they place one restriction on the use of the FSP switches - you can only press one of them at a time. If you hold down two or more switches at the same time the FSP hardware selects one of the switches and reports that value; it ignores the other switches until you release the button. Since only one switch can be read at a time the FSP hardware generates a four bit value that determines the current state of the switches. It returns these four bits as the switch values on the standard game adapter card. The following table lists the values for each of the switches:
| Value (binary) | Priority | Switch Position |
|---|---|---|
| 0000 | Highest | Up position on the cooley switch. |
| 0100 | 7 | Right position on the cooley switch. |
| 1000 | 6 | Down position on the cooley switch. |
| 1100 | 5 | Left position on the cooley switch. |
| 1110 | 4 | Trigger on the joystick. |
| 1101 | 3 | Leftmost button on the joystick. |
| 1011 | 2 | Rightmost button on the joystick. |
| 0111 | Lowest | Middle button on the joystick. |
| 1111 | - | No buttons currently down. |
Note that the buttons look just like a single button press. The cooley switch positions contain a position value in bits six and seven; bits four and five always contain zero when the cooley switch is active.
The SGDI driver for the FlightStick Pro is very similar to the standard game adapter card SGDI driver. Since the FlightStick Pro only provides three pots this code doesn't bother trying to read pot 2 (which is non-existent). Of course the switches on the FlightStick Pro are quite a bit different than those on standard joysticks so the FSP SGDI driver maps the FPS switches to eight of the SGDI logical switches. By reading switches zero through seven you can test the following conditions on the FSP:
| This SGDI Switch number: | Maps to this FSP Switch: |
|---|---|
| 0 | Trigger on joystick. |
| 1 | Left button on joystick. |
| 2 | Middle button on joystick. |
| 3 | Right button on joystick. |
| 4 | Cooley up position. |
| 5 | Cooley left position. |
| 6 | Cooley right position. |
| 7 | Cooley down position. |
The FSP SGDI driver contains one other novel feature it will allow the user to swap the functions of the left and right switches on the joystick. Many games often assign important functions to the trigger and left button since they are easiest to press (right handed players can easily press the left button with their thumb). By typing "LEFT" on the command line the FSP SGDI driver will swap the functions of the left and right buttons so left handed players can easily activate this function with their thumb as well.
The following code provides the complete listing for the FSPSGDI driver. Note that you can use the same test program from the previous section to test this driver.
.286
page 58
132
name FSPSGDI
title FSPSGDI (CH Products Standard Game Device Interface).
; FSPSGDI.EXE
;
; Usage:
; FSPSDGI {LEFT}
;
; This program loads a TSR which patches INT 15 so arbitrary game programs
; can read the CH Products FlightStick Pro joystick in a portable fashion.
wp equ <word ptr>
byp equ <byte ptr>
; 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
Int15Vect dword 0
PSP word ?
; Port addresses for a typical joystick card:
JoyPort equ 201h
JoyTrigger equ 201h
CurrentReading word 0
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
Pot0 Pot <1>
Pot1 Pot <2>
Pot3 Pot <8>
; SwapButtons- 0 if we should use normal flightstick pro buttons
; 1 if we should swap the left and right buttons.
SwapButtons byte 0
; SwBits- the four bit input value from the Flightstick Pro selects one
; of the following bit patterns for a given switch position.
SwBits byte 10h ;Sw4
byte 0 ;NA
byte 0 ;NA
byte 0 ;NA
byte 40h ;Sw6
byte 0 ;NA
byte 0 ;NA
byte 4 ;Sw 2
byte 80h ;Sw 7
byte 0 ;NA
byte 0 ;NA
byte 8 ;Sw 3
byte 20h ;Sw 5
byte 2 ;Sw 1
byte 1 ;Sw 0
byte 0 ;NA
SwBitsL byte 10h ;Sw4
byte 0 ;NA
byte 0 ;NA
byte 0 ;NA
byte 40h ;Sw6
byte 0 ;NA
byte 0 ;NA
byte 4 ;Sw 2
byte 80h ;Sw 7
byte 0 ;NA
byte 0 ;NA
byte 2 ;Sw 3
byte 20h ;Sw 5
byte 8 ;Sw 1
byte 1 ;Sw 0
byte 0 ;NA
; 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 "CH Products:Flightstick Pro"
0
byte "Written by Randall 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 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 pots to finish any past junk:
mov dx
JoyPort
out dx
al ;Trigger pots
mov cx
400h
Wait4Pots: in al
dx
and al
0Fh
loopnz Wait4Pots
; Okay
read the pots:
mov dx
JoyTrigger
out dx
al ;Trigger pots
mov dx
JoyPort
mov cx
8000h ;Don't let this go on forever.
PotReadLoop: in al
dx
and al
ah
jz PotReadDone
shr al
1
adc si
0
shr al
1
adc bp
0
shr al
2
adc di
0
loop PotReadLoop
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
je BadNorm
mov dx
[bx].Pot.Center
cmp dx
[bx].Pot.Min
jbe BadNorm
cmp dx
[bx].Pot.Max
jae BadNorm
; Clip the value if it is out of range.
cmp ax
[bx].Pot.Min
ja MinOkay
mov ax
[bx].Pot.Min
MinOkay:
cmp ax
[bx].Pot.Max
jb MaxOkay
mov ax
[bx].Pot.Max
MaxOkay:
; Scale this guy around the center:
cmp ax
[bx].Pot.Center
jb Lower128
; Scale in 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
; Scale in the range 0..127 here:
Lower128: sub ax
[bx].Pot.Min
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.Center
sub cx
[bx].Pot.Min
jz BadNorm
div cx ;Compute normalized value.
cmp ah
0
je NormDone
mov ax
0ffh ;Result must fit in 8 bits!
jmp NormDone
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
pop bx
jmp cs:Int15Vect ;Let someone else handle it!
Read4Sw: push dx
mov dx
JoyPort
in al
dx
shr al
4
mov bl
al
mov bh
0
cmp cs:SwapButtons
0
je DoLeft2
mov al
cs:SwBitsL[bx]
jmp SBDone
DoLeft2: mov al
cs:SwBits[bx]
SBDone: rol al
4 ;Put Sw0..3 in upper bits and make
not al ; 0=switch down
just like game card.
pop dx
pop bx
iret
ReadBIOSPots: pop bx ;Return a value in BX!
push si
push di
push bp
mov ah
0bh
call ReadPots
mov ax
si
mov bx
bp
mov dx
di
sub cx
cx
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
cmp dl
0
jne Try1
mov ah
Pot0.PotMask
call ReadPots
lea bx
Pot0
mov ax
si
call Normalize
jmp GotPot
Try1: cmp dl
1
jne Try3
mov ah
Pot1.PotMask
call ReadPots
lea bx
Pot1
mov ax
bp
call Normalize
jmp GotPot
Try3: cmp dl
3
jne BadPot
mov ah
Pot3.PotMask
call ReadPots
lea bx
Pot3
mov ax
di
call Normalize
jmp GotPot
BadPot: sub ax
ax ;Question: Should we pass this on
; or just 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 AL.
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
cmp dl
0
jne Try1
mov ah
Pot0.PotMask
call ReadPots
mov ax
si
jmp GotPot
Try1: cmp dl
1
jne Try3
mov ah
Pot1.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 ;Just 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. Since the flightstick
; Pro doesn't have a pot 2 installed
return zero for
; that guy.
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
0bh ;Read pots 0
1
and 3.
call ReadPots
mov ax
si
lea bx
Pot0
call Normalize
mov cl
al
mov ax
bp
lea bx
Pot1
call Normalize
mov ch
al
mov ax
di
lea bx
Pot3
call Normalize
mov dh
al ;Pot 3 value.
mov ax
cx ;Pots 0 and 1.
mov dl
0 ;Pot 2 is non-existant.
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
ja GoodMax
xchg bx
cx
GoodMax: cmp ax
cx
jb GoodMin
xchg ax
cx
GoodMin: cmp cx
bx
jb GoodCenter
xchg cx
bx
GoodCenter:
; Okay
figure out who were supposed to calibrate:
lea si
Pot0
cmp dl
1
jb DoCal
lea si
Pot1
je DoCal
cmp dl
3
jne CalDone
lea si
Pot3
DoCal: mov [si].Pot.min
ax
mov [si].Pot.max
bx
mov [si].Pot.center
cx
mov [si].Pot.DidCal
1
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
lea bx
Pot0
cmp dl
1
jb GetCal
lea bx
Pot1
je GetCal
cmp dl
3
jne BadCal
lea bx
Pot3
GetCal: mov al
[bx].Pot.DidCal
mov ah
0
BadCal: pop ds
pop bx
iret
TestCal endp
assume ds:nothing
;----------------------------------------------------------------------------
;
; ReadSw- Reads the switch whose switch number appears in DL.
SwTable byte 11100000b
11010000b
01110000b
10110000b
byte 00000000b
11000000b
01000000b
10000000b
SwTableL byte 11100000b
10110000b
01110000b
11010000b
byte 00000000b
11000000b
01000000b
10000000b
ReadSw proc near
;;;;;;; push bx ;Already on stack
mov bl
dl ;Save switch to read.
mov bh
0
mov dx
JoyPort
in al
dx
and al
0f0h
cmp cs:SwapButtons
0
je DoLeft0
cmp al
cs:SwTableL[bx]
jne NotDown
jmp IsDown
DoLeft0: cmp al
cs:SwTable[bx]
jne NotDown
IsDown: mov ax
1
pop bx
iret
NotDown: sub ax
ax
pop bx
iret
ReadSw endp
;----------------------------------------------------------------------------
;
; Read16Sw- Reads all eight switches and returns their values in AX.
Read16Sw proc near
;;;;;;;; push bx ;Already on stack
mov ah
0 ;Switches 8-15 are non-existant.
mov dx
JoyPort
in al
dx
shr al
4
mov bl
al
mov bh
0
cmp cs:SwapButtons
0
je DoLeft1
mov al
cs:SwBitsL[bx]
jmp R8Done
DoLeft1: mov al
cs:SwBits[bx]
R8Done: 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
; The following segment is tossed when this code goes resident.
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 "CH Products Flightstick Pro"
cr
lf
byte "Written by Randall Hyde"
cr
lf
byte cr
lf
byte "'FSPSGDI LEFT' swaps the left and right buttons for "
byte "left handed players"
cr
lf
byte "'FSPSGDI REMOVE' removes the driver from memory"
byte cr
lf
lf
byte 0
mov ax
1
argv ;If no parameters
empty str.
stricmpl
byte "LEFT"
0
jne NoLEFT
mov SwapButtons
1
print
byte "Left and right buttons swapped"
cr
lf
0
jmp SwappedLeft
NoLEFT: stricmpl
byte "REMOVE"
0
jne NoRmv
mov dh
81h
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
NoRmv:
; Okay
Patch INT 15 and go TSR at this point.
SwappedLeft: 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
|
Table of Content | Chapter Twenty Four (Part 4) |
Chapter Twenty Four: The PC Game
Adapter (Part 3)
29 SEP 1996