;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ; DCC-MB.ASM v 1.00 Digital Command Control Driver ; Copyright (c) 1996, Michael Brandt ; ; A memory resident driver for the DCC-MB parallel port DCC ; interface. This program must be assembled into a .COM file. ; ; Application programs access this driver through software ; interrupt 70h ;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ SOFTWARE_INTERRUPT equ 70h PrintString macro sdMessage mov ah,09h mov dx,offset sdMessage int 21h endm PrintChar macro cChar mov dl,cChar mov ah,02h int 21h endm ;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ; CODE SEGMENT STARTS HERE ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ cseg segment assume cs:cseg,ds:nothing org 100h start: jmp initialize ;------------------------------------------------------------------------------- ; MEMORY RESIDENT VARIABLES ;~~~~~ Hardware Information ~~~~~~ nPortNum db ? ; LPT port number (1 or 2) nPortAddr dw ? ; LPT port address (get from BIOS data area) nIRQ db ? ; LPT1: IRQ 7 LPT2: IRQ 5 nIntNum db ? ; LPT1: Int 0Fh LPT2: Int 0Dh lpOldLptInt dd ? ; Address of old parallel port interrupt nJoyPortAddr dw 0201h ;~~~~~~~~~~~ The Queue - an array of pointers to packets ~~~~~~~~~~~~~~~~~ nQSize dw 1 ; size of queue (# of active locos + 1) npQPacket dw offset DCCIdlePacket ; Idle packet is always 1st in queue dw 128 dup (?) ; up to 128 more packets in queue nQIndex dw 0 ; current packet index in Queue bInsertFlag db 0 ; 0 = normal queue operation ; 1 = send packet at npInsertPacket npInsertPkt dw ? ; Pointer to packet to insert ;~~~~~~~~~~~ Stuff related to the 128 Loco Packet Buffers ~~~~~~~~~~~~~~~ npLocoPkt dw 128 dup (?) ; cs:Address of Packet Buffer for ; each DCC address nLocoStatus db 128 dup (0) ; Status for each loco. 0 = inactive, ; 1 to 3 = Speed Step Mode (14, 28, 128) nMFG1Acc db 128 dup (0) ; 5 bits of Group 1 Accessory values ; (in bits 0-4) for each DCC address ;~~~~~~~~~~ Things the driver uses to keep "track" ~~~~~~~~~~~~~~~~~~~~ npNextByte dw ? ; pointer to next byte to send npLastByteInPkt dw ? ; pointer to last byte in current packet nPktCounter db 0 ; count of packets that have been sent nQueueCounter db 0 ; count of cycles of the queue nActiveLocos dw 0 ; number of active locos nDriverMode db 0 ; 0=Off 1=Operate 2=Program nPacketReps db 5 ; # of repetitions for non-queue packets ;~~~~~~~~~~~~~~ Throttle variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cJoyData db 4 dup (0) cJoyButtons db 0 ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; DCC Packet Buffers -- Each DCC Packet is stored in a buffer as follows: ; 2 bytes - Address of last byte in this packet ; n bytes - The DCC packet itself, packed into 8-bit bytes. If necessary, ; padding is done by adding bits to the preamble (this is legal.) ; A 3-byte DCC packet (plus 13-bit preamble and 3 start bits) ; gets packed into 5 bytes. ;............................................................................. ; SPEED/DIR PACKET BUFFERS - One for each DCC address from 0 to 127 LocoBuf db 128 dup (7 dup (?)) ; 128 buffers, 7 bytes each ;............................................................................. ; DCC RESET PACKET BUFFER (S-9.2 B) DCCResetPacket dw offset DCCResetPacket + 6 ; (last byte in this packet) db 0FFh, 0F8h, 0h, 0h, 0h ; the 5 packet bytes ;............................................................................. ; DCC IDLE PACKET BUFFER (S-9.2 B) DCCIdlePacket dw offset DCCIdlePacket + 6 ; (last byte in this packet) db 0FFh, 0FBh, 0FCh, 0h, 0FFh ; the 5 packet bytes ;............................................................................. ; GENERAL PURPOSE 3-BYTE PACKET BUFFER - also includes an idle packet to ; prevent repeated decoder addressing (see S-9.2 C) GP3Packet dw offset GP3Packet + 11 db 5 dup (0FFh) ; the 5 packet bytes db 0FFh, 0FBh, 0FCh, 0h, 0FFh ; the idle packet ;............................................................................. ; PROGRAM PACKET BUFFER - includes extended preamble (RP-9.2.3 E.1) ProgramPacket dw offset ProgramPacket + 7 ; (last byte in this packet) db 0FFh, 0FFh, 0F9h, 0E0h, 0h, 0h ; the 6 packet bytes ;............................................................................. ; ACCESSORY PACKET BUFFER - for accessory decoder with 10-bit addresses AccPacket dw offset AccPacket + 6 ; (last byte in this packet) db 0FFh, 0FAh, 0FFh, 0FFh, 0FFh ; the 5 packet bytes ;............................................................................. PacketA dw offset PacketA + 6 ; (last byte in this packet) db 5 dup (0FFh) ; the 5 packet bytes ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; NEW PARALLEL PORT (HARDWARE) INTERRUPT HANDLER ; ; This procedure gets called when the DCC interface sends a pulse to ; pin 10 (~ack) of the parallel port, indicating a request for 8 more ; data bits. It doesn't get called when the driver mode is OFF. LPTInterrupt proc far push ax push bx push dx push di sti ; Tell 80x86 to reenable interrupts mov al,20h ; End of Interrupt (EOI) command... out 20h,al ; ...send it to 8259 interrupt controller mov dx,nPortAddr ; dx is parallel port I/O address mov di,npNextByte ; cs:di points to byte to send mov al,cs:[di] ; al is the byte to send out dx,al ; send it! cmp di,npLastByteInPkt jae lptint_eop ; Not at end of packet: inc di ; cs:di points to next byte to send mov npNextByte,di ; save the pointer jmp lptint_exit ; and that's all for now lptint_eop: ; At end of this packet: inc nPktCounter ; increment packet counter cmp bInsertFlag,0 je lptint1 ; INSERT FLAG IS SET: mov di,npInsertPkt ; di points to "insert" packet buffer jmp lptint3 lptint1: ; INSERT FLAG NOT SET (normal queue): mov bx,nQIndex inc bx ; bx = next index in queue cmp bx,nQSize jb lptint2 ; if queue index >= queue length: mov bx,0 ; set index back to 0 inc nQueueCounter ; and increment the queue counter lptint2: mov nQIndex,bx ; save new queue index shl bx,1 mov di,npQPacket[bx] ; get pointer to next packet buffer lptint3: mov ax,cs:[di] ; ax = first 2 bytes of buffer... mov npLastByteInPkt,ax ; ...pointer to last byte in packet inc di inc di ; di points to 3rd byte in buffer... mov npNextByte,di ; ...which is the next byte to send lptint_exit: pop di pop dx pop bx pop ax iret LPTInterrupt endp ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; NEW SOFTWARE INTERRUPT 70h HANDLER SWInterrupt proc far sti cmp ah,0 jne SWI1 cmp al,255 je SWI0a call SetDriverMode jmp SWIDone SWI0a: call QueryDriver jmp SWIDone SWI1: cmp ah,1 jne SWI10 call DCCReset jmp SWIDone SWI10: cmp ah,10 jne SWI11 call ActivateLoco jmp SWIDone SWI11: cmp ah,11 jne SWI12 call DeactivateLoco jmp SWIDone SWI12: cmp ah,12 jne SWI13 call SetLocoSpeed jmp SWIDone SWI13: cmp ah,13 jne SWI14 call SetLocoDirection jmp SWIDone SWI14: cmp ah,14 jne SWI30 call Set14ExtraBit jmp SWIDone SWI30: cmp ah,30 jne SWI31 ;call SetNReps jmp SWIDone SWI31: cmp ah,31 jne SWI33 call SetGroup1Acc jmp SWIDone SWI33: cmp ah,33 jne SWI50 call SetType1Acc jmp SWIDone SWI50: cmp ah,50 jne SWI200 call WritePagedRegister jmp SWIDone SWI200: cmp ah,200 jne SWI201 call SetUnusedLine jmp SWIDone SWI201: cmp ah,201 jne SWI202 ;call ReadUnusedLine SWI202: cmp ah,202 jne SWIDone call ReadThrottles SWIDone:iret SWInterrupt endp ;............................................................................. ; GENERAL ROUTINES ;............................................................................. ; SET DRIVER MODE: ; al=0 OFF. Turns off track power. Disables LPT hardware interrupts. ; al=1 OPERATE. Turns on track power. Continuously cycles through the ; queue. Queue can be interrupted by using the "insert" packet. ; al=2 PROGRAM. Turns on current-limited track power, only on program ; track. Queue is truncated to a single idle packet; the "insert" ; packet is used to send programming commands. ; SetDriverMode proc near push cx push dx push di cmp al,0 jne setmode1 ; MODE = OFF: mov al,0 ; Turn off Track Power relay: mov bl,0 ; parallel port bit 0 controls relay... call SetPPortLine ; ...turn it off mov bl,4 ; Parallel Port bit 4 (IRQ enable)... call SetPPortLine ; ...turn it off mov dx,nPortAddr ; dx is LPT data port mov al,11111111b out dx,al ; set data to all 1's mov nDriverMode,0 ; save driver mode mov ax,0 ; return value = 0 (no error) jmp setmode_exit setmode1: cmp al,1 jne setmode2 ; MODE = OPERATE: mov ax,nActiveLocos ; take the number of active locos inc ax ; add 1 (for the initial idle packet) mov nQSize,ax ; to get size of the queue mov nDriverMode,1 ; save driver mode mov al,0 ; Turn off Program Mode relay: mov bl,2 ; parallel port bit 2 controls relay... call SetPPortLine ; ...turn it off jmp setmode3 setmode2: cmp al,2 jne setmode99 ; MODE = PROGRAM: mov nQSize,1 ; truncate queue to idle packet only mov nDriverMode,2 ; save driver mode mov al,1 ; Turn on Program Mode relay: mov bl,2 ; parallel port bit 2 controls relay... call SetPPortLine ; ...turn it on setmode3: ; MODE = OPERATE OR PROGRAM: mov nQIndex,0 ; start with first packet in queue mov di,npQPacket ; di is pointer to first packet in queue mov ax,cs:[di] ; ax = first 2 bytes of buffer... mov npLastByteInPkt,ax ; ...pointer to last byte in packet inc di inc di ; di points to 3rd byte in buffer... mov npNextByte,di ; ...which is the next byte to send ; 8259 INTERRUPT CONTROLLER CHIP: in al,21h ; read interrupt mask from 8259 mov dl,11111110b ; dl is mask for IRQ0 mov cl,nIRQ rol dl,cl ; dl is mask for nIRQ (7 or 5) and al,dl ; unmask the parallel port interrupt out 21h,al ; and replace interrupt mask mov al,1 ; Enable parallel port interrupts: mov bl,4 ; parallel port bit 4 (IRQ enable)... call SetPPortLine ; ...turn it on mov al,1 ; Turn on Track Power relay: mov bl,0 ; parallel port bit 0 controls relay... call SetPPortLine ; ...turn it on call DCCReset ; SEND 20 DCC RESET PACKETS mov ax,0 ; return value = 0 (no error) jmp setmode_exit setmode99: ; MODE > 2: not allowed mov ax,-1 ; flag error setmode_exit: pop di pop dx pop cx ret SetDriverMode endp ;............................................................................. ; DCC RESET - send 20 DCC Reset Packets ASAP. While doing this, set the ; speed for all locos to zero. DCCReset proc near push ax push bx ; INSERT RESET PACKET - mov npInsertPkt,offset DCCResetPacket ; insert packet = Reset inc bInsertFlag ; set insert flag mov nPktCounter,0 ; zero the packet counter ; SET ALL SPEEDS TO ZERO - mov bx,0 ; start with DCC address 0 mov al,0 ; al is speed (zero) dccreset1: cmp bx,127 jg dccreset2 ; bx <= 127 (valid DCC address): call SetLocoSpeed ; set speed to zero (al) inc bx ; next DCC address jmp dccreset1 ; keep going dccreset2: ; WAIT FOR 20 RESET PACKETS - cmp nPktCounter,20 ; 20 Reset packets? jb dccreset2 ; if not, keep waiting mov bInsertFlag,0 ; BACK TO QUEUE (clear insert flag) pop bx pop ax ret DCCReset endp ;............................................................................. ; ACTIVATE A LOCO - (Initialize its Packet Buffer and add to the Queue) ; al = Speed control mode -- ; 1=14 step 2=28 step 3=128 step ; bl = Loco's DCC Address (1-127) ActivateLoco proc near push bx push cx push dx push di and bx,01111111b ; force DCC address to 0-127 ;------ First, check for possible errors ------ cmp nLocoStatus[bx],0 ; check loco status je actv1 ; loco is already active: mov ax,-1 ; flag error jmp actv_exit ; and exit actv1: cmp al,1 jae actv2 ; speed control mode < 1: mov ax,-2 ; flag error jmp actv_exit ; and exit actv2: cmp al,3 jbe actv3 ; speed control mode > 3: mov ax,-2 ; flag error jmp actv_exit ; and exit actv3: mov nLocoStatus[bx],al ; save speed control mode as new status ;------ Next, Initialize the corresponding Speed/Dir Packet Buffer ------- cmp al,1 ; check al for speed control mode... je actv4 cmp al,2 je actv4 jmp actv5 actv4: ; Speed control mode = 1 or 2: call Init14_28Pkt ; Initialize 14/28 step packet jmp actv6 actv5: ; Speed control mode = 3: ; call Init128Pkt ; Initialize 128 step packet ;------ Finally, add pointer to the Packet Buffer to the Queue ------- actv6: shl bx,1 ; bx is {DCC Address * 2} mov di,npLocoPkt[bx] ; di is pointer to loco's Packet Buffer mov bx,nQSize ; bx is index to next free queue position shl bx,1 mov npQPacket[bx],di ; save the pointer in the Queue inc nQSize inc nActiveLocos mov ax,0 ; return value = 0 actv_exit: pop di pop dx pop cx pop bx ret ActivateLoco endp ;............................................................................. ; DEACTIVATE A LOCO - (Remove from the Queue) ; bl = Loco's DCC Address (1-127) DeactivateLoco proc near push bx push cx push dx push di and bx,01111111b ; force DCC address to 0-127 ;------ First, check for possible errors ------ cmp nLocoStatus[bx],0 ; check loco status jne deactv1 ; loco is not active: mov ax,-1 ; flag error jmp deactv_exit ; and exit deactv1: mov nLocoStatus[bx],0 ; save new status (inactive) ;------ Remove the Packet Buffer from the Queue ------- shl bx,1 ; bx is {DCC Address * 2} mov di,npLocoPkt[bx] ; di is pointer to loco's Packet Buffer mov bx,2 ; start with packet 1 in queue deactv2: cmp di,npQPacket[bx] ; is this the right packet? je deactv3 ; no: inc bx inc bx ; check next packet in queue jmp deactv2 deactv3: ; yes: mov ax,bx shr ax,1 ; ax is packet number (0 based) in queue inc ax ; ax is packet number (1 based) in queue cmp ax,nQSize ; last packet? jae deactv4 ; no: mov di,npQPacket[bx+2] ; di is following packet pointer mov npQPacket[bx],di ; copy it inc bx inc bx ; go on to next packet in queue jmp deactv3 deactv4: ; yes: dec nQSize dec nActiveLocos mov ax,0 ; return value = 0 deactv_exit: pop di pop dx pop cx pop bx ret DeactivateLoco endp ;............................................................................. ; SET SPEED OF A LOCO - (Set the corresponding bits in its Packet Buffer - ; method depends on speed control mode) ; al = Speed (0-15, 0-31, or 0-127) ; bl = Loco's DCC Address (0-127) SetLocoSpeed proc near and bx,01111111b ; force DCC address to 0-127 cmp nLocoStatus[bx],1 jne setsp1 ; loco status = 1: call Set14Speed ; set 14 step speed ret setsp1: cmp nLocoStatus[bx],2 jne setsp2 ; loco status = 2: call Set28Speed ; set 28 step speed ret setsp2: cmp nLocoStatus[bx],3 jne setsp3 ; loco status = 2: ; call Set128Speed ; set 128 step speed ret setsp3: ret SetLocoSpeed endp ;............................................................................. ; SET DIRECTION OF A LOCO - (Set the corresponding bit in its Packet Buffer - ; method depends on speed control mode) ; al = Direction (0=reverse, 1=forward) ; bl = Loco's DCC Address (0-127) SetLocoDirection proc near and bx,01111111b ; force DCC address to 0-127 cmp nLocoStatus[bx],1 jne setdir1 ; loco status = 1: call Set14_28Direction ; set 14/28 step direction ret setdir1: cmp nLocoStatus[bx],2 jne setdir2 ; loco status = 2: call Set14_28Direction ; set 14/28 step direction ret setdir2: cmp nLocoStatus[bx],3 jne setdir3 ; loco status = 3: ; call Set128Direction ; set 128 step direction ret setdir3: ret SetLocoDirection endp ;............................................................................. ; CONTROL MULTI-FUNTION GROUP 1 ACCESSORY ; Sets one of the 5 (on/off) Accesory Functions in a Multi-Function Decoder. ; al = Value (0=off, anything else=on) ; bl = Loco's DCC Address (0-127) ; cl = Accessory number (0 to 4) SetGroup1Acc proc near push ax push bx push cx push dx push di ; Set or clear the corresponding bit in our local "Group 1 Accessory" variable and bx,0000000001111111b ; bx is DCC address (0 to 127) cmp al,0 jne setacc1 ; TURN OFF ACCESSORY #cl (0-4): mov al,11111101b ; al is "off" mask for accessory 0 rol al,cl ; rotate it into place and nMFG1Acc[bx],al ; mask the local variable jmp setacc2 setacc1: ; TURN ON ACCESSORY #cl (0-4): mov al,00000010b ; al is "on" mask for accessory 0 rol al,cl ; rotate it into place or nMFG1Acc[bx],al ; mask the local variable ; Set Up the General Purpose DCC 3-Byte-Packet Buffer (GP3Packet) setacc2: mov di,offset GP3Packet + 2 ; di points to GP3Packet's data ; 3rd Byte - (11111111) mov byte ptr cs:[di],11111111b inc di ; 4th Byte - (1111100A) mov al,bl ; al is DCC address mov cl,6 shr al,cl ; shift right all but two bits or al,11111000b ; mask in five '1' bits mov cs:[di],al inc di ; 5th Byte - (AAAAAA01) mov al,bl ; al is DCC address again shl al,1 shl al,1 ; shift DCC address two bits left or al,00000001b ; set the LSB mov cs:[di],al inc di ; 6th Byte - (00ddddd0) mov al,nMFG1Acc[bx] ; Loco Accessory Variable (set above) mov cs:[di],al inc di ; 7th Byte - (EEEEEEEE) shr al,1 ; al is 000ddddd or al,10000000b ; al is 100ddddd (DCC byte 2) xor al,bl ; xor with 0AAAAAAA (DCC byte 1) mov cs:[di],al ; to get (EEEEEEEE) ; Send the packet times ASAP ; INSERT PACKET INTO QUEUE - mov npInsertPkt,offset GP3Packet ; insert packet = GP3 inc bInsertFlag ; set insert flag mov nPktCounter,0 ; zero the packet counter setacc3: ; WAIT FOR PACKETS - mov al,nPktCounter cmp al,nPacketReps ; packets? jb setacc3 ; if not, keep waiting mov bInsertFlag,0 ; BACK TO QUEUE (clear insert flag) pop di pop dx pop cx pop bx pop ax ret SetGroup1Acc endp ;............................................................................. ; CONTROL TYPE 1 ACCESSORY DECODER ; Sets one of the 8 (on/off) Functions in a Type 1 Accesory Decoder. ; al = Value (0=off, anything else=on) ; bx = 10-bit DCC Address (0-1023) ; cl = Accessory number (0 to 7) (aka subaddress) SetType1Acc proc near push ax push bx push cx push dx push di and bx,0000001111111111b ; bx is 10-bit DCC Address (0 to 1023) and cl,00000111b ; cl is 3-bit subaddress mov ch,cl ; (save subaddr in ch) mov dl,al ; (save data in dl) ; Set Up Accessory Packet Buffer mov di,offset AccPacket + 4 ; di points to Packet's data ;------------------------------------ ; 5th Byte - (AAAAAA0A) mov ax,bx ; ax is 10-bit address shr ax,1 ; shift right by two bits shr ax,1 ; (al = high 8 bits of addr.) and al,11111100b ; mask bottom two bits test bx,0000000000001000b ; is bit 3 of addr set? jz st1A1 ; yes: or al,00000001b ; set al's LSB st1A1: mov cs:[di],al ;------------------------------------ inc di ; 6th Byte - (AAADaaa0) mov al,bl ; al is low 8-bits of addr mov cl,5 shl al,cl ; shift DCC address 5 bits left shl ch,1 shl ch,1 ; shift subaddr (in ch) left or al,ch test dl,00000001b ; data = 1? jz st1A2 ; yes: or al,00010000b ; set bit 4 of al st1A2: mov cs:[di],al ;------------------------------------ inc di ; 7th Byte - (EEEEEEEE) shr al,1 ; al is 0AAAaaad test bl,00001000b ; addr bit 3 set? jz st1A3 ; yes: or al,10000000b st1A3: ; al is AAAAaaad (DCC byte 2) mov cl,4 shr bx,cl or bl,10000000b xor al,bl ; xor with 10AAAAAA (DCC byte 1) mov cs:[di],al ; to get (EEEEEEEE) ; Send the packet times ASAP ; INSERT PACKET INTO QUEUE - mov npInsertPkt,offset AccPacket ; insert packet = Acc inc bInsertFlag ; set insert flag mov nPktCounter,0 ; zero the packet counter st1A4: ; WAIT FOR PACKETS - mov al,nPktCounter cmp al,nPacketReps ; packets? jb st1A4 ; if not, keep waiting mov bInsertFlag,0 ; BACK TO QUEUE (clear insert flag) pop di pop dx pop cx pop bx pop ax ret SetType1Acc endp ;............................................................................. ; QUERY DRIVER - returns driver status as follows: ; ax = FACEh, to check for driver's existence ; bx = Driver Mode (0=Off, 1=Operate, 2=Program) ; cx = Number of Active Locos in Queue QueryDriver proc near mov ax,0FACEh mov bh,0 mov bl,nDriverMode mov cx,nActiveLocos ret QueryDriver endp ;............................................................................. ; ROUTINES FOR PROGRAMMING DECODERS ;............................................................................. ; WRITE TO A REGISTER USING PAGED ADDRESSING ; al = value (0-255) ; bl = register (0-7) WritePagedRegister proc near cmp nDriverMode,2 je wpreg1 ; Not in Program Mode: mov ax,-1 ; flag error ret ; and exit wpreg1: push bx push di ; FIRST, SET UP THE PROGRAM PACKET: and bl,00000111b ; bl is 00000RRR push bx ; (save it for later) mov di,offset ProgramPacket add di,5 ; 6th Byte - (111RRR0D) rol bl,1 rol bl,1 ; bl is 000RRR00 or bl,11100000b ; bl is 111RRR00 test al,10000000b ; check MSB of data byte jz wpreg2 ; MSB of data byte is set: or bl,00000001b ; bl is 111RRR01 wpreg2: mov cs:[di],bl inc di ; 7th Byte - (DDDDDDD0) mov bl,al ; bl is DDDDDDDD shl bl,1 ; bl is DDDDDDD0 mov cs:[di],bl inc di ; 8th Byte - (EEEEEEEE) pop bx ; bl is 00000RRR or bl,01111000b ; bl is 01111RRR (DCC byte 1) xor bl,al ; xor with DDDDDDDD (DCC byte 2) mov cs:[di],bl ; NEXT, SEND 10 RESET PACKETS mov npInsertPkt,offset DCCResetPacket ; insert packet = Reset inc bInsertFlag ; set insert flag mov nPktCounter,0 ; zero the packet counter wpreg3: cmp nPktCounter,10 ; 10 Reset packets? jb wpreg3 ; if not, keep waiting ; NOW SEND THE PROGRAM PACKET 4 TIMES mov npInsertPkt,offset ProgramPacket ; insert packet = Program mov nPktCounter,0 ; zero the packet counter wpreg4: cmp nPktCounter,4 ; 4 program packets? jb wpreg4 ; if not, keep waiting mov bInsertFlag,0 ; BACK TO QUEUE (clear insert flag) mov ax,0 ; Return value = 0 (no error) pop di pop bx ret WritePagedRegister endp ;............................................................................. ; ROUTINES SPECIFIC TO SPEED CONTROL MODE ;............................................................................. ; INITIAILIZE A SPEED/DIR PACKET BUFFER FOR 14 or 28 STEP MODE ; bx is DCC address (0 to 127) Init14_28Pkt proc near push ax push bx push cx push di mov ch,bl ; save DCC address in ch shl bx,1 ; bx is {DCC Address * 2} mov di,npLocoPkt[bx] ; di points to loco's Packet Buffer ; 1st and 2nd Bytes - mov ax,di add ax,6 mov cs:[di],ax ; Pointer to last byte in this packet inc di inc di ; 3rd Byte - (11111111) mov byte ptr cs:[di],11111111b inc di ; 4th Byte - (111110AA) mov bl,ch ; bl is DCC address mov cl,6 shr bl,cl ; shift right all but two bits or bl,11111000b ; mask in five '1' bits mov cs:[di],bl inc di ; 5th Byte - (AAAAAA00) mov bl,ch ; bl is DCC address shl bl,1 shl bl,1 ; shift DCC address two bits left mov cs:[di],bl inc di ; 6th Byte - (1DSSSSS0) mov byte ptr cs:[di],11000000b ; speed=0, dir=fwd inc di ; 7th Byte - (EEEEEEEE) mov bl,ch ; bl is 0AAAAAAA (DCC byte 1) xor bl,01100000b ; xor with 01DSSSSS (DCC byte 2) mov cs:[di],bl pop di pop cx pop bx pop ax ret Init14_28Pkt endp ;............................................................................. ; SET 14-STEP SPEED OF ANY LOCO ; al = Speed (0 to 15) ; bl = DCC Address (0 to 127) Set14Speed proc near push bx push dx push di and bx,01111111b ; force DCC address (bx) to 0-127 push bx ; (save DCC address for later) ; Get cs:[di] to point to correct data byte: shl bx,1 ; bx is 2 * DCC address mov di,npLocoPkt[bx] ; cs:[di] points to loco's Packet Buffer add di,5 ; cs:[di] points to byte with speed/dir ; Replace only the speed data: mov dl,cs:[di] ; dl contains speed/dir (1DXSSSS0) and dl,11100001b ; remove old speed (bits 1-4) shl al,1 ; shift new speed left one bit or dl,al ; insert new speed (shifted) into dl ; Calculate the error-correction byte in dh: pop bx ; bx is DCC address mov dh,dl ; dh is 1DXSSSS0 shr dh,1 ; dh is 01DXSSSS (DCC byte 2) xor dh,bl ; xor with DCC address (DCC byte 1) mov cs:[di],dx ; Save new speed/dir (dl) and error (dh) bytes mov ax,0 ; Return value is 0 (no error) pop di pop dx pop bx ret Set14Speed endp ;............................................................................. ; SET EXTRA BIT IN 14-STEP SPEED/DIRECTION PACKET ; al = Value (0 or 1) ; bl = DCC Address (0 to 127) Set14ExtraBit proc near push bx push dx push di and bx,01111111b ; force DCC address (bx) to 0-127 cmp nLocoStatus[bx],1 ; is loco active in 14 step mode? je set14ex0 ; no: mov ax,-1 ; flag error jmp set14exex ; and exit set14ex0: push bx ; (save DCC address for later) ; Get cs:[di] to point to correct data byte: shl bx,1 ; bx is 2 * DCC address mov di,npLocoPkt[bx] ; cs:[di] points to loco's Packet Buffer add di,5 ; cs:[di] points to byte with speed/dir ; Replace only the extra bit (X): mov dl,cs:[di] ; dl contains speed/dir (1DXSSSS0) cmp al,0 jne set14ex1 ; al is 0: and dl,11011111b ; set extra bit to zero jmp set14ex2 set14ex1: ; al is not 0: or dl,00100000b ; set extra bit to one set14ex2: ; Calculate the error-correction byte in dh: pop bx ; bx is DCC address mov dh,dl ; dh is 1DXSSSS0 shr dh,1 ; dh is 01DXSSSS (DCC byte 2) xor dh,bl ; xor with DCC address (DCC byte 1) mov cs:[di],dx ; Save new speed/dir (dl) and error (dh) bytes mov ax,0 ; Return value is 0 (no error) set14exex: pop di pop dx pop bx ret Set14ExtraBit endp ;............................................................................. ; SET 28-STEP SPEED OF ANY LOCO ; al = Speed (0 to 31) ; bl = DCC Address (0 to 127) Set28Speed proc near push bx push dx push di and bx,01111111b ; force DCC address (bx) to 0-127 push bx ; (save DCC address for later) ; First, we must rearrange the new speed bits: and al,00011111b ; speed (al) is (000SSSSs) test al,00000001b ; is bit 0 of new speed set? jz st28sp1 ; yes: or al,00100000b ; set bit 5 instead and al,00111110b ; and clear bit 0 st28sp1: ; al is now 00sSSSS0 ; Get cs:[di] to point to correct data byte: shl bx,1 ; bx is 2 * DCC address mov di,npLocoPkt[bx] ; cs:[di] points to loco's Packet Buffer add di,5 ; cs:[di] points to byte with speed/dir ; Replace only the speed data: mov dl,cs:[di] ; dl contains speed/dir (1DsSSSS0) and dl,11000001b ; remove old speed (bits 1-5) or dl,al ; insert new speed (rearranged) into dl ; Calculate the error-correction byte in dh: pop bx ; bx is DCC address mov dh,dl ; dh is 1DsSSSS0 shr dh,1 ; dh is 01DsSSSS (DCC byte 2) xor dh,bl ; xor with DCC address (DCC byte 1) mov cs:[di],dx ; Save new speed/dir (dl) and error (dh) bytes mov ax,0 ; Return value is 0 (no error) pop di pop dx pop bx ret Set28Speed endp ;............................................................................. ; SET THE DIRECTION OF A LOCO (14/28 Step Mode) ; al = Direction (0=Reverse, 1=Forward) ; bl = DCC Address (0-127) Set14_28Direction proc near push bx push dx push di and bx,01111111b ; force DCC address (bx) to 0-127 push bx ; (save DCC address for later) ; Get cs:[di] to point to correct data byte: shl bx,1 ; bx is 2 * DCC address mov di,npLocoPkt[bx] ; cs:[di] points to loco's Packet Buffer add di,5 ; cs:[di] points to byte with speed/dir ; Replace only the direction bit (D): mov dl,cs:[di] ; dl contains speed/dir (1DXSSSS0) cmp al,0 jne set14dir1 ; al is 0: and dl,10111111b ; set direction bit to zero (reverse) jmp set14dir2 set14dir1: ; al is not 0: or dl,01000000b ; set direction bit to one (forward) set14dir2: ; Calculate the error-correction byte in dh: pop bx ; bx is DCC address mov dh,dl ; dh is 1DXSSSS0 shr dh,1 ; dh is 01DXSSSS (DCC byte 2) xor dh,bl ; xor with DCC address (DCC byte 1) mov cs:[di],dx ; Save new speed/dir (dl) and error (dh) bytes mov ax,0 ; Return value is 0 (no error) pop di pop dx pop bx ret Set14_28Direction endp ;............................................................................. ; PARALLEL PORT ROUTINES ;............................................................................. ; SET AN UNUSED PARALLEL PORT CONTROL OUTPUT LINE ; This is the function called from "outside" ; al = value (0=TTL Low, anything else=TTL High) ; bl = unused line (0 for Auto LF, 1 for ~Select) SetUnusedLine proc near push bx cmp bl,0 jne setuline1 ; Auto LF (bl = 0): mov bl,1 ; this is bit 1 jmp setuline2 setuline1: ; ~Select (bl is not 0): mov bl,3 ; this is bit 3 cmp al,0 jne setuline1a ; al is 0 - turn off mov al,1 ; [this pin has negative logic] jmp setuline2 setuline1a: ; al isn't 0 - turn on mov al,0 setuline2: call SetPPortLine pop bx mov ax,0 ret SetUnusedLine endp ;............................................................................. ; SET A PARALLEL PORT CONTROL OUTPUT LINE ; al = value (0=0, 1=1, other=undefined) ; bl = line number (0-4: Strobe, Auto LF, -Init, Select, IRQ Enable) SetPPortLine proc near push ax push bx push cx push dx cmp bl,0 ; ~Strobe line (inverted)? jne setline0 ; Yes: xor al,00000001b ; invert value setline0: mov dx,nPortAddr ; dx is LPT data port inc dx inc dx ; dx is LPT control port mov cl,bl ; cl is bit number cmp al,0 jne setline1 ; new bit value is 0: in al,dx ; al is current byte in control port mov bl,11111110b ; bl is "clear" mask for bit 0 rol bl,cl ; bl is "clear" mask for desired bit and al,bl ; clear the bit (in al) jmp setline2 setline1: ; new bit value not 0: in al,dx ; al is current byte in control port mov bl,00000001b ; bl is "set" mask for bit 0 rol bl,cl ; bl is "set" mask for desired bit or al,bl ; set the bit (in al) setline2: out dx,al ; write modified byte to control port pop dx pop cx pop bx pop ax ret SetPPortLine endp ;.............................................................................. ; READ THROTTLES (T0 - T3) AND RETURN VALUES ; returns with al = switch values, ch = T0, cl = T1, dh = T2, dl = T3 ReadThrottles proc near push di call ReadJoyPort ; read values into local array mov di,offset cJoyData mov ch,cs:[di] mov cl,cs:[di+1] mov dh,cs:[di+2] mov dl,cs:[di+3] pop di ret ReadThrottles endp ;.............................................................................. ; READ ALL VALUES FROM THE JOYSTICK PORT INTO LOCAL DATA ARRAY ; returns with al = switch values ReadJoyPort proc near push bx push cx push dx mov dx,nJoyPortAddr mov di,offset cJoyData ; di points to 4-byte joystick value array cli ; don't interrupt now out dx,al ; initiate read (trigger the 555) mov cx,0100h ; max. count mov bl,00001111b ; set one flag bit for each timer rdjoy1: in al,dx ; check timers and al,bl cmp al,bl ; any changes? loopz rdjoy1 ; No: keep waiting (unless timeout) jcxz rdjoy2 ; Yes (not timeout): xor al,bl ; al is flag(s) for changed bit(s) mov ah,cl ; ah=count & al=timer flag(s)... push ax ; ...save both on stack dec cx xor bl,al ; update flags in bl jnz rdjoy1 ; continue until all timers go low jmp rdjoy3 rdjoy2: ; Timeout: push bx ; save timed out flags on stack rdjoy3: ; Finished reading all timers: sti ; Ok to interrupt again mov dl,4 ; 4 timers to take care of rdjoy4: pop ax ; Get next stored count & timer flags sub ah,0FFh neg ah ; convert countdown -> countup mov cx,4 ; 4 timer flags to check rdjoy5: shr al,1 ; shift off next timer flag jnc rdjoy6 ; This timer flag is set: mov cs:[di],ah ; save count in its variable dec dl ; done with one more timer rdjoy6: inc di ; Point to next timer's variable loop rdjoy5 ; Check next timer's flag ; Done with this count/timer flags: sub di,4 ; Point back to first timer's variable or dl,dl ; Done with all four timers?: jnz rdjoy4 ; No: there must be more count/timer flags mov dx,nJoyPortAddr ; Yes: we're done in al,dx ; Read switch values into al (bits 4-7) neg al shr al,1 shr al,1 shr al,1 shr al,1 mov cJoyButtons,al ; save switch values pop dx pop cx pop bx ret ReadJoyPort endp ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; MEMORY-RESIDENT PORTION ENDS HERE ;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ; INSTALLATION PORTION STARTS HERE ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ initialize: assume cs:cseg,ds:cseg ; (.COM file has ds = cs) PrintString sdInitMsg ; Print Init Message call ParseCommandLine call GetPortParams ; Get the LPT Port Address & IRQ cmp nPortAddr,0 ; Check LPT Port Address (is it zero?) jne port_found ; Yes, it's zero: call PrintFailInfo ; Print message mov ax,4C01h int 21h ; Exit program with return code 1 port_found: call InitVariables ; Initialize program variables mov al,nIntNum mov ah,35h int 21h ; Save old parallel port interrupt vector mov word ptr lpOldLptInt,bx mov word ptr lpOldLptInt[2],es mov dx,offset LPTInterrupt mov al,nIntNum mov ah,25h int 21h ; Set new parallel port interrupt vector mov dx,offset SWInterrupt mov al,SOFTWARE_INTERRUPT mov ah,25h int 21h ; Set new software interrupt 70h vector call PrintInstallInfo mov dx,offset initialize int 27h ; exit, remaining resident ;............................................................................. ; Print installation info (assume successful installation) PrintInstallInfo proc near PrintString sdMsg1 ; Print Port # (dec) mov ah,0 mov al,nPortNum call DecPrint PrintString sdMsg2 ; Print Port Addr (hex) mov ax,nPortAddr call HexPrint PrintString sdMsg3 ; Print IRQ Address (dec) mov ah,0 mov al,nIRQ call DecPrint PrintChar 13 ; CR-LF PrintChar 10 ret PrintString sdAccPacketMsg ; Print Acc Packet Address (hex) mov ax,cs call HexPrint PrintChar ':' mov ax,offset AccPacket call HexPrint PrintChar 13 PrintChar 10 PrintString sdLocoBufMsg ; Print LocoBuf Address (hex) mov ax,cs call HexPrint PrintChar ':' mov ax,offset LocoBuf call HexPrint PrintChar 13 PrintChar 10 PrintString sdQueueMsg ; Print Queue Address (hex) mov ax,cs call HexPrint PrintChar ':' mov ax,offset npQPacket call HexPrint PrintChar 13 PrintChar 10 PrintString sdProgPacketMsg ; Print Program Packet Address (hex) mov ax,cs call HexPrint PrintChar ':' mov ax,offset ProgramPacket call HexPrint PrintChar 13 PrintChar 10 PrintString sdIntMsg mov ax,cs call HexPrint PrintChar ':' mov ax,offset LPTInterrupt call HexPrint PrintChar 13 PrintChar 10 ret PrintInstallInfo endp ;............................................................................. PrintFailInfo proc near PrintString sdFailMsg1 mov ah,0 mov al,nPortNum call DecPrint PrintString sdFailMsg2 ret PrintFailInfo endp ;............................................................................. ; Get the port address from the BIOS data area, given the port number ; (1 or 2) stored in nPortNum. (Set the port address in the BIOS data area ; to zero to keep other programs from playing with the port.) ; Also set the correct IRQ (nIRQ) and interrupt (nIntNum) values. GetPortParams proc near push es push ax push di mov ax,0 mov es,ax ; Set segment to zero cmp nPortNum,1 jne check_port_2 ; LPT1: mov di,0408h ; port address is stored at 0:0408 mov nIRQ,7 ; IRQ 7 mov nIntNum,0Fh ; int 0Fh jmp load_address check_port_2: ; LPT2: mov di,040Ah ; port address is stored at 0:040A mov nIRQ,5 ; IRQ 5 mov nIntNum,0Dh ; int 0Dh load_address: mov ax,es:[di] mov nPortAddr,ax ; Copy port address to nPortAddr mov word ptr es:[di],0 ; Save 0 in the BIOS port address mov al,0 call SetDriverMode ; Set Idle Mode (interrupts off, data all 1's) pop di pop ax pop es ret ; Return GetPortParams endp ;............................................................................. ; Initialize Program Variables InitVariables proc near ; Save pointers to the 127 DCC Packet Buffers in a table (one buffer for ; each DCC address). This lookup table saves time while running. mov bx,0 ; bx holds {DCC address * 2} mov dx,offset LocoBuf ; dx points to Packet Buffer for DCC addr 0 next_loco: mov npLocoPkt[bx],dx ; save Packet Buffer pointer in array inc bx inc bx ; next {DCC address * 2} add dx,7 ; point to Packet Buffer for next DCC address cmp bx,256 ; done all 127 Packet Buffers? jb next_loco ; if not, keep going ; Start with the Idle Packet mov di,offset DCCIdlePacket ; di points to idle packet buffer mov ax,cs:[di] ; ax = first 2 bytes of buffer... mov npLastByteInPkt,ax ; ...pointer to last byte in packet inc di inc di ; di points to 3rd byte in buffer... mov npNextByte,di ; ...which is the next byte to send ret InitVariables endp ;............................................................................. ; Parse the command line to get port # ParseCommandLine proc near push cx push di mov nPortNum,1 ; default is LPT1 mov ch,0 mov cl,ds:[80h] ; cx is count of command line chars jcxz done ; (if zero, we're done) mov di,81h ; ds:di points to command line arguments scan_line: cmp byte ptr ds:[di],'2' jne not_a_two mov nPortNum,2 not_a_two: inc di loop scan_line done: pop di pop cx ret ParseCommandLine endp ;............................................................................. ; Print AX in hex (always 4 hex chars) HexPrint proc near push ax push bx push cx push dx mov bx,ax ; put number in bx (we need ax) mov cx,4 ; 4 chars (4 bits each) to print hxpr1: rol bx,1 rol bx,1 rol bx,1 rol bx,1 ; rotate left 4 bits mov dl,bl and dl,00001111b ; dl is the 4-bit value (0 to 15) add dl,48 ; dl is ASCII digit ('0' to '9') cmp dl,57 ; Above '9'? jbe hxpr2 ; hex 'A' to 'E': add dl,7 ; convert to right char hxpr2: mov ah,02h int 21h ; print the char loop hxpr1 pop dx pop cx pop bx pop ax ret HexPrint endp ;.............................................................................. ; Print AX in Decimal (leading zeros not printed) DecPrint proc near push ax push bx push cx push dx mov cl,0 mov bx,10000 call axpdc1 ; print 10,000's digit mov bx,1000 call axpdc1 ; print 1000's digit mov bx,100 call axpdc1 ; print 100's digit mov bx,10 call axpdc1 ; print 10's digit mov dl,al add dl,48 mov ah,6 int 21h ; print 1's digit pop dx pop cx pop bx pop ax ret axpdc1 proc near mov dx,0 div bx ; divide by power of 10; quotient in ax push dx ; push remainder mov dl,al ; quotient in dl cmp dl,0 ; is it zero? jne axpdc1a ; if not, print it cmp cl,0 ; is it a leading zero? je axpdc1b ; if not, don't print it axpdc1a:add dl,48 ; convert to ASCII character mov ah,6 ; print it int 21h mov cl,1 ; set leading zero flag axpdc1b:pop ax ; get remainder in ax ret axpdc1 endp DecPrint endp ;............................................................................. sdInitMsg db 13,10,'DCC-MB Driver version 1.00 Copyright (c) 1996 Mike Brandt',13,10,'$' sdFailMsg1 db 7,'Parallel Port Not Available: LPT$' sdFailMsg2 db '. DCC Driver was not installed.',13,10,'$' sdMsg1 db 'Parallel Port: LPT$' sdMsg2 db ' I/O Address: $' sdMsg3 db 'h IRQ: $' sdLocoBufMsg db 'LocoBuf can be found at $' sdQueueMsg db 'The Queue is at $' sdPacketAMsg db 'Packet A can be found at $' sdProgPacketMsg db 'Program Packet can be found at $' sdAccPacketMsg db 'Accessory Packet can be found at $' sdIntMsg db 'Hardware Interrupt: $' ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ cseg ends end start