ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿSERIALIOA86f@TERMINALA86gÿ title 'Interrupt-Driven Serial Port Handler' ; THIS PROGRAM IS HEREBY PLACED IN THE PUBLIC DOMAIN, ; AND MAY BE FREELY COPIED AND DISTRIBUTED. ; Context Sensitive Inc. ; 4200 Aurora Ave. N. ; Seattle, WA 98103 ; (206) NEC-0301 ; ; Program: SERIALIO.A86 ; ; Version: 1.2 23 February 1984 ; Author: Ron Blanford ; This is only a fragment of a program. The routines given ; here can be included in any (assembly language) program, ; and called as required to perform input and output to the ; primary serial (RS232) port on the NEC Advanced Personal ; Computer. These routines duplicate functions that are ; provided by CP/M-86, with the difference that input from ; the serial port is handled on an interrupt-driven basis ; and characters are stored until requested. ; ; To add these routines to your program, insert just prior ; to the END statement of your program the statement ; INCLUDE SERIALIO.A86 ; ; These routines preserve all registers, except possibly AL ; and the flags. ; ; The routines available to your program are: ; ; 1. sio_rxstatus: Returns -1 in AL if a character is ; available in the serial input buffer, ; and 0 if no character is waiting. ; ; 2. sio_receive: Returns a character from the serial ; input buffer in AL. If no character ; is available, it waits until one ; arrives. ; ; 3. sio_txstatus: Returns -1 in AL if the transmit ; buffer is empty and a character may be ; transmitted, and 0 if transmission is ; not possible. ; ; 4. sio_transmit: Sends the character in AL out the ; serial port. If the port is not ready, ; it waits until it is. ; ; 5. sio_start: Called once to initialize the port ; before using it. Sets initial values: ; baud = 300, parity = none ; data = 8, stop = 1. ; ; 6. sio_finish: Called once to de-initialize the port ; just before ending your program. ; ; 7. sio_setmode: Called to set the baud rate and mode ; of the port. DX contains the parameters ; that are described in the CP/M-86 System ; Reference Guide, page 5-40, as follows: ; ; DH = baud rate DL = asynchronous mode byte ; 0 = 150 bps 7 6 5 4 3 2 1 0 ; 1 = 200 bps _________________________________ ; 2 = 300 bps | | | | 1 0 | ; 3 = 600 bps ----^-------^-------^-------^---- ; 4 = 1200 bps | | | +- must be 10 ; 5 = 2400 bps | | +- data bits: 00=5, 01=6 ; 6 = 4800 bps | | 10=7, 11=8 ; 7 = 9600 bps | +--- parity: 00 or 10=none ; 8 = 19200 bps | 01=odd, 11=even ; +--- stop bits: 01=1, 10=1.5 ; 11=2, 00 illegal ; ; If DH is greater than 8, the baud rate will ; not be changed. If DL is 0, the mode will ; not be changed. ; ; ; An alternative entry point to these routines which is ; more suitable for high-level languages is to use the ; initial jump vector table. The vectors are allocated ; in the same sequence as listed above, and are each the ; standard 3-byte relative jump instruction. The address ; of the vector table depends on the load address assigned ; by the high-level language. cseg $ sio_jumptable: jmp sio_rxstatus ; receive status jmp sio_receive ; receive character jmp sio_txstatus ; transmit status jmp sio_transmit ; transmit character jmp sio_start ; initialize serial port jmp sio_finish ; de-initialize serial port jmp sio_setmode ; set port characteristics ; Interrupt vector locations, in data segment 0 sio_offset equ 84h ; Sio interrupt offset sio_segment equ 86h ; Sio interrupt segment ; 8259 Interrupt controller (master) port addresses ic_command equ 20h ; Interrupt command register ic_mask equ 22h ; Interrupt mask register ; 8259 commands and masks icmd_endInt equ 20h ; End of interrupt imask_timerOff equ 08h ; Disable timer interrupt imask_sioOff equ 02h ; Disable RS232 interrupt ; 8253-5 Interval timer port addresses timer_data equ 2Bh ; Baud set (for channel 1) timer_command equ 2Fh ; Baud timer command port ; 8253 Timer commands tcmd_ch1 equ 76h ; Select & init channel 1 ; 8251A USART controller port addresses sio_data equ 30h ; Data port sio_sts1 equ 32h ; in Status port 1 sio_sts2 equ 34h ; in Status port 2 sio_command equ 32h ; out Command port sio_mask equ 34h ; out Interrupt mask port sio_disable equ 36h ; out Disable trans. port ; 8251 status port 1 bits sio_RxRDY equ 02h ; Receive ready value sio_TxRDY equ 01h ; Send ready value sio_DSR equ 80h ; Data set ready ; 8251 status port 2 bits sio_CS equ 04h ; Clear to send ; 8251 initialization instructions ; command instructions scmd_TxE equ 01h ; Transmit enable scmd_DTR equ 02h ; DTR signal high scmd_RxE equ 04h ; Receive enable scmd_BRK equ 08h ; Send break scmd_ERR equ 10h ; Error reset scmd_RTS equ 20h ; RTS signal high scmd_MODE equ 40h ; Reset - accept mode inst. scmd_HUNT equ 80h ; Hunt for sync characters ; mode instructions smode_1x equ 01h ; Baud rate factor: 1x smode_16x equ 02h ; 16x smode_64x equ 03h ; 64x smode_5data equ 00h ; Data bits: 5 smode_6data equ 04h ; 6 smode_7data equ 08h ; 7 smode_8data equ 0Ch ; 8 smode_pnone equ 00h ; Parity: none smode_podd equ 10h ; odd smode_peven equ 30h ; even smode_1stop equ 40h ; Stop bits: 1 smode_15stop equ 80h ; 1.5 smode_2stop equ 0C0h ; 2 ; 8251 interrupt mask port bits sint_txOff equ 01h ; Transmit complete int. sint_rxOff equ 02h ; Receive complete int. sint_tbeOff equ 04h ; Trans. buffer empty int. ; sio_rxstatus: returns -1 in AL if a character has been received ; returns 0 if the input interrupt buffer is empty. sio_rxstatus: cmp sio_chars,0 ; Any chars in the buffer? je siorx1 ; If not, report failure or al,0FFh ; Otherwise give positive jmps siorx2 ; response siorx1: and al,0 siorx2: ret ; sio_receive: returns the next input character in AL ; waits for a character if none have arrived. sio_receive: call sio_rxstatus ; Wait for a character jz sio_receive push bx cli ; Prevent interrupts. dec sio_chars ; Uncount the character. mov bx,sio_charPtr ; Get the buffer pointer. inc bx ; Point to the next char. cmp bx,offset sio_buffer+sio_size ; Past the end? jb sio_r2 lea bx,sio_buffer ; If so wrap to the start. sio_r2: mov sio_charPtr,bx ; Save the updated pointer. mov al,[bx] ; Get the character. sti ; Re-enable interrupts. pop bx ret ; sio_int: Handles the serial port input interrupts sio_int: cli ; Prevent interrupts. push ax ; Save registers we'll use. push bx push ds mov ax,cs:sio_dataSeg ; Get our own data segment. mov ds,ax call sio_process ; Receive and store char. mov al,icmd_endInt ; Signal End of Interrupt. out ic_command,al pop ds ; Restore registers we used. pop bx pop ax iret ; Return from the interrupt. ; sio_process: Reads the input character from the serial port and ; stores it in the ring buffer. sio_process: in al,sio_sts1 ; Get the port status. and al,sio_RxRDY ; Is a character waiting? jz sio_p3 ; No, just a false alarm. in al,sio_data ; Otherwise read character. cmp sio_chars,sio_size ; Is the buffer full? je sio_p3 ; If so, discard character. inc sio_chars ; Otherwise count character. mov bx,sio_spacePtr ; Point to the next space. inc bx ; Increment pointer. cmp bx,offset sio_buffer+sio_size ; Past the end? jb sio_p2 lea bx,sio_buffer ; Yes, wrap to the start. sio_p2: mov sio_spacePtr,bx ; Save the pointer. mov [bx],al ; Save the character. sio_p3: ret ; sio_txstatus: returns -1 in AL if the serial port can accept a ; character for transmission, returns 0 otherwise. sio_txstatus: in al,sio_sts1 ; Read status port 1. and al,sio_DSR+sio_TxRDY ; Mask DSR and TxRDY bits. xor al,sio_DSR+sio_TxRDY ; Check that both are set. jnz siotx1 ; If not, port is not ready. in al,sio_sts2 ; Read status port 2. and al,sio_CS ; Check for clear to send. jz siotx1 ; If not, return failure. or al,0FFh ; Otherwise ready to send. jmps siotx2 siotx1: and al,0 siotx2: ret ; sio_transmit: sends the character in AL to the serial port. ; doesn't return until the character has been sent. sio_transmit: push ax ; Save character to be sent. sio_t1: call sio_txstatus ; Wait for port to be ready. jz sio_t1 pop ax out sio_data,al ; Send the character. ret ; sio_start: Initializes the interrupt vectors and controller ; and the serial port to default values sio_start: cmp sio_init,0 ; Skip initialization jne sio_s1 ; if already performed. mov sio_init,0FFh ; Set "initialized" flag. push ax ; Save registers we'll use. push dx push es mov ax,ds ; Save data segment in CSEG mov cs:sio_dataSeg,ax ; for the int. handler. in al,ic_mask ; Get current interrupt mask mov sio_oldInt,al ; Save for later restoration mov ax,0 ; Point interrupt vector mov es,ax ; table in page zero. mov ax,es:.sio_segment ; Save the current vector mov sio_oldSeg,ax ; segment and offset. mov ax,es:.sio_offset mov sio_oldOff,ax cli mov ax,cs ; Replace with address of mov es:.sio_segment,ax ; interrupt handler. mov ax,offset sio_int mov es:.sio_offset,ax mov dh,8 ; Set default baud 19200 mov dl,smode_1stop+smode_pnone+smode_8data+smode_16x call sio_setmode ; and default mode. mov al,00h ; Reset disable register. out sio_disable,al in al,sio_data ; Clear input buffer. mov al,sint_txOff+sint_tbeOff ; Set serial int. mask. out sio_mask,al in al,ic_mask ; Set master controller to or al,imask_timerOff ; disable timer and al,not imask_sioOff ; and enable sio. out ic_mask,al sti pop es ; Restore our registers. pop dx pop ax sio_s1: ret ; sio_finish: This routine restores the interrupt controller ; mask and vector table to the original values. sio_finish: cmp sio_init,0 ; Was initialization done? je sio_f1 ; If not, don't undo it. mov sio_init,0 push ax ; Save our registers. push es cli ; Prevent interrupts. mov al,sio_oldInt ; Restore the old mask. out ic_mask,al mov ax,0 ; restore the old vector. mov es,ax mov ax,sio_oldSeg mov es:.sio_segment,ax mov ax,sio_oldOff mov es:.sio_offset,ax sti pop es ; Restore our registers. pop ax sio_f1: ret ; sio_setmode: sets the baud rate, data bits, parity, and stop ; bits for the serial port. DH contains an index to ; the baud rate table, and DL contains the mode byte. sio_setmode: push ax ; Save our registers. push bx or dl,dl ; See if mode is to be set: jz sio_m1 ; not if DL = 0. mov al,0 ; Suggested reset sequence: out sio_command,al ; 3 zeros and scmd_MODE. mov al,0 out sio_command,al mov al,0 out sio_command,al mov al,scmd_MODE out sio_command,al push ax ; waste some time to allow pop ax ; the 8251 time to reset mov al,dl ; Set mode with supplied out sio_command,al ; parameters. mov al,scmd_RTS+scmd_ERR+scmd_RxE+scmd_DTR+scmd_TxE out sio_command,al ; RTS & DTR high, Tx & Rx on sio_m1: mov al,dh ; Get the baud rate index. cmp al,8 ; Validate range (0-8). ja sio_m2 lea bx,cs:sio_baud ; Point to baud table. add al,al ; Make index a word offset. mov ah,0 add bx,ax ; Point to desired entry. mov al,tcmd_ch1 ; Set timer channel 1 mode. out timer_command,al mov ax,cs:[bx] ; Get the baud table entry. out timer_data,al ; Output low byte. mov al,ah out timer_data,al ; Output high byte. sio_m2: pop bx ; Restore our registers. pop ax ret sio_dataSeg dw 0 ; Storage in CSEG to point ; to DSEG. ; Interval Timer values (assumes 16x baud rate mode) sio_baud dw 0400h ; 150 baud 0 dw 0300h ; 200 baud 1 dw 0200h ; 300 baud 2 dw 0100h ; 600 baud 3 dw 0080h ; 1200 baud 4 dw 0040h ; 2400 baud 5 dw 0020h ; 4800 baud 6 dw 0010h ; 9600 baud 7 dw 0008h ; 19200 baud 8 dseg $ ; Impure storage with contents that are changed dynamically sio_init db 0 ; "Initialized" flag. sio_oldInt db 0 ; 8259 interrupt mask sio_oldSeg dw 0 ; Sio interrupt vector sio_oldOff dw 0 sio_size equ 128 ; Size of circular buffer. sio_buffer rb sio_size ; Circular character buffer. sio_spacePtr dw sio_buffer ; Char. insertion pointer. sio_charPtr dw sio_buffer ; Char. retrieval pointer. sio_chars dw 0 ; Number of chars in buffer.  title 'Interrupt-Driven Terminal Emulator' ; THIS PROGRAM IS HEREBY PLACED IN THE PUBLIC DOMAIN, ; AND MAY BE FREELY COPIED AND DISTRIBUTED. ; Context Sensitive Inc. ; 4200 Aurora Ave. N. ; Seattle, WA 98103 ; (206) NEC-0301 ; ; Program: TERMINAL.A86 ; ; Version: 1.2 23 February 1984 ; Author: Ron Blanford ; This program uses the interrupt-driven serial port handler ; routines to perform terminal emulation on the NEC APC. ; ; The emulation continues until the CTRL-B character is ; entered at the keyboard. cseg ; Set up some initial constant definitions exit_char equ 02h ; Make CTRL-B our exit character bdos_conio equ 06h ; BDOS function for console I/O bdos_print equ 09h ; BDOS function to display a string ; First do all required initialization to our own ; data area and to the serial I/O port cli ; Make sure we aren't interrupted. mov ax,ds ; Set up our own stack area. mov ss,ax lea sp,stack sti call sio_start ; Initialize ports and interrupts. mov dh,8 ; Set baud rate at 9600 mov dl,4EH ; but leave default mode alone. call sio_setmode lea dx,sign_on ; Print the "Hello" message. mov cl,bdos_print int 224 ; This is the main "figure-8" loop of terminal emulation, ; alternately checking the keyboard and serial port for input. keyboard: call con_status ; Is a console character available? jz serial_port ; No, go check the serial port. call con_input ; Otherwise get the character. cmp al,exit_char ; Is this the signal to end? jz exit ; If so, end the program. call sio_transmit ; Otherwise send the character out. serial_port: call sio_rxstatus ; Is an input character available? jz keyboard ; No, go check the console. call sio_receive ; Otherwise get the character, and al,7Fh ; make it valid ASCII, call con_output ; and display it on console. jmps keyboard ; Finally, clean up the environment we created initially. exit: call sio_finish ; Undo to the ports and interrupts mov cl,0 ; what we did initially. mov dl,0 int 224 ; and gracefully exit to CP/M-86. retf ; These are the CP/M utility routines to perform console I/O. con_status: mov cl,bdos_conio ; Check if character is waiting mov dl,0FEh ; at the keyboard. int 224 or al,al ; Zero means no character waiting. ret con_input: mov cl,bdos_conio ; Read a char. from the keyboard. mov dl,0FFh int 224 or al,al ; Ignore any zero characters. jz con_input ret con_output: mov dl,al ; Display the char. on the screen. mov cl,bdos_conio int 224 ret dseg org 100h ; Skip the area reserved for CP/M. rb 80h ; Reserve space for our stack stack dw 0 ; and mark its bottom. cr equ 0Dh ; Define carriage return lf equ 0Ah ; and line feed. sign_on db cr,lf,' NEC APC Terminal Emulator' db cr,lf,'Press CTRL-B to return to CP/M' db cr,lf,lf,'$' INCLUDE SERIALIO.A86 ; Include the serial port handler. end