The application is from a serial device driver used in an embedded PC running DOS and VFX Forth for DOS. Not all the code is provided here, so you’ll have to use this code as an illustration from a real-world example – it is not complete in itself. The point is to illustrate how the Forth interpreter can be used both at compile time and at run time. To configure a serial port, not only do you need to specify the baud rate, parity and so on at run time, but you also need to be able to specify the I/O port address, interrupt number and so on. The hardware configuration for the first three serial ports is (mostly) standard and can be done at compile time. Industrial applications can require a very large number of serial ports and since their configurations may change (it is difficult to buy the same PC motherboard for more than two or three years in succession), the configuration may have to be done during installation and commissioning of the equipment. This configuration information can be held in a file which is INCLUDEd at program start. It uses the Forth interpreter to set up the system.
The Forth system accesses I/O devices through a “Generic I/O structure”. This is a data structure whose first part is common to all devices, and the following parts contain data private to each device. For each serial device, a configuration string is used to the data into the data structure. The strings are similar to those used by the DOS MODE command, e.g.
COM2: 38400 baud N,8,1 8 kb 4 kb buffers
specifies that the device uses the hardware for COM2, runs at 38400 baud, no parity, eight data bits, one stop bit and has input and output buffers of 8 and 4 kilobytes respectively.
Interpretation of the string starts with the address of a structure on the data stack. Each word that processes part of the configuration, such as BAUD and N,8,1, consumes relevant data and leaves the structure address on the stack ready for the next word.
Using the Forth interpreter
Implementation
The code is taken directly from one of the VFX Forth for DOS source files. The comment lines that start ‘\ *x’ are processed by documentation tools to produce HTML and PDF documentation directly from the source code.
struct /ComData \ -- n ; size of uart/queue structure \ *G The /ComData structure holds the data needed to process \ ** a channel, including the UART address, queue addresses, \ ** and transmit flags. This is a Generic I/O structure. \ GENIO data
int C.handle \ device handle or -1 int C.vectors \ pointer to Gen I/O vectors \ UART configuation
int C.mode \ mode flags, b0=16450, b1=16550,
int C.baud \ baud rate
byte C.int# \ DOS interrupt# byte C.#data \ #data bits, 5, 7, 8 byte C.parity \ parity
byte C.#stop \ stop bits \ buffer configuation
int C.port \ UART port base address int C./inpQ \ size of input queue
int C.inpQ \ input queue (allocated from heap) int C./outQ \ size of output queue
int C.outQ \ output queue
int C.flag \ true if TX in progress \ PIC configuation
int C.pic0 \ first PIC port (must be set) int C.pic1 \ second PIC port (0=unused) byte C.irqPic0 \ PIC0 IRQ#
byte C.enPic0 \ PIC0 channel enable mask byte C.disPic0 \ PIC0 channel disable mask byte C.irqPic1 \ PIC1 IRQ#
byte C.enPic1 \ PIC1 channel enable mask byte C.disPic1 \ PIC1 channel disable mask aligned
\ Interrupt counters and state
int C.#ints \ number of times interrupts checked int C.#MSRints \ number of MSR interrupts
int C.#RXints \ number of receive interrupts int C.#TXints \ number of transmit interrupts int C.#LSRints \ number of LSR interrupts byte C.lastMSR \ last value from MSR byte C.lastLSR \ last value from LSR aligned
\ Saved UART configuation
/UARTstate field C.US \ UART state save area (all bytes) aligned
Using the Forth interpreter
\ ************************ \ *S Configuring serial ports \ ************************
\ *P Serial ports are initalised and opened using command \ ** strings similar to those used in DOS commands. The \ ** commands are Forth words in a private vocabulary. vocabulary COMsettings \ --
\ *G Used for serial port set up commands. All words in this \ ** vocabulary must return the /Comdata structure passed \ ** to them, e.g.
\ *C 115200 baud ( struct n -- struct )
\ ******************************** \ *N Words used in command strings \ ********************************
\ *P The /ComData structures are initialised using command \ ** strings held in a private vocabulary. The same strings \ ** are used when opening the device.
\ *C COM2: 38400 baud N,8,1 8 kb 4 kb buffers also COMsettings definitions
: baud \ struct baudrate -- struct \ *G Set the structure's baudrate.
\ ** The default is 9600. over C.baud ! ;
: port \ struct port -- struct
\ *G Set the structure's I/O port base address. \ ** No default unless SET by COM1..COM4 below. over C.port ! ;
: int# \ struct int# -- struct \ *G Set the structure's DOS interrupt number. \ ** No default unless SET by COM1..COM4 below. over C.int# c! ;
\ LCR - Line control register
: #data \ struct #bits -- struct
\ *G Set the structure's number of data bits. Defaults to 5.
5 - %00000011 and \ LCR bits 1:0
over C.#data c! ;
: #stop \ struct #stop -- struct
\ *G Set the structure's number of stop bits. Must be 1 or 2. \ ** The default is 1.
1- 0<> $04 and over C.#stop c! ; \ LCR bit 2
$00 constant no \ LCR bits 5:3
Using the Forth interpreter
$28 constant par1 $38 constant par0
: parity \ struct #bits -- struct
\ *G Set the structure's parity usage. #bits must be one of \ ** NO, ODD, EVEN, PAR1 or PAR0. The default is NO.
over C.parity c! ;
: buffers \ struct #in #out -- struct
\ *G Set the input and output queue buffer sizes, which \ ** must be a power of 2. If unused, defaults to 1024 \ ** and 256.
rot tuck C./outQ ! tuck C./inpQ ! ;
: PIC0 \ struct bit# port -- struct
\ *G Set the bit# and base address of the PIC that will be \ ** used first to clear/enable/disable an interrupt. \ ** Defaults to the COM1 standard settings or as set \ ** by COM1..4.
... ;
: PIC1 \ struct bit# port -- struct
\ *G Set the bit# and base address of the PIC that will be \ ** used second to clear/enable/disable an interrupt. \ ** Use 0 0 PIC1 if the second PIC is unused.
\ ** Defaults to the COM1 standard settings or as set \ ** by COM1..4.
... ;
: COM1: \ struct -- struct \ *G Set the default conditions for COM1: $03F8 port $0C int# 4 $20 PIC0 0 0 PIC1 ;
: COM2: \ struct -- struct \ *G Set the default conditions for COM2: $02F8 port $0B int# 3 $20 PIC0 0 0 PIC1 ;
: COM3: \ struct -- struct \ *G Set the default conditions for COM3: $03E8 port $0C int# 4 $20 PIC0 0 0 PIC1 ;
: COM4: \ struct -- struct \ *G Set the default conditions for COM4: $02E8 port $0B int# 3 $20 PIC0 0 0 PIC1 ;
Using the Forth interpreter
: N,8,1 \ struct -- struct
\ *G Set the structure to no parity, 8 data bits, 1 stop bit. no parity 8 #data 1 #stop
;
previous definitions
\ *************************** \ *N Handling command strings \ *************************** also COMsettings
: ([COM) \ struct -- struct
-1 over C.handle ! \ mark as closed
COM1: #9600 baud N,8,1 \ default initialisation /ComRxQ /ComTxQ buffers
;
previous
: [COM \ struct -- struct
\ *G Starts a definition of a /ComData structure.
\ ** [COM may be followed by any of the words above up to \ ** the closing COM]. Unless overriden, sets
\ ** COM1:9600,N,8,1. Use as indicated \ ** in the example below:
\ *C struct [COM COM2: 38400 baud N,8,1 COM] state @
if postpone ([COM) else ([COM) endif also COMsettings
; immediate
: COM] \ struct --
\ *G Closes the definition started by *\fo{[COM} above. state @
if postpone drop else drop endif previous
; immediate
SysErrDef err-COMstring "Error in COM command/open string" : (SetComData) \ caddr len struct --
depth >r
[COM -rot evaluate COM]
depth r> swap - 3 <> err-COMstring ?throw ;
: SetComData \ caddr len struct --
\ *G The string caddr/len is processed by EVALUATE as \ ** between [COM string COM]. Any error causes a THROW. \ ** Zero length strings are ignored so that previously \ ** set data is used.
Using the Forth interpreter
else
drop 2drop endif
;
A serial port device is created by the word SerDev: which can then be set up using the same command strings.
SerDev: COM1dev \ -- sid
\ *G Standard PC COM1 port device set by default to: \ *C COM1: #115200 baud
COM1dev [COM COM1: #115200 baud COM] SerDev: COM2dev \ -- sid
\ *G Standard PC COM2 port device set by default to: \ *C COM2: #38400 baud
COM2dev [COM COM2: #38400 baud COM]