-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathserial.dow
executable file
·1 lines (1 loc) · 39 KB
/
serial.dow
1
SERIAL.SCR contains general purpose queue routines, plus serial input and output routines. The serial buffer is kept within the dictionary. Uses either multi-tasking serial port polling or interrupts. copyright 1989-2007 Frank C. Sergeant - [email protected] BSD/MIT/X-style license, see file license20040130.txt. ( load screen Serial output and input) By commenting out one of each pair, (a) choose whether to use CODE or high-level versions of the QUEUE words, (b) choose whether to use multi-tasking polling or an interrupt service routine for serial input. ( create a queue of bytes) BYTEQ: Size must be a power of two, e.g. 1024. That way, we can do a cheap mod by ANDing the pointers with a mask which is one less than the size. The byte queue created is a circular buffer. QRESET Reset the queue by zeroing the head & tail pointers. Equivalent high-level code : QMASK ( queue - mask) @ ; : QFROM ( queue - offset) 2 + @ ; ( head pointer) : QTO ( queue - offset) 4 + @ ; ( tail pointer) : Q? ( queue - #items) DUP QTO SWAP QFROM - ; : Q@ ( queue - c) BEGIN DUP Q? UNTIL ( ie wait forever if necessary) DUP QMASK OVER QFROM AND OVER + 6 + C@ 1 ROT 2 + +! ; : Q! ( c queue -) DUP QMASK OVER QTO AND OVER 4 + 1 SWAP +! + 6 + C! ; (Q@ this fetches a byte from the queue whether one is present or not. So, don't call it unless Q? returns true. Q? return number of items waiting in the queue. Q@ wait until an item is available, then fetch it. Q! store byte c into the queue. No checking is done to see is there is room for it. We speed this up somewhat by using LITERAL, e.g. : QTO ( queue - a) [ 2 CELLS ] LITERAL + @ ; instead of : QTO ( queue - a) 2 CELLS + @ ; ( Set or clear specific bits of an I/O port ) $03F8 $0F +BITS set the lower 4 bits of port $03F8 without altering the setting of the upper 4 bits. $03FD $AA -BITS clear every other bit, leaving the others unchanged. (Result is 0x0x0x0x) $03FD $AA +BITS set every other bit, leaving the others unchanged. (Result is 1x1x1x1x) To set a group of bits to a particular pattern, use -BITS first to clear the entire group, then use +BITS to set the specific bits that should be turned on, or simply write the bytewith PC! . +BITS and -BITS are candidates to become CODE words. ( serial port variables) PORT active serial port number IOBASE serial port register base, e.g. $03F8 for com1 ( serial port registers) DIV-LSB address of least significant byte of baud rate divisor DIV-MSB address of most significant byte of baud rate divisor LCR its bit 7 chooses whether divisor or data is addressed ( Modem Control Register bit settings) +DTR turn on the DTR line (i.e. make it about +9 volts). -DTR turn off the DTR line (i.e. make it about -9 volts). Similar words could control the RTS line, etc. OUT2 enables or disables the buffer between the 8250's interrupt line and the PC's interrupt controller chip (PIC) in the version using serial interrupts, we turned DTR, RTS, and OUT2 on with $0B MCR PC! (ie +DTR +RTS +OUT2). When not using interrupts, we could either do -OUT2 or leave it alone. ( Modem Status Register read bits) At run time, words created by MSR: take as a parameter the value of the modem status register and return a flag indicating whether the particular bit was set. These words do not themselves read the MSR as that would allow only a single test of the delta bits because the process of reading the MSR would clear the bits. The approach we use allows multiple tests, e.g. MSR PC@ ( msr) DUP dCTS ( msr f) SWAP dDSR ( f f ) ( select a serial port and create the queue) COM fetches the base I/O address from the BIOS data area, and sets up the serial port variables. E.g. 1 COM to select COM1: or 2 COM to select COM2: SERIAL is the name of a 4096 byte queue. Note, the length must be a power of two, or the cheap mod mask trick will not work. ( Set databits and parity) Bits 1,0 of the Line Contol Register control the number of data bits (5, 6, 7, or 8). Bits 5,4,3 of the Line Control Register determine the parity. First we clear all 3 bits, then we set the proper ones for the requested parity. E.g. 8 DATABITS NO-PARITY 7 DATABITS EVEN-PARITY $87 to the FCR should turn on the fifo and clear the tx & rx buffers and set the trigger point to 8 characters ($07 sets the trigger point to 1 character) ( baud rate) DIV-LSB address of least significant byte of baud rate divisor DIV-MSB address of most significant byte of baud rate divisor LCR's bit 7 chooses whether divisor or data is addressed. >DIVISOR choose divisior registers >DATA choose data register DIV@ fetch 16-bit divisor DIV! store 16-bit divisor to establish baud rate BPS calculate & set divisor for given baud rate (bits per second). It works for 110 300 600 1200 4800 9600 19200 38400 57600 and 115200. Note special trick for 115200 since 115200 won't fit in a 16-bit number. eg 1200 BPS 57600 BPS 115200 BPS ( display serial port status ) .bps display the baud rate .STA display the current status of serial port (should we display the irq# & sint#?) ( Serial input ) RESET-SER-IN empties the SERIAL queue SER-IN? returns true if at least one item is waiting SER-IN fetch a byte from the SERIAL queue. GET-2BYTES get a 16-bit word from the serial port, where 1st byte is most significant. ( Serial output >SERIAL ) SER-OUT write a byte directly to the serial port hardware, but wait until the transmit data register is empty. Don't wait forever, though. >SERIAL point the main Forth input and output words, or perhaps just the output, to the serial port. ( send a string to the modem) >MODEM send a string to the modem -- used for controlling the mode, e.g either " ATM1" >MODEM or M" ATM1" sets the speaker on while making the connection. ANSWER put modem in auto-answer mode NOANSWER take modem out of auto-answer mode MODEM-INIT +++ HANG-UP STRING-IN? See if the 1st string is contained in the 2nd string. If the 2nd string is not at least as long as the 1st string, then return false. Otherwise, compare the 1st string with the 1st string's length to the beginning of the 2nd string. If it matches, return true. Else bump the beginning address of 2nd string and compare again. Continue until we either find a match or have tried every possible position. ( user interface) GET-Y/N Wait for the user to press a y, n, Y, or N key. Return true if y or Y. Return false if n or N. TRY-AGAIN? Display a message and ask whether the user wants to try again. E.g. " Drive not ready" TRY-AGAIN? IF ... THEN .MSG clears the top 2 lines of the screen and displays a message, leaving the cursor positioned just past the message. E.g. " Starting the transfer ..." .MSG ?SER-IN similar to SER-IN except it allows the user to interrupt by pressing any key and it echos the incoming character. It is used, particulary, by WAIT-FOR and GET-MODEM-STRING so the user can escape if necessary. WAIT-FOR Takes a counted string and reads the serial port until we have an exact match or until we run out of input and a time-out occurs. We don't do a very sophisticated search, but we are generally dealing with very short strings, such as " CONNECT". GET-MODEM-STRING wait for a non-zero-length, CR-terminated string from the modem and echo it. If there is no such string, we will eventually time out and abort. We might also find a use for a less rigid version that waits awhile and returns an empty string if none arrives in time. ( examine the serial queue for testing) ?ECHO for testing, dump the SERIAL buffer as characters ?HEX for testing, dump the SERIAL buffer in hexadecimal ?DUMP dump the serial queue non-destructively DRAIN While the serial chip has characters ready, read them and stuff them into the SERIAL queue. ( multi-tasking, READ-SERIAL SERIAL-TASK ) ( Read serial port and stuff characters into the serial queue) The question is how often we should PAUSE while reading the serial port. Do we want to run all waiting tasks between each serial port read or do we want to drain the serial port with no intervening tasks if one or more characters are waiting? We will start with the second choice and only PAUSE after draining all waiting characters. If this causes problems, we may need to put a PAUSE every 4 or 8 or so serial characters.On a fast machine, I do not believe the serial port can cause an overrun, but we will see. ( multi-tasking, COMM initialize serial port, etc. ) COMM initialize the serial port and start the task that reads the serial port into a queue. UNINSTALL-SINT does nothing, but must be defined in case higher-level code needs to work the same regardless of whether the serial routines use multi-tasking polling or interrupts. ( get & set interrupt vectors) INT-VECTOR@ ask DOS for the contents of an interrupt vector. INT-VECTOR! tell DOS the contents of an interrupt vector. ( 8259 interrupt controller chip I/O port addresses) PIC_CTRL write $20 to this I/O port at end of interrupt PIC_MASK determines which hardware interrupts are enabled IRQ_MASK the hardware interrupt we want to enable for the chosen serial port These assembler macros are to be used to build a serial interrupt handler _after_ the serial port is set up (e.g. 2 COM ). Then, at run time (of the handler), we won't need to access variables to find the port addresses, etc. Instead, they will be hard- coded into the interrupt handler. Thus, the variables must be set up before the macro executes. EOI, send an unspecific "end of interrupt" to the PIC ENABLE_PIC, unmasks the correct serial irq line DISABLE_PIC, masks the correct serial irq line DRAIN, while character(s) are waiting, read them and stuff them into the proper serial queue. CLEAR-IID, while bit 0 of IIR is zero, take the most likely steps to clear the pending interrupt flags. Note, this should not be necessary, since we do not enable any ints except for data received and reading the DATA port should clear all the interrupt flags associated with received data. However, others have warned that this step is needed because some of the other interrupt flags still get set from time to time. HANDLER, This lays down the assembly code for the interrupt handler. It must not be done until after the PORT SIRQ# etc variables are set, e.g. with 2 COM, as the macros need to know port and interrupt values. It goes through a rather elaborate procedure to attempt to guarantee the irq line will never be stuck on. See the Serial Port FAQ for the reasoning behind this. Probably the handler does not need to be this elaborate if only a single port is attached to the irq. ( install serial interrupt routine ) OLD-SVECTOR place to save old serial interrupt vector INSTALL-SINT save the old vector, install vector pointing to our own handler, use IRQ_MASK to clear the bit in the 8259 chip to allow our hardware interrupt, set certain of the serial port output lines, tell the serial chip to interrupt only on data in, clear any accidentally pending interrupt, and make sure the SERIAL buffer is empty. UNINSTALL-SINT disable the serial interrupt at the 8259 and restore the saved vector (not that it would do any good with the int disabled at the 8259) BUILD-HANDLER This is used to define the serial interrupt handler code at run-time. It must not be done until the com port has been established. It builds a custom handler for the specific com port that is active when BUILD-HANDLER is executed. START Establish the correct com port and define and install an interrupt handler for it. ( DUMB terminal on COM1 ) DUMB example of using the serial routines to make a dumb terminal. It prints to the screen any characters that come in on the serial port. It sends to the serial port any keys pressed on the keyboard. Except, pressing the Esc key stops DUMB. DUMBER is a slightly smarter dumb terminal that should be able to handle end-of-line a little better than DUMB