Category : Forth Source Code
Archive   : FPCTUTOR.ZIP
Filename : LESSON11.SEQ

 
Output of file : LESSON11.SEQ contained in archive : FPCTUTOR.ZIP
\ Lesson 11 - Terminal Program Using Interrupts
\ The Forth Course
\ by Richard E. Haskell
\ Dept. of Computer Science and Engineering
\ Oakland University, Rochester, MI 48309

comment:



Lesson 11

TERMINAL PROGRAM USING INTERRUPTS


11.1 8086/8088 INTERRUPTS 11-2

11.2 THE 8250 ACE CHIP 11-3

11.3 A QUEUE DATA STRUCTURE 11-5

11.4 SENDING CHARACTERS TO THE SCREEN
AND/OR TO DISK 11-8

11.5 DOWNLOADING FILES 11-10

11.6 MAIN TERMINAL PROGRAM 11-12




























11.1 8086/8088 INTERRUPTS

In this lesson we will write a terminal program using interrupts
that we can use to communicate with other computers or to
download Forth code to Forth chips such as the 68HC11 single-chip
microcomputer that contains Max-Forth.

We will want to communicate at baud rates up to 9600 baud. This
means that we will need to use interrupts to store incoming
characters without losing them while scrolling the screen. We
will write an interrupt service routine that is called every time
a character is received in the serial port. This interrupt service
routine will read the character and store it in a queue. The main
terminal program will then alternate between checking the keyboard
for key pressings and checking the queue for received characters.
When a key is pressed the character will be transmitted out the
serial port. When a character is in the queue (i.e. has been
received in the serial port) it will be displayed on the screen
and, optionally, stored on disk.

The segment and offset addresses of the interrupt service routine
must be stored in the interrupt vector table at the beginning of
segment zero in memory. The DOS functions 25H (set interrupt
vector) and 35H (get interrupt vector) can be used for this purpose.
The following Forth words make this easy.
comment;

