The TALK Program

The simple terminal-emulator program TALK.ASM (Figure 7-1) is an example of a useful program that performs screen, keyboard, and serial-port I/O. This program recapitulates all of the topics discussed in Chapters 5 through 7. TALK uses the IBM PC's ROM BIOS video driver to put characters on the screen, to clear the display, and to position the cursor; it uses the MS-DOS character-input calls to read the keyboard; and it contains its own interrupt driver for the serial-port controller.

name talk

page 55,132

.lfcond ; List false conditionals too

title TALK--Simple terminal emulator

;

; TALK.ASM--Simple IBM PC terminal emulator

;

; Copyright Ó 1988 Ray Duncan

;

; To assemble and link this program into TALK.EXE:

;

; C>MASM TALK;

; C>LINK TALK;

;

stdin equ 0 ; standard input handle

stdout equ 1 ; standard output handle

stderr equ 2 ; standard error handle

cr equ 0dh ; ASCII carriage return

lf equ 0ah ; ASCII linefeed

bsp equ 08h ; ASCII backspace

escape equ 1bh ; ASCII escape code

dattr equ 07h ; display attribute to use

; while in emulation mode

bufsiz equ 4096 ; size of serial-port buffer

echo equ 0 ; 0 = full-duplex, -1 = half-duplex

equ -1

false equ 0

com1 equ true ; use COM1 if nonzero

com2 equ not com1 ; use COM2 if nonzero

pic_mask equ 21h ; 8259 interrupt mask port

pic_eoi equ 20h ; 8259 EOI port

if com1

com_data equ 03f8h ; port assignments for COM1

com_ier equ 03f9h

com_mcr equ 03fch

com_sts equ 03fdh

com_int equ 0ch ; COM1 interrupt number

int_mask equ 10h ; IRQ4 mask for 8259

endif

if com2

com_data equ 02f8h ; port assignments for COM2

com_ier equ 02f9h

com_mcr equ 02fch

com_sts equ 02fdh

com_int equ 0bh ; COM2 interrupt number

int_mask equ 08h ; IRQ3 mask for 8259

endif

_TEXT segment word public 'CODE'

assume cs:_TEXT,ds:_DATA,es:_DATA,ss:STACK

talk proc far ; entry point from MS-DOS

mov ax,_DATA ; make data segment addressable

mov ds,ax

mov es,ax

; initialize display for

; terminal emulator mode...

mov ah,15 ; get display width and

int 10h ; current display mode

dec ah ; save display width for use

mov columns,ah ; by the screen-clear routine

cmp al,7 ; enforce text display mode

je talk2 ; mode 7 ok, proceed

cmp al,3

jbe talk2 ; modes 0-3 ok, proceed

mov dx,offset msg1

mov cx,msg1_len

jmp talk6 ; print error message and exit

talk2: mov bh,dattr ; clear screen and home cursor

call cls

call asc_enb ; capture serial-port interrupt

; vector and enable interrupts

mov dx,offset msg2 ; display message

mov cx,msg2_len ; 'terminal emulator running'

mov bx,stdout ; BX = standard output handle

mov ah,40h ; function 40h = write file or device

int 21h ; transfer to MS-DOS

talk3: call pc_stat ; keyboard character waiting?

jz talk4 ; nothing waiting, jump

call pc_in ; read keyboard character

cmp al,0 ; is it a function key?

jne talk32 ; not function key, jump

call pc_in ; function key, discard 2nd

; character of sequence

jmp talk5 ; then terminate program

talk32: ; keyboard character received

if echo

push ax ; if half-duplex, echo

call pc_out ; character to PC display

pop ax

endif

call com_out ; write char to serial port

talk4: call com_stat ; serial-port character waiting?

jz talk3 ; nothing waiting, jump

call com_in ; read serial-port character

cmp al,20h ; is it control code?

jae talk45 ; jump if not

call ctrl_code ; control code, process it

jmp talk3 ; check keyboard again

talk45: ; noncontrol char received,

call pc_out ; write it to PC display

jmp talk4 ; see if any more waiting

