;*********************************************************************** ;* INT - Interrupt driven terminal handler for KAYPRO's * ;* * ;* Copyright (c) 1984 by Robert Philip * ;* All Rights Reserved * ;* * ;* This program was written to ease the use of Kaypro computers as * ;* computer terminals running at HIGH (>1200 baud) speed. It is * ;* freely released to the public for non-commercial purposes. Please * ;* give this program to your friends. * ;* * ;* Thanks to Rick Parson, upon whose idea this program was built * ;* Commercial use is prohibited except by written release by * ;* Robert Philip. * ;* * ;* Robert Philip * ;* 106-1300 Richmond Road * ;* Ottawa, Ontario * ;* Canada * ;* K2B 8L2 * ;* * ;*********************************************************************** ;*********************************************************************** ;** Micro Cornucopia Fixes.................. ** ;** ** ;** 1. Fixed the console output to strip parity bit to get rid ** ;** of funny characters on our 4-84.............4/4/84 DmC ** ;** ** ;** 2. Rewrote source to take advantage of phase and dephase ** ;** capabilities of M80 reducing .COM file size from 36K to ** ;** approx 5K...................................4/5/84 DmC ** ;** ** ;** 3. Added keypad and cursor control key translation ** ;** ( One key generates up to 16 characters)....5/23/84 DmC ** ;** ** ;*********************************************************************** ;*********************************************************************** .z80 ; use Z80 instructions vers equ 1 subvers equ 2 year equ 84 month equ 4 day equ 5 bdos equ 5 pstring equ 9 conin equ 1 dcio equ 6 mdmctl equ 06h ; use modem rs232 port mdmport equ 04h kbdctl equ 07h ; the usual keyboard arrangement kbdport equ 05h TXreset equ 028h ;reset after error on sio modbusy equ 04h statreset equ 010h ;external/status reset wr0 equ 0 ; SIO write registers wr1 equ 1 wr2 equ 2 wr3 equ 3 wr4 equ 4 wr5 equ 5 wr6 equ 6 wr7 equ 7 rr1 equ 1 cr equ 0dh ;ascii carriage return lf equ 0ah ;ascii linefeed stack equ progend+1000h intorg equ 08000h ;My kaypro 10 manual says this is above ;the rom-bios memory (the alternate bank) intcode equ 8900H ; where interrupt drivers begin qsmdmin equ 2048 ;2Kb input queue allows 19.2Kbaud input to the ;screen... qsmdmout equ 100 ;not too likely to overrun this from the keyboard qskbdin equ 100 ;this one either ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; some macros for saving/restoring the registers. ; since these routines are intended to work even when later ; releases include disc file capture and other good stuff, ; I save everything. ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: regsav macro push af ;save everything on the stack push bc ;since the KAYPRO uses the alternate push de ;registers for the rom-bios push hl push ix endm regres macro pop ix ;restore the registers saved with pop hl ;the above macro pop de pop bc pop af endm qins equ 0+1 ;disp to insertion pointer qrem equ qins+2 ;disp to removal pointer qsize equ qrem+2 ;disp to qsize qchrs equ qsize+2 ;disp to #chars in queue qdata equ qchrs+1 ;disp to qdata queue macro qn,qs qn: dw 0 ;insertion pointer dw 0 ;removal pointer dw qs ;queue size dw 0 ;number of characters in queue ds qs ;reserve space endm aseg org 0100h ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; all the initialization stuff is org'ed to 0100h ; so that later incarnations can initialize and then re-use ; the initialization memory. ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: start: ld sp,stack jp start1 ;branch around this data stuff.... ; program up 'the chip'. ; this good stuff is for the modem 'modem' port siost: db 00011000b ;channel reset db wr4 ;write register 4 next db 01000100b ;16x clock, 1stop bit, no parity db wr3 ;write register 3 db 11000001b ;Rx 8bits/char, Rx enable db wr5 ;write register 5 db 11101010b ;DTR, Tx 8bits/char,Tx enable,RTS db wr1 ;write register 1 db 00010010b ;int on all Rx chars, Tx int enable db wr2 ;write register 2 db (intbase and 0ffh) ;base of interrupt vector sioend: ; ; now some programming for the keyboard ; we only want to recieve characters from it ; ksioint: db wr1 ;write register 1 db 00010100b ;int on all Rx chars, first Rx, ;Tx int disable, external int disable db wr2 ;write register 2 db (intbase and 0ffh) ;base of interrupt vector ksioend: hello: db lf,'INT version ',vers+'0','.',subvers+'0',' ' db '(19',year/10+'0',year mod 10+'0',' ' db month/10+'0',month mod 10+'0',' ' db day/10+'0',day mod 10+'0',')',cr,lf,lf db 'wait until your discs deselect, then ' db 'press any key to start',cr,lf,lf db 'press RESET to exit the program',cr,lf,'$' start1: ; First move the q's into LD DE,INTORG ; high RAM LD HL,NEXTCODE+1 LD BC,INTEND-INTORG LDIR ld DE,intcode ; Now move the interrupt handlers LD HL,NEXTSEG+1 ; up to high memory LD BC,PROGEND-intcode LDIR ld de,hello ld c,pstring call bdos ld c,conin call bdos ;get a char(let discs de-select) ; program the modem port ld c,mdmctl ;the port ld b,sioend-siost ;how many bytes ld hl,siost ;from here otir ;stuff.............. ; now set the interrupt parameters for keyboard ld c,kbdctl ;kbd port ld b,ksioend-ksioint ;this many ld hl,ksioint ;from here otir ;stuff it.... ; set up interrupt reg, interrupt mode ld hl,intbase ;address of interrupt vectors ld a,h ;only want the high byte ld i,a ;in the interrupt register im 2 ;the best.. ; now go sit in a loop looking for characters jp doit NEXTCODE: NOP ; org up high, so that we don't interrupt into the middle of ; the rom-bios while it is doing screen stuff (or other good ; things planned for 'the next release') .phase intorg ; ; i/o queues for modem and keyboard ; queue inqmdm,qsmdmin ;modem input queue queue outqmdm,qsmdmout;modem output queue queue kbdq,qskbdin ;keyboard input queue intend: NOP ; move to an even page boundary ; .dephase NEXTSEG: NOP .phase intcode ; this is the 'interrupt vector table' intbase: dw outkbd ;keyboard output interrupt dw stkbd ;keyboard status interrupt dw inkbd ;input interrupt from keyboard dw inkbd ;input interrupt from kbd (parity) dw outmdm ;modem output interrupt dw stmdm ;modem status interrupt dw inmdm ;modem input interrupt dw inmdm ;modem input interrupt (with parity?) mstatmsg: db '....modem status interrupt .... $' kstatmsg: db '....keyboard status interrupt .... $' hexmsg: db ' ',0,0,'h $' crlfmsg: db cr,lf,'$' hexbytes: db '0','1','2','3','4','5','6','7' db '8','9','a','b','c','d','e','f' ; ; print hex value passed in 'a' ; prhex: regsav ;save regs push af ;save accumulator again ld hl,hexbytes ;base addr of hex table and 0fh ;bottom nibble first ld b,0 ;add to the base of hex table ld c,a add hl,bc ld a,(hl) ;get the 'hex' character ld (hexmsg+1),a ;into the message buffer pop af ;get our character again rr a ;we want the left half this time rr a rr a rr a and 0fh ;make sure ld hl,hexbytes ld b,0 ld c,a add hl,bc ld a,(hl) ld (hexmsg),a ld c,pstring ;now print it out ld de,hexmsg call bdos regres ;restore regs ret ; print cr,lf on screen crlf: ld de,crlfmsg ld c,pstring call bdos ret ; common interrupt return common: regres ;restore registers ei ;just in case reti ;return from interrupt ; keyboard status interrupt stkbd: regsav ;save regs ld de,kstatmsg ld c,pstring call bdos in a,(kbdctl) ;get status call prhex ;print it ld a,rr1 out (kbdctl),a ;ask for rr1 in a,(kbdctl) call prhex ;print it call crlf ld a,statreset out (kbdctl),a ;reset status on modem jp common ;go to common return ; keyboard output interrupt outkbd: ld a,TXreset ;rarely do we output out (kbdctl),a ;to the keyboard..but sometimes.. jp common ; keyboard input interrupt inkbd: regsav ;save regs in a,(kbdport) ;get character ld ix,kbdq ;address of keyboard input queue call qinsert ;insert character into queue or a ;queue full? jp z,common ;no...just return pop af ;should probably care, but I don't, so just jp common ;bit-bucket the character ; modem input interrupt inmdm: regsav ;save regs in a,(mdmport) ;get character ld ix,inqmdm ;address of modem input queue call qinsert ;stuff character into queue or a ;if queue full, I should probably do jp z,common ;something, but instead, I just pitch pop af ;the overflow character jp common ;common exit path ; modem status interrupt stmdm: regsav ;save regs ld de,mstatmsg ld c,pstring call bdos in a,(mdmctl) ;get status call prhex ;print it ld a,rr1 out (mdmctl),a ;ask for rr1 in a,(mdmctl) call prhex ;print it call crlf ld a,statreset out (mdmctl),a ;reset status on modem jp common ;go to common return ; output interrupt on modem port outmdm: regsav ;save registers ld ix,outqmdm ;modem output queue call qget ;get a character into 'e' or a ;did we get one jr nz,omdm1 ;no..reset the output interrupt ld a,e ;character into 'a' out (mdmport),a ;there it goes... jp common ;split omdm1: ld a,TXreset out (mdmctl),a ;reset Tx int pending jp common ;and split ; queue insertion routine ; assume regs were saved before call ; IX points to first byte of queue structure ; if the queue is full, return with 0ffh in 'a' ; otherwise stuff the character and increment the ; insertion pointer. qinsert: di ;no ints while playing with q's push af ;save character ld d,(ix+qchrs) ;is q full? ld e,(ix+(qchrs-1)) ld a,d cp (ix+qsize) jr nz,qok ;nope ld a,e cp (ix+(qsize-1)) jr nz,qok ;no...insert char ld a,0ffh ;yes..let the caller take care of it ei ;af is on the stack ret qok: inc de ;one more char in queue ld (ix+qchrs),d ld (ix+(qchrs-1)),e ld d,(ix+qins) ;get insertion pointer ld e,(ix+(qins-1)) ld bc,qdata ;displacement to data pop af ;get char back push ix ;save it pop hl ;move into hl add hl,bc ;hl points at beginning of data area add hl,de ;plus insertion pointer ld (hl),a ;insert in queue inc de ;increment insertion pointer ld a,d ;if > size of the queue, cp (ix+qsize) ;then back to the start jr nz,okinc ld a,e cp (ix+(qsize-1)) jr nz,okinc ld de,0 okinc: ld (ix+qins),d ;save insertion pointer ld (ix+(qins-1)),e xor a ;success!!! ei ret ; queue removal routine.....return the character in ; e, 0ff in 'a' if error, 0 in 'a' if ok ; IX points to beginning of queue structure qget: di ;no ints while i play with q's ld a,(ix+(qchrs-1)) ;is size zero? or a jr nz,qgetch ;nope...remove a char ld a,(ix+qchrs) or a jr nz,qgetch ld a,0ffh ;yep...return a 0ffh in 'a' ei ;ok....hit a key ret qgetch: ld d,(ix+qchrs) ;number of chars in the queue ld e,(ix+(qchrs-1)) dec de ;we are removing one ld (ix+qchrs),d ld (ix+(qchrs-1)),e ld d,(ix+qrem) ld e,(ix+(qrem-1)) ;get removal pointer ld bc,qdata ;displacement to data push ix pop hl add hl,bc ;point to data add hl,de ;to the char to remove ld a,(hl) ;get char push af ;save it for later inc de ;increment removal ptr ld a,d cp (ix+qsize) ;if > qsize then back to 0 jr nz,okinc2 ld a,e cp (ix+(qsize-1)) jr nz,okinc2 ld de,0 okinc2: ld (ix+qrem),d ;save it in structure ld (ix+(qrem-1)),e pop af ;get char back ld e,a ;into the right reg xor a ;zero 'a' ei ;any time now ret ; now, the queue processors ; first, the keyboard prinkbd: ld ix,kbdq ;address of the keyboard queue call qget ;get a character if there is one or a ret nz ;no characters.... ;************************************************************************** ;** The keyboard translation takes place here. When a keyboard character ** ;** is picked out of the keyboard queue, it is checked to see if it ** ;** is a vector or data pad key. If not the character is passed into ** ;** the output queue. If it is from the special keys it is translated** ;** and as many bytes as need to be sent are dumped into the output ** ;** queue. ** ;************************************************************************** ld a,e ;get character push hl push bc push de call kbdmap pop de pop bc pop hl ld a,(tranflag) ; see if translation just done or a ld a,0ffh ; fix translation flag for next time ld (tranflag),a ret z ; if translation done get out now oqqqq: ld ix,outqmdm ;output queue ld a,e call qinsert ;stuff character ;** If a character makes it into the queue af is popped off stack by qok ** ;** If not stack needs to be purged of the af left there by qinsert ** or a ;stuffed? ret z ;yep pop af ;nope ret ;and return kbdmap: ld hl,mapin ld bc,mapout-mapin cpir ret nz ; return if not a special key trnslt: ; translate special keys ld de,mapin xor a ld (tranflag),a sbc hl,de ; this forms index to translate table ld de,0000 ld b,l ; keep it in b dec b ; for true index (hl inc'ed first on ; cpir jr z,fstent ; kludge to get at first entry ld de,17 ; number of entries possible per key fstent: ld hl,mapout loopp: add hl,de djnz loopp trnloop: ld a,(hl) ; get byte of translation string or a ; is it 0? ret z ; return if so ld e,a ; oqqqq wants character in e push hl call oqqqq ; stuff in output queue pop hl inc hl ; bump pointer jr trnloop ; get next byte of string tranflag: db 0ffh ;************************************************************************** ;** The mapin and mapout tables ** ;************************************************************************** mapin: db 0f1h,0f2h,0f3h,0f4h ;up, down left right db 0b1h,0c0h,0c1h,0c2h ;0,1,2,3 db 0d0h,0d1h,0d2h,0e1h ;4,5,6,7 db 0e2h,0e3h,0e4h,0d3h ;8,9, '-' ',' db 0c3h,0b2h ; enter, '.' db 0ffh ;************************************************************************** ;**** The Translated Values. 0 is the string delimiter which must be *** ;**** the last character in the string. There are 17 bytes for each *** ;**** special key so you can output up to 16 bytes ( to leave the *** ;**** last byte a 0 ). *** ;************************************************************************** mapout: db 0bh,0,0,0,0,0,0,0 ; up arrow db 0,0,0,0,0,0,0,0,0 db 0ah,0,0,0,0,0,0,0 ; down arrow db 0,0,0,0,0,0,0,0,0 db 08h,0,0,0,0,0,0,0 ; left arrow db 0,0,0,0,0,0,0,0,0 db 0ch,0,0,0,0,0,0,0 ; right arrow db 0,0,0,0,0,0,0,0,0 db '0',0,0,0,0,0,0,0 ; 0 on datapad db 0,0,0,0,0,0,0,0,0 db '1',0,0,0,0,0,0,0 ; 1 on datapad db 0,0,0,0,0,0,0,0,0 db '2',0,0,0,0,0,0,0 ; 2 on data pad db 0,0,0,0,0,0,0,0,0 db '3',0,0,0,0,0,0,0 ; 3 on data pad db 0,0,0,0,0,0,0,0,0 db '4',0,0,0,0,0,0,0 ; 4 on data pad db 0,0,0,0,0,0,0,0,0 db '5',0,0,0,0,0,0,0 ; 5 on data pad db 0,0,0,0,0,0,0,0,0 db '6',0,0,0,0,0,0,0 ; 6 on data pad db 0,0,0,0,0,0,0,0,0 db '7',0,0,0,0,0,0,0 ; 7 on data pad db 0,0,0,0,0,0,0,0,0 db '8',0,0,0,0,0,0,0 ; 8 on data pad db 0,0,0,0,0,0,0,0,0 db '9',0,0,0,0,0,0,0 ; 9 on data pad db 0,0,0,0,0,0,0,0,0 db '-',0,0,0,0,0,0,0 ; '-' on data pad db 0,0,0,0,0,0,0,0,0 db ',',0,0,0,0,0,0,0 ; ',' on data pad db 0,0,0,0,0,0,0,0,0 db 0dh,0,0,0,0,0,0,0 ; ENTER on data pad db 0,0,0,0,0,0,0,0,0 db '.',0,0,0,0,0,0,0 ; '.' on data pad db 0,0,0,0,0,0,0,0,0 mapend: ;************************************************************************** ;*** now the output queue *** ;************************************************************************** proutmdm: in a,(mdmctl) ;check if it's busy or not and modbusy ret z ;busy....back later ld ix,outqmdm call qget ;get a character or a ret nz ;there are none ld a,e out (mdmport),a ;send it ret ; modem input queue...get a char and use bdos to ; print it on the screen prinmdm: ld ix,inqmdm ;input queue call qget ;get a character or a ret nz ld c,dcio ;no 'cpm' processing ld a,e and 7fh ld e,a call bdos ;print the char that's in 'e' jr prinmdm ;get all chars input so far doit: ei ;in case they aren't halt ;wait til sumthin happens call prinkbd ;input from the keyboard call prinmdm ;process input from the modem call proutmdm ;process output to the modem jr doit ;skip back for the next int progend: end start