PREFIX HEX
\ Get interrupt vector
CODE get.int.vector ( int# -- seg offset )
POP AX
PUSH ES
PUSH BX \ AL = interrupt number
MOV AH, # 35 \ DOS service 35H
INT 21 \ ES:BX = segment:offset
MOV DX, ES \ of interrupt handler
MOV AX, BX
POP BX
POP ES
2PUSH
END-CODE

\ Set interrupt vector
CODE set.int.vector ( segment offset int# -- )
POP AX \ AL = interrupt number
POP DX \ DX = offset addr
POP BX \ BX = segment addr
MOV AH, # 25 \ DOS service 25H
PUSH DS \ save DS
MOV DS, BX \ DS:DX -> int handler
INT 21 \ DOS INT 21H
POP DS \ restore DS
NEXT
END-CODE

\ Store interrupt vector of routine at addr
: store.int.vector ( addr int# -- )
?CS: -ROT set.int.vector ;

\ We will need words from Lessons 7, 8 and 10. Therefore,
\ we will FLOAD these files.
DECIMAL

fload lesson7
fload lesson8
fload lesson10

comment:

11.2 THE 8250 ACE CHIP
Serial communication is handled by the 8250 Asynchronous
Communication Element (ACE) chip. The interrupt line from
this chip goes to IRQ4 of the Priority Interrupt Controller (PIC)
chip for COM1 and to IRQ3 of the PIC for COM2. OUT2 of the modem
control register of the 8250 must be set to enable the output
buffer of the 8250 IRQ line.
comment;

HEX
300 CONSTANT COM1 \ base address for COM1
200 CONSTANT COM2 \ base address for COM2
0C CONSTANT INT#1 \ interrupt number for COM1
0B CONSTANT INT#2 \ interrupt number for COM2
EF CONSTANT ENABLE4 \ interrupt 4 enable mask
10 CONSTANT DISABLE4 \ interrupt 4 disable mask
F7 CONSTANT ENABLE3 \ interrupt 3 enable mask
08 CONSTANT DISABLE3 \ interrupt 3 disable mask
\ Default COM1
COM1 VALUE COM \ current COM base address
INT#1 VALUE INT# \ interrupt # for current COM
ENABLE4 VALUE ENABLE \ enable mask for current COM
DISABLE4 VALUE DISABLE \ disable mask for current COM

\ The following values are added to the base COM address to obtain
\ the corresponding register addresses:
F8 CONSTANT txdata \ transmit data reg (write only)
F8 CONSTANT recdat \ receive data reg (read only)
FC CONSTANT mcr \ modem control reg
F9 CONSTANT ier \ interrupt enable reg
FD CONSTANT lsr \ line status reg
21 CONSTANT imask \ mask reg in PIC
20 CONSTANT eoi \ end of int value
20 CONSTANT ocw2 \ PIC ocw2

VARIABLE int.vec.addr \ save int vector offset address
VARIABLE int.vec.seg \ save int vector segment address
DECIMAL



\ We will use the BIOS INT 14H (20 decimal) initialize communications
\ port routine (AH = 0) to set the baud rate. This MUST be done
\ before the modem control register bits are set to enable interrupts
\ because the INT 14H call will undo them!

\ The following table contains the control register masks
\ for baud rates of 300, 1200, 2400, 4800 and 9600
\ with no parity, 8 data bits and 1 stop bit.

CREATE baud.table 67 , 131 , 163 , 195 , 227 ,
\ Index Baud rate
\ 0 300
\ 1 1200
\ 2 2400
\ 3 4800
\ 4 9600

CODE INIT-COM ( mask -- )
POP AX
MOV AH, # 0
MOV DX, # 0
INT 20
NEXT
END-CODE

\ Default 9600 baud
\ Modify this word if you want to change the baud rate from the screen.
: get.baud# ( -- n )
4 ;

: set.baud.rate ( -- )
get.baud# 2*
baud.table + @
INIT-COM ;

comment:



















11.3 A QUEUE DATA STRUCTURE
A circular queue will be used to store the received characters
in an interrupt service routine

The following pointers are used to define the queue
comment;

VARIABLE front \ pointer to front of queue (oldest data at front+1)
VARIABLE rear \ pointer to rear of queue (most recent data at rear)
VARIABLE qmin \ pointer to first byte in queue
VARIABLE qmax \ pointer to last byte in queue
VARIABLE qbuff.seg \ segment of queue
10000 CONSTANT qsize \ size of queue in bytes

\ Initialize queue
: initq ( -- )
qsize alloc.mem qbuff.seg ! \ allocate memory for queue
0 front ! \ front = 0
0 rear ! \ rear = 0
0 qmin ! \ qmin = 0
qsize 1- qmax ! ; \ qmax = qsize - 1

\ Check queue
: checkq ( -- n tf | ff )
front @ rear @ <> \ if front = rear
IF \ then empty
INLINE
CLI \ disable interrupts
NEXT
END-INLINE
1 front +! \ inc front
front @ qmax @ > \ if front > qmax
IF
qmin @ front ! \ then front = qmin
THEN
qbuff.seg @ front @ C@L \ get byte
TRUE \ set true flag
INLINE
STI \ enable interrupts
NEXT
END-INLINE
ELSE
FALSE \ set false flag
THEN ;











\ Store byte in AL in queue
LABEL qstore
PUSH SI
PUSH ES
MOV SI, qbuff.seg
MOV ES, SI \ ES = qbuff.seg
INC rear WORD \ inc rear
MOV SI, rear \ if rear > qmax
CMP SI, qmax
JBE 2 $
MOV SI, qmin \ then rear = qmin
MOV rear SI
2 $: CMP SI, front \ if front = rear
JNE 4 $ \ then full
DEC SI \ dec rear
CMP SI, qmin \ if rear < qmin
JAE 3 $ \ then rear = qmax
MOV SI, qmax
MOV rear SI
3 $: POP ES
POP SI
RET
4 $: MOV ES: 0 [SI], AL \ else store at rear
POP ES
POP SI
RET
END-CODE

\ Interrupt service routine
\ This routine gets data from the receive serial port
\ and stores it in the queue.
LABEL INT.SRV ( -- )
PUSH AX
PUSH DX
PUSH DS
MOV AX, CS
MOV DS, AX \ DS = CS
MOV DX, # COM \ if data is ready
ADD DX, # lsr
IN AL, DX
TEST AL, # 1
JE 1 $
MOV DX, # COM
ADD DX, # recdat
IN AL, DX \ read it
CALL qstore
1 $: MOV AL, # eoi
MOV DX, # ocw2
OUT DX, AL \ clear eoi
POP DS
POP DX
POP AX
IRET
END-CODE

\ Set up interrupts
: int.setup ( -- )
12 COM mcr + PC! \ modem cr out2 lo
1 COM ier + PC! \ enable recv int
INT# get.int.vector \ save old int vector
int.vec.addr ! int.vec.seg !
INT.SRV INT# store.int.vector ; \ set new int vector

\ Terminal initialization routine
: init.term ( -- )
initq \ initialize queue
int.setup \ set up interrupts
imask PC@
ENABLE AND \ enable irq4 (COM1 default)
imask PC! ;

: disable.term ( -- )
imask PC@
DISABLE OR \ disable irq4 (COM1 default)
imask PC!
0 COM mcr + PC! \ 0 -> modem control reg
int.vec.seg @ \ restore original
int.vec.addr @ \ interrupt vector
INT# set.int.vector ;
comment:






























11.4 SENDING CHARACTERS TO THE SCREEN AND/OR TO DISK
Characters in the queue will be displayed on the screen and,
optionally, sent to a disk file.
comment;

FALSE VALUE ?>disk \ flag to "send to disk"
0 VALUE col.at \ saved cursor position
0 VALUE row.at

VARIABLE t_handle \ terminal file handle
CREATE edit_buff 70 ALLOT \ temporary edit buffer

: $HCREATE ( addr -- f ) \ create file for counted string at addr
SEQHANDLE HCLOSE DROP
SEQHANDLE $>HANDLE
SEQHANDLE HCREATE ;

: file.open.error ( -- )
33 12 65 14 box&fill
." Could not open file!!"
KEY DROP ;

\ The following word will display a window on the screen in which
\ to enter a filename, which will then be opened. Data coming in
\ the serial port will be sent to this file. This word will be
\ called by pressing function key F1.

: select.nil.file ( -- )
20 4 60 7 box&fill
." Enter a filename"
" " ">$
edit_buff OVER C@ 1+ CMOVE
21 6 edit_buff 30 lineeditor
IF
edit_buff $HCREATE
IF
file.open.error
ELSE
SEQHANDLE >HNDLE @
DUP handl ! t_handle !
TRUE !> ?>disk
THEN
THEN ;

: >term ( -- )
t_handle @ handl ! ;

\ Pressing function key F1 will turn 'data capture' on
: disk.on.nil ( -- )
IBM-AT? !> row.at !> col.at
SAVESCR
select.nil.file
RESTSCR
col.at row.at AT ;

\ Pressing function key F2 will turn 'data capture' off
: disk.off ( -- )
t_handle @ ?DUP
IF
close.file
0 t_handle !
THEN
FALSE !> ?>disk ;

\ Transmit ascii code out serial port
: XMT ( ascii -- )
COM \ use base address in COM
BEGIN
DUP lsr + \ wait for bit 5 in line status
PC@ 32 AND \ register (TDRE) to be set
UNTIL
txdata + PC! ; \ send data

\ Pressing CTRL P will toggle the printer on and off
: ?PRINT ( -- )
PRINTING C@ NOT PRINTING C! ;

\ Send character to the screen
: do.emit ( n -- )
DUP 13 = \ if CR
IF
DROP CR \ do a carriage return
ELSE
DUP 32 >= \ ignore other control characters
IF
EMIT
ELSE
DROP
THEN
THEN ;

: ?EMIT ( n -- )
127 AND \ mask parity bit
DUP 13 = \ ignore control char
OVER 10 = OR \ other than CR and LF
OVER 32 >= OR
IF
?>disk \ if data capture on
IF
DUP >term send.byte \ send to disk
THEN
do.emit \ send to screen
ELSE
DROP
THEN ;

comment:



11.5 DOWNLOADING FILES

The following words can be used to download a file containing
MaxForth code to the 68HC11. MaxForth will load a line at a
time, compiling the words in the dictionary. After loading a
line it will send a line-feed (ASCII 10) back to the PC.
comment;

VARIABLE wait.count

\ Transmit a string given its address and length

: xmt.str ( addr cnt -- ) \ XMT string + CR
0 DO
DUP I + C@
XMT
LOOP
DROP
13 XMT ;

\ Wait for a particular character to be received

: wait.for ( ascii -- )
0 wait.count !
BEGIN
checkq \ char n tf | char ff
IF \ char n | char
DUP ?EMIT \ char n
OVER = \ char f
0 wait.count !
ELSE
1 wait.count +! FALSE \ char ff
THEN
wait.count @ 32000 = \ char f f
IF
CONTROL G EMIT 2DROP \ beep -- no response
CR ." No response..."
KEY DROP
2R> 2DROP \ exit wait.for
2R> 2DROP \ exit file.download
EXIT \ exit DO-KEY
THEN
UNTIL
DROP ;











\ Download a file to the 68HC11

: file.download ( -- )
GETFILE
DARK
IF
$HOPEN
IF
file.open.error
ELSE
." File: " .SEQHANDLE CR
BEGIN
LINEREAD COUNT 2- \ addr cnt
OVER C@ 26 = NOT \ while not EOF
WHILE
xmt.str \ send line
10 wait.for \ wait for LF
REPEAT
CLOSE
THEN
ELSE
2R> 2DROP
EXIT \ exit DO-KEY
THEN ;

comment:





























11.6 MAIN TERMINAL PROGRAM

Pressing the ESC key will exit the terminal word HOST
comment;

: ESC.HOST ( -- )
disable.term \ disable all interrupts
disk.off \ close file if necessary
qbuff.seg @ release.mem \ release queue buffer
DARK
ABORT ;

\ This is the jump table for all key pressings

EXEC.TABLE DO-KEY
CONTROL P | ?PRINT ( PRINTER ON/OFF )
27 | ESC.HOST ( ESCAPE KEY )
187 | disk.on.nil ( F1 ) 188 | disk.off ( F2 )
189 | file.download ( F3 ) 190 | UNUSED ( F4 )
191 | UNUSED ( F5 ) 192 | UNUSED ( F6 )
193 | UNUSED ( F7 ) 194 | UNUSED ( F8 )
195 | UNUSED ( F9 ) 196 | UNUSED ( F10 )

199 | UNUSED ( HOME ) 200 | UNUSED ( UP )
201 | UNUSED ( PUP ) 203 | UNUSED ( LEFT )
205 | UNUSED ( RIGHT ) 207 | UNUSED ( END )
208 | UNUSED ( DOWN ) 209 | UNUSED ( PGDN )
210 | UNUSED ( INS ) 211 | UNUSED ( DEL )
DEFAULT| XMT


: T-LINK ( -- )
set.baud.rate
CURSOR-ON
FALSE !> ?>disk
DARK
." 4thterm is on-line..." CR CR
init.term ;

\ To run the terminal program, type HOST

: HOST T-LINK
BEGIN
KEY?
IF
KEY DO-KEY
THEN
checkq
IF
?EMIT
THEN
AGAIN ;



  3 Responses to “Category : Forth Source Code
Archive   : FPCTUTOR.ZIP
Filename : LESSON11.SEQ

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/