talk5: ; function key detected,

; prepare to terminate...

mov bh,07h ; clear screen and home cursor

call cls

mov dx,offset msg3 ; display farewell message

mov cx,msg3_len

talk6: push dx ; save message address

push cx ; and message length

call asc_dsb ; disable serial-port interrupts

; and release interrupt vector

pop cx ; restore message length

pop dx ; and address

mov bx,stdout ; handle for standard output

mov ah,40h ; function 40h = write device

int 21h ; transfer to MS-DOS

mov ax,4c00h ; terminate program with

int 21h ; return code = 0

talk endp

com_stat proc near ; check asynch status; returns

; Z = false if character ready

; Z = true if nothing waiting

push ax

mov ax,asc_in ; compare ring buffer pointers

cmp ax,asc_out

pop ax

ret ; return to caller

stat endp

com_in proc near ; get character from serial-

; port buffer; returns

; new character in AL

push bx ; save register BX

com_in1: ; if no char waiting, wait

mov bx,asc_out ; until one is received

cmp bx,asc_in

je com_in1 ; jump, nothing waiting

mov al,[bx+asc_buf] ; character is ready,

; extract it from buffer

inc bx ; update buffer pointer

cmp bx,bufsiz

jne com_in2

xor bx,bx ; reset pointer if wrapped

com_in2:

mov asc_out,bx ; store updated pointer

pop bx ; restore register BX

ret ; and return to caller

com_in endp

com_out proc near ; write character in AL

; to serial port

push dx ; save register DX

push ax ; save character to send

mov dx,com_sts ; DX = status port address

com_out1: ; check if transmit buffer

in al,dx ; is empty (TBE bit = set)

and al,20h

jz com_out1 ; no, must wait

pop ax ; get character to send

mov dx,com_data ; DX = data port address

out dx,al ; transmit the character

pop dx ; restore register DX

ret ; and return to caller

com_out endp

pc_stat proc near ; read keyboard status; returns

; Z = false if character ready

; Z = true if nothing waiting

; register DX destroyed

mov al,in_flag ; if character already

or al,al ; waiting, return status

jnz pc_stat1

mov ah,6 ; otherwise call MS-DOS to

mov dl,0ffh ; determine keyboard status

int 21h

jz pc_stat1 ; jump if no key ready

mov in_char,al ; got key, save it for

mov in_flag,0ffh ; "pc_in" routine

pc_stat1: ; return to caller with

ret ; Z flag set appropriately

pc_stat endp

pc_in proc near ; read keyboard character,

; return it in AL

; DX may be destroyed

mov al,in_flag ; key already waiting?

or al,al

jnz pc_in1 ; yes, return it to caller

call pc_stat ; try to read a character

jmp pc_in

pc_in1: mov in_flag,0 ; clear char-waiting flag

mov al,in_char ; and return AL = character

ret

pc_in endp

pc_out proc near ; write character in AL

; to the PC's display

mov ah,0eh ; ROM BIOS function 0eh =

; "teletype output"

push bx ; save register BX

xor bx,bx ; assume page 0

int 10h ; transfer to ROM BIOS

pop bx ; restore register BX

ret ; and return to caller

pc_out endp

cls proc near ; clear display using

; char attribute in BH

; registers AX, CX,

; and DX destroyed

mov dl,columns ; set DL,DH = X,Y of

mov dh,24 ; lower right corner

mov cx,0 ; set CL,CH = X,Y of

; upper left corner

mov ax,600h ; ROM BIOS function 06h =

; "scroll or initialize

; window"

int 10h ; transfer to ROM BIOS

call home ; set cursor at (0,0)

ret ; and return to caller

cls endp

clreol proc near ; clear from cursor to end

; of line using attribute

; in BH, registers AX, CX,

; and DX destroyed

call getxy ; get current cursor position

mov cx,dx ; current position = "upper

; left corner" of window;

mov dl,columns ; "lower right corner" X is

; max columns, Y is same

; as upper left corner

mov ax,600h ; ROM BIOS function 06h =

; "scroll or initialize

; window"

int 10h ; transfer to ROM BIOS

ret ; return to caller

clreol endp

home proc near ; put cursor at home position

mov dx,0 ; set (X,Y) = (0,0)

call gotoxy ; position the cursor

ret ; return to caller

home endp

gotoxy proc near ; position the cursor

; call with DL = X, DH = Y

push bx ; save registers

push ax

mov bh,0 ; assume page 0

mov ah,2 ; ROM BIOS function 02h =

; set cursor position

int 10h ; transfer to ROM BIOS

pop ax ; restore registers

pop bx

ret ; and return to caller

gotoxy endp

getxy proc near ; get cursor position,

; returns DL = X, DH = Y

push ax ; save registers

push bx

push cx

mov ah,3 ; ROM BIOS function 03h =

; get cursor position

mov bh,0 ; assume page 0

int 10h ; transfer to ROM BIOS

pop cx ; restore registers

pop bx

pop ax

ret ; and return to caller

getxy endp

ctrl_code proc near ; process control code

; call with AL = char

cmp al,cr ; if carriage return

je ctrl8 ; just send it

cmp al,lf ; if linefeed

je ctrl8 ; just send it

cmp al,bsp ; if backspace

je ctrl8 ; just send it

cmp al,26 ; is it cls control code?

jne ctrl7 ; no, jump

mov bh,dattr ; cls control code, clear

call cls ; screen and home cursor

jmp ctrl9

ctrl7:

cmp al,escape ; is it Escape character?

jne ctrl9 ; no, throw it away

call esc_seq ; yes, emulate CRT terminal

jmp ctrl9

ctrl8: call pc_out ; send CR, LF, or backspace

; to the display

ctrl9: ret ; return to caller

ctrl_code endp

esc_seq proc near ; decode Televideo 950 escape

; sequence for screen control

call com_in ; get next character

cmp al,84 ; is it clear to end of line?

jne esc_seq1 ; no, jump

mov bh,dattr ; yes, clear to end of line

call clreol

jmp esc_seq2 ; then exit

esc_seq1:

cmp al,61 ; is it cursor positioning?

jne esc_seq2 ; no jump

call com_in ; yes, get Y parameter

sub al,33 ; and remove offset

mov dh,al

call com_in ; get X parameter

sub al,33 ; and remove offset

mov dl,al

call gotoxy ; position the cursor

esc_seq2: ; return to caller

ret

esc_seq endp

asc_enb proc near ; capture serial-port interrupt

; vector and enable interrupt

; save address of previous

; interrupt handler...

mov ax,3500h+com_int ; function 35h = get vector

int 21h ; transfer to MS-DOS

mov word ptr oldvec+2,es

mov word ptr oldvec,bx

; now install our handler...

push ds ; save our data segment

mov ax,cs ; set DS:DX = address

mov ds,ax ; of our interrupt handler

mov dx,offset asc_int

mov ax,2500h+com_int ; function 25h = set vector

int 21h ; transfer to MS-DOS

pop ds ; restore data segment

mov dx,com_mcr ; set modem-control register

mov al,0bh ; DTR and OUT2 bits

out dx,al

mov dx,com_ier ; set interrupt-enable

mov al,1 ; register on serial-

out dx,al ; port controller

in al,pic_mask ; read current 8259 mask

and al,not int_mask ; set mask for COM port

out pic_mask,al ; write new 8259 mask

ret ; back to caller

asc_enb endp

asc_dsb proc near ; disable interrupt and

; release interrupt vector

in al,pic_mask ; read current 8259 mask

or al,int_mask ; reset mask for COM port

out pic_mask,al ; write new 8259 mask

push ds ; save our data segment

lds dx,oldvec ; load address of

; previous interrupt handler

mov ax,2500h+com_int ; function 25h = set vector

int 21h ; transfer to MS-DOS

pop ds ; restore data segment

ret ; back to caller

asc_dsb endp

asc_int proc far ; interrupt service routine

; for serial port

sti ; turn interrupts back on

push ax ; save registers

push bx

push dx

push ds

mov ax,_DATA ; make our data segment

mov ds,ax ; addressable

cli ; clear interrupts for

; pointer manipulation

mov dx,com_data ; DX = data port address

in al,dx ; read this character

mov bx,asc_in ; get buffer pointer

mov [asc_buf+bx],al ; store this character

inc bx ; bump pointer

cmp bx,bufsiz ; time for wrap?

jne asc_int1 ; no, jump

xor bx,bx ; yes, reset pointer

asc_int1: ; store updated pointer

mov asc_in,bx

sti ; turn interrupts back on

mov al,20h ; send EOI to 8259

out pic_eoi,al

pop ds ; restore all registers

pop dx

pop bx

pop ax

iret ; return from interrupt

asc_int endp

_TEXT ends

_DATA segment word public 'DATA'

in_char db 0 ; PC keyboard input char

in_flag db 0 ; <>0 if char waiting

columns db 0 ; highest numbered column in

; current display mode (39 or 79)

msg1 db cr,lf

db 'Display must be text mode.'

db cr,lf

msg1_len equ $-msg1

msg2 db 'Terminal emulator running...'

db cr,lf

msg2_len equ $-msg2

msg3 db 'Exit from terminal emulator.'

db cr,lf

msg3_len equ $-msg3

oldvec dd 0 ; original contents of serial-

; port interrupt vector

asc_in dw 0 ; input pointer to ring buffer

asc_out dw 0 ; output pointer to ring buffer

asc_buf db bufsiz dup (?) ; communications buffer

_DATA ends

STACK segment para stack 'STACK'

db 128 dup (?)

STACK ends

end talk ; defines entry point

Figure 7-1. TALK.ASM: A simple terminal-emulator program for IBM PC—compatible computers. This program demonstrates use of the MS-DOS and ROM BIOS video and keyboard functions and direct control of the serial-communications adapter.

The TALK program illustrates the methods that an application should use to take over and service interrupts from the serial port without running afoul of MS-DOS conventions.

The program begins with some equates and conditional assembly statements that configure the program for half- or full-duplex and for the desired serial port (COM1 or COM2). At entry from MS-DOS, the main routine of the program——the procedure named talk——checks the status of the serial port, initializes the display, and calls the asc_enb routine to take over the serial-port interrupt vector and enable interrupts. The talk procedure then enters a loop that reads the keyboard and sends the characters out the serial port and then reads the serial port and puts the characters on the display——in other words, it causes the PC to emulate a simple CRT terminal.

The TALK program intercepts and handles control codes (carriage return, linefeed, and so forth) appropriately. It detects escape sequences and handles them as a subset of the Televideo 950 terminal capabilities. (You can easily modify the program to emulate any other cursor-addressable terminal.) When one of the PC's special function keys is pressed, the program disables serial-port interrupts, releases the serial-port interrupt vector, and exits back to MS-DOS.

There are several TALK program procedures that are worth your attention because they can easily be incorporated into other programs. These are listed in the table on the following page.

Procedure Action

asc_enb Takes over the serial-port interrupt vector and enables

interrupts by writing to the modem-control register of

the INS8250 and the interrupt-mask register of the

8259A.

asc_dsb Restores the original state of the serial-port

interrupt vector and disables interrupts by writing to

the interrupt-mask register of the 8259A.

asc_int Services serial-port interrupts, placing received

characters into a ring buffer.

com_stat Tests whether characters from the serial port are

waiting in the ring buffer.

com_in Removes characters from the interrupt handler's ring

buffer and increments the buffer pointers

appropriately.

com_out Sends one character to the serial port.

cls Calls the ROM BIOS video driver to clear the screen.

clreol Calls the ROM BIOS video driver to clear from the

current cursor position to the end of the line.

home Places the cursor in the upper left corner of the

screen.

gotoxy Positions the cursor at the desired position on the

display.

getxy Obtains the current cursor position.

pc_out Sends one character to the PC's display.

pc_stat Gets status for the PC's keyboard.

pc_in Returns a character from the PC's keyboard.