/* Change this string to update program name. */

char progVersion[8]="DRO5.4";

/*
 Oct 23, 1999
 Steve Lindsay  slindsay@lindsayengraving.com
 I have been hanging on to this source for a long time and I tend not to
 want to give away something I work hard at.  But here it
 is.  You are free do what you may with it....but one thing
 If it helps you in coming up with a cool encoder reading
 program I would like to receive a copy.
 Below there is some text talking about that this is a
 dll for Visual Basic...  It is not!..   When I started this program
 that is what I was trying but it ended up being just a c++
 dos program.  It was written with Microsoft Visual C++ version 1.5
 You will probably have to do some messing with it to get it to
 compile in newer or other c++ compilers.  The first part of
 the file tells helpful info learned while writing the program.  
 You will discover that I cannot spell....but compilers don't care as 
 long as you consistenaly missell. My screen was set at 1280x1024 
 while writing....and I seem to make the lines very long...  
 So be sure you have line wrap shut
 off with whatever you use to view this code.
*/


/* November 25, 2000   AGE1
After having received and used this program (version A), I am attempting to make some
minor changes/additions to increase the functionality for me (and others if they 
desire). The changes that I am trying to accompolish are:

	1.  Add a machine name to the top of the screen as I intend to use the same PC 
	    and monitor for a readout and have a switchbox for switching between machines.
		This will require "Manual" editing of the machine name in the origional cfg
		file at this time, I intend to have a DOS screen driven menu to copy the 
		intended cfg file to the default one and then load the program. A concious 
		decision has been made NOT to include a menu driven config file input as I 
		or others may want a different naming convention. If at some time my 
		programing skills become sufficient enough to allow program naming and 
		retrevial, I may attempt that also.
	2.	As there are two versions of the program DRO40 and DRO40A, I hope to combine 
	    all functionality into one so the user can simply toggle the proper bit in 
		the control port to allow one program to be used on various machines. (See 
		Steves Discussion on how parallel ports are not created equal)
	3.	Possible LOOOONG term may be to add a fourth encoder?

After trying to compile in Version 5.0, I gave up and dug out my old copy of 1.52 to 
compile the program.

NOTE: If I can remember, any changes made to the original cpp (DRO40.cpp) file obtained 
from Steve will be comented with the usual notation of AGE1

I can be reached at axtein@surfsouth.com

*/


/* January 2001,  Robert Duncan

  "Full" rewrite with following features:
  Used C 1.52, removed C++ constructs (didn't really use many of them anyway).

  Config filename can now be placed on the startup command.  Syntax is like:
	 DRO mill
  or
	 DRO lathe
  You could also build a batch file called MILL.BAT and put the command "DRO MILL" in
  it and just type "MILL".

  Invented the ENCODER_DATA structure, that contains all data for control of an axis.  
  Routines that work on an axis now take a pointer to one of these structures as the 
  only argument.  One routine can work for all axis's rather than having ???X, ???Y, ???Z 
  routines.  This also made addition of another axis very simple.  The screen display row
  for an axis is defined in the data structure.  An axis "exists" if it has a non-zero 
  display row defined in the structure.  The Program Options allows adding or deleting
  an axis.

  The new "Machine Configuration" command on the main menu allows the user to edit the control
  information in the ENCODER_DATA structure.  You can set the machine name, add or delete an 
  axis (to turn on the 4th axis), assign a name to an axis, change the label (X,Y,Z), turn 
  ON/OFF the hig res graphics mode, and set autoDisplay mode.  The autoDisplay causes the 
  program to go directly to the "Read Encoders" routines on startup, an <ESC> returns to 
  the main menu loop.  Once a machine has been configured, it is handy to turn ON this 
  option so the tool is ready to use with no commands required.  If a AUTOEXEC.BAT file
  is built, it is possible to go from computer power up to encoder display with no
  keyboard input.

  A calibration command has been added to the "Machine Configuration" routines.  It allows
  the user to enter the fixed length of a measuring device, then move the axis from one end 
  to the other and computes/stores the "units" calibration value.

  Added an elastic circular buffer system to aquire the data bytes from the parallel port.
  This is used at either interrupt level or in a polling mode.  By stuffing the data byte 
  into the buffer and dismissing the interrupt or poll, the chance of missed data is reduced.
  The buffer can be read at a later time and will "catchup" with no loss of position.  The
  buffer can be consumed quite fast if, for example, a 2000 line encoder (16,000 pulses/rev),
  is spun quickly.  So the buffer is large, 64k.

  Polling is now done in a central keyboard character routine.  So position is maintained
  even while in screens other than the "read encoder" loop.

  The "previous history" has been reduced to a single byte of four two bit values.  This byte
  is indexed into a table to determine the step direction with a single operation.  This 
  increased program speed dramatically.

  The position value is now a long int, it is steped up or down by one for each encoder step
  regardless of whether we are in linear or rotary mode.  The offsets are also stored as
  long int.  The position is converted to a floating point number at the time of display.  
  This eliminates a lot of floating point math that is not required until we display the
  number.  The only long term floating point numbers are the units per step values and
  rotary step conversion values. 

  The help filename defaults to the name of the program with a .HLP extension.  So if you 
  rename the .EXE file, rename the .HLP file.

  Developed a custom font, based on HELVB Microsoft font.  It has big digits that look like
  7 segment displays.  When using the graphic font display interrupts should be used.  We
  loose control for too long doing screen updates to maintain polling.  The character mode is
  still supported and is the default when not in interrupt mode.  Note that the "Machine 
  Configuration" command can be used to set the graphicMode flag.

  The symbol _DEBUG can be defined at complie time to get some debug features.  One is
  the ability to inject encoder steps based on a timer.  The step direction and time between
  injection step can be controled, as well as which axis is stepped.  Look in the mainCommand_R
  routine for the command codes.  The other feature is an internal trace table.  This requires 
  trace calls to be placed at selected locations and allows capture of a 30 byte string at 
  "full speed" within the program.  An sprintf can be used to build a string with numeric
  values to be traced.  A time stamp with 18ms accuracy is attached to each trace entry.

  Ideas for future:
  -backlash needs some work, I am not sure if it still functions
  -convert all arithmetic to scaled integers to eliminate all floating point operations

  I can be reached at duncancomputer@hotmail.com or rlduncan@chartermi.net
*/


/*   Edit History - Most recent changes first.

   Date      Who Version    Comment
 -------------------------------------------------------------------------------------------
 Feb 26,2004 RLD 5.4	Did not like the direct video writing.  Add back several DOS calls to
						handle screen writing (much fewer calls than than before).
						Added math features, triangle calculator, expression evaluator.
						Added lathe mode features, sum/vector axis, diameter display.
						Added sound for various functions (and a way to turn it off).
						Added Center and Edge find that work with both serial and parallel mode.
						Added hole list processing (Circle, Arc, or Line).
 
 Jan 03,2004 RLD 5.3	Developed an AT90 based interface board (thanks Paul Cooper) that can
						do step decoding in the interface, this mode can be selected in port 
						setting command.  

 Nov 11,2003 RLD 5.3	Switched development to Open Watcom 1.1.  Fixed problem with cursor not
						going away on CURSOF_OFF setting by moving cursor to address 0X3FFF when "OFF"

 Feb 20,2003 RLD 5.3	Switched development to Open Watcom 1.0 and converted to 32-bit operation
						using the CauseWay DOS extender package.  Removed many 16 bit DOS/BIOS calls
						by direct writing to video memory.

 Feb 14,2003 RLD 5.2	Added an "auto zero edge finding" feature.  Using an input bit on the
						parallel port status byte (SELECT/Xflag).  When the "E"dge find command is 
						given, wait until an axis moves or is selected from the keyboard, then look 
						for a signal on the input bit to indicate the edge has been found, then 
						offset the current count by the radius of the edge finer probe.

 Feb 11,2003 RLD 5.2	Added a tachometer function using an input pulse from a serial port
						modem control line (RI is what I used).  Also displays Surface Feet
						per Minute (SFM) if a diameter is entered.

 Jan 22,2003 RLD 5.2	Fixed possible issues with mismatched or bad .CFG file overlaying good 
						existing data.
						Added more complete processing of the program path information.  Use the
						program path for access to the.HLP and .FON files.
						Added slew rate detection and display of axis movement per minute.
						Added RPM input and display via serial port RI input line.
						Added <TAB> command feature.  Pressing the TAB key anytime that a number
						or string input is not requested goe directly to the encoder display 
						screen.

 Jan 22,2003 RLD 5.1	Fixed problem with incremental mode current value setting not
						applying axis calibration.  Changed mode names to Absolute and
						Incremental ?.  Expanded filename storage arrays to 255 bytes.
						Fixed rounding problem with "current value" input. Fixed logic
						problems with "zeroing" axis.

 Jan 11,2003 RLD 5.2	Added printer port detection.  Will detect SPP, PS/2, EPP, and ECP
						port types.  Whatever is found is set to bidirectional mode Removed
						old user specified port control modes.
 */
 
/* 
	System include files.
 */
#include <dos.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <graph.h>
#include <bios.h>
#include <time.h>
#include <math.h>
#include <malloc.h>
#include <ctype.h>
#include <setjmp.h>

#include <signal.h>

/*
	Constants definitions.
 */

/* These #defines allow large chunks of code to be commented out.
   Needed this to debug under MS VC1.52 which only allows 1 code
   seg in a single file (got too big and I haven't been able to get
   Open Watcom debugger to work). */
#define LATHECODE
#define BOLTLISTCODE
#define BACKLASHCODE
#define TRIANGLECODE

/* Uncomment this define to get debugging features
   or use the compiler switch (-D_DEBUG) */

//#define _DEBUG

#ifdef _WATCOMC_ /* Open Watcom 1.0 compiler */

#define _ffree free
#define _fmalloc malloc
#define _videoconfig videoconfig
#define _rccoord rccoord
#define _REGS REGS
#define far 

#else /* Not Watcom (MS Visual C 1.52 compiler) */

#ifndef _DEBUG /* If not debug build, select intrinsics for optimized code. */
#pragma intrinsic (_enable, _disable, inp, outp, labs)
#endif

#endif

/* 
Default color table for DOS standard displays.

Index	Color	Index	Color
 0		Black	8		Dark gray
 1		Blue	9		Light blue
 2		Green	10		Light green
 3		Cyan	11		Light cyan
 4		Red		12		Light red
 5		Magenta	13		Light magenta
 6		Brown	14		Yellow
 7		White	15		Bright white

Blinking is selected by adding 16 to the normal color value. */

#define BLACK			0
#define BLUE			1
#define GREEN			2
#define CYAN			3
#define RED				4
#define MAGENTA			5
#define BROWN			6
#define WHITE			7
#define DARK_GRAY		8
#define LIGHT_BLUE		9
#define LIGHT_GREEN		10
#define LIGHT_CYAN		11
#define LIGHT_RED		12
#define LIGHT_MAGENTA	13
#define YELLOW			14
#define BRIGHT_WHITE	15
#define BLINK			16

/*
	Character constants and macros
 */
#define EOT 3
#define BACKSPACE 8
#define TAB 9
#define ENTER 13
#define SYN 16
#define ESC 27
#define SPACE 32
#define HOME 199
#define UPARROW 200
#define PAGEUP 201
#define LEFTARROW 203
#define RIGHTARROW 205
#define END 207
#define DOWNARROW 208
#define PAGEDOWN 209
#define F1 187
#define F2 188
#define F3 189
#define F4 190
#define F5 191
#define F6 192
#define F7 193
#define F8 194
#define F9 195
#define F10 196
#define F11 261
#define F12 262

#define TRUE 1
#define FALSE 0

/* Trace table entry codes. */
#define TR_command 0
#define TR_poll 1
#define TR_decode 2
#define TR_error 3
#define TR_intrup 4
#define TR_step 5

/* Some "sound" types. */
#define ERRnote 0
#define DONEnote 1
#define QUESTnote 2
#define BEEPnote 3

/* Macros to obtain values from the BIOS data area */

#define TICKS (long)((void far *) 0x46C)
#define KB_STAT (char)((void far *) 0x417)
#define KB_HEAD *((short far *) 0x41A)
#define KB_TAIL *((short far *) 0x41C)
#define s_getscreenwidth (*(short far *) 0x44A)
#define s_getscreenheight ((short) *(unsigned char far *) 0x484)
#define s_getvideomode ((short) *(short far *) 0x449)
#define windowVideoAddress(row, col) ((unsigned char far *)(screen_videoBuffer+(((row-1+window_top)*screen_width)+(col-1+window_left))*2))
#define videoAddress(row, col) ((unsigned char far *)(screen_videoBuffer+(((row-1)*screen_width)+(col-1))*2))

/* Macros for working with the keyboard buffer */

#define KB_HIT (KB_HEAD != KB_TAIL)
#define NO_KB_HIT (KB_HEAD == KB_TAIL)
#define CTRL() (KB_STAT & 4)
#define SHIFT() (KB_STAT & 2)

/* Values for the cursor attribute are listed below:

  VGA Cursor Shape
	0x0D0D	Underline
	0x000D	Full block cursor
	0x0C0D	Double underline
	0x2000	No cursor

 Note that this function works only in text video modes. */

#define CURSOR_ON  0x0C0D
#define CURSOR_OFF 0x2000


/* 
   Parallel port auto detect constants
*/
#define	lptN_A 0	/* Port Not Available, probably an error condition. */
#define	lptSPP 1	/* Original IBM PC mode, output only, probably an error condition. */
#define	lptPS2 2	/* IBM PS/2 mode, simple byte in/out */
#define	lptEPP 3	/* Extended Parallel Port mode, can be used in PS/2 mode */
#define	lptECP 4	/* Extended Capabilities Parallel port mode, can be set to byte mode. */


/* 
   Serial port auto detect constants
*/
#define	serN_A 0	/* Port Not Available, probably an error condition. */
#define	ser8250 1	/* Original IBM PC type. */
#define	ser16450 2	/* More advanced type */
#define	ser16550 3	/* Most popular type, has FIFOs */
#define	ser16550A 4	/* Most advanced type. */


/* 
   Parallel port constants.
*/
#define DTR	0		/* Offset from port base address of data register */
#define STR	1		/* Offset from port base address of status register */
#define CTR	2		/* Offset from port base address of control register */
#define EPPADR	3	/* Offset from port base address of EPP address register */
#define EPPDAT0	4	/* Offset from port base address of EPP data 0 register */
#define EPPDAT1	5	/* Offset from port base address of EPP data 0 register */
#define EPPDAT2	6	/* Offset from port base address of EPP data 0 register */
#define EPPDAT3	7	/* Offset from port base address of EPP data 0 register */
#define ECP	0x400	/* Offset from port base address of ECP register group */
#define ECR	2		/* Offset from ECP base address of ECP Extended Control register */

#define AUTO_ZERO_INPUT 0x10	/* Select In bit - used for Auto Zero sensor input. */


/* 
   Definitions for serial communications on the IBM PC
*/

/* Relative 8250 i/o port addresses */

/* The following 3 registers require bit 7 of line control register to be 0 */
#define SR_TRANSMIT_DATA 0	/* Transmitter data register (out) */
#define SR_RECEIVE_DATA 0	/* Receiver data register (in) */
#define SR_INTERRUPT_ENABLE 1	/* Interrupt enable (out) */

/* The following 2 registers require bit 7 of line control register to be 1 */
#define SR_BAUD_LSB 0		/* Low bit of baud rate divisor register (out) */
#define SR_BAUD_MSB 1		/* High order baud rate divisor (out) */
#define SR_FIFO_CONTROL 2	/* FIFO control register (out) */
#define SR_INTERRUPT_ID 2	/* Interrupt identification register (in) */
#define SR_LINE_CONTROL 3	/* Line control (out) */
#define SR_MODEM_CONTROL 4	/* Modem control (out) */
#define SR_LINE_STATUS 5	/* Line status (in) */
#define SR_MODEM_STATUS 6	/* Modem status (in) */
#define SR_SCRATCH 7		/* Scratch register (in/out) */

/* Interrupt enable register bits */
#define INTR_RECEIVE 1		/* Interrupt when receiver data is ready */
#define INTR_TRANSMIT 2 	/* Interrupt when transmitter data register is empty. */
#define INTR_ERROR 4		/* Interrupt on error or break */
#define INTR_STATUS 8		/* Interrupt on change of modem status lines */

/* Modem control register bits */
#define MODM_DTR 1			/* Data terminal ready output */
#define MODM_RTS 2			/* Request to send output */
#define MODM_OUT1 4			/* Output 1 */
#define MODM_OUT2 8			/* Output 2 */
#define MODM_INTR_EN 8		/* Must be 1 to enable Modem interrupts on a PC */
#define MODM_LOOP_BACK 0x10	/* If 1, uart transmitter is looped back to its receiver. */

/* Definitions for the interrupt identification register */
#define INTID_PENDING 0x01	/* 1 if interrupt conditions are pending */
#define INTID_MASK 0x0E		/* Mask to get interrupt identification code */

/* Interrupt identification codes */
#define INTID_MODEM 0x00	/* Change in modem status */
#define INTID_TRANSMIT 0x02	/* Transmitter register empty */
#define INTID_RECEIVE 0x04	/* Received data ready */
#define INTID_ERROR 0x06	/* Error or break */
#define	INTID_RXFIFO 0x0C	/* Received data ready (FIFO timeout). */

/* Bits for the modem status register */
#define MSTS_CTS_CNG 0x01	/* Change in clear-to-send */
#define MSTS_DSR_CNG 0x02	/* Change in data-set-ready */
#define MSTS_RING_CNG 0x04 	/* Change in ring indicator */
#define MSTS_DCD_CNG 0x08	/* Change in data-carrier-detect */
#define MSTS_CTS 0x10		/* clear-to-send */
#define MSTS_DSR 0x20		/* data-set-ready */
#define MSTS_RING 0x40		/* ring indicator */
#define MSTS_DCD 0x80		/* data-carrier-detect */

/* Bits for the line control register */
#define LC_STOP_BITS 4		/* 1 stop bit if 0, 2 if 1 (or 1.5 if 5 bit char) */
#define LC_PARITY_ENABLE 8	/* 0 disables parity, 1 enables */
#define LC_PARITY_EVEN 0x10	/* 0 for odd, 1 for even */
#define LC_PARITY_STUCK 0x20	/* if 1, then parity is stuck at the opposite of
				   LC_PARITY_EVEN. */
#define LC_BREAK 0x40		/* if 1, then uart output is set to SPACE */
#define LC_DIVISOR 0x80 	/* if 1, then relative i/o addresses 0 and 1
				   point to the baud rate divisor.  Otherwise,
				   they point to transmit/receive data and
				   interrupt enable. */

#define LC_8BITS 3		/* 8 Bit characters */
#define LC_7BITS 2		/* 7 Bit characters */
#define LC_6BITS 1		/* 6 Bit characters */
#define LC_5BITS 0		/* 5 Bit characters */

/* FIFO control bits */
#define FIFO_ENABLE		0x01	/* Enable Tx and Rx FIFOs */
#define FIFO_RX_RESET	0x02	/* Clear (reset) Rx FIFO */
#define FIFO_TX_RESET	0x04	/* Clear (reset) Tx FIFO */
#define FIFO_1		    0x00	/* Interrupt at one byte */
#define FIFO_4		    0x40	/* Interrupt at four bytes */
#define FIFO_8		    0x80	/* Interrupt at eight bytes */
#define FIFO_14		    0xC0	/* Interrupt at fourteen bytes */

/* Bits for the line status register */
#define LSTS_RECEIVE	0x01	/* Receive data ready */
#define LSTS_OVERRUN	0x02	/* Overrun error */
#define LSTS_PARITY		0x04	/* Parity error */
#define LSTS_FRAMING	0x08	/* Framing error */
#define LSTS_BREAK		0x10 	/* Break detect */
#define LSTS_TRANSMIT	0x20	/* Transmitter register empty */

/* Any error */
#define LSTS_ERRORS (LSTS_OVERRUN | LSTS_PARITY | LSTS_FRAMING | LSTS_BREAK)

/* Most significant byte of baud rate divisor word */
#define BD50_MSB 9
#define BD75_MSB 6
#define BD110_MSB 4
#define BD150_MSB 3
#define BD300_MSB 1
#define BD600_MSB 0
#define BD1200_MSB 0
#define BD2400_MSB 0
#define BD4800_MSB 0
#define BD9600_MSB 0
#define BD19200_MSB 0


/* Least significant byte of baud rate divisor word */
#define BD50_LSB 0
#define BD75_LSB 0
#define BD110_LSB 0x17
#define BD150_LSB 0
#define BD300_LSB 0x80
#define BD600_LSB 0xc0
#define BD1200_LSB 0x60
#define BD2400_LSB 0x30
#define BD4800_LSB 0x18
#define BD9600_LSB 0xc
#define BD19200_LSB 0x6

#define END_OF_INTERRUPT 0x20	/* Value to write to the interrupt command
				   register to signal the end of an interrupt
				   to the 8259. */

/* Location in ram where the rom BIOS stores the list of serial i/o ports. */
#define RS232_BASE ((unsigned int far *) 0x400)




/*  General background documentation...

                               data    status      control 
lpt1 addresses in decimal		888		889			890
lpt2 addresses in decimal		632		633			634    
              
          ***************************************************     
                 What the 25 pins do on a lpt port
  
1	STROBE  			(CONTROL)
2	DATA BIT 0
3	DATA BIT 1
4 	DATA BIT 2
5	DATA BIT 3
6	DATA BIT 4
7	DATA BIT 5
8	DATA BIT 6
9	DATA BIT 7
10	ACKNOWLEDGE			(STATUS)
11	BUSY				(STATUS)
12	PE (OUT OF PAPER)	(STATUS) 
13	PRINTER ON LINE		(STATUS)
14	AUTO LINEFEED		(CONTROL)
15	PRINTER ERROR		(STATUS)
16	INITIALIZE PRINTER	(CONTROL)
17	SELECT\DESELECT 	(CONTROL)
18-25 UNUSED OR GROUND      


    How two data inputs go on and off for a quaduture encoder
		binary		decimal
		cw ccw		cw	ccw
		00 00       0	0
		10 01       2   1
		11 11       3   3
		01 10       1	2
		00 00       0   0
  
_____________________________________________________________________________________
                 BITWISE STUFF
  
you can use the bitwise and (&) to a decimal number to learn if a certain bit
is 0 or 1.  

binary counting places are as follows (like with base 10 it is 1s 10s 100s 1000s)
1=1
10=2
100=4
1000=8
10000=16
100000=32
1000000=64
10000000=128  

to use the bitwise and (&) do this:
Like if you have a
decimal  236 (11101100)
and you want to know what the fifth bit over is do this:

check=236&16;
if (check == 0) it is a 0 otherwise it is a one
			   check will not hold a one. It will only hold a zero and if not you 
			   assume it is a one.

to write back and just change a certain bit you can do this after you know the 
current decimal number like with the above example if you want to change the fifth 
bit from a 0 to a 1 do this;

change=236+16 now it will be a decimal  
252 or in bits: 11111100   

of course to change the same bit from a 1 to a 0 just subtract 16 from the decimal
  
*****************************************************************************************
                PARALLEL PORT STUFF

These are some ways to detect what type of port a system has: SPP (original), PS/2 
(simple bidirectional), EPP, or ECP. The explanations assume that you have some familiarity 
with the different port types and how to access port registers. These tests detect only 
what type of port is currently enabled! If the advanced modes are disabled on the port 
controller chip, the tests won't detect them. 

On many ports that support advanced modes, you can configure the port either in the CMOS 
setup, or with jumpers, or with configuration software that comes with the port. Most have 
an option that causes the port to emulate the original SPP, plus one or more options that 
enable the advanced modes. If the port is configured as an SPP, the advanced modes will 
be locked out and the port will fail any tests for PS/2, EPP, or ECP abilities. 

Detecting an ECP 

In testing a port, you might think that the first step would be to test for an SPP, and 
work your way on up from there. But if the port is an ECP, and it happens to be in its 
internal SPP-emulation mode, the port will fail the PS/2 (bidirectional) test. For this 
reason, I begin by testing for an ECP, and work down from there. This is the method 
Microsoft's ECP document (in the Developer's Network CD-ROM) recommends for detecting an 
ECP: 1. Read the ECP's extended control register (ECR) at base address + 0x402 and verify 
that bit 0 (fifo empty) =1 and bit 1 (fifo full) =0. These bits should be distinct from 
bits 0 and 1 in the port's control register (at base address + 2). You can verify this 
by toggling one of the bits in the control register, and seeing that the corresponding 
bit in the ECR doesn't change. 2. A further test is to write 0x34 to the ECR and read it 
back. Bits 0 and 1 in the ECR are read-only, so if you read 0x35, you almost certainly 
have an ECP. If an ECP exists, you can read and set the ECP's internal mode in the ECR. 
(See below.) 

Detecting an EPP 

In addition to the SPP's three registers, an EPP has four additional registers, at base 
address + 3 through base address + 6. These additional registers provide a way to test 
for the presence of an EPP, by writing a couple of values to one of the EPP registers 
and reading them back, much like you would test for an SPP. If the reads are successful, 
the register exists and you probably have an EPP. I'm not sure if this test works on all 
EPPs. Because the EPP handshake doesn't complete, there's no guarantee of the contents 
of the register after the transfer times out. But on the tests I've done, I was able to 
read back the values written. Be sure to clear the EPP timeout bit (bit 0 of the status 
port, at base address + 1) after each read or write. Unfortunately, the method for 
clearing the bit varies with the controller chip. On some ports, you clear the bit by 
writing 1 to it. On others, simply reading the status register clears the bit. And, 
though I haven't seen any controllers that clear the bit in the conventional way, by 
writing 0 to it, you may as well do that too, just to be safe. 

Beware #1: on SMC's chips (& maybe others), a set timeout bit can make the port unusable 
in any mode, until a hard reset (or clearing the bit)! 

Beware #2: Don't test for an EPP at address 0x3BC. The added EPP registers aren't available 
at this address, and may be used for video. 

Detecting an SPP 

To test for an SPP, use the tried and true method of writing two values to the data port 
and reading them back. If the values match, the port exists. Otherwise, the port doesn't 
exist, or it's not working properly. Also note that the port-test routine only verifies 
the existence of the data port. It doesn't test the status and control lines. The other 
port types (EPP, ECP. PS/2) should also pass this test. 

Detecting a PS/2-type Port 

To test for simple bidirectional ability, first try to put the port in input mode by 
writing 1 to bit 5 in the port's control register (at base address + 2). If the port 
is bidirectional, this tri-states the data port's outputs. Then write two values to the 
data port and read each back. If the outputs have been tri-stated, the reads won't match 
what was written, and the port is almost certainly bidirectional. If the reads do match 
the values written, you're reading back what you wrote, which tells you that the data-port 
outputs weren't disabled and the port isn't bidirectional. An ECP in its internal PS/2 
mode and some EPPs will pass this bidirectional test. Test for a PS/2-type port only 
after you've verified that an SPP, EPP, or ECP exists. Because the PS/2 test uses the 
failure of a port read to determine that a port is bidirectional, a non-existent port 
will pass the test! 

An ECP has several internal modes. In addition to ECP mode, an ECP can emulate an SPP 
(original) or PS/2-type (Byte-mode, or simple bidirectional) port. Many ECPs also 
support EPP emulation. Fast Centronics is an additional mode that gives faster 
performance with many SPP-type peripherals. 

ECP internal modes: 

000 SPP
001 Byte
010 Fast Centronics
011 ECP
100 EPP
101 Reserved
110 Test 
111 Config 

Set the mode in bits 7, 6, and 5 of the ECR at base address + 0x402. For example, 
to set an ECP at 0x378 to ECP mode in 'C':

The ECR is at 0x77A (0x378 + 0x402) 
EcrAddress=0x77A;
The code for the selected mode (ECP) from the table above: 
EcpMode=3;
Read the ECR 
ECRData=Inp(EcrAddress);
Set the highest 3 bits to match the selected mode. 
ECRData=(ECRData & 0x1F) + EcpMode<<5; 
Write the result back to the ECR. 
Out(EcrAddress, ECRData );


*****************************************************************************************
									INTERRUPT STUFF
Most computers lpt1 port is irq7 (bit 7) in the interrupt mask address (8259 chip) 
(decimal 33)(hex 21) Older computer with different types of video cards like cga or 
ega have there lpt1 irq somewhere else.									


Typical Assignments base addresses

Addr	MDPA   no MDPA
 0x3BC	LPT1	n/a	Monochrome Display and Printer Adapter (MDPA)
 0x378	LPT2	LPT1	Primary Printer Adapter
 0x278	LPT3	LPT2	Secondary Printer Adapter

Name	MDPA	no MDPA
 LPT1	0x3BC	 0x378
 LPT2	0x378	 0x278
 LPT3	0x278	  n/a

Typicaly irqs are assigned like this
 lpt1 = irq7
 lpt2 = irq5
 lpt3 = ?

Example to turn on the 8259 chip so it will use irq5. You have to turn off the mask 
bit 5 at base address of decimal 33 from a 1 to a zero.  REMEMBER the first bit is 
bit 0 up to bit 7 that makes 8 bits in all.  Be sure to first find out what all the 
bits are and just change bit 5 though or you will turn off or on some other irqs that 
will screw up the puter.

*/  




/*
	Data structure definitions.
 */

/* Master data structure for an encoder.  All encoder state is stored in one of these.
   There is one for each axis, a pointer to the structure is passed to routines
   that work on the encoder data.  This allows general purpose routines to be written
   that get passed the data structure to do work on a specific axis.

   All items in the structure begin with "ed_" to allow easy identification of
   the originating structure while reading the program code.
 */


/* Define number of offset modes, in this case 4+1 (master mode is
   always mode 0 */
#define numberOfModes 5

typedef struct encDataStr
{
	char ed_name[16];				/* String that "names" this axis (usually X, Y, or Z could be "table", "knee" etc) */
	unsigned char ed_encoder;		/* Encoder number; 1, 2, 3, 4 */
	unsigned char ed_label;			/* One character command code for this axis. */
	int ed_displayName;				/* Flag for use Name rather than Label on display */
	volatile int ed_changed;		/* Flag that indicates that an encoder stepped up or down. */
	long int ed_value;				/* Current step count of this axis (the number we are here for!). */

	volatile double ed_slewLast;		/* Display value, as stored at last timer pop. */
	volatile double ed_slewLastLast;	/* Old last display value, stored at last timer pop. */
	double ed_slewValue;			/* Last slew rate displayed */
	double ed_lastValue;			/* Last fully adjusted value displayed to screen. */
	int ed_displaySlew;				/* Flag that indicates user wants slew to be displayed. */
	int ed_displayDiam;				/* Flag that indicates user wants diameter to be displayed. */

	double ed_units;				/* Measurement units in each step of encoder, like .001" */
	unsigned short ed_encoderNew;	/* Current encoder state, new two bit value. */
	unsigned short ed_encoderOld;	/* History of encoder state, old two bit value. */
	short ed_displayRow;			/* Row on "encoder" screen that this axis gets displayed upon. */
	short ed_displayPixRow;			/* Row, as a pixel count, for graphics mode */
	short ed_displayPixCol;			/* Column, as a pixel count, for graphics mode */
	char ed_displayString[16];		/* string of last displayed digits for this axis */
	char ed_errorCountString[8];	/* string of last displayed error count for this axis */
	char ed_displayIPMString[8];	/* string of last displayed IPM value for this axis */
	char ed_rotaryCountString[8];	/* string of last rotary count value displayed */
	int ed_displayError;			/* on off switch indicates if the error counts should be displayed */
	long ed_possible_error_count;	/* Number of encoder step errors is stored here */
	int ed_count_reverse;			/* Flag for the user to toggle +- in the way it counts (up or down) used to swap the  */
									/* encoders neg to positive and positive to negative and to reverse the direction  */
									/* of the counting */
	int ed_decimal_places_shown;	/* Number of signifigent digits in display. */
	int ed_conversion;				/* Metric conversion control */
	int ed_error_changed;			/* Flag that indicates error count has changed. */

	/* Backlash stuff. */
	int ed_backlash;				/* Flag for encoder backlash turned on or off */
	long int ed_currently_displayed;/* what is currently on the screen no matter if it is currenly compensating */
	double ed_backlashDist;			/* how much to compensate for backlash */
	int ed_zero_all;				/* Flag for zeroing when backlash is ON */
	int ed_zero_all_mode;			/*  ... */
	int ed_rightleft;				/* direction to compensate for backlash this is not user changeable.  It is part of the  */
									/* backlash function itself  */
	int ed_tension;					/* Flag to take up backlashtension. */
	int ed_swapsign;				/* turned on by user in encode setup to swap the sign of the number */
	long int ed_lasttime, ed_lastlasttime, ed_lastlastlasttime;
	long int ed_changed_direction_point; /* limit */

	/* Rotary display stuff. */
	int ed_rotaryOn;				/* Flag is true if this axis is in rotaty display mode */
	int ed_number_of_blips;			/* Encoder pulses in a full revolution */
	double ed_rotaryFactor;			/* Factor used to convert steps to degrees */
	int ed_display_min;				/* 1=display minutes and secs. and 0=display decimal */
	int ed_show_revolutions;		/* Flag that rotary display shows counts as well as degrees */
	int ed_revolutionsDisplayed;	/* Flag that rotary display shows current revolutions */

	/* Storage for origin offsets, there are four offsets plus the master. */
 	long int ed_offset[numberOfModes];	/* These are indexed by the global currentMode */

	/* Pointers to companion axis when in lathe compound angle mode. */
	void *ed_compoundZ;				/* Pointer to Z, or long axis */
	void *ed_compoundX;				/* Pointer to X, or cross feed axis */
	double ed_compoundAngle;		/* Angle, in degrees, if this is the compound axis. */
	long int ed_compoundSteps[numberOfModes]; /* If this axis is the companion, add this many steps. */
	double ed_compoundUnits;		/* If this axis is the companion, this much added each step. */
} ENCODER_DATA; 



/* Structure to define a circular buffer.

   All items in the structure begin with "c_" to allow easy identification of
   the originating structure while reading the program code.
 */
typedef struct cBufStr {
	unsigned short far *c_buffer;		/* Pointer to dynamiclly allocated buffer */
	unsigned int c_buflen;				/* Size of the buffer */
	volatile unsigned int c_count;		/* Number of data items currently in the buffer */
	unsigned short far *c_bufferEnd;	/* Pointer to end of buffer */
	volatile unsigned short far *c_add;		/* Pointer to the last data that was added to the buffer. */
	volatile unsigned short far *c_remove;	/* Pointer to the next data to remove from the buffer. */
} CIRCULAR_BUFFER;




/* Structure to define an X/Y position, like in a bolt pattern.
 */

typedef struct xyPosStr				/* Declare POINT structure */
{
   double x;						/* Define members x and y */
   double y;
}	POINT;



typedef struct triangle				/* Declare Triangle structure */
{
	double side1;					/* Define side members */
	double side2;					/* Define side members */
	double side3;					/* Define side members */
	double angle1;					/* Define angle members */
	double angle2;					/* Define angle members */
	double angle3;					/* Define angle members */
}	TRIANGLE;



/* Parallel Port data structure. */

typedef struct ppData
{
	unsigned short pp_data;			/* Data port address of this instance of LPT */
	unsigned short pp_status;		/* Status port address of this instance of LPT */
	unsigned short pp_control;		/* Control port address of this instance of LPT */
	unsigned short pp_type;			/* Type code, SPP, EPP, ECP etc */
	unsigned short pp_lpt;			/* 'LPTn' number, zero if not an LPT */
	unsigned short pp_irq;			/* IRQ number */
	unsigned short pp_picaddr;		/* Programmable Interrupt Controller (PIC) Base Address */
	unsigned short pp_picmask;		/* PIC's Mask */
	unsigned short pp_intno;		/* Interrupt Vector Number */
	unsigned short pp_use_irq;		/* Use interupts on this port flag. */
	unsigned short pp_lastDataByte;	/* Used for detecting a change in port data when polling. */
	void (interrupt far *pp_saveHandler)();/*To save old handle for parallel port interrupt */
}	PARPORT;



/* Serial Port data structure. */

typedef struct spData
{
	unsigned short sp_data;			/* Data port address of this instance of COM */
	unsigned short sp_type;			/* Type code, FIFO etc */
	unsigned short sp_com;			/* 'COMn' number, zero if not a COM port */
	unsigned short sp_irq;			/* IRQ number */
	unsigned short sp_picaddr;		/* Programmable Interrupt Controller (PIC) Base Address */
	unsigned short sp_picmask;		/* PIC's Mask */
	unsigned short sp_intno;		/* Interrupt Vector Number */
	unsigned short sp_status;		/* Last line status read for this COM port */
	unsigned short sp_flags;		/* Flag bits for this device. */
#define CF_FIFO 0x01		/* Have a FIFO flag */
	void (interrupt far *sp_saveHandler)();/*To save old handle for serial port interrupt */
}	SERPORT;



/*
	Expression evaluator definitions. 
 */
#define VARLEN		15			/* Max length of variable names */
#define MAXVARS		50			/* Max user-defined variables */
#define TOKLEN		30			/* Max token length */

#define VAR			1			/* Token scanner type codes. */
#define DEL			2
#define NUM			3

typedef struct
{
	char name[VARLEN + 1];		/* Variable name */
	double value;				/* Variable value */
} VARIABLE;

typedef struct
{
	char* name;					/* Function name */
	int   args;					/* Number of arguments to expect */
	double (*func)();			/* Pointer to function */
} FUNCTION;

#define iswhite(c)  (c == ' ' || c == '\t')
#define isnumer(c)  ((c >= '0' && c <= '9') || c == '.')
#define isdelim(c)  (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' \
 || c == '^' || c == '(' || c == ')' || c == ',' || c == '=')

/* Codes returned from the expression evaluator */
#define E_OK		0	/* Successful evaluation */
#define E_SYNTAX	1	/* Syntax error */
#define E_UNBALAN	2	/* Unbalanced parenthesis */
#define E_DIVZERO	3	/* Attempted division by zero */
#define E_UNKNOWN	4	/* Reference to unknown variable */
#define E_MAXVARS	5	/* Maximum variables exceeded */
#define E_BADFUNC	6	/* Unrecognised function */
#define E_NUMARGS	7	/* Wrong number of arguments to funtion */
#define E_NOARG		8	/* Missing an argument to a funtion */
#define E_EMPTY		9	/* Empty expression */


#define ERR(n) {ERROR=n; ERPOS=expression-ERANC-1; strcpy (ERTOK,token); longjmp(jb,1);}


#define PI 3.14159265358979323846
#define M_E 2.71828182845904523536
#define DPR (180.0/PI)
#define RPD (PI/180.0)
#define ABS(a) ((a)>=0 ? (a) : -(a))
#define SGN(a) ((a)!=0 ? ((a)>0 ? 1 : -1) : 0)
#define ACSD(a) DPR*acos((ABS(a)<1) ? (a) : SGN(a))
#define CLOSE(a,b) (((ABS(a-b))<1.e-6)?TRUE:FALSE)


/*************************************************************************
**                                                                       **
** VARIABLE DECLARATIONS                                                 **
**                                                                       **
 *************************************************************************/

int   ERROR;				/* The error number */
char  ERTOK[TOKLEN + 1];	/* The token that generated the error */
int   ERPOS;				/* The offset from the start of the expression */
char *ERANC;				/* Used to calculate ERPOS */

/*
   Add any "constants" here...  These are "read-only" values that are
   provided as a convienence to the user.  Their values can not be
   permanently changed.  The first field is the variable name, the second
   is its value.
*/
VARIABLE Consts[] =
{
	/* name, value */
	{ "pi",		PI },
	{ "e",		M_E },
	{ "dpr",	DPR },
	{ "rpd",	RPD },
	{ 0 }
};


VARIABLE Vars[MAXVARS];				 /* Array for user-defined variables */
unsigned char *expression;			/* Pointer to the user's expression */
unsigned char token[TOKLEN + 1];	/* Holds the current token */
int tokType;						/* Type of the current token */
jmp_buf jb;							/* jmp_buf for errors */





/* 
	Forward Procedure declarations.
 */
int main(int argc,char *argv[]);
int selectInputMode(short initial);
void mainCommandLoop(void);
void mainCommand_R(void);
void mainCommand_A(void);
void mainCommand_D(void);
void mainCommand_G(void);
void mainCommand_G_bolt_circle(short type);
void mainCommand_G_bolt_line(void);
void mainCommand_G_tri(void);
int solve (TRIANGLE *tri, TRIANGLE *alt);
void asa (double *s1, double *s2, double *s3, double *a1, double *a2, double *a3);
void saa (double *s1, double *s2, double *s3, double *a1, double *a2, double *a3);
void sas (double *s1, double *s2, double *s3, double *a1, double *a2, double *a3);
int ssa ( double *s1, double *s2, double *s3, double *a1, double *a2, double *a3,\
		  double *s1p, double *s2p, double *s3p, double *a1p, double *a2p, double *a3p);
void mainCommand_G_chord(void);
void mainCommand_G_expression(void);
void mainCommand_Z(void);
int selectParallelPort(void);
void mainCommand_M(void);
void mainCommand_M_file(short answer);
void mainCommand_M_name(void);
void mainCommand_M_edit(void);
void mainCommand_M_lathe(void);
short display_axis_settings(void);
void mainCommand_M_axis(void);
void mainCommand_P(void);
int mainCommand_Q(void);
void mainCommand_M_tach();
void mainCommand_M_az(void);
int selectSerialPort(void);
void updateTachometerDisplay(void);
void updateEncoderDisplay(struct encDataStr *axis);
void s_outTextxytbDiff ( short row, short col, short textColor, short bkgdColor, char *newString, char *oldString);
void update_error_count(struct encDataStr *axis);
void update_rotary_count(struct encDataStr *axis);
void encoderSetupF2(struct encDataStr *axis);
void encoder_setup(struct encDataStr *axis);
void quick_rezero(struct encDataStr *axis);
void initEncoderData(short encoderNo,char *name,char label,short row);
void pollPort (PARPORT *par);
void interrupt far parallelPortHandler(void);
void interrupt far intervalTimerHandler(void);
void interrupt far serialPortHandler(void);
void decodeDataValue(void);
void decodeValueMsg(short msgData);
void processStepValue(struct encDataStr *axis);
void processAutoZero(void);
void processAutoCenter(void);
int readConfigFile(void);
void saveConfigFile(void);
int numbers_only(double *answer,short row,short colum,char *message,short color);
int intnumbers_only(short *answer,short row,short colum,char *message,short color, short base);
int strtoi(char *nptr, short base);
char *collect_number_string(short row, short col, char *message,short color,short hex);
long int checkbacklash(struct encDataStr *axis);
char *decimal_to_degrees(double decimal);
int auto_degrees_decimal(double *answer,char *message,short row_string,short colum_string,short row_prompt,short colum_prompt,short color);
void open_scroll_help(void);
void buildMainScreen(void);
void buildMainBackground(short mainMenu);
void clearStoredStrings (void);
void rotarySetupScreen(struct encDataStr *axis);
void init_screen_font(void);
void draw_encode_screen(void);
void draw_encode_screen_graphic(void);
void draw_encode_screen_text(void);
void encoderSetupScreen(struct encDataStr *axis);
void modeSetupScreen(struct encDataStr *axis);
void encoderSetupF2Screen(struct encDataStr *axis);
int CreateBuf(CIRCULAR_BUFFER *cBuf, int bufsize);
void colorGtext(short x,short y,short color,char *string);
void colorGchar(short col, short row, short color, short background, char c);
void loadBoltHole(void);
short getKeyBoard(void);
short getLCchar(void);
short getUCchar(void);
short getUCchare(short color);
short getLCchare(short color);
short getLine(char *line);
int get_string(char *buffer, int count, short color);
long int round2Units(ENCODER_DATA *axis, double theValue);
void Sound (int noteType);
void Note( int frequency, int duration );
void Sleep( clock_t wait );

void parallelPortScan(void);
void disablePPIRQ(PARPORT *par);
void enablePPIRQ(PARPORT *par);
void parallelPortConfig(PARPORT *par);
void detectPPort(PARPORT *par);
short detectPortType(short thePort);
short ECPdetect(short thePort);
short EPPdetect(short thePort);
short PPPdetect(short thePort);
void EPPclear(short thePort);
void ResetLPT(short thePort);

void serialPortScan(void);
void sendZeroCommand (unsigned char counter);
void sendSetCommand (unsigned char counter, long int value);
void sendAllCommand();
void sendSerialData(unsigned char *string, short count);
void disableSPIRQ(SERPORT *ser);
void enableSPIRQ(SERPORT *ser);
void serialPortConfig(SERPORT *ser);
void serialPortUnConfig(SERPORT *ser);
void detectSERport (SERPORT *ser);
short detect_UART(short thePort);
void setSerialIntNo (SERPORT *ser);

/* Text output routines. */
int s_init_outText ( short mode);
void s_setTextPosition ( short row, short col);

void s_outCharxy (short r, short c, unsigned char ch);
void s_outCharxyt (short r, short c, short textColor, unsigned char ch);
void s_outCharxytb (short r, short c, short textColor, short backColor, unsigned char ch);

void s_outText (unsigned char *string);
void s_outTextxy (short r, short c, unsigned char *string);
void s_outTextxyt (short r, short c, short textColor, unsigned char *string);
void s_outTextxytb (short r, short c, short textColor, short backColor, unsigned char *string);

void s_printf (const char *format, ...);
void s_printfxy (short row, short col, const char *format, ...);
void s_printfxyt (short row, short col, short  textColor, const char *format, ...);
void s_printfxytb (short row, short col, short  textColor, short  backGround, const char *format, ...);

void s_setTextWindow (short top, short left, short bottom, short right, short color);
void s_clearScreen (short area, short color);

void overlay_box (short r, short c, char *title);
void drop_box (short h, short w,  short r,  short c, char *title, short color);
void centered_text(short row, unsigned char *string);

void help_main();
void help_encoder();
void help_machine();
void help_medit();
void help_mfile();
void help_mtachport();
void help_lptport();
void help_modesetup();
void help_rotarysetup();

/* Expression evaluator routines. */
int GetSymbol (char *s, double *v);
void ClearAllVars ();
int ClearVar (char* name);
int GetValue (char *name, double *value);
int SetValue (char *name, double *value);
void Parse (void);
int Level1 (double *r);
void Level2 (double *r);
void Level3 (double *r);
void Level4 (double *r);
void Level5 (double *r);
void Level6 (double *r);
int Evaluate (char *e, double *result, int *a);
double deg(double x);
double rad(double x);
double sind (double x);
double cosd (double x);
double tand (double x);
double asind (double x);
double acosd (double x);
double atand (double x);

#ifdef _DEBUG /* used for debugging and testing */
void inject_data (int flag);
void TraceInit(int n);
void Trace(char type, char *string);
int TraceDump(char *Tfile, int n);
void TrackbufferUsage();
#endif /* _DEBUG */




/*
	Global data area
 */


/* Number of consecutive empty "polls" to parallel port before we should update screen.  
   Used to init the following count down counter.  The counter is counted down once
   each call to the poll routine.  If the poll fetchs data the count is reset.  It must
   hit zero to update screen.  This forces high speed read of the port while an encoder 
   is moving fast.  The display will catch up when it slows down.  This feature is not 
   needed when interrupts are enabled. */
short timeDisplay;					
short timeCounter;					/* Count down of above */
int soundOff = FALSE;				/* Sound control flag. */

short parallelEnable;				/* TRUE if using parallel port for input. */
PARPORT PPtable[3];					/* Storage for 3 parallel port data structures. */
PARPORT pp;							/* The operating parallel port. */
static char *ppTypeStr[6] = {"N/A","SPP","PS/2","EPP","ECP","EPPc"};

short serialEnable;					/* TRUE if using serial port for input. */
SERPORT SPtable[4];					/* Storage for 4 serial port data structures. */
SERPORT sp;							/* The operating serial port. */
static char *spTypeStr[5] = {"N/A", "8250", "16450", "16550", "16550A"};
unsigned short outBoardVersion = 0;	/* Version info from out-board micro front end */

int autoZeroAxisWait = FALSE;		/* Flag set TRUE when waiting for an axis to move for auto-zero. */
double autoZeroOffset = 0.1;		/* Radius of autozero probe, added to position after zeroing */
long int autoZeroStart = 0;			/* Position value when auto zero started. */
ENCODER_DATA *autoZeroAxis = NULL;	/* Contains the axis pointer when in AutoZero mode. */

int autoCenterAxisWait = FALSE;		/* Flag set TRUE when waiting for an axis to move for auto-Center. */
long int autoCenterStart = 0;		/* Position value when auto center started. */
ENCODER_DATA *autoCenterAxis = NULL;/* Contains the axis pointer when in AutoCenter mode. */

ENCODER_DATA *compoundAxis=NULL;	/* Non null if a compound axis exists (lathe mode). */

int rpmEnable;						/* Flag indicating tachometer in use */
char lastRPMString[8];				/* Last RPM displayed string */
char lastSFMString[8];				/* Last SFM displayed string */
volatile long int rpmCount = 0;		/* Counts up RPM pulses as they occur */
volatile long int rpmCurrent = 0;	/* Count of RPM pulses last 1 second interval. */
short rpmFactor = 1;				/* Number of RPM pulses per revolution */
double rpmDiameter = 1.0;			/* Diameter of "cutter" or workpiece, used to compute */
									/*  Surface Feet per Minute (SFM) */
int rpmCircumference = (int)((PI*1.0)*100.0); /* Compute circumference (dia=1.0), scale up by 100. */

POINT holePattern[99];				/* Up to 99 bolt hole storage. */
short currentHole = -1;				/* Current bolt hole index (-1 means none). */
short lastHole = 0;					/* Index of last hole in pattern. */

jmp_buf command;					/* Data structure for setjmp()/longjmp() routines. */

short saveTextColor;				/* Initial text color, saved at startup, restored on exit. */
long saveBkColor;					/* Initial background color... */
short saveWrapon;					/* Initial state of wrap mode */
void (interrupt far *rpm_saveHandler)();/*To save old handle for interrupt serial interrupt */
void (interrupt far *saveHandler1C)();/*To save old handle for interrupt 1C (timer) */

char programDrive[_MAX_DRIVE];		/* Drive letter this program ran from. */
char programDir[_MAX_DIR];			/* Directory path this program ran from. */
char programExt[_MAX_EXT];			/* File extension this program ran from. */
char programName[_MAX_FNAME];		/* Name of the file this program ran from. */
char helpFileName[_MAX_PATH ];		/* Default help file name is <program name>.HLP, setup at startup. */
char configName[_MAX_PATH ] = "encoder"; /* Default Config File name */
char machine[26] = "myMachine";/* Default machine name */

short initializationComplete = FALSE; /* Some functions should not work until this is true. */

int currentMode = 0;				/* Offset mode, 0 is absolute mode, 1 is mode A, 2 is mode B, etc... */
static char *modeString[numberOfModes] = {"Absolute Mode","Incremental Mode A","Incremental Mode B","Incremental Mode C","Incremental Mode D"};  

/* This is TRUE when the encoder display is "up".  Used in the getUCchar polling loop to
   determine whether to update encoder display values or just count steps. */
int encoderDisplayMode = FALSE;

/* When in graphic display mode, these contain the screen and font info. */
struct _videoconfig vc;
struct _fontinfo fi;
char fontSelect[8];					/* Used to switch fonts during execution */
int pix_row, pix_col;				/* Pixel counts for a 22x40 text display in hi-res */
int videoMode = 0;					/* "Current" video mode stored here. */
int noFonts = FALSE;				/* Flag is TRUE if no fonts for high res mode */
int graphicMode = FALSE;			/* Flag to turn on graphic screen mode */
int autoDisplay = FALSE;			/* autoDisplay causes program to go to Encoder Display on startup */


/* 
	Text screen control data area.
*/
short screen_width;					/* Width in characters of physical screen. */
short screen_height;				/* Height in chatacters of physical screen. */

short window_left;					/* Physical screen location of logical window. */
short window_top;
short window_bottom;
short window_right;

short window_height;				/* Size of logical window. */
short window_width;


#ifdef _DEBUG
/* For data injection debug feature. */
int inject = FALSE;
int inject_shift;					/* '1' */
int inject_time = 2;				/* Ticks between injects */
static unsigned char inject_table1[4] = {0,2,3,1 /* CW */ }; /* '+'/'-' will reverse direction */
static unsigned char inject_table2[4] = {0,1,3,2 /* CCW */};
unsigned char *inject_table = inject_table1;
#endif /* _DEBUG */


/*
   Add any math functions that you wish to recognise here...  The first
   field is the name of the function as it would appear in an expression.
   The second field tells how many arguments to expect.  The third is
   a pointer to the actual function to use.
*/
FUNCTION Funcs[] =
{
	/* name, funtion to call */
	{ "sin",     1,    sind },
	{ "cos",     1,    cosd },
	{ "tan",     1,    tand },
	{ "asin",    1,    asind },
	{ "acos",    1,    acosd },
	{ "atan",    1,    atand },
	{ "sinr",    1,    sin },
	{ "cosr",    1,    cos },
	{ "tanr",    1,    tan },
	{ "asinr",   1,    asin },
	{ "acosr",   1,    acos },
	{ "atanr",   1,    atan },
	{ "sinh",    1,    sinh },
	{ "cosh",    1,    cosh },
	{ "tanh",    1,    tanh },
	{ "exp",     1,    exp },
	{ "ln",      1,    log },
	{ "log",     1,    log10 },
	{ "sqrt",    1,    sqrt },
	{ "sqr",     1,    sqrt },
	{ "floor",   1,    floor },
	{ "ceil",    1,    ceil },
	{ "abs",     1,    fabs },
	{ "hypot",   2,    hypot },
	{ "rss",     2,    hypot },
	{ "deg",     1,    deg },
	{ "rad",     1,    rad },
	{ 0 }
};


/*
	Pointer and structure storage.
 */

CIRCULAR_BUFFER encoderDataBuffer;	/* Raw parallel port data collected here. */
#define BUFFERSIZE 16000			/* This buffer allows data collection to run faster than */
									/* we are displaying for bursts up to the given size. */

CIRCULAR_BUFFER serialOutputBuffer;	/* Serial port output data collected here. */

ENCODER_DATA encoder1, encoder2, encoder3, encoder4;	/* Encoder data structures */

/* List of encoder structures, used to index or walk across all the encoders in a loop. */
ENCODER_DATA *encoderList[5] = {&encoder1, &encoder2, &encoder3, &encoder4, NULL};



/* Use this macro (pollParallelPort) to poll the parallel port and store any collected 
   data into the circular buffer.  Should be called very often to reduce the chances of 
   missed data. Calls are sprinkled throughout the program after or during a compute 
   intensive series of operations.  It is also called in any character collection loops.  
   Data is removed from the buffer with a call to decodeDataValue, this is called in the 
   getChar routine.  The data is a 16 bit value with the parallel port status register 
   value in the upper byte.
 */

#define pollParallelPort if(parallelEnable&&!pp.pp_use_irq)pollPort(&pp);




/* 

	 Program start point.

 */
main (int argc, char *argv[])

{
#ifdef _DEBUG
	TraceInit(1000);					/* *** Debug init internal 1000 element trace table *** */
#endif /* _DEBUG */

	/* _splitpath() splits a full MS-DOS path name into the component parts (drive, directory, 
	   filename, filetype).  The path argument should point to a buffer holding the complete 
	   path name.  Here we are scanning the program name from the command line. */
	_splitpath( argv[0], programDrive, programDir, programName, programExt);

	/* Process the command line arguments.  We know how many "tokens" are on the command
	   line by the argument count, argc.  It is always at least one, which is the program
	   name given on the command line (the name of the .EXE file).  We will accept one
	   optional argument, which is the configuration filename. */
	if (argc>1)
	{
		if (argc > 2)
		{
			printf ("%s: Command line arguments not understood.\n", programName);
			printf ("Proper format is \"%s [<config file>]\"\nwhere <config file> is the ", programName);
			printf ("filename of the configuration you want to use with the program.\n");
			return 1;						/* Exit to DOS with error code */
		}
		else
			strcpy(configName, argv[1]);	/* Get config filename */
	}
		
	/* Allocate a circular buffer to receive parallel port data */
	if (!CreateBuf(&encoderDataBuffer, BUFFERSIZE))
	{
		printf ("%s: Buffer memory allocation failure, can't run.\n", programName);
		return 1;
	}

	/* Allocate a circular buffer to output serial port data */
	if (!CreateBuf(&serialOutputBuffer, 256))
	{
		printf ("%s: Buffer memory allocation failure, can't run.\n", programName);
		_ffree(encoderDataBuffer.c_buffer);	/* Delete the circular buffer storage. */
		return 1;
	}

	/* Default help file name is <program name>.HLP, look in the same directory as the
	   program came from. */
	_makepath (helpFileName, programDrive, programDir, programName, "HLP");

	saveTextColor = _gettextcolor();	/* Save the users current text color */
	saveBkColor = _getbkcolor();		/* Save background color too... */
	saveWrapon = _wrapon (_GWRAPOFF);	/* Save state of screen text wrap mode and set wrap OFF */
	s_init_outText(_DEFAULTMODE);		/* Set initial video mode */
	timeDisplay = 10;					/* Init the TimeDisplay value. */

	serialEnable = FALSE;				/* TRUE if using serial port for input. */
	parallelEnable = FALSE;				/* TRUE if using parallel port for input. */
	rpmEnable = FALSE;					/* Initially no tachometer configured. */

	/* Initialize the axis data structures.  Defaults to three axises. */
	initEncoderData (0, "X Encoder", 'X', 7);	/* Init the data block for X axis, display on line 7. */
	initEncoderData (1, "Y Encoder", 'Y', 10);	/* Init the data block for Y axis, display on line 10. */
	initEncoderData (2, "Z Encoder", 'Z', 13);	/* Init the data block for Z axis, display on line 13. */
	/* *** Axis does not display if row assignment is zero ***. */
	initEncoderData (3, "W Encoder", 'W', 0);	/* Init the data block for W axis, no display. */

	parallelPortScan();					/* Determine this computers parallel port addresses */
	serialPortScan();					/*  and serial port addresses. */

	if (!readConfigFile())				/* Read program configuration file. */
	{
		_ffree(encoderDataBuffer.c_buffer);	/* Error--Delete the circular buffer storage. */
		_ffree(serialOutputBuffer.c_buffer);/* Delete the circular buffer storage. */
		return 2;						/* Exit if readConfigFile failed. */
	}

	/* Must be in either parallel or serial input mode. */
	if (!parallelEnable && !serialEnable)
	{
		if (!selectInputMode(TRUE))				/* Ask user, returns TRUE if he set something. */
		{
			_ffree(encoderDataBuffer.c_buffer);	/* Error--Delete the circular buffer storage. */
			_ffree(serialOutputBuffer.c_buffer);/* Delete the circular buffer storage. */
			return 0;
		}
	}
	else
	{
		if (parallelEnable)
		{
			parallelPortConfig(&pp);		/* Configure parallel port */
			if ((pp.pp_type==lptN_A) || (pp.pp_type==lptSPP))	/* If port seems to not exist or output only, warn user */
			{
				drop_box ( 5, 60, 0, 0, "Parallel Port Error", RED); /* Red box with yellow text prompt */
				s_printfxyt (3, 2, YELLOW, "Parallel port decimal %i (0x%03X) ", pp.pp_data, pp.pp_data);
				if (pp.pp_type==lptSPP)			/* If port seems to be output only... */
					s_outText ("seems to be output only.");
				else	
					s_outText ("does not seem to exist.");

				s_outTextxy (4, 2, "Continue anyways (Y or N) [N] ? ");
				Sound (QUESTnote);
				if (getUCchare(BLUE)!='Y') 
				{
					_ffree(encoderDataBuffer.c_buffer);	/* Delete the circular buffer storage. */
					_ffree(serialOutputBuffer.c_buffer);/* Delete the circular buffer storage. */
					return 3;					/* If not Yes, exit program. */
				}
			}
		}

		if (serialEnable)
		{
			serialPortConfig(&sp);
			if (sp.sp_irq == 0)					/* Serial port config failed. */
			{
				drop_box ( 5, 60, 0, 0, "Parallel Port Error", RED); /* Red box with yellow text prompt */
				s_printfxyt (3, 2, YELLOW, "Serial port decimal %i (0x%03X) setup failed.", 
							sp.sp_data, sp.sp_data);
				s_outTextxy (4, 2, "Continue anyways (Y or N) [N] ? ");
				Sound (QUESTnote);
				if (getUCchare(BLUE)!='Y') 
				{
					_ffree(encoderDataBuffer.c_buffer);	/* Delete the circular buffer storage. */
					_ffree(serialOutputBuffer.c_buffer);/* Delete the circular buffer storage. */
					return 3;					/* If not Yes, exit program. */
				}
			}
		}
	}

	init_screen_font();					/* Setup font info for high res graphics mode. */
	ClearAllVars();						/* Clear expression evaluator symbol table. */

	/* The DOS '0x1C' timer interrupt is hooked to produce a time reference to do slew rate
	   computations and RPM calculations. */
	saveHandler1C = _dos_getvect (0x1C);		/* Save the old interrrupt vector for interrupt address 0x1C (timer) */
	_dos_setvect (0x1C, &intervalTimerHandler); /* Set the intercept for timer interrrupt vector */
	enablePPIRQ(&pp);					/* Enable Parallel Port interrupts, if needed, now */
	enableSPIRQ(&sp);					/* Enable Serial Port interrupts, if needed, now */

	initializationComplete = TRUE;		/* Can now run */
	mainCommandLoop();					/* Enter main command loop. */

	/* Clean up any allocated data, save current config, and reset screen so computer is back
	   to normal when we exit. */

	saveConfigFile();					/* Save current state of config info. */

	disablePPIRQ(&pp);					/* Remove the PP IRQ enable bits. */
	disableSPIRQ(&sp);					/* Remove the Serial port IRQ enable bits. */
	_dos_setvect (0x1C, saveHandler1C);	/* Restore the old Timer interrrupt vector */

	s_init_outText(_DEFAULTMODE);		/*  video mode */
	_wrapon(saveWrapon);				/*  wrap mode */
    _settextcolor(saveTextColor);		/*  text color */
	_setbkcolor(saveBkColor);			/*  screen background */

	_unregisterfonts();					/* Done with fonts... */

	_ffree(encoderDataBuffer.c_buffer);	/* Delete the circular buffer storage. */
	_ffree(serialOutputBuffer.c_buffer);/* Delete the circular buffer storage. */

	Sound (DONEnote);					/* Done sound. */
	return 0;							/* return back to DOS */
} /* End of main() */






/* 
	Main command loop, get command characters and dispatch to handler routines. 
 */

void mainCommandLoop()
{
	int c, key;

	/* Save status to allow longjmp() to here from anywhere in program.  First call will return
	   a zero, future returns (from a longjmp() call) will return the command byte to be stuffed
	   into the command loop. */
	key = setjmp (command);

	if (autoDisplay && key==0)			/* Force an 'R' command if autoDisplay is TRUE and first return from 'setjmp()'. */
		key = 'R';

	while (key!=ESC)	
	{
		_settextcursor (CURSOR_OFF);

		/* If first entry, or looping back from end of while loop, key will be 0... */
		if (key == 0)					/* Don't flash screen if forcing a command */
		{
			buildMainScreen();			/* Build screen with main menu in it. */
			key = getUCchar();			/* Read command key stroke, convert to uppercase. */
		}
		c = key;						/* Save character as command key. */
		key = 0;						/* Inialize for next time through loop. */
		switch (c)						/* Switch on command byte */
		{
			case HOME:						/* Get here when user uses HOME key to go to main menu. */
				break;

			case TAB:						/* The <tab> key, Read encoders (the real work) */
			case 'R':						/* The R key, Read encoders (the same real work) */
				mainCommand_R();
				break;

			case 'A':						/* The A key for About text */
				mainCommand_A();
				break;

			case 'G':						/* The G key for Geometry commands. */
				mainCommand_G();
				break;

			case ESC:
			case 'Q':						/* The Q key or ESC to quit */
				if (mainCommand_Q())		/* Ask "Are you sure?" */
					key = ESC;				/* If he did answer yes, set key to ESC */
				break;						/* Cycle to top of while() loop to exit. */

			case 'H':						/* The H key to give help text.*/
			case '?':						/*  accept ? also.*/
				open_scroll_help();			/* HELP screen */
				break;

			case 'Z':						/* The Z key, zero all origins */
				mainCommand_Z();
				break;

			case 'M':						/* The M key, set machine/config */
				mainCommand_M();
				break;

			case 'P':						/* The P key, set program options */
				mainCommand_P();
				break;

			default:						/* Any other, Beep user. */
				Sound (ERRnote);
				break;

	#ifdef _DEBUG
			case '~':
			case 'P'-'@':					/* Control-P */
				drop_box ( 4, 50, 0, 0, "Dump Trace Table", RED); /* Red box with yellow text prompt */
				s_outTextxyt (3, 2, YELLOW, "Trace Table is being dumped to file 'DRO.TRC'");
				TraceDump("DRO.TRC", 0);	/* *** Dump the internal trace table *** */
				Sleep (4000);				/* Show dialog for 4 seconds. */
				Sound (DONEnote);
				break;
	#endif /* _DEBUG */
		} /* End of command switch */
	} /* The while loop terminates upon <esc>. */
} /* End of mainCommandLoop() */








/* Most execution is spent here.  The R command displays the axis counters screen and
   loops processing encoder data and updating the screen.  The getUCchar() routine
   will maintain the display as long as "encoderDisplayMode" is set. */

void mainCommand_R()

{	
	int c=0;							/* Make sure c does not equal ESC to start loop. */
	register ENCODER_DATA **p;			/* Pointer to a list of ENCODER_DATA pointers. */

	if (serialEnable)
		sendAllCommand();				/* If in serial interface mode, get all counters now. */

	while (c!=ESC)						/* Loop forever, more or less, processing command characters. */
	{
		draw_encode_screen();			/* Draw the encoder display screen, set "encoderDisplayMode" */
		c = getUCchar();				/* Wait for a command character, updates display while waiting. */
		encoderDisplayMode = FALSE;		/* Not in "encoderDisplayMode" while processing command */
		
		switch (c)
		{
			case 'H':						/* help screen */
			case '?':						/* This too... */
				open_scroll_help();
				break;
				
			case '0':						/* the 0 key quick rezero all axis */
				quick_rezero (&encoder1);
				quick_rezero (&encoder2);
				quick_rezero (&encoder3);
				quick_rezero (&encoder4);
				break;

			case F1:						/* the F1 key quick rezero axis 1 */
				quick_rezero (&encoder1);
				break;
				
			case F2:						/* the F2 key quick rezero axis 2 */
				quick_rezero (&encoder2);
				break;
				
			case F3:						/* the F3 key quick rezero axis 3 */
				quick_rezero (&encoder3);
				break; 		
				
			case F4:						/* the F4 key quick rezero axis 4 */
				quick_rezero (&encoder4);
				break; 		
				
			case SPACE:						/* the space bar key or the M key (currentMode) */
			case 'M':
				if (++currentMode>=numberOfModes)/* Step forward through the modes. */
					currentMode = 0;		/* Wrap to zero at max */
				break;
				
			case BACKSPACE:
				if (--currentMode<0)		/* Step backwards through the modes. */
					currentMode = numberOfModes-1; /* Wrap to max at zero */
				break;
				
			case 'L':						/* the L key (Load bolt hole) */
				loadBoltHole();				/* Load a bolt hole coord. */
				break;
				
			case '/':						/* Cause the edge find probe touch action. */
			case END:
				if (autoZeroAxis!=NULL)
					processAutoZero ();
				else if (autoCenterAxis!=NULL)
					processAutoCenter ();
				break;
				
			case 'C':						/* Auto-center find mode. */
				if (autoCenterAxis==NULL)	/* First key hit sets waiting mode. */
				{
					autoCenterAxisWait = TRUE;	/* Waiting for an axis to move state */
					autoCenterAxis = NULL;		/*  and no axis chosen yet for centering. */
					autoZeroAxisWait = FALSE;	/* Cancel possible autozero */
					autoZeroAxis = NULL;		/* Cancel possible autozero */
				}
				else
				{							/* Key hit after axis selection, marks edge touch. */
					processAutoCenter ();
					#ifdef _DEBUG			/* If Debug - stop injecting when hit "edge" */
					inject = FALSE;
					#endif					/* _DEBUG */
				}
				break;
				
			case 'E':						/* Edgefind autoZero mode. */
				if (autoZeroAxis==NULL)		/* First key hit sets waiting mode. */
				{
					autoZeroAxisWait = TRUE;	/* Waiting for an axis to move state */
					autoZeroAxis = NULL;		/*  and no axis chosen yet for zeroing. */
					autoCenterAxisWait = FALSE;	/* Cancel possible autoCenter */
					autoCenterAxis = NULL;		/* Cancel possible autoCenter */
				}
				else
				{							/* Key hit after axis selection, marks edge touch. */
					processAutoZero ();
					#ifdef _DEBUG			/* If Debug - stop injecting when hit "edge" */
					inject = FALSE;
					#endif					/* _DEBUG */
				}
				break;
				
			case HOME:						/* <Home> does same thing. */
				c = ESC;
			case ESC:						/* The ever available abort. */
				autoZeroAxisWait = FALSE;	/* No longer waiting */
				autoCenterAxisWait = FALSE;	/*  for an axis to move state */
				autoZeroAxis = NULL;		/* Cancel autozero */
				autoCenterAxis = NULL;		/*  and no axis chosen yet for centering. */
				break;
				
	#ifdef _DEBUG			/* ***Debug hack*** */
			case 'I':		/* Inject values, for debugging.  Generates table value and shoves into input buffer. */
				inject = !inject;			/* I toggles inject, < or > swaps direction, +/- increase/decrease timer. */
				break;
			case '|':
				inject_data (1);			/* Insert an autozero probe touch */
				break;
			case '<':
				inject_table = inject_table1; /* CCW */
				break;
			case '>':
				inject_table = inject_table2; /* CW */
				break;
			case '^':						/* Step encoder axis */
				if (inject_shift == 0) inject_shift = 2;
				else if (inject_shift == 2) inject_shift = 4;
				else if (inject_shift == 4) inject_shift = 6;
				else if (inject_shift == 6) inject_shift = 0;
				else inject_shift = 0;
				break;
			case '-':
				inject_time = inject_time/2;	/* Half the timer */
				if (inject_time <= 0)			/* Min of zero */
					inject_time = 0;
				break;
			case '+':
				inject_time = inject_time*2;	/* Double the timer */
				if (inject_time <= 1)			/* Min of one */
					inject_time = 1;
				break;
			case '~':
			case 'P'-'@':						/* Control-P */
				drop_box ( 5, 35, 0, 0, "Dump Trace Table", RED); /* Red box with yellow text prompt */
				s_outTextxyt (3, 2, YELLOW, "Trace Table is being dumped'");
				s_outTextxyt (4, 2, YELLOW, "to file 'DRO.TRC'");
				TraceDump("DRO.TRC", 0);		/* *** Dump the internal trace table *** */
				Sleep (4000);					/* Show dialog for 4 seconds. */
				Sound (DONEnote);
				break;
	#endif /* _DEBUG */
				
			default:							/* Get here if no match on character. */
				/* Sweep all axis, look for command key match on axis label. If character matches 
				   label and axis exists, call setup for this axis. */
				p = encoderList;				/* Point to list of encoder data blocks. */
				do
				{
					if ((*p)->ed_label==c && (*p)->ed_displayRow!=0)
					{	/* Got an axis key hit. */
						if (autoZeroAxisWait)	/* If we are in autozero wait, select this axis. */
						{
							autoZeroStart = (*p)->ed_value; /* Save starting point. */
							autoZeroAxis = *p;	/* Now watching for this axis to move to new zero location. */
							autoZeroAxisWait = FALSE; /* No longer waiting to pick an axis. */
						}
						else if (autoCenterAxisWait) /* If we are in autoCenter wait, select this axis. */
						{
							autoCenterStart = (*p)->ed_value; /* Save starting point. */
							autoCenterAxis = *p;	/* Now watching for this axis to move to new Center location. */
							autoCenterAxisWait = FALSE; /* No longer waiting to pick an axis. */
						}
						else
							encoder_setup((*p));/* Otherwise do setup call for this axis. */
						break;
					}
				} while (*++p != NULL);			/* Walk down list until NULL */

				if ((*p)==NULL)					/* If did not find a match... */
					Sound (ERRnote);			/* Make an annoying sound if don't understand key */
				break;
		}
	} /* End of while */
} /* End of mainCommand_R() */






/* Main menu command "A", display ABOUT text. */

void mainCommand_A()

{
	unsigned short c;
	unsigned char string[40];

	sprintf(string, "About Version %s", progVersion);
	overlay_box (19, 60, string);

	s_outTextxyt( 2, 3,   RED, "This program will make a PC computer into a DRO display");
	s_outTextxy ( 3, 3,        "    for up to four 5V TTL level quadrature encoders.");

	s_outTextxyt( 5, 3, BRIGHT_WHITE,"This program is free to everyone. Feel free to use and");
	s_outTextxy ( 6, 3,        "modify it for your needs.  If you make some helpful");
	s_outTextxy ( 7, 3,        "modifications please place it and the code on the web to");
	s_outTextxy ( 8, 3,        "be freely used by everyone.  ");
	s_outTextxyt( -1, -1, BLUE, "Steve@LindsayEngraving.com");

	s_outTextxyt(10, 6, BLACK, "Modified by Art Eckstein (axtein@dicomm.net), and");
	s_outTextxy (11, 6,        "  Robert Duncan (duncancomputer@hotmail.com)");

	s_outTextxyt(13, 3, BLACK, "For help and ideas, visit:");
	s_outTextxyt(14, 4,  BLUE,  "http://ns1.dicomm.net/~axtein/dro/");
	s_outTextxy (15, 4,         "http://www.lindsayengraving.com/other_interests/dro.html");
	s_outTextxy (16, 4,         "http://webpages.charter.net/robertduncan/dro.html");

	s_outTextxyt(18, 3, BLACK,  "Thanks extended to M. W. Klotz for math routines");

	s_outTextxyt(19, 32, YELLOW,"Press any key to continue...");

	c = getUCchar();
	if (c == HOME || c == TAB)
		longjmp(command, c);	/* Jump to command loop, stuff a command. */
	return;
} /* End of mainCommand_A() */






/* Main menu command "P", change program options. */

void mainCommand_P()

{
	short answer=0;
	
	while (answer!=ESC)
	{
		overlay_box (19, 60, "Program options setup");

		s_outTextxyt(3,  6, BLACK, "Read a different Configuration File...");
		s_outTextxy (5,  6, "Save current Configuration to File");
		s_outTextxy (7,  6, "Create a new Configuration File...");
		s_outTextxy (9,  6, "Display update time delay setup...");
		s_outTextxy (11, 6, "Input port selection (currently ");
		if (parallelEnable)
			s_outText ("Parallel port)...");
		if (serialEnable)
		{
			if (outBoardVersion==0)
				s_outText ("Serial port)...");
			else
				s_printf ("Serial port, V%i.%i)...", outBoardVersion>>8, outBoardVersion&0xFF);
		}
		s_outTextxy (13, 6, "Graphic display mode (now ");
		if (graphicMode)
			s_outText ("ON) ");
		else
			s_outText ("OFF)");
		s_outTextxy (15, 6, "Auto display mode (now ");
		if (autoDisplay)
			s_outText ("ON)");
		else
			s_outText ("OFF)");
		s_outTextxy (17, 6, "Sound ");
		if (soundOff)
			s_outText ("ON (now OFF)");
		else
			s_outText ("OFF (now ON)");
		
		s_outCharxyt (3, 6, RED, 'R');
		s_outCharxy  (5, 6, 'S');
		s_outCharxy  (7, 6, 'C');
		s_outCharxy  (9, 6, 'D');
		s_outCharxy  (11, 6, 'I');
		s_outCharxy  (13, 6, 'G');
		s_outCharxy  (15, 6, 'A');
		s_outCharxy  (17, 12, 'O');

		switch (answer=getUCchar())
		{
			case 'R':
			case 'C':
				mainCommand_M_file(answer);
				break;

			case 'S':
				saveConfigFile();
				break;

			case 'D':						/* The D key, display time value */
				mainCommand_D();
				break;

			case 'A':
				autoDisplay = !autoDisplay;	/* Invert autoDisplay mode flag. */
				break;

			case 'G':
				graphicMode = !graphicMode;	/* Invert graphics mode flag. */
				if (graphicMode&&noFonts)
				{
					graphicMode = FALSE;	/* Can't use font graphics */
					drop_box (6, 45, 0, 0, "Font file error", RED);
					s_outTextxyt (3, 2, YELLOW, "Font file (DRO.FON) missing or invalid!");
					s_outTextxy (4, 2, "Can't use graphics mode.");
					s_outTextxy (5, 2, "Press any key to continue ...");
					Sound (ERRnote);
					getUCchar();
				}
				break;

			case 'I':						/* The I key, set input port type/number */
				selectInputMode(FALSE);
				break;

			case 'M':						/* The M key, toggle input mode. */
				if (parallelEnable)
				{
					serialEnable = TRUE;
					parallelEnable = FALSE;
				}
				else
				{
					serialEnable = FALSE;
					parallelEnable = TRUE;
				}
				break;

			case 'O':
				soundOff = !soundOff;		/* Invert sound mode flag. */
				Sound (DONEnote);
				break;

			case ESC:
				break;

			case HOME:						/* <Home> go right to commandLoop */
			case TAB:						/* <tab> go right to encoder display mode */
				longjmp(command, answer);	/* Jump to command loop, stuff a command. */

			default:						/* All others answers */
				Sound (ERRnote);
				break;
		} /* End of switch */
	} /* End of while */
	return;
} /* End of mainCommand_P() */







/* Main menu command "Q", quit program. Return TRUE if user really wants 
   to exit. */

int mainCommand_Q()

{
	int c;

	drop_box ( 4, 40, 0, 0, "Exit Program?", RED); /* Red box with yellow text prompt */
	s_outTextxyt (3, 2, YELLOW, "Are you sure you want to exit (Y/N) ?");

	Sound (QUESTnote);

	c = getUCchare(BLUE); 
	if (c==HOME || c==TAB)
		longjmp(command, c);		/* Jump to command loop, stuff a command. */
	if (c!='Y') 
		return FALSE;				/* If not Yes, return FALSE.  Won't exit to DOS */
	return TRUE;					/* return flag to go back to DOS */
} /* End of mainCommand_Q() */








/* D command - Display/set the timedisplay value. */

void mainCommand_D()

{
	overlay_box (15, 50, "Display Time Delay");
	
	s_outTextxyt (2,3, YELLOW, "Time display value is used when IRQ is off.");
	s_outTextxy  (3,3,"DRO tries to watch the port and display the");
	s_outTextxy  (4,3,"incoming value changes to the screen.  When");
	s_outTextxy  (5,3,"info begins to come in too fast the program");
	s_outTextxy  (6,3,"will stop updating to the screen and just");
	s_outTextxy  (7,3,"concentrate watching the port and counting.");
	s_outTextxy  (8,3,"The time display value determines when the");
	s_outTextxy  (9,3,"program should quit displaying to the screen");
	s_outTextxy (10,3,"and only concentrate watching the port.  Try");  
	s_outTextxy (11,3,"to set this value to the smallest number");  
	s_outTextxy (12,3,"without errors showing up.  Between 1 & 300");
	
	s_outTextxyt (14, 7, BLACK, "Current display time delay is: ");
	s_printfxyt (-1, -1, RED, "%d",timeDisplay);
	
	_settextcolor (BLACK);
	intnumbers_only (&timeDisplay, 15, 7, "Enter new time delay value ? ", BLUE, 10);
	return;
} /* End of mainCommand_T() */








/* G command - Geometry commands.. */

void mainCommand_G()

{
	short answer;
	
	do
	{
		overlay_box (19, 60, "Geometry Utilities");

		s_outTextxyt( 3,  6, BLACK, "Circle bolt hole position list...");
		s_outTextxy ( 5,  6, "Arc bolt hole position list...");
		s_outTextxy ( 7,  6, "Line bolt hole position list...");
		s_outTextxy ( 9,  6, "Triangle solving...");
		s_outTextxy (11,  6, "Expression Evaluator...");
//		s_outTextxy (13,  6, "CHord computations...");
		s_outCharxyt( 3,  6, RED, 'C');
		s_outCharxy ( 5,  6, 'A');
		s_outCharxy ( 7,  6, 'L');
		s_outCharxy ( 9,  6, 'T');
		s_outCharxy (11,  6, 'E');
//		s_outCharxy (131,  7, 'H');

		 switch (answer=getUCchar())
		{
			case 'A':
			case 'C':
				mainCommand_G_bolt_circle(answer);
				break;

			case 'L':
				mainCommand_G_bolt_line();
				break;

			case 'T':
				mainCommand_G_tri();
				break;

//			case 'H':
//				mainCommand_G_chord();
//				break;

			case 'E':
				mainCommand_G_expression();
				break;

			case ESC:
				break;

			case HOME:						/* <Home> go right to commandLoop */
			case TAB:						/* <tab> go right to encoder display mode */
				longjmp(command, answer);	/* Jump to command loop, stuff a HOME command. */

			default:						/* All others answers */
				Sound (ERRnote);
				break;
		} 
	} while (answer!=ESC);
} /* End of mainCommand_G() */





void mainCommand_G_bolt_circle(short type)

{
#ifdef BOLTLISTCODE
	short answer=0, holeCount;
	int i, row;
	double radius, holeDia, angleSpace, holeSpace, angle, aStart, aEnd, x0, y0;
	char *titleString;

	if (type=='C')
		titleString = "Bolt Hole Circle Utility";
	if (type=='A')
		titleString = "Bolt Hole Arc Utility";

	do {
		overlay_box (19, 60, titleString);
		radius = holeDia = x0 = y0 = aStart = 0.0;
		holeCount = 0;
		while (holeCount > (sizeof(holePattern)/sizeof(POINT)) || holeCount<2)
		{
			_settextcolor (BLACK);
			if (!intnumbers_only (&holeCount, 2, 3, "Number of holes (2-99) ? ", BLUE, 10)) return;
			s_outTextxy (3, 3, "                                         "); /* Erase possible error msg. */
			if (holeCount > (sizeof(holePattern)/sizeof(POINT)) || holeCount<2)
				s_printfxy (3, 3, "Number of holes must be between 2 and %i!", 
									   sizeof(holePattern)/sizeof(POINT));
		}
		row = 3;			/* Row number of next question prompt. */
		if (type=='C')		/* Ask circle questions. */
		{
			if (!numbers_only (&radius, row++, 3, "Radius of bolt circle ? ", BLUE)) return;
			s_outTextxyt (row+1, 5, BLACK, "(0.0\xF8 = 12 o'clock)");
			if (!numbers_only (&aStart, row++, 3, "Starting angle of first hole [0.0\xF8] ? ", BLUE)) return;
			if (!numbers_only (&holeDia, row++, 3, "Diameter of bolt holes [0.0] ? ", BLUE)) return;
			if (!numbers_only (&x0, row++, 3, "X offset of bolt circle center [0.0] ? ", BLUE)) return;
			if (!numbers_only (&y0, row++, 3, "Y offset of bolt circle center [0.0] ? ", BLUE)) return;

			angleSpace = 360.0 / (double)holeCount;						/* Degrees between bolt holes. */
		}
		if (type=='A')		/* Ask arc questions. */
		{
			aEnd = 90.0;
			if (!numbers_only (&radius, row++, 3, "Radius of bolt arc ? ", BLUE)) return;
			s_outTextxyt (row+1, 5, BLACK, "(0.0\xF8 = 12 o'clock)");
			if (!numbers_only (&aStart, row++, 3, "Starting angle for first hole [0.0\xF8] ? ", BLUE)) return;
			if (!numbers_only (&aEnd, row++, 3, "Ending angle for last hole [90.0\xF8] ? ", BLUE)) return;
			if (!numbers_only (&holeDia, row++, 3, "Diameter of bolt holes [0.0] ? ", BLUE)) return;
			if (!numbers_only (&x0, row++, 3, "X offset of bolt arc center [0.0] ? ", BLUE)) return;
			if (!numbers_only (&y0, row++, 3, "Y offset of bolt arc center [0.0] ? ", BLUE)) return;

			angleSpace = fabs(aEnd - aStart) / (double)(holeCount-1);	/* Degrees between bolt holes. */
		}

		s_outTextxyt (10, 3, BLACK, "Hole  Angle       X            Y");
		if (holeCount>8)
			s_outTextxyt (-1, 44, BLACK, "(first 8 shown)");

		for (i=0,row=11; i<holeCount; i++,row++)
		{
			angle = aStart + (double)i * angleSpace;			/* Angle in degrees for next hole. */
			holePattern[i].x = radius * cosd(angle) + x0;
			holePattern[i].y = radius * sind(angle) + y0;

			if (row<19)
				s_printfxyt ( row, 4, BLACK, "%2i  %6.2f %12.5f %12.5f", 
						i+1, angle, holePattern[i].x, holePattern[i].y);
		}

		/* See if bolt holes will touch because there are too many/too large. */
		holeSpace = 2.0 * radius * sin(0.5 * angleSpace * RPD) - holeDia;
		if (holeSpace < 0.0)
		{
			Sound(ERRnote);
			s_printfxyt (19, 1, RED, "WARNING:Holes will overlap! ");
		}
		else
			s_setTextPosition (19, 2);

		s_printfxyt (19, -1, BLACK, "Accept these values (Y or N) [Y] ? ");
		currentHole = -1;

		answer = getUCchare(BLUE);
		if (answer==ESC) return;
		if (answer==TAB || answer==HOME)
			longjmp(command, answer);		/* Jump to command loop, stuff a command. */
		if (answer==ENTER) answer = 'Y';
	} while (answer!='Y');
	currentHole = 0;
	lastHole = holeCount - 1;
#endif /* BOLTLISTCODE */
}





void mainCommand_G_bolt_line()

{
#ifdef BOLTLISTCODE
	short answer=0, holeCount;
	int i, row;
	double length, angle, angle1, angle2, x0, y0, holeSpace, holeDia, segment;

	do {
		overlay_box (19, 60, "Bolt Hole Line Utility");
		holeCount=0;
		length = angle = holeDia = x0 = y0 = 0.0;
		while (holeCount > (sizeof(holePattern)/sizeof(POINT)) || holeCount<2)
		{
			_settextcolor (BLACK);
			if (!intnumbers_only (&holeCount, 2, 3, "Number of holes (2-99) ? ", BLUE, 10)) return;
			s_outTextxy (3, 3, "                                         "); /* Erase possible error msg. */
			if (holeCount > (sizeof(holePattern)/sizeof(POINT)) || holeCount<2)
				s_printfxy (3, 3, "Number of holes must be between 2 and %i!", 
									   sizeof(holePattern)/sizeof(POINT));
		}
		if (!numbers_only (&length, 3, 3, "Length of line ? ", BLUE)) return;
		s_outTextxyt (5,  5, BLACK, "(0.0\xF8 = 12 o'clock)");
		if (!numbers_only (&angle, 4, 3, "Angle of line [0.0] ? ", BLUE)) return;
		if (!numbers_only (&x0, 5, 3, "X offset of first hole [0] ? ", BLUE)) return;
		if (!numbers_only (&y0, 6, 3, "Y offset of first hole [0] ? ", BLUE)) return;
		if (!numbers_only (&holeDia, 7, 3, "Diameter of bolt holes [0.0] ? ", BLUE)) return;
			
		holeSpace = length / (double)(holeCount-1);		/* Length between bolt holes. */

		s_outTextxyt (10,  3, BLACK, "Hole      X            Y");
		if (holeCount>8)
			s_outTextxyt (-1, 38, BLACK, "(first 8 shown)");

		for (i=0,row=11; i<holeCount; i++,row++)
		{
			/* Compute new segment length for next hole and use triangle utility to
			   solve for two sides, then add offsets. */
			segment = (double)i * holeSpace;
			angle1 = 90;	/* The other known angle. */
			saa (&segment, &holePattern[i].x, &holePattern[i].y, &angle1, &angle2, &angle);
			holePattern[i].x += x0;
			holePattern[i].y += y0;

			if (row<19)
				s_printfxyt ( row, 4, BLACK, "%2i %12.5f %12.5f", 
								i+1, holePattern[i].x, holePattern[i].y);
		}

		/* See if bolt holes will touch because there are too many/too large. */
		if (holeSpace < (2.0 * holeDia))
		{
			Sound(ERRnote);
			s_printfxyt (19, 1, RED, "WARNING:Holes will overlap! ");
		}
		else
			s_setTextPosition (19, 2);

		s_printfxyt (19, -1, BLACK, "Accept these values (Y or N) [Y] ? ");
		currentHole = -1;

		answer = getUCchare(BLUE);
		if (answer==ESC) return;
		if (answer==TAB || answer==HOME)
			longjmp(command, answer);		/* Jump to command loop, stuff a command. */
		if (answer==ENTER) answer = 'Y';
	} while (answer!='Y');
	currentHole = 0;
	lastHole = holeCount - 1;
#endif /* BOLTLISTCODE */
}





void mainCommand_G_tri()

{
#ifdef TRIANGLECODE
	TRIANGLE tri;		/* Primary triangle being solved. */
	TRIANGLE alt;		/* Alternate solution, if any. */
	int sol;			/* number of solutions */
	int k;
	short answer=0;
	char *lastError = NULL;

	k = 0;	/* Count of input variables. */

	while (answer!=ESC)	/* Solving loop. */
	{
		/* Zero out triangle storage. */
		tri.side1 = tri.side2 = tri.side3 = 0.0;
		tri.angle1 = tri.angle2 = tri.angle3 = 0.0;
		alt = tri;

		while (1)	/* Data collection loop. */
		{
			overlay_box (19, 60, "Solution of Plane Triangles");
			k = 0;	/* Count of input variables. */
			if (lastError != NULL)
			{
				s_outTextxyt (18, 2, RED, lastError);
				lastError = NULL;
				Sound (ERRnote);
			}
			s_outTextxyt (2, 2, BLACK, "Number sides of the triangle 1-3, and answer the following");
			s_outTextxyt (3, 2, BLACK, "questions. Input whatever data you know; press return if");
			s_outTextxyt (4, 2, BLACK, "notknown. You need three items (at least one side).");
			if (!numbers_only (&tri.side1, 5, 3, "Length of side 1 [0] ? ", BLUE)) return;	/* Hit Esc */
			if (tri.side1 != 0.0) k++;
			if (!numbers_only (&tri.side2, 5+k, 3, "Length of side 2 [0] ? ", BLUE)) return;	/* Hit Esc */
			if (tri.side2 != 0.0) k++;
			if (!numbers_only (&tri.side3, 5+k, 3, "Length of side 3 [0] ? ", BLUE)) return;	/* Hit Esc */
			if (tri.side3 != 0.0) k++;
			if (k == 3) break;	/* If given Side/Side/Side, solve now. */
			if (k == 0)			/* At least one side must be given... */
			{
				lastError = "At least one side must be specified!";
				continue;		/* Loop again on error. */
			}
			if (!numbers_only (&tri.angle1, 5+k, 3, "Angle opposite side 1 [0 deg] ? ", BLUE)) return;	/* Hit Esc */
			if (tri.angle1 != 0.0) k++;
			if (k == 3) break;	/* If given Side/Side/Angle, solve now. */
			if (!numbers_only (&tri.angle2, 5+k, 3, "Angle opposite side 2 [0 deg] ? ", BLUE)) return;	/* Hit Esc */
			if (tri.angle2 != 0.0) k++;
			if (k == 3) break;	/* If given Side/Side/Angle or Side/Angle/Angle solve now. */
			if (!numbers_only (&tri.angle3, 5+k, 3, "Angle opposite side 3 [0 deg] ? ", BLUE)) return;	/* Hit Esc */
			if (tri.angle3 != 0.0) k++;
			if (k == 3) break;	/* If given Side/Side/Angle or Side/Angle/Angle solve now. */
			lastError = "At least one side and two angles must be specified!";		/* Loop again on error. */
		} /* End of data collection loop. */

		sol = solve (&tri, &alt);
		if (sol == 0)
		{
			Sound (ERRnote);
			lastError = "Insufficient data for solution!";
			continue;	/* Run solution loop again. */
		}

		s_printfxyt( 9, 3, BLACK, "side 1 = %.5f opposite angle = %.5f deg", tri.side1, tri.angle1);
		s_printfxy (10, 3, "side 2 = %.5f opposite angle = %.5f deg", tri.side2, tri.angle2);
		s_printfxy (11, 3, "side 3 = %.5f opposite angle = %.5f deg", tri.side3, tri.angle3);
		s_printfxy (12, 3, "area of triangle = %.5f",0.5 * tri.side1 * tri.side2 * sind(tri.angle3));
		if ((sol != 1) && !(CLOSE(tri.side1,alt.side1) && CLOSE(tri.side2,alt.side2) && 
							CLOSE(tri.side3,alt.side3) && CLOSE(tri.angle1,alt.angle1) &&
							CLOSE(tri.angle2,alt.angle2) && CLOSE(tri.angle3,alt.angle3)) )
		{
			s_outTextxyt(13, 2, RED, "An alternate solution exists:");
			s_printfxyt (14, 3, BLACK, "side 1 = %.5f opposite angle = %.5f deg", alt.side1, alt.angle1);
			s_printfxy  (15, 3, "side 2 = %.5f opposite angle = %.5f deg", alt.side2, alt.angle2);
			s_printfxy  (16, 3, "side 3 = %.5f opposite angle = %.5f deg", alt.side3, alt.angle3);
			s_printfxy  (17, 3, "area of triangle = %.5f",0.5 * alt.side1 * alt.side2 * sind(alt.angle3));
		}

		s_outTextxyt (19, 2, BLACK, "Solve another triangle (Y or N) [Y] ? ");
		answer = getUCchare(BLUE);
		if (answer==TAB || answer==HOME)
			longjmp(command, answer);		/* Jump to command loop, stuff a command. */
	} /* End of solving loop. */
#endif /* TRIANGLECODE */
} /* End of mainCommand_G_tri() */






/* asa.. */

void asa (double *s1, double *s2, double *s3, double *a1, double *a2, double *a3)

{
	*a1 = 180.0 - *a2 - *a3;
	*s2 = *s1 * sind(*a2) / sind(*a1);
	*s3 = *s1 * cosd(*a2) + *s2 * cosd(*a1);
}

/* saa.. */

void saa (double *s1, double *s2, double *s3, double *a1, double *a2, double *a3)

{
	*a2 = 180.0 - *a1 - *a3;
	*s2 = *s1 * sind(*a2) / sind(*a1);
	*s3 = *s1 * cosd(*a2) + *s2 * cosd(*a1);
}

/* sas.. */

void sas (double *s1, double *s2, double *s3, double *a1, double *a2, double *a3)

{
	double p;

	*s3 = sqrt(*s1 * *s1 + *s2 * *s2 - 2. * *s1 * *s2 * cosd(*a3));
	p = 0.5 * (*s1 + *s2 + *s3);
	*a1 = 2.0 * ACSD(sqrt(p * (p - *s1) / (*s2 * *s3)));
	*a2 = 2.0 * ACSD(sqrt(p * (p - *s2) / (*s1 * *s3)));
}

/* ssa, returns the number of solutions (1 or 2) */

int ssa ( double *s1, double *s2, double *s3, double *a1, double *a2, double *a3,\
  double *s1p, double *s2p, double *s3p, double *a1p, double *a2p, double *a3p)

{
	*a2 = asind((*s2) * sind(*a1) / (*s1));
	*a3 = 180.- *a1 - *a2;
	*s3 = *s1 * cosd(*a2) + *s2 * cosd(*a1);
	if ((*s2 > *s1) && (*a2 !=  90.0))
	{
		*s1p = *s1;
		*s2p = *s2;
		*a1p = *a1;
		*a2p = 180.0 - *a2;
		*a3p = 180.0 - *a1 - *a2p;
		*s3p = *s1 * cosd(*a2p) + *s2 * cosd(*a1);
		return 2;
	}
	return 1;
}





/* solve plane triangle, returns the number of solutions (0, 1, or 2) */

int solve (TRIANGLE *tri, TRIANGLE *alt)

{
#ifdef TRIANGLECODE
	if (tri->side1 && tri->side2 && tri->side3)	/* sss */
	{
		double p;

		p = 0.5 * (tri->side1 + tri->side2 + tri->side3);
		tri->angle1 = 2.0 * ACSD(sqrt(p * (p-tri->side1) / (tri->side2 * tri->side3)));
		tri->angle2 = 2.0 * ACSD(sqrt(p * (p-tri->side2) / (tri->side1 * tri->side3)));
		tri->angle3 = 180.0 - tri->angle1 - tri->angle2;
		return 1;
	}
	if (tri->side1 && tri->angle2 && tri->angle3)	/* asa */
	{
		asa (&tri->side1, &tri->side2, &tri->side3, &tri->angle1, &tri->angle2, &tri->angle3);
		return 1;
	}
	if (tri->side2 && tri->angle1 && tri->angle3)	/* asa */
	{
		asa (&tri->side2, &tri->side3, &tri->side1, &tri->angle2, &tri->angle3, &tri->angle1);
		return 1;
	}
	if (tri->side3 && tri->angle1 && tri->angle2)	/* asa */
	{
		asa (&tri->side3, &tri->side1, &tri->side2, &tri->angle3, &tri->angle1, &tri->angle2);
		return 1;
	}
	if (tri->side1 && tri->angle1 && tri->angle3)	/* saa */
	{
		saa (&tri->side1, &tri->side2, &tri->side3, &tri->angle1, &tri->angle2, &tri->angle3);
		return 1;
	}
	if (tri->side1 && tri->angle1 && tri->angle2)	/* saa */
	{
		saa (&tri->side1, &tri->side3, &tri->side2, &tri->angle1, &tri->angle3, &tri->angle2);
		return 1;
	}
	if (tri->side2 && tri->angle1 && tri->angle2)	/* saa */
	{
		saa (&tri->side2, &tri->side3, &tri->side1, &tri->angle2, &tri->angle3, &tri->angle1);
		return 1;
	}
	if (tri->side2 && tri->angle2 && tri->angle3)	/* saa */
	{
		saa (&tri->side2, &tri->side1, &tri->side3, &tri->angle2, &tri->angle1, &tri->angle3);
		return 1;
	}
	if (tri->side3 && tri->angle2 && tri->angle3)	/* saa */
	{
		saa (&tri->side3, &tri->side1, &tri->side2, &tri->angle3, &tri->angle1, &tri->angle2);
		return 1;
	}
	if (tri->side3 && tri->angle1 && tri->angle3)	/* saa */
	{
		saa (&tri->side3, &tri->side2, &tri->side1, &tri->angle3, &tri->angle2, &tri->angle1);
		return 1;
	}
	if (tri->side1 && tri->side2 && tri->angle3)	/* sas */
	{
		sas (&tri->side1, &tri->side2, &tri->side3, &tri->angle1, &tri->angle2, &tri->angle3);
		return 1;
	}
	if (tri->side2 && tri->side3 && tri->angle1)	/* sas */
	{
		sas (&tri->side2, &tri->side3, &tri->side1, &tri->angle2, &tri->angle3, &tri->angle1);
		return 1;
	}
	if (tri->side1 && tri->side3 && tri->angle2)	/* sas */
	{
		sas (&tri->side3, &tri->side1, &tri->side2, &tri->angle3, &tri->angle1, &tri->angle2);
		return 1;
	}
	if (tri->side1 && tri->side2 && tri->angle1)	/* ssa */
		return ssa (&tri->side1, &tri->side2, &tri->side3, &tri->angle1, &tri->angle2, &tri->angle3, 
					&alt->side1, &alt->side2, &alt->side3, &alt->angle1, &alt->angle2, &alt->angle3);
	if (tri->side1 && tri->side2 && tri->angle2)	/* ssa */
		return ssa (&tri->side2, &tri->side1, &tri->side3, &tri->angle2, &tri->angle1, &tri->angle3,
					&alt->side2, &alt->side1, &alt->side3, &alt->angle2, &alt->angle1, &alt->angle3);
	if (tri->side2 && tri->side3 && tri->angle2)	/* ssa */
		return ssa (&tri->side2, &tri->side3, &tri->side1, &tri->angle2, &tri->angle3, &tri->angle1, 
					&alt->side2, &alt->side3, &alt->side1, &alt->angle2, &alt->angle3, &alt->angle1);
	if (tri->side2 && tri->side3 && tri->angle3)	/* ssa */
		return ssa (&tri->side3, &tri->side2, &tri->side1, &tri->angle3, &tri->angle2, &tri->angle1, 
					&alt->side3, &alt->side2, &alt->side1, &alt->angle3, &alt->angle2, &alt->angle1);
	if (tri->side1 && tri->side3 && tri->angle3)	/* ssa */
		return ssa (&tri->side3, &tri->side1, &tri->side2, &tri->angle3, &tri->angle1, &tri->angle2, 
					&alt->side3, &alt->side1, &alt->side2, &alt->angle3, &alt->angle1, &alt->angle2);
	if (tri->side1 && tri->side3 && tri->angle1)	/* ssa */
		return ssa (&tri->side1, &tri->side3, &tri->side2, &tri->angle1, &tri->angle3, &tri->angle2, 
					&alt->side1, &alt->side3, &alt->side2, &alt->angle1, &alt->angle3, &alt->angle2);
#endif /* TRIANGLECODE */
	return 0;		/* no solution found */
} /* End of solve() */






//void mainCommand_G_chord()
//{
//}





/*************************************************************************
**                                                                       **
** EE.C         Expression Evaluator                                     **
**                                                                       **
** AUTHOR:      Mark Morley                                              **
** COPYRIGHT:   (c) 1992 by Mark Morley                                  **
** DATE:        December 1991                                            **
** HISTORY:     Jan 1992 - Made it squash all command line arguments     **
**                         into one big long string.                     **
**                       - It now can set/get VMS symbols as if they     **
**                         were variables.                               **
**                       - Changed max variable name length from 5 to 15 **
**              Jun 1992 - Updated comments and docs                     **
**                                                                       **
** You are free to incorporate this code into your own works, even if it **
** is a commercial application.  However, you may not charge anyone else **
** for the use of this code!  If you intend to distribute your code,     **
** I'd appreciate it if you left this message intact.  I'd like to       **
** receive credit wherever it is appropriate.  Thanks!                   **
**                                                                       **
** I don't promise that this code does what you think it does...         **
**                                                                       **
** Please mail any bug reports/fixes/enhancments to me at:               **
**      morley@camosun.bc.ca                                             **
** or                                                                    **
**      Mark Morley                                                      **
**      3889 Mildred Street                                              **
**      Victoria, BC  Canada                                             **
**      V8Z 7G1                                                          **
**      (604) 479-7861                                                   **
**                                                                       **
 *************************************************************************/

void mainCommand_G_expression()

{
	char* ErrMsgs[] = {
		"Syntax error",
		"Unbalanced parenthesis",
		"Division by zero",
		"Unknown variable",
		"Maximum variables exceeded",
		"Unrecognised funtion",
		"Wrong number of arguments to funtion",
		"Missing an argument",
		"Empty expression" };

	double result;
	int i, ec, assign;
	char answer, line[1024];

	overlay_box (19, 60, "Equation Evaluator");
	/* Shrink window by one line, so title won't scroll. */
	s_setTextWindow (window_top+1, window_left, window_bottom, window_right, -1);

	/* Input one line at a time from the user.  Start with help text. */
	answer = '?';
	while (answer!=ESC)
	{
		if (answer=='?')
		{
			s_outTextxyt (18, 1, BLACK, " (a)sin/cos/tan(r,h), exp, ln, log, sqr(t), floor, ceil\n");
			s_outTextxy (18, 1, " abs, rss, deg, rad, +,-,*,/,^,%     16 char variable names\n");
			s_outTextxy (18, 1, " CONS-show constants VARS-show variables CLR-zero variables\n");
			s_outTextxy (18, 1, " EXIT-or Q/q to quit    ?-Help\n");
		}
		s_outTextxy (18, 1, " ? ");
		answer = get_string(line, sizeof(line), BLUE);
		if (answer==FALSE) break;
		s_outText ("\n");

		strlwr(line);	/* Lower caseify it. */

		/* Did the user ask to exit? */
		if (!strcmp(line,"exit") || !strcmp(line,"q") || !strcmp(line,"quit"))
			break;

		/* Did the user ask for help? */
		else if (!strcmp(line,"?") || !strcmp(line,"help"))
			answer = '?';

		/* Did the user ask to see the variables in memory? */
		else if (! strcmp(line,"vars"))
		{
			for (i=0; i < MAXVARS; i++)
				if (*Vars[i].name)
					s_printfxyt (18, 1, BLACK, "%s = %.9g\n", Vars[i].name, Vars[i].value);
		}

		/* Did the user ask to see the constants in memory? */
		else if (!strcmp(line,"cons"))
		{
			for (i=0; *Consts[i].name; i++)
				s_printfxyt (18, 1, BLACK, "%s = %.9g\n", Consts[i].name, Consts[i].value);
		}

		/* Did the user ask to clear all variables? */
		else if (! strcmp(line,"clr"))
			ClearAllVars();

		/* If none of the above, then we attempt to evaluate the user's input. */
		else
		{
			/* Call the evaluator. */
			if ((ec=Evaluate(line, &result, &assign)) == E_OK)
			{
				/* Only display the result if it was not an assignment. */
				if (!assign)
					s_printfxyt (18, 1, BLACK, " = %.9g\n",result);
			}
			else if (ec != E_EMPTY)
			{
				/* Display error information.  E_EMPTY is ignored. */
				s_printfxyt (18, 1, BLACK, "ERROR: %s - %s\n", ErrMsgs[ERROR - 1], ERTOK);
				s_printfxyt (18, 1, BLACK, "%s\n", ERANC);
				s_printfxyt (18, 1, BLACK, "%*s^\n", ERPOS, "");
			}
		}
	} /* End of while() */
} /* End of mainCommand_G_expression() */







/* Z key - Zero out all encoders. */

void mainCommand_Z()

{
	int c, i, j;
	register ENCODER_DATA *p;

	drop_box ( 6, 55, 0, 0, "Master Zero?", RED); /* Red box with yellow text prompt */
	s_outTextxyt (3, 3, YELLOW, "Are you sure you want to zero all encoders,"); 
	s_outTextxy  (4, 3, "including Master and Incremental modes (Y or N)? "); 
	Sound (QUESTnote);

	switch(c=getUCchare(BLUE))
	{
		case 'N':					/* No, do nothing. */
		case ESC:					/* ESC, abort command */
			return;
			
		case 'Y':					/* Yes */
			for (i=0; (p=encoderList[i])!=NULL; i++)
			{
				for (j=0; j<numberOfModes; j++)
				{
					p->ed_offset[j] = 0;				/* offset storage set to zero */
					p->ed_compoundSteps[j] = 0;			/* compound steps set to zero */
				}
				if (serialEnable)						/* If talking to an outboard processor. */
					sendZeroCommand (p->ed_encoder);	/* Set counter in micro. */
				p->ed_value = 0;
				p->ed_currently_displayed = 0;
				p->ed_possible_error_count = -1;
				p->ed_zero_all = FALSE;
			}
			Sound (DONEnote);
			break;

		case TAB:					/* <tab> go right to encoder display mode */
		case HOME:					/* <Home> go right to commandLoop */
			longjmp(command, c);	/* Jump to command loop, stuff a command. */

		default:					/* Any other, Beep user. */
			Sound (ERRnote);
			s_outTextxyt (6, 3, YELLOW, "Nothing done!  Press any key to continue...");
			c = getUCchar(); 
			return;
	}
	return;	
} /* End of mainCommand_Z() */







/* Set LPT port address.  Returns TRUE if a port was set. */

int selectParallelPort()

{
	short i, answer, error=FALSE;
	char string[80], temp[16];
	PARPORT par, *p;

	/* Copy settings of current port into a temp work area. */
	par = pp;

	do
	{
		answer = ' ';
		while (answer!=ENTER)	/* Pick up a new port assignemnt. */
		{
			overlay_box (15, 50, "LPT Port Selection");

			s_outTextxyt (15,3, RED, "<Enter>");
			_settextcolor (YELLOW);
			s_outText (" to accept current");
			s_outTextxyt (15, 35, RED, "<Esc>");
			_settextcolor (YELLOW);
			s_outText(" to Cancel");

			s_outTextxyt (3,9, BLACK, "Name     Address         IRQ#   Type");

			par.pp_lpt = 0;		/* No match on LPTn yet */

			/* Display a table of known LPT ports. */
			for (p=PPtable,i=1; p<PPtable+(sizeof(PPtable)/sizeof(PARPORT))&&p->pp_data!=0; p++,i++) 
			{
				s_printfxyt (3+i, 6, RED, "%i", i);
				s_printfxyt (-1, -1, BLACK, ": LPT%i   %4i(0x%03X)        %s     %s", i, p->pp_data,
					p->pp_data, (p->pp_irq==0)?"?":itoa(p->pp_irq,temp,10), ppTypeStr[p->pp_type]);
				if (par.pp_data == p->pp_data)		/* If the port address matches */
					par.pp_lpt = p->pp_lpt;			/* Say this is the same LPTn */
			}
			s_outCharxyt (3+i, 6, RED, 'C');
			s_outTextxyt (-1, -1, BLACK, ": Enter Custom address (for advanced users)");

			s_printfxyt (6+i, 3, BLACK, "Current setting: "); 
			if (par.pp_lpt == 0)				/* If no match was found, show address in decimal */
			{
				if (par.pp_data == 0)			/* If address is zero, no port defined. */
					sprintf (string, "no port defined");
				else
					sprintf (string, "port %i(0x%03X) IRQ%s int addr 0x%02X type %s", 
							par.pp_data, par.pp_data, par.pp_irq==0 ? "?" : itoa(par.pp_irq,temp,10), 
							(par.pp_irq==0)?0:par.pp_intno, ppTypeStr[par.pp_type]);
			}
			else
				sprintf (string, "LPT%i IRQ%s int addr 0x%02X type %s",
						par.pp_lpt, par.pp_irq==0 ? "?" : itoa(par.pp_irq,temp,10),
						(par.pp_irq==0)?0:par.pp_intno, ppTypeStr[par.pp_type]);
			s_outTextxyt (7+i, 4, BLACK, string);
			s_printfxy (8+i, 4, "Interrupts currently %s (press I to turn %s)",
						(par.pp_use_irq)?"ON":"OFF", (par.pp_use_irq)?"OFF":"ON");

			s_outTextxyt (12, 3, BLACK, "Choose an entry from above list: ");   
			_settextcolor (BLUE);
			s_outCharxy (-1, -1, (unsigned char)answer);
			s_setTextPosition (12, 36);			/* Backup one */

			switch (answer=getUCchare(BLUE))
			{
				case '1':						/* number 1 */
				case '2':						/* number 2 */
				case '3':						/* number 3 */
					/* Check for invalid (no port addr defined. */
					if (PPtable[answer-'1'].pp_data==0)
					{
						Sound (ERRnote);
						answer = ' ';
						break;
					}
					par = PPtable[answer-'1'];	/* Copy desired table entry into our temp. */
					break;

				case HOME:						/* <Home> go right to commandLoop */
				case TAB:						/* <tab> go right to encoder display mode */
					longjmp(command, answer);	/* Jump to command loop, stuff a command. */

				case 'C':						/* number 4 */
					/* Blank out "<Enter> to accept..." */
					s_outTextxyt (15,3, BLACK, "                              ");
					/* Blank out Interrupts prompt */
					s_outTextxyt (8+i, 3, BLACK, "                                             ");
					s_outTextxyt (13, 4, BLACK, "(enter as decimal or prefix hex with '0x')");
					_settextcolor (RED);		/* Color the prompt text. */
					if (!intnumbers_only((short*)&par.pp_data, 12, 3, "Enter Parallel Port Address to use: ", BLUE, 0))
						return FALSE;					/* FALSE is returned from numbers_only if the user hit the escape key */
					par.pp_lpt = 0;				/* Assume it is not a LPTn */
					s_outTextxyt (13, 3, BLACK, "                                              ");
					detectPPort(&par);			/* Identify the parallel port (SPP, EPP, ECP) */
					_settextcolor (RED);		/* Color the prompt text. */
					if (!intnumbers_only((short*)&par.pp_irq, 13, 3, "Enter IRQ for Parallel Port: ", BLUE, 10))
						return FALSE;					/* FALSE is returned from numbers_only if the user hit the escape key */
					break;

				case 'I':						/* Toggle interrupt mode. */
					par.pp_use_irq = !par.pp_use_irq;
					break;

				case ESC:						/* The ever available abort. */
					return FALSE;

				case ENTER:						/* Accept current */
					break;

				default:						/* All others answers */
					Sound (ERRnote);
					answer = ' ';
					break;;
			}
		}

		/* Check if port does not exist or is output only. Allow user to ignore error if desired. */
		/* If port seems to not exist or is output only, warn user */
		if ((par.pp_type==lptN_A) || (par.pp_type==lptSPP))	
		{
			/* Red box with yellow text prompt */
			drop_box ( 5, 65, 0, 0, "Parallel port error?", RED);
			s_printfxyt (3, 3, YELLOW, "Parallel port decimal %i (0x%03X) ", par.pp_data, par.pp_data);

			if (par.pp_type==lptSPP)		/* If port seems to be output only... */
				s_outText ("seems to be output only.");
			else	
				s_outText ("does not seem to exist.");

			s_outTextxy (4, 3, "Continue anyway (Y or N) [N] ? ");
			if ((answer=getUCchare(BLUE))=='Y') 
				error = FALSE;		/* If Yes, live with the "error".*/
			else
				error = TRUE;		/* If No, try again. */
		}
	} while (error==TRUE && answer!=ENTER);

	disablePPIRQ(&pp);				/* Turn off interrupts before changing address. */
	pp = par;						/* User has accepted the port, store as the current port */
	parallelPortConfig(&pp);		/* May have changed ports, set config bits. */
	enablePPIRQ(&pp);				/* Turn on interrupts after address change. */
	return TRUE;
} /* End of selectParallelPort() */






/* Machine Config setup */	 

void mainCommand_M()

{
	short answer=0;
	
	while (answer!=ESC)
	{
		overlay_box (19, 60, "Machine Tool Options");

		s_outTextxyt (3, 6, BLACK, "Name this machine...");
		s_outTextxy (5, 6, "Edit axis configuration (add/delete/name/slew)...");
		s_outTextxy (7, 6, "Axis step calibration...");
		s_outTextxy (9, 6, "Lathe mode options...");
		s_outTextxy (11, 6, "Tachometer configuration...");
		s_outTextxy (13, 6, "AutoZero configuration...");
		
		s_outCharxyt (3, 6, RED, 'N');
		s_outCharxy (5, 6, 'E');
		s_outCharxy (7, 6, 'A');
		s_outCharxy (9, 6, 'L');
		s_outCharxy (11, 6, 'T');
		s_outCharxy (13, 10, 'Z');

		switch (answer=getUCchar())
		{
			case 'N':
				mainCommand_M_name();
				break;

			case 'E':
				mainCommand_M_edit();
				break;

			case 'A':
				mainCommand_M_axis();
				break;

			case 'L':						/* The L key, set Lathe options */
				mainCommand_M_lathe();
				break;

			case 'T':
				mainCommand_M_tach();		/* Configure tachometer. */
				break;

			case 'Z':
				mainCommand_M_az();			/* Configure autozero. */
				break;

			case ESC:
				break;

			case HOME:						/* <Home> go right to commandLoop */
			case TAB:						/* <tab> go right to encoder display mode */
				longjmp(command, answer);	/* Jump to command loop, stuff a HOME command. */

			default:						/* All others answers */
				Sound (ERRnote);
				break;
		} /* End of switch */
	} /* End of while */
	return;
} /* End of mainCommand_M() */








/* Machine Configfile, setup the cfg file and filename to read in or write out. */	 

void mainCommand_M_file(short answer)

{	/*AGE1 print out a listing of *.cfg files found in current directory. */   
	struct _find_t  c_file;
	char configTemp[sizeof(configName)];
	short r=4, c=0, fileError=FALSE;
	
	do
	{
		s_setTextWindow (3, 10, 23, 70, BLUE);
	
		s_outTextxyt (3, 13, YELLOW, "List of Current cfg files:");

		/* Find first .cfg file in current directory. */
		if (_dos_findfirst( "*.cfg", _A_NORMAL, &c_file )!=0)
			s_outTextxyt (r, c, BRIGHT_WHITE, "(no files found in current directory)");
		else
		{
			s_outTextxyt (r, c, BRIGHT_WHITE, c_file.name);
			c+=15;					/* Advance column. */
			
			/* Find the rest of the .cfg files. */
			while( _dos_findnext( &c_file ) == 0 ) 
			{         
				s_outTextxyt (r, c, BRIGHT_WHITE, c_file.name);
				if (c>=45)			/* Use up to 45 columns */
				{
					r++ ;			/* Count up rows. */
					c=1;			/* First column in next row. */
				}
				else
					c+=15;			/* Advance column. */
			}
		}

		/* Define a drop shadow box window to do filename prompt */
		drop_box (8, 47, 14, 17, "Configuration file", WHITE);
		
		s_outTextxyt (5, 2, RED, "________________________________________");  
		s_outTextxyt (8, 2, BLACK, "Maximum 40 Characters (press ENTER when done)");  

		if (answer=='C')						/* C for create */
		{     
			s_outTextxyt (3, 2, BLACK, "Enter name of configuration file to create");  
			s_setTextPosition (5, 2);
			
			if (!get_string(configTemp, sizeof(configTemp), BLACK))	/* Scan character string. */
				return;							/* Return if ESC. */

			strcpy ( configName, configTemp  );	/* Copy name to file name storage. */

			/* saveConfigFile may display error messages.  Give it a full screen to do so. */
			s_clearScreen (_GCLEARSCREEN, BLACK);
			s_init_outText (_DEFAULTMODE);		/* video mode */
			saveConfigFile();					/* write the new config file with current status */
		}

		if (answer=='R')						/* R  for Read */
		{
			unsigned short save_ppPort = pp.pp_data;		/* Incase new config file changes it. */
			unsigned short save_spPort = sp.sp_data;		/* Incase new config file changes it. */

			s_outTextxyt (3, 2, BLACK, "Enter a .CFG filename from the list above");
			s_setTextPosition (5, 2);
			
			if (!get_string(configTemp, sizeof(configTemp), BLACK))	/* Scan character string. */
				return;							/* Return if ESC. */

			strcpy ( configName, configTemp  );	/* Copy name to file name storage. */

			/* readConfigFile may display error messages.  Give it a full screen to do so. */
			s_clearScreen (_GCLEARSCREEN, BLACK);
			s_init_outText (_DEFAULTMODE);		/*  video mode */

			disablePPIRQ(&pp);					/* Disable for this port, in case new config changes ports. */
			disableSPIRQ(&sp);					/* Disable for this port, in case new config changes ports. */
			if (fileError=(!readConfigFile()))	/* Read file, loop again on error. */
				break;

			/* New config may have changed ports and/or IRQ settings. */
			if (parallelEnable && (save_ppPort!=pp.pp_data))
			{
				/* Yep, it changed and we need it, more work to do. */
				detectPPort(&pp);
				if ((pp.pp_type==lptN_A) || (pp.pp_type==lptSPP))	/* If port seems to not exist or output only, warn user */
				{
					drop_box ( 8, 35, 0, 0, "Parallel Port Error?", RED); /* Red box with yellow text prompt */
					s_printfxyt (2, 4, YELLOW, "Parallel port decimal %i (0x%03X)", programName, pp.pp_data, pp.pp_data);

					if (pp.pp_type==lptSPP)		/* If port seems to be output only... */
						s_outTextxy (2, 5, "seems to be output only.");
					else	
						s_outTextxy (2, 5, "does not seem to exist.");
					s_outTextxy (2, 7, "Continue anyways (Y or N) [N] ? ");
					if (getUCchar()!='Y') 
						exit(0);				/* If not Yes, exit program. */
				}
				parallelPortConfig(&pp);		/* Configure parallel port */
			}
			enablePPIRQ(&pp);					/* Re-enable if required. */

			if ((serialEnable || rpmEnable) && save_spPort!=sp.sp_data)	/* If a new serial port number was stored... */
			{
				/* Yep, it changed and we need it, more work to do. */
				detectSERport(&sp);
				if (sp.sp_irq == 0)				/* Serial port config failed. */
				{
					drop_box ( 7, 35, 0, 0, "Serial Port Error?", RED); /* Red box with yellow text prompt */
					s_printfxyt (3, 4, YELLOW, "Port %i(0x%03X) setup failed", sp.sp_data, sp.sp_data);
					if (rpmEnable)
						s_outTextxy (4, 4, "Tachometer turned off.");
					s_outTextxy (5, 4, "Press any key to continue...");
					getUCchar();
					rpmEnable = FALSE;
				}
				serialPortConfig(&sp);
			}
			enableSPIRQ(&sp);					/* Re-enable if required. */
		}
	} while (fileError);

	/* Rebuild blue background window */
	buildMainBackground(FALSE);
	return;
} /* End of mainCommand_M_file() */








void mainCommand_M_name(void)

{
	char string[80];

	overlay_box (19, 60, "Machine Name Editor");

	s_outTextxyt (6, 5, BLACK, "Current machine name is ");
	s_outTextxyt (-1, -1, BLUE, machine);
	s_outTextxyt (10, 6, BLACK, "(max of 25 characters, press Enter when done)");
	s_outTextxy (9, 5, "Enter new machine name: ");
	if (!get_string (string, sizeof(machine), BLUE))	/* Get string, return if ESC */
		return;
	if (string[0] != '\0')						/* Empty string means don't change. */
		strcpy (machine, string);

	/* Rebuild blue background window with new name. */
	buildMainBackground(FALSE);

	return;
} /* End of mainCommand_M_name() */








/* Routine that allows user to change the configuration of the axis parameters
   stored in the encoder data structures. */

void mainCommand_M_edit(void)

{
	ENCODER_DATA *axis;
	char string[80];
	short answer, encoder, questionRow;
	
	while (TRUE)	/* Loop til user is done (hits ESC) */
	{
		overlay_box (19, 60, "Axis Configuration Editor");

		encoder = display_axis_settings();		/* Display current axis settings, return count of encoders. */
		
		questionRow = 11;						/* Row to start question display on. */
		answer = 0;
		while (answer<1 || answer>encoder)		/* Must enter a number between 1 and (encoder-1) */
		{
			s_outTextxyt (questionRow+1, 4, BLACK, "(ESC if done...)");
			s_outTextxyt (questionRow, 4, BLACK, "Enter # of the axis you wish to change ? ");
			answer = getUCchare(RED);			/* Get and echo character to display */
			if (answer==ESC) return;
			if (answer==TAB || answer==HOME)	/* <Home> or <Tab> go to commandLoop */
				longjmp(command, answer);		/* Jump to command loop, stuff a command. */
			answer -= '0';
		}
		
		s_outTextxyt (questionRow+8, 4, RED, "Press ENTER to retain current stored value.");

		encoder = answer-1;
		axis = encoderList[encoder];
	
		/* First ask if the axis should be used.  If no, set row to zero, reassign all axis display
		rows and loop.  If yes, set to non-zero, continue questions, and then reassign. */
		s_printfxyt (questionRow+1, 4, BLACK, "Do you want axis %i to display (Y or N) [%c] ? ", 
						encoder+1, axis->ed_displayRow?'Y':'N');
		_settextcursor (CURSOR_ON);				/* Always want cursor ON if getting input */
		answer = getUCchar();					/* Get character */
		if (answer==ESC) return;				/* Abort if ESC */
		if (answer==TAB || answer==HOME)		/* <Home> or <Tab> go to commandLoop */
			longjmp(command, answer);			/* Jump to command loop, stuff a command. */
		if (answer!=ENTER)						/* Return means no change, if not check for Yes, otherwise No. */
			if (answer=='Y') axis->ed_displayRow = 1; /* Non zero turns on display of this axis, assign row later. */
			else axis->ed_displayRow = 0;		/* Zero turns off axis display */

		if (axis->ed_displayRow!=0)
		{
			/* If we got here, answer was Yes or no change and we were displaying this axis.  
			   Echo the answer to screen. */
			s_outCharxyt ( -1, -1, BLUE, 'Y');
			display_axis_settings();			/* Update display of current axis settings. */

			/* Get a single character label, used to label the display field on screen. */
			answer = 0;
			while (TRUE)						/* Must enter a printable character. */
			{
				s_printfxyt (questionRow+2, 4, BLACK, "For axis %i, enter a single character label ? ", encoder+1);
				_settextcursor (CURSOR_ON); /* Always want cursor ON if getting input */
				answer = getUCchar();			/* Get character */
				if (answer==ESC) return;		/* Abort if ESC */
				if (answer==ENTER) break;		/* Enter means no change. */
				if (answer==TAB || answer==HOME)/* <Home> or <Tab> go to commandLoop */
					longjmp(command, answer);	/* Jump to command loop, stuff a command. */
				if (isgraph(answer)) break;		/* If an acceptable character, leave loop. */
				Sound (ERRnote);				/* Illegal character. */
			}
			if (answer!=ENTER)					/* Enter means no change. */
				axis->ed_label = (unsigned char)answer;
			s_outCharxyt (-1, -1, BLUE, axis->ed_label);	/* Echo whatever we ended up with */
			display_axis_settings();			/* Update display of current axis settings. */
			
			/* Get a 12 character name, used to display information about the axis. */
			s_printfxyt (questionRow+3, 4, BLACK, "For axis %i, enter a name (max 12 characters)", encoder+1);
			s_outTextxy (questionRow+4, 4, " ? ");
		    if (!get_string(string, 12, BLUE)) return; /* Scan character string, return if ESC. */
			if (string[0]=='\0')				/* Empty string means don't change. */
				s_outTextxyt (-1, -1, BLUE, axis->ed_name);	/* Display the existing stored value if NULL */
			else
				strcpy(axis->ed_name, string);	/* Copy new name string to data structure. */
			display_axis_settings();			/* Update display of current axis settings. */

			/* Ask whether to use label or name on screen. */
			answer = 0;
			while (!(answer=='L'||answer=='N'))		/* Must enter a L or N character. */
			{
				s_outTextxyt (questionRow+5, 4, BLACK, "Label (");
				s_printfxyt (-1, -1, YELLOW, "%c", axis->ed_label);
				s_outTextxyt (-1, -1, BLACK, ") or Name (");
				s_outTextxyt (-1, -1, BLUE, axis->ed_name);
				s_printfxyt (-1, -1, BLACK, ") display (L or N) [%c] ? ", axis->ed_displayName?'N':'L');
				_settextcursor (CURSOR_ON);		/* Always want cursor ON if getting input */
				answer = getUCchar();			/* Get character */
				if (answer==ESC) return;		/* ESC means done. */
				if (answer==TAB || answer==HOME)/* <Home> or <Tab> go to commandLoop */
					longjmp(command, answer);	/* Jump to command loop, stuff a command. */
				if (answer==ENTER) break;		/* Enter means no change. */
			}
			if (answer!=ENTER)
				if (answer=='N')				/* If answer was 'N'ame, make it TRUE */
					axis->ed_displayName = TRUE;
				else
					axis->ed_displayName = FALSE;
			/* Echo whatever we ended up with */
			s_outCharxyt (-1, -1, BLUE, (unsigned char)((axis->ed_displayName)?'N':'L'));
			display_axis_settings();				/* Update display of current axis settings. */

			/* Ask for status of slew rate display. */
			s_printfxyt (questionRow+6, 4, BLACK, "Display slew rate for axis %i (Y or N) [%c] ? ", 
							encoder+1, axis->ed_displaySlew?'Y':'N');
			answer = getUCchare(BLUE);				/* Get character */
			if (answer==ESC) return;				/* Abort if ESC */
			if (answer==TAB || answer==HOME)		/* <Home> or <Tab> go to commandLoop */
				longjmp(command, answer);			/* Jump to command loop, stuff a command. */
			if (answer!=ENTER)						/* Return means no change, if not check for Yes, otherwise No. */
				if (answer=='Y')
					axis->ed_displaySlew = TRUE;	/* Non zero turns on display of this axis, assign row later. */
				else
					axis->ed_displaySlew = FALSE;	/* Zero turns off axis display */
		}
	} /* End of while(TRUE) */
}






/* Displays the axis configuration settings for the edit routine.  Returns the
   highest number encoder found. */

short display_axis_settings(void)

{
	ENCODER_DATA *axis;
	short encoder, i, j;

	for (i=0,j=0; encoderList[i]!=NULL; i++)				/* Sweep all axis and re assign display rows. */
	{
		if (encoderList[i]->ed_displayRow!=0)
			encoderList[i]->ed_displayRow = 7 + (3*j++);	/* Assign to every third row starting at 7 */
	}

	s_outTextxyt (3, 5, BLACK, "#  Label    Name           Display    Row   Slew");
	for (encoder=0; (axis=encoderList[encoder])!=NULL; encoder++)
	{
		s_printfxyt (4+encoder, 5, RED, "%i", encoder+1);
		s_outTextxyt ( -1, -1, BLACK, ":");
		s_printfxyt ( -1, -1, (axis->ed_displayRow==0)?DARK_GRAY:BLUE, "   %c     %-15s  ", axis->ed_label, axis->ed_name);
		s_printfxyt ( -1, -1, (axis->ed_displayRow==0)?DARK_GRAY:BLACK, axis->ed_displayName?"name ":"label");
		if (axis->ed_displayRow==0)
			s_outText ("  (none)");
		else
			s_printf  ("      %2i", axis->ed_displayRow);
		s_outText (axis->ed_displaySlew?"    Yes":"     No");
	}
	return (encoder);
} /* End of display_axis_settings() */






void mainCommand_M_lathe()

{
#ifdef LATHECODE
	ENCODER_DATA *axis;
	ENCODER_DATA *pX=NULL, *pZ=NULL;
	short i, answer=0, answerC, answerX, answerZ, encoder, maxEncoder, color;
	char *lastError=NULL;
	double angle;
	
	while (answer!=ESC)		/* Loop til user is done (hits ESC) */
	{
		overlay_box (19, 60, "Lathe Options Configuration");

		/* Display a list of existing axis and the state of the lathe settings. */
		s_outTextxyt (2, 4, BLACK, "# Label  Name           Diam    Compound");
		for (i=0,encoder=0; (axis=encoderList[i])!=NULL; i++,encoder++)
		{
			s_printfxyt (3+i, 4, RED, "%i", i+1);
			color = (axis->ed_displayRow==0)?DARK_GRAY:BLUE;
			s_printfxyt (-1, -1, color, ":  %c   %-15s %s", axis->ed_label, axis->ed_name,
						axis->ed_displayDiam?"YES":" NO");
			if (axis==compoundAxis)
			{
				pX= axis->ed_compoundX;
				pZ= axis->ed_compoundZ;
				s_printfxyt (-1, -1, color, "  %s, angle %.3f\xF8", 
								(pZ==NULL)?"sum":"vector", axis->ed_compoundAngle);
			}
			if (axis->ed_compoundUnits>0)	/* is this a companion axis? */
			{
				s_printfxyt (-1, -1, color, "  companion %c, ratio %.1f:1", 
					axis==compoundAxis->ed_compoundX?'X':'Z', compoundAxis->ed_units/axis->ed_compoundUnits);
			}
		}
		maxEncoder = encoder-1 + '0';		/* Max encoder number. */

		s_outTextxyt ( 8, 6, BLACK, "Vector axis (angled compound)");
		s_outTextxy  (10, 6, "Sum axis (slide+compound)");
		s_outTextxy  (12, 6, "Angle of compound");
		s_outTextxy  (14, 6, "Diameter display");
		
		s_outCharxyt ( 8, 6, RED, 'V');
		s_outCharxy  (10, 6, 'S');
		s_outCharxy  (12, 6, 'A');
		s_outCharxy  (14, 6, 'D');
		
		if (lastError!=NULL)
		{
			s_outTextxyt (19, 2, RED, lastError);
			lastError = NULL;
		}

		switch (answer=getUCchar())
		{
			/* Summing mode is indicated by having a compound axis set (non-null) and
			   a companion X axis set with no Z axis set. */
			case 'S':
				if (compoundAxis!=NULL && pX!=NULL && pZ!=NULL)	/* See if in vector mode now. */
				{
					lastError = "Now in Vector mode, delete before setting Sum mode.";
					break;
				}
				s_outTextxyt (16, 3, RED, " (zero to delete Sum mode)");
				s_outTextxyt (15, 3, RED, "Enter number of compound axis ? ");
				answerC = getUCchare(BLUE);
				if (answerC=='0')	/* Zero deletes compound setting. */
				{
					if (compoundAxis==NULL)
					{
						lastError = "No compound axis has been set yet!";
						break;
					}
					/* Remove compound definition if '0' */
					pX->ed_compoundUnits = 0;
					pZ->ed_compoundUnits = 0;
					compoundAxis->ed_compoundAngle = 0;
					compoundAxis->ed_compoundX = NULL;
					compoundAxis->ed_compoundZ = NULL;
					compoundAxis = NULL;
					break;
				}
				if (answerC < '1' || answerC > maxEncoder)
				{
					lastError = "Invalid axis number!";
					break;
				}

				s_outTextxyt (16, 3, RED, "Enter number of cross-feed axis ? ");
				answerX = getUCchare(BLUE);
				if (answerX < '1' || answerX > maxEncoder)
				{
					lastError = "Invalid axis number!";
					break;
				}

				compoundAxis = encoderList[answerC-'1'];
				compoundAxis->ed_compoundX = pX = encoderList[answerX-'1'];
				compoundAxis->ed_compoundZ = pZ = NULL;
				compoundAxis->ed_compoundAngle = 90;
				/* One for one compound to companion steps. */
				pX->ed_compoundUnits = compoundAxis->ed_units;
				for (i=0; i<numberOfModes; i++)
					pX->ed_compoundSteps[i] = 0;	/* No compound offset steps. */
				break;

			/* Vector mode is indicated by having a compound axis set (non-null) and
			   both a companion X and Z axis set. */
			case 'V':
				if (compoundAxis!=NULL && pX!=NULL && pZ==NULL)	/* See if in summing mode now. */
				{
					lastError = "Now in Sum mode, delete before setting Vector mode.";
					break;
				}

				s_outTextxyt (16, 3, RED, " (zero to delete Vector mode)");
				s_outTextxyt (15, 3, RED, "Enter number of compound axis ? ");
				answerC = getUCchare(BLUE);
				if (answerC=='0')	/* Zero deletes setting. */
				{
					if (compoundAxis==NULL)
					{
						lastError = "No compound axis has been set yet!";
						break;
					}
					/* Remove compound definition if '0' */
					pX->ed_compoundUnits = 0;
					pZ->ed_compoundUnits = 0;
					compoundAxis->ed_compoundAngle = 0;
					compoundAxis->ed_compoundX = NULL;
					compoundAxis->ed_compoundZ = NULL;
					compoundAxis = NULL;
					break;
				}
				if (answerC < '1' || answerC > maxEncoder)
				{
					lastError = "Invalid axis number!";
					break;
				}

				s_outTextxyt (16, 3, RED, "Enter number of cross-feed axis ? ");
				answerX = getUCchare(BLUE);
				if (answerX < '1' || answerX > maxEncoder)
				{
					lastError = "Invalid axis number!";
					break;
				}

				s_outTextxyt (17, 3, RED, "Enter number of bed (long) axis ? ");
				answerZ = getUCchare(BLUE);
				if (answerZ < '1' || answerZ > maxEncoder)
				{
					lastError = "Invalid axis number!";
					break;
				}
				compoundAxis = encoderList[answerC-'1'];
				compoundAxis->ed_compoundX = pX = encoderList[answerX-'1'];
				compoundAxis->ed_compoundZ = pZ = encoderList[answerZ-'1'];
				for (i=0; i<numberOfModes; i++)
				{
					pX->ed_compoundSteps[i] = 0;	/* No compound offset steps. */
					pZ->ed_compoundSteps[i] = 0;	/* No compound offset steps. */
				}

			case 'A':
				if (compoundAxis==NULL)
				{
					lastError = "No compound axis has been set yet!";
					break;
				}
				if (compoundAxis->ed_compoundZ==NULL)
				{
					lastError = "Not in Vector mode, no angle to set!";
					break;
				}
				_settextcolor (RED);
				angle = 5.7105931374996;	/* Gives 10:1 ratio for default. */
				if (!numbers_only (&angle, 18, 3, "Enter angle of compound [5.7105\xF8] ? ", BLUE)) return;
				compoundAxis->ed_compoundAngle = angle;
				/* SIN/COS ratio of compound to companion steps. */
				pX->ed_compoundUnits = sind(angle) * compoundAxis->ed_units;
				pZ->ed_compoundUnits = cosd(angle) * compoundAxis->ed_units;
				break;

			case 'D':
				s_outTextxyt (15, 3, RED, "Enter number of axis to set diameter display ? ");
				answer = getUCchare(BLUE);
				if (answer < '1' || answer > maxEncoder)
				{
					lastError = "Invalid axis number!";
					break;
				}
				axis = encoderList[answer-'1'];
				axis->ed_displayDiam = !axis->ed_displayDiam;
				break;

			case ESC:
				break;

			case HOME:						/* <Home> go right to commandLoop */
			case TAB:						/* <tab> go right to encoder display mode */
				longjmp(command, answer);	/* Jump to command loop, stuff a HOME command. */

			default:						/* All others answers */
				Sound (ERRnote);
				break;
		} /* End of switch() */
	} /* End of while() */
#endif /* LATHECODE */
} /* End of mainCommand_M_lathe() */







/* This routine allows the user to calibrate the encoder to a measurement artifact of some sort.
   The user is asked to position the axis to one end of the artifact and then move to the other
   end.  The count of encoder steps divided into the length of the artifact gives the number of 
   units per encoder step. */

void mainCommand_M_axis(void)

{
	ENCODER_DATA *axis;
	short answer, encoder, i;
	double calibration, artifact;

	while (TRUE)	/* Loop til user is done (hits ESC) */
	{
		overlay_box (19, 60, "Axis Calibration Process");

		/* Display a list of existing axis and the current calibration value. */
		s_outTextxyt (3, 8, BLACK, "#  Label    Name           Calibration units");
		for (encoder=0,i=1; (axis=encoderList[encoder])!=NULL; encoder++,i++)
		{
			if (axis->ed_displayRow!=0)		/* Axis must exist. */
			{
				s_printfxyt (3+i, 8, RED, "%i", i);
				s_printfxyt (-1, -1, DARK_GRAY, ":   %c     %-15s ", axis->ed_label, axis->ed_name);
				s_printfxyt (-1, -1, BLUE, "%1.15f", axis->ed_units);
			}
		}

		/* Must enter a number between 1 and i (number of valid axis found) */
		answer = 0;
		while (answer<1 || answer>i)
		{
			s_outTextxyt (13, 2, BLACK, "  (ESC if done...)");
			s_outTextxyt (12, 2, BLACK, "Enter # of the axis you wish to calibrate ? ");
			if (answer!=0) Sound (ERRnote);	/* If not first time through, Beep the invalid char. */
			answer = getUCchare(BLUE);		/* Get and echo character to display */
			if (answer==ESC)
				return;
			if (answer==TAB || answer==HOME)/* <Home> or <Tab> go to commandLoop */
				longjmp(command, answer);	/* Jump to command loop, stuff a command. */
			answer -= '0';					/* Convert from ASCII to binary */
		}

		/* Match the answer to the encoder list written above. i.e. the 'n'th encoder that
		   exists. */
		for (encoder=0,i=1; (axis=encoderList[encoder])!=NULL; encoder++)
		{
			if (axis->ed_displayRow!=0)		/* Axis must exist. */
			{
				if (answer==i++)
					break;
			}
		}

		/* First ask for length of calibration artifact. */
		artifact = 0.0;
		if (!numbers_only(&artifact, 13, 2, "Enter length of the calibration measure ? ", BLUE))	/* Get float number, Check for escape flag */
			return;

		/* Next ask user to position to one extreme of calibration artifact. */
		s_outTextxyt (14, 2, BLACK, "Position axis to one end of calibration measure");
		s_outTextxyt (15, 2, BLACK, " then press any key, except Esc.");
		answer = getUCchare(BLUE);			/* Get character */
		if (answer==ESC)
			return;
		if (answer==TAB || answer==HOME)/* <Home> or <Tab> go to commandLoop */
			longjmp(command, answer);	/* Jump to command loop, stuff a command. */

		/* Set axis current value to zero (absolute) and clear error counter, set flags
		   so display routines update screen. */
		if (serialEnable)				/* If talking to an outboard processor. */
			sendZeroCommand (axis->ed_encoder);	/* Set counter in micro. */
		axis->ed_value = 0;
		axis->ed_possible_error_count = -1;
		axis->ed_error_changed = TRUE;

		/* Next ask user to move to other extreme. */
		s_outTextxyt (15, 2, BLACK, "Position axis to other end of calibration measure");
		s_outTextxyt (16, 2, BLACK, " then press any key, except Esc.");
		answer = getUCchare(BLUE);		/* Get character (and read encoders) */
		if (answer==ESC)
			return;
		if (answer==TAB || answer==HOME)/* <Home> or <Tab> go to commandLoop */
			longjmp(command, answer);	/* Jump to command loop, stuff a command. */

		/* Watch for divide by zero if user didn't move axis */
		calibration = (axis->ed_value==0)? 0.0 : (artifact / (double)labs(axis->ed_value));

		if (axis->ed_possible_error_count>0)
			s_printfxyt (16, 2, RED, "%i Errors recorded! May need to move axis slower.", axis->ed_possible_error_count);
		else
			/* Erase the "press any key" */
			s_outTextxy (16, 2, "                                ");

		/* Display value and confirm store of new value. */
		s_outTextxyt (17, 2, BLACK, "New ");
		s_outTextxyt (-1, -1, BLUE, axis->ed_name);
		s_outTextxyt (-1, -1, BLACK, " calibration value is ");
		s_printfxyt (-1, -1, BLUE, "%.14f", calibration);

		s_outTextxyt (18, 2, BLACK, "Store this value for this axis (Y or N) [Y] ? ");
		answer = getUCchare(BLUE);		/* Get character */
		if (answer==TAB || answer==HOME)/* <Home> or <Tab> go to commandLoop */
			longjmp(command, answer);	/* Jump to command loop, stuff a command. */
		if (answer=='Y' || answer==ENTER)
		{
			axis->ed_units = calibration;
			if (axis==compoundAxis)		/* If a compound axis, track change in companion axis. */
			{
				register ENCODER_DATA *pX, *pZ;

				if ((pX=axis->ed_compoundX)!=NULL)
					pX->ed_compoundUnits = sind(axis->ed_compoundAngle) * axis->ed_units;
				if ((pZ=axis->ed_compoundZ)!=NULL)
					pZ->ed_compoundUnits = cosd(axis->ed_compoundAngle) * axis->ed_units;
			}
		}
	} /* End of while(TRUE) */
	return;
} /* End of mainCommand_M_axis() */







/* Configure tachometer.  Configuration control for the tachometer input.  The serial port 
   RI (Ring Indicator) input is used to generate an interrupt pulse for the tachometer.
   DTR and RTS outputs supply power to the LED photosensor device (Omron EE-SV3-B is what I used). */

void mainCommand_M_tach()

{
	short answer=0;

	while (answer!=ESC)
	{
		overlay_box (19, 60, "Tachometer Configuration");

		s_outTextxyt (3, 4, BLACK, rpmEnable?
					"Tachometer disable, currently ENABLED":
					"Tachometer enable, currently DISABLED");
		s_printfxy (5, 4, "Set Address of serial port to use, currently 0x%03X ...", sp.sp_data);
		s_printfxy (7, 4, "Set Pulses per revolution, currently %i ...", rpmFactor);
		s_printfxy (9, 4, "Set Effective Diameter, currently %.3f ...", rpmDiameter);

		s_outCharxyt (3, 4, RED, 'T');
		s_outCharxy (5, 8, 'A');
		s_outCharxy (7, 8, 'P');
		s_outCharxy (9, 18, 'D');

		switch (answer = getUCchar())
		{
			case 'T':	/* Toggle state of Enable/Disable tachometer. */
				if (rpmEnable)
				{
					rpmEnable = FALSE;				/* Was enabled, disable now. */
				}
				else
				{
					rpmEnable = TRUE;				/* Was disabled, enable now. */
					if (sp.sp_data == 0)			/* If no port assigned. */
						selectSerialPort ();		/* Assign a port number now. */
					else
					{	/* Configure the tachometer serial port. */
						if (sp.sp_irq != 0)			/* If not already configured. */
						{
							serialPortConfig(&sp);
							if (sp.sp_irq == 0)			/* Serial port config failed. */
							{
								/* Red box with yellow text prompt */
								drop_box ( 7, 35, 0, 0, "Serial Port Error!", RED);
								s_printfxyt (3,4, YELLOW, "Port %i(0x%03X) setup failed", 
											sp.sp_data, sp.sp_data);
								s_outTextxy (4,4, "Tachometer turned off.");
								s_outTextxy (5,4, "Press any key to continue.");
								rpmEnable = FALSE;
								getUCchar();
							}
						}
					}
				}
				break;

			case 'A':	/* Port address. */
				selectSerialPort ();
				break;

			case 'P':	/* Pulse count per revolution. */
				s_outTextxyt(12, 4, BLACK, "Enter number of sensor pulses");
				s_outTextxy (13, 4, "per revolution of spindle");
				intnumbers_only (&rpmFactor, 14, 4, " ? ", BLUE, 10);	
				break;

			case 'D':	/* Effective Diameter. */
				s_outTextxyt(12, 4, WHITE, "Enter diameter of tool or workpiece.");
				s_outTextxy (13, 4, "Used to display Surface Feet per Minute.");
				numbers_only (&rpmDiameter, 14, 4, " ? ", BLUE);
				rpmCircumference = (int)((PI*rpmDiameter)*100.0); /* Compute circumference,scale up by 100. */
				rpmCurrent += 1;			/* Force an RPM update, make the count change */
				break;

			case ESC:	/* Exit this window. */
				break;

			case HOME:	/* <Home> go right to encoder display mode */
			case TAB:	/* <Tab> go right to encoder display mode */
				longjmp(command, answer);	/* Jump to command loop, stuff a command. */

			default:						/* All others answers */
				Sound (ERRnote);
				break;
		} /* End of switch */
	} /* End of while */
	return;
} /* End of mainCommand_M_tach() */







void mainCommand_M_az()

{
	short answer=0;

	while (answer!=ESC)
	{
		overlay_box (15, 50, "Autozero Configuration");

		s_printfxyt (5, 3, BLACK, "Set AutoZero probe diameter, currently %.3f", autoZeroOffset*2.0);

		s_outCharxyt (5, 7, RED, 'A');

		switch (answer = getUCchar())
		{
			case 'Z':	/* Autozero probe Diameter. */
				s_outTextxyt (9, 4, RED, "Enter diameter of autozero probe.");
				numbers_only (&autoZeroOffset, 10, 4, " ? ", BLUE);
				autoZeroOffset = autoZeroOffset / 2.0; /* Compute radius. */
				break;

			case ESC:	/* Exit this window. */
				break;

			case HOME:	/* <Home> go right to encoder display mode */
			case TAB:	/* <Tab> go right to encoder display mode */
				longjmp(command, answer);	/* Jump to command loop, stuff a command. */

			default:						/* All others answers */
				Sound (ERRnote);
				break;
		} /* End of switch */
	} /* End of while */
	return;
} /* End of mainCommand_M_az() */







/* Ask user for serial port configuration. 
   The standard DOS serial port configuration is:
	COM1:   IRQ 4     Address 03F8
	COM2:   IRQ 3     Address 02F8
	COM3:   IRQ 4     Address 03E8
	COM4:   IRQ 3     Address 02E8
   Returns TRUE of a port was set.
*/

int selectSerialPort ()

{
	short i, answer, error=FALSE;
	char string[80], temp[16];
	SERPORT ser, *p;

	ser = sp;			/* Copy current port data into a local temp area. */

	do
	{
		answer = ' ';
		while (answer!=ENTER)
		{
			overlay_box (15, 50, "Serial Port Selection");

			s_outTextxyt (15,3, RED, "<Enter>");
			_settextcolor (YELLOW);
			s_outText (" to accept current");
			s_outTextxyt (15,34, RED, "<Esc>");
			_settextcolor (YELLOW);
			s_outText (" to abort");

			s_outTextxyt (3,9, BLACK, "Name     Address         IRQ#   Type");
			ser.sp_com = 0;		/* No match on COMn yet */

			/* Display a list of ports found in the DOS low memory list. */
			for (p=SPtable,i=1; p<SPtable+(sizeof(SPtable)/sizeof(SERPORT))&&p->sp_data!=0; p++,i++) 
			{
				s_printfxyt (3+i, 6, RED, "%i", i);	/* Number each table entry in RED. */
				s_printfxyt (-1, -1, BLACK, ": COM%i   %4i(0x%03X)        %s     %s", i, p->sp_data,
						p->sp_data, p->sp_irq==0 ? "?" : itoa(p->sp_irq,temp,10), spTypeStr[p->sp_type]);

				if (ser.sp_data == p->sp_data)		/* If the port address matches */
					ser.sp_com = p->sp_com;			/* Say this is the same COMn */
			}
			s_outCharxyt (3+i,6, RED, 'C');
			s_outTextxyt (-1, -1, BLACK, ": Enter Custom address (for advanced users)");
			s_outTextxy (4+i,9, "");

			/* Display current settings. */
			s_outTextxyt (10, 2, BLACK, "Current setting:");

			if (ser.sp_com == 0)				/* If no match was found, show address in decimal */
			{
				if (ser.sp_data == 0)			/* If address is zero, no port defined. */
					sprintf (string, "no port defined");
				else
					sprintf (string, "port %i(0x%03X) IRQ%s int addr 0x%02X type %s", 
							ser.sp_data, ser.sp_data, ser.sp_irq==0 ? "?" : itoa(ser.sp_irq,temp,10), 
							ser.sp_irq==0 ? 0 : ser.sp_intno, spTypeStr[ser.sp_type]);
			}
			else
				sprintf (string, "COM%i IRQ%s int addr 0x%02X type %s", 
						ser.sp_com, ser.sp_irq==0 ? "?" : itoa(ser.sp_irq,temp,10), 
						ser.sp_irq==0 ? 0 : ser.sp_intno, spTypeStr[ser.sp_type]);
			s_outTextxyt (11, 4, BLACK, string);

			s_outTextxyt (13, 2, BLACK, "Choose an entry from list: ");
			switch (answer=getUCchare(BLUE))
			{
				case '1':						/* number 1 */
				case '2':						/* number 2 */
				case '3':						/* number 3 */
				case '4':						/* number 4 */
					/* Check for invalid (no port addr defined). */
					if (SPtable[answer-'1'].sp_data==0)
					{
						Sound (ERRnote);
						answer = ' ';
						break;
					}
					ser = SPtable[answer-'1'];	/* Copy desired table entry into our temp. */

					if (ser.sp_irq < 1)
					{
						s_printfxyt  (13,2,   RED, "Could not determine IRQ for port %i(0x%03X)", 
										ser.sp_data, ser.sp_data);
						s_outTextxyt (15,3, BLACK, "                              ");
						_settextcolor (RED);	/* Color the prompt text. */
						if (!intnumbers_only ((short*)&ser.sp_irq, 14, 2, "Enter Decimal IRQ number to use ? ", BLUE, 10))
							return FALSE;		/* FALSE is returned from numbers_only if the user hit the escape key */
						setSerialIntNo (&ser);
					}
					break;

				case TAB:						/* <tab> go right to encoder display mode */
				case HOME:						/* <Home> go right to encoder display mode */
					longjmp(command, answer);	/* Jump to command loop, stuff a command. */

				case 'C':						/* Custom entry. */
					s_outTextxyt (15,3, BLACK, "                              "); /* Blank out "<Enter> to accept..." */
					s_outTextxyt (14, 4, BLACK, "(enter in decimal or prefix hex with '0x')");
					_settextcolor (RED);		/* Color the prompt text. */
					if (!intnumbers_only ((short*)&ser.sp_data, 13, 2, "Enter Serial Port Address to use: ", BLUE, 0))
						return FALSE;			/* FALSE is returned from numbers_only if the user hit the escape key */
					s_outTextxyt (14, 4, BLACK, "                                          ");
					_settextcolor (RED);		/* Color the prompt text. */
					if (!intnumbers_only ((short*)&ser.sp_irq, 14, 2,  "Enter Decimal IRQ number to use: ", BLUE, 10))
						return FALSE;			/* FALSE is returned from numbers_only if the user hit the escape key */
					setSerialIntNo (&ser);
					break;

				case ESC:						/* The ever available abort. */
					return FALSE;

				case ENTER:						/* No change */
					break;

				default:						/* All others answers */
					Sound (ERRnote);
					break;
			}
		} /* End of while (answer!=ENTER) */

		/* Check if port does not have an IRQ set. Allow user to ignore error if desired. */
		if (ser.sp_irq==0)	
		{
			/* Red box with yellow text prompt */
			drop_box ( 5, 65, 0, 0, "Serial port error?", RED);
			s_printfxyt (3, 3, YELLOW, "Serial port decimal %i (0x%03X) has no IRQ level set.",
							ser.sp_data, ser.sp_data);

			s_outTextxy (4, 3, "Continue anyway (Y or N) [N] ? ");
			if ((answer=getUCchare(BLUE))=='Y') 
				error = FALSE;		/* If Yes, live with the "error".*/
			else
				error = TRUE;		/* If No, try again. */
		}
	} while (error==TRUE&&answer!=ENTER);

	disableSPIRQ(&sp);				/* Remove old port IRQ stuff. */
	sp = ser;						/* User has accepted the port, store as the current port */
	serialPortConfig(&sp);			/* May have changed ports, set config bits. */
	enableSPIRQ(&sp);				/* Turn on interrupts after address change. */
	if ((rpmEnable) && (sp.sp_irq == 0))	/* If tach was enabled and config failed. */
	{
		rpmEnable = FALSE;
		drop_box ( 7, 35, 0, 0, "Serial Port Error!", RED); /* Red box with yellow text prompt */
		s_printfxyt (3,4, YELLOW, "Port %i(0x%03X) setup failed", sp.sp_data, sp.sp_data);
		s_outTextxy (4,4, "Tachometer turned off.");
		s_outTextxy (5,4, "Press any key to continue.");
		getUCchar();
	}
	return TRUE;
} /* End of selectSerialPort() */






/* Update the displayed numbers based on the stored encoder data and current modes.
   Call with a pointer to an encoder data structure.

   Called while in encoder screen display mode. */

void updateEncoderDisplay(register ENCODER_DATA *axis)

{
	char theString[41];
	long int printValue;
	double printFloat;
	static char *formatString[7] = {	/* Indexed by decimal places user wants to show on display (0-6). */
		"% 6.0f      ", "% 7.1f     ", "% 8.2f    ", "% 9.3f   ", "% 10.4f  ", "% 11.5f ", "% 12.6f"};

	if (axis->ed_rotaryOn)				/* If rotary display mode, show decimal degrees or deg,min,sec */
	{
		/* Mod by <steps per revolution> to get current rotary position (throw away the 
		   rotations part).  Then apply conversion factor. */
		printValue = axis->ed_value - axis->ed_offset[currentMode];
		printFloat = (double)(printValue % axis->ed_number_of_blips) * axis->ed_rotaryFactor;  
		pollParallelPort;				/* Poll after floating point operations. */

		/* Decide if it should be displayed in decimal degrees or minutes and secs. */
		if (axis->ed_display_min)
			strcpy(theString, decimal_to_degrees(printFloat));
		else
			sprintf(theString, "% 10.4f\xF8", printFloat);	/* Embed a degree thing. */
		pollParallelPort;				/* Poll after floating point operations. */

		if (axis->ed_show_revolutions)	/* If rotary and rotations display ... */
			update_rotary_count(axis);
	}
	else	/* Rotary is off. */
	{
		if (axis->ed_backlash)		/* If doing blacklash, number displayed is based on backlash */
		{
			if ((printValue = checkbacklash(axis)) == 999)
				return;			/* 999 is a flag to not print yet. */
		}
		else
			printValue = axis->ed_value;

		/* ed_currently_displayed will always be the number that would be displayed no matter if 
		   backlash is on or off. Since the modes have been added ed_currently_displayed is only 
		   what is displayed on the master mode.  When other modes are enabled, it still holds 
		   what would have been printed on the master mode */
		axis->ed_currently_displayed = printValue;

		/* Convert from step units to floating point */
		printFloat = (double)printValue * axis->ed_units;
		
		/* If this axis is a companion to a lathe compound, add the extra steps, based on mode 0. */
		if (axis->ed_compoundSteps[0]!=0)
			printFloat += (double)axis->ed_compoundSteps[0] * axis->ed_compoundUnits;

		pollParallelPort;				/* Poll after floating point operations. */
		
		/* Subtract the proper offset, which is the position the encoder was in 
		   when the user reset this mode to zero, and convert to display units */
		if (currentMode!=0)
			printFloat -= ((double)axis->ed_offset[currentMode] * axis->ed_units) + 
			              ((double)axis->ed_compoundSteps[currentMode] * axis->ed_compoundUnits);

		/* If in lathe "diameter" mode, double the value. */
		if (axis->ed_displayDiam)
			printFloat *= 2;

		if (axis->ed_conversion!=0)		/* Apply E/M metric conversion. */
			printFloat *= (axis->ed_conversion==1?25.4:.03937);

		axis->ed_lastValue = printFloat;	/* Last fully adjusted value displayed to screen. */

		/* Convert printFloat into a string, using appropiate number of decimal places. */ 
		sprintf(theString, formatString[axis->ed_decimal_places_shown], printFloat);
		pollParallelPort;				/* Poll after floating point operations. */
	}

	/* Sweep number string and translate digits to the special big font numbers */
	if (graphicMode)
	{
		register unsigned char *p=theString-1;		/* Pointer to number string */
		while (*++p!=0)
			if ((*p>='+')&&(*p<='9')) *p += 84;		/* Offset each number by 84 */
	}

	/* Display only the characters from the number string that have changed. */
	s_outTextxytbDiff ( axis->ed_displayRow, 13, BRIGHT_WHITE, DARK_GRAY, theString, axis->ed_displayString);

	if (axis->ed_displaySlew && !axis->ed_rotaryOn)	/* If user wants slew rate displayed */
	{
		/* Display the slew rate for this axis. Convert count last interval into distance with ed_units.
		   Then divide by the actual time of the interval (just under .5 seconds) divided by 60 to get
		   distance over one minute of time. */
		printFloat = fabs(axis->ed_slewLast-axis->ed_slewLastLast);
		if (printFloat != axis->ed_slewValue)	/* Only display if a change in slew count. */
		{
			axis->ed_slewValue = printFloat;	/* Update with new slew value now */
			printFloat /= (0.494505/60.0);
			if (axis->ed_conversion!=0)			/* Apply metric conversion. */
				printFloat *= (axis->ed_conversion==1?25.4:.03937);
			if (printFloat > 999.9)				/* Don't overflow display number field. */
				strcpy (theString, "999.9");
			else
				sprintf (theString, "%5.1f", printFloat);
			s_outTextxytbDiff ( axis->ed_displayRow, 27, BRIGHT_WHITE, DARK_GRAY, theString, axis->ed_displayIPMString);
		}
	}

	/* if the user has the possible error thingy turned ON in the setup screen... */
	if (axis->ed_displayError && axis->ed_error_changed)
		update_error_count(axis);

	axis->ed_changed = FALSE;			/* Clear the change flag, we have updated the display for this axis. */

	if (rpmEnable)
		updateTachometerDisplay();		/* If the tachometer is enabled update display. */

	/* if the user enters a new value in master mode 0 this backlash thing will get 
	turned on (it will not get turned off unless backlash is turned on so I shut 
	them off here in case they are on */
	encoder1.ed_zero_all_mode=0;
	encoder2.ed_zero_all_mode=0;
	encoder3.ed_zero_all_mode=0;
	encoder4.ed_zero_all_mode=0;
	encoder1.ed_zero_all=0;
	encoder2.ed_zero_all=0;
	encoder3.ed_zero_all=0;
	encoder4.ed_zero_all=0;
	return;
} /* End of updateEncoderDisplay() */







/* Update the displayed numbers based on the stored RPM count.

   Called while in encoder screen display mode. */

void updateTachometerDisplay(void)

{
	int sfm;
	long int rpm;
	char theString[16];
	static long int rpmLast=0;

	if (rpmCurrent == rpmLast)			/* Don't display if no change. */
		return;
	rpmLast = rpmCurrent;				/* Store current as new last displayed. */

	rpm = (rpmCurrent*60l)/rpmFactor;	/* Compute RPM from pulses received during last second. */
if (rpm<0)
	rpm = -rpm;
	if (labs(rpm) > 9999)				/* Don't overflow display number field. */
		strcpy (theString, "9999");
	else
		sprintf(theString,"%4i", rpm);	/* Build a number display string */

	/* Write the characters from the number string that have changed. */
	s_outTextxytbDiff ( 5, 20, BRIGHT_WHITE, DARK_GRAY, theString, lastRPMString);

	if (rpmDiameter!=0)					/* A non zero diameter means display Surface Feet/Minute */
	{
		/* Compute Inches Per Minute, divide by 12 to convert to feet.  Circumference is scaled up 
		   by 100, so divide by 100 also. */
		sfm = (int)( (rpm*(long)rpmCircumference) / 1200l);

		if (sfm > 999)					/* Don't overflow display number field. */
			strcpy (theString, "999");
		else
			sprintf(theString,"%3i", sfm);

		s_outTextxytbDiff ( 5, 28, BRIGHT_WHITE, DARK_GRAY, theString, lastSFMString); /* Col 28, row 5 */
	}
}







void update_error_count(ENCODER_DATA *axis)

{
	char theString[8];

	axis->ed_error_changed = FALSE;

	if (axis->ed_possible_error_count>9999)	/* Wrap error count at 10,000 */
		axis->ed_possible_error_count = 1;

	/* Starts out at -1 to account for first two errors as encoders sync up. */
	if (axis->ed_possible_error_count<=0)
		strcpy (theString, "E0000");	/* Don't display negative value. */
	else
		sprintf (theString, "E%04li", axis->ed_possible_error_count);

	s_outTextxytbDiff ( axis->ed_displayRow+(short)1, 34, WHITE, BLACK, 
							theString, axis->ed_errorCountString);
}







void update_rotary_count(ENCODER_DATA *axis)

{
	char theString[41];
	int theNumber;

	/* Number of rotations is given by (number of steps) / (steps per revolution) */
	theNumber = (int)labs(axis->ed_value / axis->ed_number_of_blips);

	if (theNumber>999)			/* Wrap rev count at 1,000 */
		theNumber %= 1000;

	/* Only update display if number of rotations has changed. */
	if (theNumber!=axis->ed_revolutionsDisplayed)
	{
		axis->ed_revolutionsDisplayed = theNumber;
		sprintf (theString, "%3i", theNumber);
		s_outTextxytbDiff ( axis->ed_displayRow, 27, BRIGHT_WHITE, DARK_GRAY, theString, axis->ed_rotaryCountString);
	}
}





/* Selection of input mode.  initial is TRUE if this is a call to set value
   for the first time (i.e. no config file, first time program ever run)
   Retruns FALSE if ESC is entered.
*/

int selectInputMode(short initial)

{
	short answer=0;

	while (answer == 0)
	{
		overlay_box (15, 50, "Input Mode Selection");

		s_outTextxyt (15,3, RED, "<Enter>");
		_settextcolor (YELLOW);
		s_outText (" for default value");
		s_outTextxyt (15,34, RED, "<Esc>");
		_settextcolor (YELLOW);
		s_outText (" to Cancel");

		if (initial)
			s_outTextxyt (3, 3, BLACK, "No encoder input mode has been selected.");
		else if (parallelEnable)
			s_outTextxyt (3, 3, BLACK, "Now configured to use a parallel port interface.");
		else if (serialEnable)
			s_outTextxyt (3, 3, BLACK, "Now configured to use a serial port interface.");

		s_outTextxyt (5, 3, BLACK, "Encoders may be connected one of two ways:");
		s_outTextxy  (7, 4, "1) directly to the parallel port (default)");
		s_outTextxy  (8, 4, "2) to an external processor on a serial port");
		s_outTextxyt (7, 4, RED, "1");
		s_outTextxy  (8, 4, "2");

		s_outTextxyt (12, 5, BLACK, "Choose which connection to use [1] ? ");

		switch (answer = getUCchare(BLUE))
		{
			case ENTER:
			case '1':
				if (selectParallelPort ())
				{
					parallelEnable = TRUE;	/* TRUE if using parallel port for input. */
					serialEnable = FALSE;
					return TRUE;			/* Return if something set. */
				}
				answer = 0;					/* Loop if not. */
				break;

			case '2':
				if (selectSerialPort ())
				{
					serialEnable = TRUE;	/* TRUE if using serial port for input. */
					parallelEnable = FALSE;
					return TRUE;
				}
				answer = 0;
				break;

			case ESC:
				return FALSE;			

			case HOME:						/* <Home> go right to commandLoop */
			case TAB:						/* <tab> go right to encoder display mode */
				if (initializationComplete)	/* Comamnd loop not ready if not TRUE */
					longjmp(command, answer);/* Jump to command loop, stuff a HOME command. */

			default:					/* Any other, Beep user. */
				Sound (ERRnote);
		}
	}
	return TRUE;
} /* End of selectInputMode() */








/* Setup the graphics screen mode used to display encoder data.  Reads the font
   from the .FON file to load the custom font used with graphic text output.  
   If no file found or load failure, flag is set to allow execution in text mode.  */

void init_screen_font(void)

{
	int i;
	char fontFile[_MAX_PATH];

	/* Default Font file name is <program name>.FON */
	_makepath(fontFile, programDrive, programDir, programName, "FON");

	if( _registerfonts(fontFile) <= 0 )
	{
		printf ("\n%s: Can't register %s font file.\n\nHigh res graphic mode not available.",
						programName, fontFile);
		printf ("\npress any key to continue ...\n");
		getUCchar();
		noFonts = TRUE;
		graphicMode = FALSE;
		return;
	}

	/* Set highest available graphics mode and get configuration. */
	if( !s_init_outText( _MAXRESMODE ) )
	{
		printf ("\n%s: Can't set _MAXRESMODE, high res graphic mode not available.", programName);
		printf ("\n\npress any key to continue ...\n");
		getUCchar();
		noFonts = TRUE;
		graphicMode = FALSE;
		return;
	}
	_getvideoconfig( &vc );

	/* These are used to compute "character" positions in screen x,y cordinates */
	pix_row = vc.numypixels / 19;		/* 20 rows on screen, numbered 0-19 */
	pix_col = vc.numxpixels / 39;		/* 40 columns on screen, numbered 0-39 */
	
	i = _setfont( "t\'Digits\' h24 b" );
	if (i<0)
	{
		printf ("\n%s: Can't _setfont.\n\nHigh res graphic mode not available.",
						programName, fontFile);
		printf ("\npress any key to continue ...\n");
		getUCchar();
		noFonts = TRUE;
		graphicMode = FALSE;
		return;
	}

	sprintf(fontSelect, "n%i", i);		/* Build a string to select this font in the future */

	if (_getfontinfo( &fi )<0)
	{
		printf ("\n%s: Can't _getfontinfo.\n\nHigh res graphic mode not available.",
						programName, fontFile);
		printf ("\npress any key to continue ...\n");
		getUCchar();
		noFonts = TRUE;
		graphicMode = FALSE;
		return;
	}

	if (fi.pixwidth==0)
		fi.pixwidth = 18;				/* Set width to size of our custom font digits. */
	noFonts = FALSE;
}







/* Pop up a drop shadow box centered on screen.  If the title string is non-NULL, it is
   centered on line one of the box. */

void overlay_box (short r, short c, char *title)

{
	buildMainBackground(FALSE);		/* Clear to background. */

	drop_box (r, c, 0, 0, title, WHITE);
}







/* Draw a drop box character based "window" of height 'h' and width 'w'.  With
   top left corner at row R and column C.  If R and/or C are zero, center on 
   full screen.  The window is the given color with a BLACK shadow. */


void drop_box (short h, short w, short r, short c, char *title, short color)

{
	_settextcolor (BLACK);
	_setbkcolor ((long)color);

	if (r==0)
		r = ((screen_height-h)/2);	/* Center row. */

	if (c==0)
		c = ((screen_width-w)/2);	/* Center column. */

	/* Make a BLACK background shadow box one space off the given row/col and 
	   of the given height/width. */
	s_setTextWindow (r+1, c+1, r+h, c+w, BLACK);

	/* Make a 'color' foreground box at the given row/col of the given height/width. */
	s_setTextWindow (r, c, r+h-1, c+w-1, color);

	/* BLACK title bar with BRIGHT_WHITE text. */
	if (title!=NULL)
	{
		short i;

		/* Make a BLACK bar across the top of the current window. */
		for (i=1; i<=window_width; i++)
			s_outCharxytb ( 1, i, BRIGHT_WHITE, BLACK, ' ');
		
		/* Center the given title text in the black bar. */
		centered_text (1, title);
	}
	_settextcolor (BLACK);
	_setbkcolor ((long)color);
}







/* Write a string on the given row centered on the window width. */

void centered_text(short row, unsigned char *string)

{
	s_outTextxy (row, (short)(((window_width-strlen(string))/2)+1), string);
}







/* Writes the given newString to the display, only for the character positions
   that are different from the oldString.  Minimizes screen writing.  Assumes the
   strings are the same length! */

void s_outTextxytbDiff ( short row, short col, short textColor, short bkgdColor, char *newString, char *oldString)

{
	register char *np, *op;
	register short i;

	/* Compare new string to old, and only send changed characters. */
	np = newString-1;				/* Pointer to new number string */
	op = oldString;					/* Pointer to old number string */
	for (i=0; *++np!=0; i++,op++)
	{
		if (*np!=*op)				/* Only write to display if byte has changed. */
		{
			if (graphicMode)
				colorGchar ((col-1)*pix_col+(i*fi.pixwidth), (row-1)*pix_row, textColor, bkgdColor, (unsigned char)*np);
			else
				s_outCharxytb ( row, col+i, textColor, bkgdColor, *np);
			*op = *np;				/* Copy new string as "new" old string */
		}
	}
}









/* Print a graphic string to screen with color.  Location is given by x,y, if either
   is -1 neither is used.  If color is -1 it is not used. */

void colorGtext(short x, short y, short color, char *string)

{
	if (color!=-1)
		_setcolor (color);			/* Set text color. */
	if ((x!=-1)&&(y!=-1))
		_moveto (x, y);				/* Set position. */
	pollParallelPort;				/* Display update is slow, so poll after each call. */
	_outgtext(string);
	pollParallelPort;				/* Display update is slow, so poll after each call. */
}









/* Print a graphic char to screen with color.  Background is erased before the character
   is written.  Pixel location is given by col, row.  If any parameter is -1, it is
   not changed from current setting. */

void colorGchar (short col, short row, short color, short background, char c)

{
	static char string[2] = {0,0};

	string[0] = c;

	/* Need to blank out the old displayed char */
	_setcolor(background);

	/* Clear the character space. */
	_rectangle(_GFILLINTERIOR, col, row, col+fi.pixwidth-1, row+fi.pixheight-1);
	pollParallelPort;				/* Display update is slow, so poll again. */

	_setcolor (color);				/* Set text color. */
	_moveto (col, row);				/* Set position. */
	pollParallelPort;				/* Display update is slow, so poll after each call. */
	_outgtext(string);
	pollParallelPort;				/* Display update is slow, so poll after each call. */
}









/* Set and remember video mode.  Remembered so we can reduce the number of times the screen
   flashes switching between modes.  Mode is not changed if it already matches. */

int s_init_outText (short mode)
{
	static short oldMode = -1;
	int result = TRUE;

	if (oldMode != mode)				/* No change... */
		result = _setvideomode (mode);	/* Set new mode */

	oldMode = mode;						/* Remember new mode. */
	_wrapon( _GWRAPOFF );				/* Not wrap mode. */
	screen_width = s_getscreenwidth;	/* Reset screen width to match. */
	screen_height = s_getscreenheight;	/* Reset screen height to match. */
	screen_height += 1;					/*  value is -1. */
	window_height = screen_height;		/* Initially a full screen window. */
	window_width = screen_width;
	_settextcursor (CURSOR_OFF);
	return result;
}







/* Set text write position in row, columns relative to current window. If either
   row or col is -1, it is not changed from current value. */

void s_setTextPosition ( register short row, register short col)
{
	struct rccoord position;

	if ((row == -1) || (col == -1))
	{
		position = _gettextposition();
		if (row == -1) row = position.row;
		if (col == -1) col = position.col;
	}

	_settextposition (row, col);
}








/* Write a single character at given row, column with current attribute. */

void s_outCharxy (short row, short col, unsigned char c)
{
	s_setTextPosition (row, col);
	_outmem ( &c, 1);
}





/* Write a single character at given row, column with given text color and current
   background attribute. */

void s_outCharxyt (short row, short col, short textColor, unsigned char c)
{
	_settextcolor (textColor);
	s_outCharxy (row, col, c);
}





/* Write a single character at given row, column with given text color and given 
   background attribute. */

void s_outCharxytb (short row, short col, short textColor, short backColor, unsigned char c)
{
	_setbkcolor ((long)backColor);
	s_outCharxyt (row, col, textColor, c);
}




/* Write a character string at current row, column with current attribute. */

void s_outText (unsigned char *string)
{
	_outtext (string);
}





/* Write a character string at given row, column with current attribute. */

void s_outTextxy (short row, short col, unsigned char *string)
{
	s_setTextPosition (row, col);
	s_outText (string);
}





/* Write a character string at given row, column with given text color and current 
   background attribute. */

void s_outTextxyt (short row, short col, short textColor, unsigned char *string)
{
	_settextcolor (textColor);
	s_outTextxy (row, col, string);
}





/* Write a character string at given row, column with given text color and given 
   background attribute.
*/

void s_outTextxytb (short row, short col, short textColor, short backColor, unsigned char *string)
{
	_setbkcolor ((long)backColor);
	s_outTextxyt (row, col, textColor, string);
}




/* Formated string output to current row, column with current attributes. */

void s_printf (const char *format,...)
{
	va_list args;
	unsigned char buffer[512];

	va_start (args, format);
	vsprintf (buffer, format, args);
	va_end (args);
	s_outText (buffer);
}





/* Formated string output to given row, column with current attributes. */

void s_printfxy (short row, short col, const char *format,...)
{
	va_list args;
	unsigned char buffer[512];

	va_start (args, format);
	vsprintf (buffer, format, args);
	va_end (args);
	s_outTextxy (row, col, buffer);
}




/* Formated string output to given row, column with given text color and current background. */

void s_printfxyt (short row, short col, short  textColor, const char *format,...)
{
	va_list args;
	unsigned char buffer[512];

	va_start (args, format);
	vsprintf (buffer, format, args);
	va_end (args);
	s_outTextxyt (row, col, textColor, buffer);
}




/* Formated string output to given row, column with given text color and given background. */

void s_printfxytb (short row, short col, short  textColor, short  backGround, const char *format,...)
{
	va_list args;
	unsigned char buffer[512];

	va_start (args, format);
	vsprintf (buffer, format, args);
	va_end (args);
	s_outTextxytb (row, col, textColor, backGround, buffer);
}






void s_setTextWindow (short top, short left, short bottom, short right, short color)
{
	window_top = top;
	window_left = left;
	window_bottom = bottom;
	window_right = right;

	window_height = bottom - top + 1;
	window_width = right - left + 1;
	_settextwindow (top, left, bottom, right);
	if (color != -1)
		s_clearScreen (_GWINDOW, color);
}





void s_clearScreen (short area, short color)

{
	if (color != -1)
		_setbkcolor (color);
	_clearscreen (area);
}







long int checkbacklash(ENCODER_DATA *axis)

{
#ifdef BACKLASHCODE
	long int dif;
	int far_enough = FALSE;
	
	/* Handle sign swaping of the axis */
	if (axis->ed_swapsign)
	{
		axis->ed_swapsign = FALSE; 
		axis->ed_rightleft = !axis->ed_rightleft;			/* Invert direction flag */
		axis->ed_lasttime = -axis->ed_lasttime;				/*  and swap sign of these... */
		axis->ed_lastlasttime = -axis->ed_lastlasttime;
		axis->ed_lastlastlasttime = -axis->ed_lastlastlasttime;
		axis->ed_changed_direction_point = -axis->ed_changed_direction_point;
		if (axis->ed_value > axis->ed_lastlastlasttime)		/* back one unit if needed. */
		{
			axis->ed_lasttime = axis->ed_value - 1;
			axis->ed_lastlasttime = axis->ed_lasttime - 1;
			axis->ed_lastlastlasttime = axis->ed_lastlasttime - 1;
		}
		if (axis->ed_value < axis->ed_lastlastlasttime)		/* back one unit, other direction, if needed. */
		{
			axis->ed_lasttime = axis->ed_value + 1;
			axis->ed_lastlasttime = axis->ed_lasttime + 1;
			axis->ed_lastlastlasttime = axis->ed_lastlasttime + 1;
		}
	}
 
	if (axis->ed_zero_all)			/* this if in incase the user rezeros from the master mode 0 */
	{
		axis->ed_zero_all = FALSE;
		axis->ed_changed_direction_point = 0;
		if (serialEnable)				/* If talking to an outboard processor. */
			sendZeroCommand (axis->ed_encoder);	/* Set counter in micro. */
		axis->ed_value = 0;
		if (!axis->ed_tension)
		{
		/* fldk(1 and 2) along with swaping the 1 and 2 around in InPort where it figures 
		   what value to use from the binary[8] */
			axis->ed_lasttime = 1;
			axis->ed_lastlasttime = 1*2;
			axis->ed_rightleft = TRUE;
		}
		else
		{
			axis->ed_lasttime = -1;
			axis->ed_lastlasttime = axis->ed_lasttime - 1;
			axis->ed_rightleft = FALSE;
		}  
	}

	pollParallelPort;				/* poll again. */

	if (axis->ed_zero_all_mode)
	/* this if in incase the user rezeros while in mode 1,2,3 or 4 (but not master mode 0)
	   Without it it will sometime show zero or the backlash distance, or nothing.  With 
	   it it will redraw with the current changes this if has done. */
	{
		axis->ed_zero_all_mode = FALSE;				/* Clear flag */
		axis->ed_changed = TRUE;					/* Pretend the value changed, so we redraw. */
		axis->ed_changed_direction_point = axis->ed_value; /* Remember the value as we changed direction. */
		
		if (currentMode==0) /* user this for modesetup when the user enters a new value (used to reset to whatever they enter) */
		{
			if (axis->ed_tension)
			{
				axis->ed_lasttime = axis->ed_value - 1;
				axis->ed_lastlasttime = axis->ed_lasttime - 1;
				axis->ed_rightleft = FALSE;
			}
			else
			{
				axis->ed_lasttime = axis->ed_value + 1;
				axis->ed_lastlasttime = axis->ed_lasttime + 1;
				axis->ed_rightleft = TRUE;
			}
		}
		else  /* else for all the other modes (1,2,3,4) do this for rezeroing */
		{
			if (!axis->ed_tension) /* swap rightleft around (1 and 2) a */
			{
				axis->ed_lasttime = axis->ed_changed_direction_point + 1;
				axis->ed_lastlasttime = axis->ed_lasttime + 1;
				axis->ed_rightleft = TRUE;
			}
			else 
			{
				axis->ed_lasttime = axis->ed_changed_direction_point - 1;
				axis->ed_lastlasttime = axis->ed_lasttime - 1;
				axis->ed_rightleft = FALSE;
			}
		}
		return axis->ed_value;
	}

	pollParallelPort;				/* poll again. */
	
	if ((axis->ed_value>axis->ed_changed_direction_point)&&(axis->ed_rightleft==0))
		far_enough = TRUE; /* backlash_direction would be set to 0 */
	if ((axis->ed_value<axis->ed_changed_direction_point)&&(axis->ed_rightleft==1))
		far_enough = TRUE; /* backlash_direction would be set to 1 */
	
	if (axis->ed_changed_direction_point>axis->ed_value)      /* gone down since the last change point */
	{
		dif = axis->ed_changed_direction_point-axis->ed_value;
		if (dif>=axis->ed_backlash)
			far_enough=1;
	}
	if (axis->ed_changed_direction_point<axis->ed_value)      /* gone up since the last change point */
	{
		dif = axis->ed_value-axis->ed_changed_direction_point;
		if (dif>=axis->ed_backlash)
			far_enough=1; 
	}
	
/* these couts are for testing  
 	if (mywatch==1) 
 	{
 	   _settextposition (14,1); 
 	   cout<<"\n"<<"dif "<<dif<<"\n"<<"far_enough "<<far_enough<<"\n"<<"axis->ed_rightleft "<<axis->ed_rightleft);
	   cout<<"\n"<<"axis->ed_value "<<axis->ed_value<<"\n"<<"axis->ed_lasttime "<<axis->ed_lasttime<<"\n"<<"axis->ed_lastlasttime "<<axis->ed_lastlasttime);
 	   cout<<"\n"<<"axis->ed_changed_direction_point "<<axis->ed_changed_direction_point<<"\n"<<"axis->ed_tension "<<axis->ed_tension
 		   <<"\n"<<"axis->ed_lastlastlasttime"<<axis->ed_lastlastlasttime);
 	} */

	if (axis->ed_lastlasttime<axis->ed_lasttime)	/* was going up */
	{
		if (axis->ed_lasttime>axis->ed_value)	/* now going down    */
		{
			if (far_enough==1) 
			{
				axis->ed_changed_direction_point=axis->ed_value; /* set the change point here   */  
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value; 
				axis->ed_rightleft=0;
				return 999;		/* in the calling funciton this will not print anything */
			}
			if (far_enough==0)
			{
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value; 	
				return 999;  /* in the call funciton this will not print anything */
			}	
		}
		/* If still going, compensate if we have moved far enough. */
		if (axis->ed_lasttime<axis->ed_value)
		{
			if (far_enough) 
			{
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value; 
				if (axis->ed_tension==1)
					return axis->ed_value;
				return axis->ed_value-axis->ed_backlash;
			}
			else
			{
				if (axis->ed_rightleft)  
				{
					axis->ed_lastlastlasttime=axis->ed_lastlasttime;
					axis->ed_lastlasttime=axis->ed_lasttime; 
					axis->ed_lasttime=axis->ed_value; 
					if (axis->ed_tension==1)
						return axis->ed_changed_direction_point+axis->ed_backlash;
					return axis->ed_changed_direction_point;
				}
				else
				{
					axis->ed_lastlastlasttime=axis->ed_lastlasttime;
					axis->ed_lastlasttime=axis->ed_lasttime; 
					axis->ed_lasttime=axis->ed_value; 
					if (axis->ed_tension==1)
						return axis->ed_changed_direction_point;
					return axis->ed_changed_direction_point-axis->ed_backlash;   
				}
			}
		}
	}

	pollParallelPort;				/* poll again. */

//	if ((axis->ed_lastlastlasttime>axis->ed_lastlasttime)&&
//		(axis->ed_lastlasttime>axis->ed_lasttime) )
	/* was going down  */
	if (axis->ed_lastlastlasttime>axis->ed_lastlasttime) /* was going down */
	{
		if (axis->ed_lasttime<axis->ed_value)   /* now going up    */
		{
			if (far_enough) 
			{
				axis->ed_changed_direction_point=axis->ed_value; /* set the change point here    */  
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value;  
				axis->ed_rightleft=1;    
				return 999;			/* Flag call funciton to not print anything */
			}
			else
			{
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value; 	
				return 999;			/* Flag call funciton to not print anything */
			}
		}
		if (axis->ed_lasttime>axis->ed_value) /*  still going down means compensate if backlash_direction is 0 */
		{
			if (far_enough==1)		/* &&(backlash_direction==1))  */
			{
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value; 
				if (axis->ed_tension==1)
					return axis->ed_value+axis->ed_backlash;
				return axis->ed_value;
			}
			if ((axis->ed_rightleft==1)&&(far_enough==0))  
			{
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value;  
				if (axis->ed_tension==1)
					return axis->ed_changed_direction_point+axis->ed_backlash;
				return axis->ed_changed_direction_point;
			}
			if ((axis->ed_rightleft==0)&&(far_enough==0))
			{
				axis->ed_lastlastlasttime=axis->ed_lastlasttime;
				axis->ed_lastlasttime=axis->ed_lasttime; 
				axis->ed_lasttime=axis->ed_value; 
				if (axis->ed_tension==1)
					return axis->ed_changed_direction_point;
				return axis->ed_changed_direction_point - axis->ed_backlash;
			}
		}
	}
	axis->ed_lastlastlasttime = axis->ed_lastlasttime;
	axis->ed_lastlasttime = axis->ed_lasttime; 
	axis->ed_lasttime = axis->ed_value; 
	/* should not get to this point.  If it does 777 should show up on the screen
	   will sometimes happen if there are errors */
#endif /* BACKLASHCODE */
	return axis->ed_currently_displayed;
}





/* Init the data block for the given axis. */

void initEncoderData(short encoderNo, char *name, char label, short row)

{
	int i;
	ENCODER_DATA *encoder;

	encoder = encoderList[encoderNo];

	strcpy(encoder->ed_name, name);		/* store given name of this axis */
	encoder->ed_encoder = (unsigned char) encoderNo;	/* Number of this encoder. */
	encoder->ed_label = label;			/* Label used to annotate diaplay screen. */
	encoder->ed_displayName = FALSE;	/* Default to use label on display */
	encoder->ed_displaySlew = TRUE;		/* Flag that indicates slew should be displayed. */
	encoder->ed_changed = FALSE;		/* Flag indicating that an encoder has changed state. */
	encoder->ed_encoderOld = 0;			/* History of encoder state, last two bit value. */
	encoder->ed_value = 0;				/* Current value of this axis (the number we are here for!). */
	encoder->ed_units = .0005;			/* Units in each step of encoder, default to 1/2 thou */
	encoder->ed_displayError = TRUE;	/* Initially displaying errors. */
	encoder->ed_possible_error_count = -1; /* Total number of errors, starts out at -1 to account for encoders sync'ing up. */
	encoder->ed_error_changed = FALSE;	/* Flag to indicate we need to update display counter on screen. */
	encoder->ed_count_reverse = FALSE;	/* For the user to toggle +- in the way it counts (up or down)  */
	encoder->ed_decimal_places_shown = 4;/* number of significent digits in display. */
	encoder->ed_conversion = 0;			/* Metric conversion control */

	encoder->ed_currently_displayed = 0;/* what is currently on the screen no matter if it is currenly compensating */
	encoder->ed_backlashDist = .01;		/* how much to compensate for backlash */
	encoder->ed_backlash = FALSE;		/* Flag this encoder backlash turned on or off */
	encoder->ed_zero_all = FALSE;		/* For zeroing when backlash is ON */
	encoder->ed_zero_all_mode = FALSE;	/*  ... */
	encoder->ed_rightleft = TRUE;		/* direction to compensate for backlash this is not user changeable. */
	encoder->ed_tension = FALSE;		/* tension control flag  */
	encoder->ed_swapsign = FALSE;		/* turned on by user in encode setup to swap the sign of the  */

	encoder->ed_number_of_blips = 720;	/* Counts in a rev */
	encoder->ed_rotaryFactor = 360.0 / 720.0;	/* Conversion factor */
	encoder->ed_rotaryOn = FALSE;		/* Not in rotary display state */
	encoder->ed_display_min = FALSE;	/* TRUE=display minutes and secs. and FALSE=display decimal */
	encoder->ed_show_revolutions = FALSE;/* Show revs */
	encoder->ed_compoundAngle = 0.0;	/* No compound angle set. */
	encoder->ed_compoundZ = NULL;		/* No Z, or long axis */
	encoder->ed_compoundX = NULL;		/* No X, or cross feed axis */

	for (i=0; i<numberOfModes; i++)
	{
		encoder->ed_compoundSteps[i] = 0; /* No compound offset steps. */
		encoder->ed_offset[i] = 0;		/* offset storage set to zero. */
	}
	encoder->ed_displayRow = row;		/* Row on display screen for this axis. */

	return;
} /* End of initEncoderData() */







/* Draws the graphics screen used to display encoder data.  Called from the mainCommand_R()
   routine.  May go into high res graphics mode here. */

void draw_encode_screen()

{
	clearStoredStrings ();				/* Clear the stored strings to force display updates. */
	rpmCurrent += 1;					/* Force an RPM update, make the count change */

	if (graphicMode)
		draw_encode_screen_graphic();
	else
		draw_encode_screen_text();

	encoderDisplayMode = TRUE;			/* Now in "encoderDisplayMode" */
} /* End of draw_encode_screen() */








/* Draws the screen for graphic mode display of encoder data.
   Go into high res graphics mode here. The high res format is
   20 rows of 39 characters. */

void draw_encode_screen_graphic()

{
	int i, color;
	char string[41];
	register ENCODER_DATA *axis;

	s_init_outText (_MAXRESMODE);		/* Go into high res graphics mode. */
	s_clearScreen (_GCLEARSCREEN, BLACK);
	/* Write title on top and a prompt menu across the bottom of the screen with graphic text 
	   output. The text is written with the smaller font.  The title length and font width is 
	   used to center the title info and the pix_row value is used to position menu on "row" 24 */
	colorGtext (((vc.numxpixels-(strlen(machine)*fi.avgwidth))/2)+1, 0*pix_row, YELLOW, machine ); /* Col n, row 1 */
	colorGtext (((vc.numxpixels-(strlen(modeString[currentMode])*fi.avgwidth))/2)+1, (1*pix_row)+6, YELLOW, modeString[currentMode] ); /* Col n, row 2 */
	colorGtext (12*pix_col, (19*pix_row)-fi.pixheight, RED, "M");	/* Col 12, row 20 */
	colorGtext (-1, -1,	YELLOW, "ode");
	colorGtext (17*pix_col+3, (19*pix_row)-fi.pixheight, RED, "L");	/* Col 18, row 20 */
	colorGtext (-1, -1,	YELLOW, "oad");

	colorGtext (22*pix_col, (19*pix_row)-fi.pixheight, RED, "E");	/* Col 23, row 20 */
	colorGtext (-1, -1,	(autoZeroAxis!=NULL)?BLUE:YELLOW, "dge");

	colorGtext (27*pix_col, (19*pix_row)-fi.pixheight, RED, "C");	/* Col 28, row 20 */
	colorGtext (-1, -1,	(autoCenterAxis!=NULL)?BLUE:YELLOW, "enter");
	colorGtext (34*pix_col, (19*pix_row)-fi.pixheight, RED, "0 Esc");

	/* If the tachometer is enabled put up titles */
	if (rpmEnable)
	{
		colorGtext (14*pix_col, 4*pix_row, WHITE, "RPM");	/* Col 15, row 5 */
		if (rpmDiameter!=0)				/* A non zero diameter means display Surface Feet/Minute */
			colorGtext (33*pix_col, 4*pix_row, WHITE, "SFM");	/* Col 35, row 5 */
	}

	if (autoZeroAxisWait)
		/* Say we are in autozero wait-for-axis-move state. */
		colorGtext (1*pix_col, 16*pix_row+8, YELLOW, "Axis to auto-zero ?");
	if (autoCenterAxisWait)
		/* Say we are in autozero wait-for-axis-move state. */
		colorGtext (1*pix_col, 16*pix_row+8, YELLOW, "Axis to auto-center ?");
	if ((autoZeroAxis != NULL) || (autoCenterAxis != NULL))
		/* Say we are in autozero wait-for-probe-touch state. */
		colorGtext (1*pix_col, 16*pix_row+8, YELLOW, "Waiting for edge touch ...");

	/* Loop across all the encoders and setup display. */
	for (i=0; (axis=encoderList[i])!=NULL; i++) /* For each encoder structure */
	{
		if (axis->ed_displayRow==0) /* If row is zero, axis does not display. */
			continue;

		/* Compute pixel location to display encoder output numbers, used in updateEncoderDisplay(). */
		axis->ed_displayPixRow = (axis->ed_displayRow-1) * pix_row;
		axis->ed_displayPixCol = 12 * pix_col;

		/* Blank out the DRO counter display area with a background color. */
		_setcolor (DARK_GRAY);
		_rectangle (_GFILLINTERIOR, axis->ed_displayPixCol, axis->ed_displayPixRow-2, 
					axis->ed_displayPixCol+(12*fi.pixwidth)+1, axis->ed_displayPixRow+fi.pixheight+2);

		/* Add the axis label to a few places (typically X, Y, Z), highlight axis name
		   if this is the axis we are auto-zeroing. */
		color = ((autoZeroAxis == axis)||(autoCenterAxis == axis))? BLUE : WHITE;
		if (axis->ed_displayName)
		{
			sprintf(string, "%+9s", axis->ed_name);	/* Need right-justified 9 char name string... */
			colorGtext (0*pix_col, axis->ed_displayPixRow, color, string);
		}
		else
			colorGchar (8*pix_col, axis->ed_displayPixRow, color, BLACK, axis->ed_label);

		colorGchar ( ((i*2)*(pix_col+2))+3, (19*pix_row)-fi.pixheight, RED, BLACK, axis->ed_label); /* Space the axis prompts one char apart. */ 
		if (axis->ed_rotaryOn)
			colorGtext (28*pix_col, axis->ed_displayPixRow+pix_row, WHITE, "R");
		else
		{
			if (axis->ed_displayDiam)
				colorGtext (28*pix_col, axis->ed_displayPixRow+pix_row, WHITE, "D");
			if (axis->ed_conversion!=0)
				colorGtext (29*pix_col+6, axis->ed_displayPixRow+pix_row, WHITE,
							(axis->ed_conversion==1)?"E":"M");
		}
		if (axis->ed_backlash)
			colorGtext (30*pix_col+12, axis->ed_displayPixRow+pix_row, WHITE, "B");

		if (axis->ed_displaySlew && !axis->ed_rotaryOn)	/* If user wants slew rate displayed */
		{
			if (axis->ed_conversion==1)			/* 0 is off 1 is (inch to mm) and 2 is (mm to inch) */
				colorGtext (33*pix_col, axis->ed_displayPixRow, WHITE, "CmPM");
			else
				colorGtext (33*pix_col, axis->ed_displayPixRow, WHITE, "IPM  ");

			/* Force slewCount to be different so we get a first update. */
			axis->ed_slewValue += .0001;
		}

		/* If the user has the possible error thingy turned ON in the setup screen... */
		if (axis->ed_displayError)
			update_error_count(axis);
	} /* End of for() loop. */
} /* End of draw_encode_screen_graphic() */









/* Draws the text mode screen used to display encoder data. */

void draw_encode_screen_text()

{
	short i, blink;
	char string[41];
	register ENCODER_DATA *axis;

	s_init_outText (_TEXTC40);
	s_setTextWindow (1, 1, 25, 40, BLACK);

	_settextcolor (YELLOW);
	centered_text (1, machine);
	centered_text (2, modeString[currentMode]);

	s_outTextxyt(24, 11, YELLOW, "Mode Load");
	s_outCharxyt(24, 11, RED, 'M');
	s_outCharxy (24, 16, 'L');

	blink = (autoZeroAxis!=NULL)?BLINK:0;
	s_outTextxyt(24, 21, RED+blink, "E");
	s_outTextxyt(-1, -1, YELLOW+blink, "dge");

	blink = (autoCenterAxis!=NULL)?BLINK:0;
	s_outTextxyt(24, 26, RED+blink, "C"); 
	s_outTextxyt(-1, -1, YELLOW+blink, "enter");

	s_outCharxyt(24, 33, RED, '0');
	s_outTextxy (24, 37, "Esc");

	/* If the tachometer is enabled put up titles */
	if (rpmEnable)
	{
		s_outTextxyt (5, 16, WHITE, "RPM");
		if (rpmDiameter!=0)				/* A non zero diameter means display Surface Feet/Minute */
			s_outTextxyt (5, 34, WHITE, "SFM");
	}

	if (autoZeroAxisWait)	/* Say we are in autozero wait-for-axis-move state. */
		s_outTextxyt (22, 1, YELLOW, "Axis to auto-zero ?");
	if (autoCenterAxisWait)	/* Say we are in auto center wait-for-axis-move state. */
		s_outTextxyt (22, 1, YELLOW, "Axis to auto-center ?");
	if ((autoZeroAxis!=NULL) || (autoCenterAxis!=NULL))	/* In auto wait-for-probe-touch state. */
		s_outTextxyt (22, 1, YELLOW, "Waiting for edge touch ...");

	/* Loop across all the encoders and setup display. */
	for (i=0; (axis=encoderList[i])!=NULL; i++) /* For each encoder structure */
	{
		if (axis->ed_displayRow==0) /* If row is zero, axis does not exist. */
			continue;

		/* Add the axis label to a few places (typically X, Y, Z), blink axis name
		   if this is the axis we are auto-zero/centering. */
		blink = ((autoZeroAxis == axis)||(autoCenterAxis == axis))? BLINK : 0;
		if (axis->ed_displayName)
		{
			sprintf(string, "%+12s", axis->ed_name);	/* Need right-justified 12 char name string... */
			s_outTextxyt (axis->ed_displayRow, 1, WHITE+blink, string);
		}
		else
			s_outCharxyt (axis->ed_displayRow, 12, WHITE+blink, axis->ed_label);

		/* Space the axis prompts one char apart starting in col 2. */ 
		s_outCharxytb ( 24, ((i*2)+2), RED, BLACK, axis->ed_label);

		if (axis->ed_rotaryOn)
			s_outCharxyt (axis->ed_displayRow+1, 29, WHITE, 'R');
		else
		{
			if (axis->ed_displayDiam)
				s_outCharxyt (axis->ed_displayRow+1, 29, WHITE, 'D');
			if (axis->ed_conversion!=0)
				s_outCharxyt (axis->ed_displayRow+1, 30, WHITE, 
								(unsigned char)((axis->ed_conversion==1)?'M':'E')); 
		}
		if (axis->ed_backlash)
			s_outCharxyt (axis->ed_displayRow+1, 31, WHITE, 'B');

		if (axis->ed_displaySlew && !axis->ed_rotaryOn)	/* If user wants slew rate displayed */
		{
			if (axis->ed_conversion==1)			/* 0 is off 1 is (inch to mm) and 2 is (mm to inch) */
				s_outTextxyt (axis->ed_displayRow, 34, WHITE, "CmPM");
			else
				s_outTextxyt (axis->ed_displayRow, 34, WHITE, "IPM");

			/* Force slewCount to be different so we get a first update. */
			axis->ed_slewValue += .0001;
		}

		/* If the user has the possible error thingy turned ON in the setup screen... */
		if (axis->ed_displayError)
			update_error_count(axis);
	} /* End of for() loop. */
} /* End of draw_encode_screen_text() */









/* This routine builds (or rebuilds) the main menu screen. */

void buildMainScreen(void)

{
	short m;
	unsigned char portString[16], *typeString;
	static char *menu[8]={
		"Read Encoders, display counters",
		"Zero All Encoders",
		"Program Options setup",
		"Machine Tool Options setup",
		"Geometry Utilities",
		"Help",
		"About",
		NULL};

	buildMainBackground(TRUE);

	/* Build a drop box on screen for menu display. */
	drop_box (18, 50,  4,  15, "Main Menu", WHITE);
	
	for (m=0; menu[m]!=NULL; m++)			/* Display command menu. */
	{
		s_outCharxyt ((m*2)+3, 8, RED, *menu[m]);	/* Write command letter in RED, every other line */
		s_outTextxyt (-1, -1, BLACK, (*(&menu[m])+1));	/* Write rest of command in black. */
	}
	
	if (parallelEnable)
	{
		if (pp.pp_lpt>0)					/* If no LPT number found, show address in decimal */
			sprintf(portString, "LPT%i", pp.pp_lpt);
		else
			sprintf(portString, "parallel %i", pp.pp_data);
		typeString = ppTypeStr[pp.pp_type];
	}

	if (serialEnable)
	{
		if (sp.sp_com>0)					/* If no COM number found, show address in decimal */
			sprintf(portString, "COM%i", sp.sp_com);
		else
			sprintf(portString, "serial %i(0x%03X)", sp.sp_data, sp.sp_data);
		typeString = spTypeStr[sp.sp_type];
	}

	/* Right justify port data in lower right corner of window */
	s_outTextxy (17, (window_width-strlen(portString)), portString);
	s_outTextxy (18, (window_width-strlen(typeString)), typeString);
	return;
} /* End of buildMainScreen() */








/* Builds the background of the main screen.  Overall BLUE screen with yellow outline
   and BLACK title bar with white text.  The mainMenu flag is used to not put up
   the <Home> key instructions. */

void buildMainBackground(short mainMenu)

{
	short m;
	unsigned char string[255];
	
	s_init_outText(_TEXTC80);

	/* Set window to max size, cursor OFF, and fill screen with BLUE background. */
	s_setTextWindow (1, 1, 25, 80, BLUE);

	/* Write a RED title bar with YELLOW title text centered. */
	sprintf(string, "            Digital ReadOut - Quadrature Encoder Reader %s                 ", progVersion);
	string[78] = 0;		/* trim string to 78 bytes. */
	s_outTextxytb (1, 2, YELLOW, RED, string);

	/* Center the machine name on line 2. */
	_setbkcolor (BLUE);
	centered_text (2, machine);

	/* Build a double line box around the window */
	s_outCharxytb (24,2, RED, BLUE, (unsigned char)0xC8);
	s_outCharxy (24,79, (unsigned char)0xBC);

	for (m=2;m<=23;m++)
	{
		s_outCharxy (m, 2, (unsigned char)0xBA);
		s_outCharxy (m, 79, (unsigned char)0xBA);
	}
	
	for (m=3;m<79;m++)
		s_outCharxy (24, m, (unsigned char)0xCD);

	if (initializationComplete)		/* Don't display menus until */
	{
		s_outTextxyt (24,  4, WHITE, " <Tab>=display counters ");
		if (!mainMenu)
			s_outTextxyt (24, 34, WHITE, " <Home>=goto main menu ");
		s_outTextxyt (24, 62, WHITE, " <Esc>=Quit ");
	}
} /* End of buildMainBackground(FALSE) */









/* Clear the stored strings for numbers that have been written to the screen.
   This will cause them to be re-displayed next draw cycle. */

void clearStoredStrings (void)

{
	register ENCODER_DATA **p;

	p = encoderList;				/* Point to list of encoder data blocks. */

	/* Loop across all the encoders. */
	do
	{
		memset ((*p)->ed_displayString, 0, sizeof((*p)->ed_displayString));
		memset ((*p)->ed_displayIPMString, 0, sizeof((*p)->ed_displayIPMString));
		memset ((*p)->ed_errorCountString, 0, sizeof((*p)->ed_errorCountString));
		memset ((*p)->ed_rotaryCountString, 0, sizeof((*p)->ed_rotaryCountString));

		(*p)->ed_revolutionsDisplayed = 32767; /* Set to an unlikley value to force screen update */

		/* Set this on to force display of the value for the encoders.  Otherwise they will not show 
		   until the encoders are moved.  */
		(*p)->ed_changed = TRUE; 
	} while (*++p != NULL);			/* Walk down list until NULL pointer */

	memset (lastRPMString, 0, sizeof(lastRPMString));
	memset (lastSFMString, 0, sizeof(lastSFMString));
} /* End of clearStoredStrings() */






void quick_rezero (ENCODER_DATA *axis) 

{
	double dif;

	/* Get the absolute value of the difference of what we show and where we are. */
	dif = fabs(axis->ed_value - axis->ed_currently_displayed);
		
	/* *.25 is the same as /4 (1/4th) (axis->ed_units*5))  Take about 5 units and see 
	   if we are over that many blips and if so the user must of come up from the other 
	   way with the tension before he rezeros.  So then whatever the current lash 
	   direction is it needs to be set the other way. If not leave it alone. It is 
	   already set correct */
	if (dif>(axis->ed_backlashDist*.25))
		axis->ed_tension = !axis->ed_tension;

	axis->ed_possible_error_count = -1;	/* Reset the error counter. */

	if (currentMode==0)					/* If in absolute mode, then zero with the following. */
	{
		axis->ed_value = 0;				/* Current value gets set to zero (absolute) */
		axis->ed_compoundSteps[currentMode] = 0;
		if (serialEnable)				/* If talking to an outboard processor. */
			sendZeroCommand (axis->ed_encoder);	/* Set counter to zero in micro. */
		axis->ed_zero_all = TRUE;		/* this turns on the zero stuff in the backlash function */
	}
	else
	{
		/* Offset modes get current absolute location.  In effect "moving" the origin for this
		   offset to where the encoder is right now. */
		axis->ed_offset[currentMode] = axis->ed_value;
		axis->ed_compoundSteps[currentMode] = axis->ed_compoundSteps[0];
		axis->ed_zero_all_mode = TRUE;  /* this turns on the zero stuff in the backlash function */
	}

	/* Now the current displayed value gets set to either the current location, if
	   in an offset mode, or absolute zero, if in master mode (selected above). */
	axis->ed_currently_displayed = axis->ed_value;

	autoZeroAxisWait = FALSE;			/* No longer waiting */
	autoCenterAxisWait = FALSE;			/*  for an axis to move state */
	autoZeroAxis = NULL;				/* Cancel autozero */
	autoCenterAxis = NULL;				/*  and no axis chosen yet for centering. */

	axis->ed_changed = TRUE;			/* Force Encoder Display update. */
	axis->ed_error_changed = TRUE;		/* Flag as changed for screen update. */
	axis->ed_slewLast = 0.0;			/* Zap the slew rate stored values also. */
	axis->ed_slewLastLast = 0.0;		/* Zap the slew rate stored values also. */
} /* End of quick_rezero() */







void encoderSetupF2Screen(ENCODER_DATA *axis)

{
	drop_box (14, 20, 5, 10, "Conversion setup", WHITE);

	s_outTextxyt(5, 4, BLACK, "Conversion");
	s_outTextxy (7, 4, "inch TO mm");
	s_outTextxy (9, 4, "mm TO inch");

	s_outCharxyt(5, 4, RED, 'C');
	s_outCharxy (7, 4, 'i');
	s_outCharxy (9, 4, 'm');
	s_outTextxy (14, 16, "Esc");

	if(axis->ed_conversion==0)
	{
		s_outTextxyt(5, 16, YELLOW, "Off");
		s_outTextxy (7, 16, "Off");
		s_outTextxy (9, 16, "Off");
	} 
	if(axis->ed_conversion==1)
	{
		s_outTextxyt(5, 16, YELLOW, "On ");
		s_outTextxy (7, 16, "On ");
		s_outTextxy (9, 16, "Off");
	} 
	if(axis->ed_conversion==2) 
	{
		s_outTextxyt(5, 16, YELLOW, "On ");
		s_outTextxy (7, 16, "Off");
		s_outTextxy (9, 16, "On ");
	}  
} /* End of encoderSetupF2Screen() */






void encoderSetupF2(ENCODER_DATA *axis)

{
	short c=0;

	while  (c!=ESC)		/*  only go back after the esc key */
	{
		encoderSetupF2Screen(axis);	/* Build display */

		c = getUCchar();	/* Get a command character */

		if (c=='C')			/* the c key conversion 0 is off 1 is (inch to mm) and 2 is (mm to inch)  */
			axis->ed_conversion = !axis->ed_conversion;
		
		if (c=='I')	/* the i key inch to metric   */
		{
			if(axis->ed_conversion==0)
			{
				s_outTextxy (11, 4, "Turn Conversion");
				s_outTextxy (12, 4, "      on first.");
				c = getUCchar();
				s_outTextxy (11, 4, "               ");
				s_outTextxy (12, 4, "               ");
			}
			if(axis->ed_conversion==2) axis->ed_conversion = 1; 	
			if(axis->ed_conversion==1) { }/* do nothing it is already set  */	  
		}
		
		if (c=='M')	/* the m key mm to inch    */
		{
			if(axis->ed_conversion==0)
			{
				s_outTextxy (11, 4, "Turn Conversion");
				s_outTextxy (12, 4, "      on first.");
				c = getUCchar();
				s_outTextxy (11, 4, "               ");
				s_outTextxy (12, 4, "               ");
			}
			if(axis->ed_conversion==2) { }/* do nothing it is already set  */
			if(axis->ed_conversion==1) axis->ed_conversion = 2; 	
		}
	}   
} /* End of F2 */








/* Routine to allow user to setup encoder parameters. Maintains absolute, incremental,
   and rotary modes.

   Common commands
	R     Toggle Rotary/Cart coordinates
	C     Count up/down
	Z     Reset to Zero
	V     Set Current Value


   Cart Axis commands
	U     Unit to Count by
	B     Backlash on/off
	L     Lash Distance
	T     Lash Tension Dir.
	P     Possible Errors
	S     Places Shown Right of Decimal

   Rotary Axis commands      
	S     Show revolution count
    D     Display in deg,min,sec (polar cordinates)
    N     Set number of counts in 360 degrees  
  
     .5 degreess = 30 min and 0 sec
*/

void encoder_setup(ENCODER_DATA *axis)

{
	short i, c = 0;
	int zeroed = FALSE;
	long int longint = 0;
	double longdouble = 0.0;
	char *zeroMsg = NULL;
	static char *zeroDone   = {"Now Zero   "};
	static char *zeroUndone = {"Zero Undone"};
	/* Save stuff that might change and restore if the reset zero command is used. */
	int save_tension = axis->ed_tension;
	long int save_value = axis->ed_value;
	long int save_compoundSteps = axis->ed_compoundSteps[currentMode];
	long int save_offset = axis->ed_offset[currentMode];
	long int save_error = axis->ed_possible_error_count;	/* save the error count */

	while (c!=ESC)							/* Terminate after the esc key */
	{
		if (axis->ed_rotaryOn)
			rotarySetupScreen(axis);		/* Draw setup screen. */
		else
			encoderSetupScreen(axis);		/* Draw setup screen. */

		if (zeroMsg!=NULL)					/* If a zero status update msg is required. */
		{
			s_outTextxyt (7, 25, RED, zeroMsg); /* Update screen */
			zeroMsg = NULL;					/* Clear msg pointer now. */
		}

		c = getUCchar();
		_settextcolor (RED);				/* Make any prompts RED. */
		if (axis->ed_rotaryOn)
			switch (c)						/* Rotary coordinate mode commands.*/
			{
				case 'D':						/* The D key, display minutes and seconds or display decimal degrees  */
					axis->ed_display_min = !axis->ed_display_min; /* Invert flag */
					break;

				case 'S':						/* S, show revolutions   */
					axis->ed_show_revolutions = !axis->ed_show_revolutions;
					break;

				case 'V':						/* V, set current value    */
					if (axis->ed_display_min)	/* Display min:seconds or decimal? */
					{
						s_outTextxyt (20, 4, RED, "Enter degrees, minutes, seconds");
						i = auto_degrees_decimal(&longdouble,"Seperate by comas and no spaces", 21, 4, 22, 15, BLUE);
					}
					else
						i = numbers_only (&longdouble, 21, 4,"Enter decimal degrees ? ", BLUE);
					
					if (i)						/* Did user hit escape? (don't store if so) */
					{
						longint = (long int)(longdouble / axis->ed_rotaryFactor);
						if (currentMode==0)
						{
							axis->ed_value = longint;
							if (serialEnable)	/* If talking to an outboard processor. */
								sendSetCommand (axis->ed_encoder, longint);	/* Set counter in micro. */
						}
						else
							axis->ed_offset[currentMode] = axis->ed_value - longint;
					}
					zeroed = FALSE;				/* Can't unzero any more. */
					break;

				case 'N':						/* N key, number of blips in 360 */
					if (numbers_only (&longdouble, 21, 4,"# of counts in 360 degrees ? ", BLUE)) /* Did user hit escape? (don't store if so) */
					{
						axis->ed_number_of_blips = (int)longdouble;
						axis->ed_rotaryFactor = 360.0 / longdouble;	/* Conversion factor */
					}
					break;
			}
		else
			switch (c)						/* Cartesian coordinate mode commands.*/
			{
				case 'S':				/* S key places shown right of decimal  */     
					if (++axis->ed_decimal_places_shown>6) /* Wrap to zero at 6 */
						axis->ed_decimal_places_shown = 0;
					break;

				case F2:				/* the F2 key Conversion mm / inch */
					encoderSetupF2(axis);
					break;

				case 'P':				/*  the P key show or not show possible error */
					axis->ed_displayError = !axis->ed_displayError; /* Invert flag */
					break;

				case 'U':				/* the U key unit to count encoder steps by */
					numbers_only (&axis->ed_units, 23, 3, "Enter counting unit ? ", BLUE);
					if (axis==compoundAxis)		/* If a compound axis, track change in companion axis. */
					{
						register ENCODER_DATA *pX, *pZ;

						if ((pX=axis->ed_compoundX)!=NULL)
							pX->ed_compoundUnits = sind(axis->ed_compoundAngle) * axis->ed_units;
						if ((pZ=axis->ed_compoundZ)!=NULL)
							pZ->ed_compoundUnits = cosd(axis->ed_compoundAngle) * axis->ed_units;
					}
					break;

				case 'B':				/* the B key backlash on/off */
					axis->ed_backlash = !axis->ed_backlash;
					break;

				case 'T':				/* the T key lash tension dir */
					save_tension, axis->ed_tension = !axis->ed_tension;
					break;

				case 'V':				/* the V key  set current value */
					if (numbers_only (&longdouble, 23, 3, "Change current value to ? ", BLUE))
					{
						/* Store new location value, Round off. */
						axis->ed_currently_displayed = round2Units(axis, longdouble);
						if (currentMode==0)
						{
							axis->ed_value = axis->ed_currently_displayed;
							axis->ed_compoundSteps[0] = 0;			 /* Clear compound steps. */
						}
						else
						{
							axis->ed_offset[currentMode] = axis->ed_value - axis->ed_currently_displayed;
							axis->ed_compoundSteps[currentMode] = 0; /* Clear compound steps. */
						}

						if (serialEnable)				/* If talking to an outboard processor. */
							sendSetCommand (axis->ed_encoder, axis->ed_currently_displayed);
						axis->ed_zero_all_mode = TRUE;
					}
					zeroed = FALSE;				/* Can't unzero any more. */
					break;
				
				case 'L':						/* the L key lash distance */
					if (numbers_only (&longdouble, 23, 3, "Amount of backlash ? ", BLUE))
						axis->ed_backlashDist = round2Units(axis, longdouble);
					break;
			} /* End of switch (c) */
			
		/* Handle the common command codes between rotary and non-rotary modes. */
		switch (c)
		{
			case 'R':						/* The R key, rotary counting on/off */
				axis->ed_rotaryOn = !axis->ed_rotaryOn;	/* Toggle flag */
				zeroed = FALSE;				/* Can't unzero any more. */
				break;

			case 'C':						/* The C key for count other way (direction) -+     */
				axis->ed_count_reverse = !axis->ed_count_reverse;	/* Invert flag */
				/* Subtract from 0 to swap the sign of the number -/+ for both the
				   absolute and offset values. */
				save_value, axis->ed_value = -axis->ed_value;
				save_compoundSteps = -save_compoundSteps;
				for (i=0; i<numberOfModes; i++)
				{
					axis->ed_offset[i] = -axis->ed_offset[i];
					axis->ed_compoundSteps[i] = -axis->ed_compoundSteps[i];
				}
				axis->ed_currently_displayed = -axis->ed_currently_displayed;
				/* this being on will be found in the backlash function and will change
				   the sign of the numbers in there too */
				axis->ed_swapsign = TRUE;
				axis->ed_tension = !axis->ed_tension;	/* Invert tension flag */
				save_tension = !save_tension;
				break;

			case 'Z':						/* the Z key  reset to zero */ 
				zeroed = !zeroed;
				if (zeroed)					/* Toggle flag, is it ON now? */
				{
					if (currentMode==0)
						save_value = axis->ed_value;	/* Save current value. */
					else
						save_offset = axis->ed_offset[currentMode];
					save_compoundSteps = axis->ed_compoundSteps[currentMode];
					save_error = axis->ed_possible_error_count;/* Save the error count */
					save_tension = axis->ed_tension;
					quick_rezero (axis);	/* Zero this axis */
					zeroMsg = zeroDone;
				}
				else
				{	/* Undo the zero just done. Restore saved values. */
					if (currentMode==0)
					{
						axis->ed_value = save_value;
						if (serialEnable)		/* If talking to an outboard processor. */
							sendSetCommand (axis->ed_encoder, save_value);	/* Set counter in micro. */
					}
					else
						axis->ed_offset[currentMode] = save_offset;
					axis->ed_compoundSteps[currentMode] = save_compoundSteps;
					axis->ed_possible_error_count = save_error;	/* set this back to what it was */
					axis->ed_tension = save_tension;
					axis->ed_zero_all = FALSE; /* turn this off so the backlash function doesn't change it's zero   */
					zeroMsg = zeroUndone;
				}
				break;

			case SPACE:						/* The space bar key or the M key (currentMode) */
			case 'M':
				if (++currentMode>=numberOfModes)/* Step forward through the modes. */
					currentMode = 0;		/* Wrap to zero at max */
				zeroed = FALSE;				/* Can't unzero any more. */
				break;
				
			case BACKSPACE:
				if (--currentMode<0)		/* Step backwards through the modes. */
					currentMode = numberOfModes-1; /* Wrap to max at zero */
				zeroed = FALSE;				/* Can't unzero any more. */
				break;

	#ifdef _DEBUG
			case 'P'-'@':					/* Control-P */
				TraceDump("DRO.TRC", 0);	/* *** Dump the internal trace table *** */
				break;
	#endif /* _DEBUG */

			case ESC:						/* The ever available abort. */
				break;

			case HOME:						/* <Home> go right to encoder display mode */
			case TAB:						/* <tab> go right to encoder display mode */
				longjmp(command, c);		/* Jump to command loop, stuff a TAB command. */

			case 'P':						/* Handled above if Cartesian... */
			case 'U':
			case 'B':
			case 'T':
			case 'L':
			case F2:
				if (axis->ed_rotaryOn)
					Sound (ERRnote);
				break;

			case 'D':						/* Handled above if Rotary... */
			case 'N':
				if (axis->ed_rotaryOn)
					break;

			default:
				Sound (ERRnote);
			case 'S':						/* Handled above... */
			case 'V':
				break;
		}
	} /* End of while (c!=ESC) */
} /* End of encoder_setup() */






void encoderSetupScreen(ENCODER_DATA *axis)

{
	char string[41];

	s_init_outText(_TEXTC40);
	s_setTextWindow (1, 1, 25, 40, WHITE);

	/* Write a BLUE title bar with WHITE text centered. */
	s_outTextxytb (1, 1, WHITE, BLUE, "                                        ");
	sprintf (string, "%s %s", axis->ed_name, modeString[currentMode]);
	centered_text (1, string);

	/* Display menu text as BLACK on WHITE */
	s_outTextxytb( 3, 5, BLACK, WHITE, "Rotary coordinates");
	s_outTextxy  ( 5, 5, "Count up/down");
	s_outTextxy  ( 7, 5, "Reset to Zero");
	s_outTextxy  ( 9, 5, "Current Value");
	s_outTextxy  (11, 5, "Unit to Count by");
	s_outTextxy  (13, 5, "Backlash on/off");
	s_outTextxy  (15, 5, "Lash Distance");
	s_outTextxy  (17, 5, "Lash Tension Dir.");
	s_outTextxy  (19, 5, "Possible Errors");
	s_outTextxy  (21, 5, "Places Shown Right of Decimal");

	s_outTextxy  (25, 1, "F2 E/M Conversion   Mode   Esc to return");

	/* Highlight command key letters in red. */
	s_outCharxyt( 3, 5, RED, 'R');
	s_outCharxy ( 5, 5, 'C');
	s_outCharxy ( 7,14, 'Z'); 
	s_outCharxy ( 9,13, 'V'); 
	s_outCharxy (11, 5, 'U');   
	s_outCharxy (13, 5, 'B');   
	s_outCharxy (15, 5, 'L');    
	s_outCharxy (17,10, 'T');  
	s_outCharxy (19, 5, 'P');   
	s_outCharxy (21,12, 'S');
	s_outTextxy (25, 1, "F2");
	s_outCharxy (25,21, 'M');
	s_outTextxy (25,28, "Esc");

	/* Display data in BLUE. */
	s_outTextxyt( 3, 24, BLUE, axis->ed_rotaryOn?"ON":"OFF");
	s_outTextxy ( 5, 24, axis->ed_count_reverse?"-":"+");
	s_printfxy  ( 9, 24, "%15.6f", (currentMode==0)?\
			(double)axis->ed_value*axis->ed_units:\
			(double)(axis->ed_value-axis->ed_offset[currentMode])*axis->ed_units);
	s_printfxy  (11, 24, "%.14f", axis->ed_units);
	s_outTextxy (13, 24, axis->ed_backlash?"ON ":"OFF");
	s_printfxy  (15, 24, "%15.6f", axis->ed_backlashDist);
	s_outTextxy (17, 24, axis->ed_tension?"Right":"Left ");
	s_outTextxy (19, 24, axis->ed_displayError?"Displayed    ":"Not Displayed");
	s_printfxy  (21, 35, "%i", axis->ed_decimal_places_shown);
} /* End of encoderSetupScreen() */







/* Build the rotary setup screen */

void rotarySetupScreen(ENCODER_DATA *axis)

{
	char string[41];
	double temp;

	s_init_outText(_TEXTC40);

	s_setTextWindow (1, 1, 25, 40, WHITE);
	
	/* Write a BLUE title bar with WHITE text centered. */
	s_outTextxytb (1, 1, WHITE, BLUE, "                                        ");
	sprintf (string, "%s %s", axis->ed_name, modeString[currentMode]);
	centered_text (1, string);

	/* Display menu text as BLACK on WHITE */
	s_outTextxytb( 3, 5, BLACK, WHITE, "Rotary coordinates");
	s_outTextxy  ( 5, 5, "Count up/down");
	s_outTextxy  ( 7, 5, "Reset to Zero");
	s_outTextxy  ( 9, 5, "Current Value");
	s_outTextxy  (11, 5, "Num. in 360 degrees");
	s_outTextxy  (13, 5, "Display as");
	s_outTextxy  (15, 5, "Show Revolutions");

	s_outTextxy (25, 21, "Mode   Esc to return");

	/* Highlight command key letters in red. */
	s_outCharxyt( 3, 5, RED, 'R');
	s_outCharxy ( 5, 5, 'C');
	s_outCharxy ( 7,14, 'Z');  
	s_outCharxy ( 9,13, 'V');   
	s_outCharxy (11, 5, 'N'); 
	s_outCharxy (13, 5, 'D');
	s_outCharxy (15, 5, 'S');
	s_outCharxy (25,21, 'M');
	s_outTextxy (25,28, "Esc"); 

	/* Display data in BLUE. */
	s_outTextxyt( 3,24, BLUE, axis->ed_rotaryOn?"ON ":"OFF");
	s_outTextxy ( 5,24, axis->ed_count_reverse?"-          ":"+          ");

	temp = (double)axis->ed_value * axis->ed_rotaryFactor;
	if (axis->ed_display_min)	/* 1 is display minutes and secs. and 0 is display decimal   */
		s_outTextxy ( 9, 24, decimal_to_degrees(temp));
	else
		s_printfxy ( 9, 24, "%15.6f", temp); /* decimal degrees  */
	s_printfxy  (11, 25, "%i", axis->ed_number_of_blips);
	s_outTextxy (13, 24, axis->ed_display_min?"minutes, secs ":"decimal degrees");
	s_outTextxy (15, 24, axis->ed_show_revolutions?"ON ":"OFF");
} /* End of rotarySetupScreen() */




/* The get keyboard character routine.  While waiting for a key press, poll for 
   parallel port data.  Also calls the decodeDataValue() routine.  This will maintain 
   the position value even when not in the display loop.  Data values arrive either 
   from the poll or from an interrupt.

   Returns a keyboard character as an unsigned int.

   Probably 99% of program execution time is spent in this while() loop, this
   is the most critical loop in the program.

   If NO_KB_HIT macro is a problem, use "!_bios_keybrd (_KEYBRD_READY)".  This was
   changed to remove a DOS call.
*/

short getKeyBoard()

{
	ENCODER_DATA **p;						/* Pointer to a list of ENCODER_DATA pointers. */

	fflush( stdout );						/* Flush any writes to the screen. */

	if (initializationComplete)				/* Don't poll until init is complete. */
		while (!_bios_keybrd (_KEYBRD_READY))	/* Check for key press, loop while NO key ready. */
		{
			pollParallelPort;					/* Poll data port while waiting for a key press. */

			/* While looping do step decoding.  If using interrupts or serial port, just do it.  
			   If polling, must wait til the 'timeDisplay' counter 'timeCounter' goes below zero.  
			   It will hit zero often if user is moving axis slowly or we are a faster machine.  
			   The timer is counted down in the pollParallelPort routine, so the faster we poll 
			   the more we display. */
			if (pp.pp_use_irq || serialEnable || (timeCounter<0))
			{
				/* Fetch data from circular buffer, decode encoder steps, and update position
				   counters. */
				decodeDataValue();
				if (encoderDisplayMode)			/* If encoder screen is up, do a display update. */
				{
					p = encoderList;			/* Point to list of encoder data blocks. */
					do
					{
						/* The change flag for an axis is set in processStepValue(). DisplayRow is zero 
						   if this encoder input is not being used. */
						if ((*p)->ed_changed && (*p)->ed_displayRow!=0)	/* If this axis needs service ... */
							updateEncoderDisplay(*p);					/*  finally! display the freakin' value */
					} while (*++p != NULL);		/* Walk down list until NULL */
					timeCounter = timeDisplay;	/* Did a display cycle, re-init the timer/counter. */					
				}
				#ifdef _DEBUG					/* ***Debug hack*** */
				if (inject) inject_data (0);	/* ***Debug, inject port data with 'I' command *** */
				#endif							/* ***Debug hack*** */
			}
		} /* End while */
	return _getch();						/* Get key, return. */
}






/* Load a bolt hole coord from the storage array into a location register (absolute or
   incremental mode). */

void loadBoltHole()

{
#ifdef BOLTLISTCODE
	short i, j, endRow, startIndex, currentIndex, color, encoderX, encoderY, incrMode, answer=0;
	ENCODER_DATA *pX, *pY;
	/* Number of holes in selection list. */
	#define BOLTLIST 12

	buildMainBackground(FALSE);

	if (currentHole < 0)
	{
		/* Red box with yellow text prompt */
		drop_box ( 9, 50, 0, 0, "Hole Pattern Load Error", RED);
		s_outTextxyt (3, 3, YELLOW, "No hole pattern has been set.");
		s_outTextxy  (5, 3,         "Do you want to define one now (Y or N) [Y] ? ");
		answer = getUCchare(BLUE);
		if (answer==TAB || answer==HOME)
			longjmp(command, answer);		/* Jump to command loop, stuff a command. */
		if (answer!='Y' && answer!=ENTER)
			return;
		s_outTextxy  (7, 3,         "Do you want a Circle, Arc, or Line pattern");
		s_outTextxy  (8, 3,         " (C,A, or L) [C] ? ");
		answer = getUCchare(BLUE);
		if (answer==TAB || answer==HOME)
			longjmp(command, answer);		/* Jump to command loop, stuff a command. */
		if (answer==ENTER)
			answer = 'C';					/* Set default on ENTER */
		if (answer=='C' || answer=='A')
			mainCommand_G_bolt_circle(answer);	/* Setup the bolt hole pattern. */
		else if (answer=='L')
			mainCommand_G_bolt_line();		/* Setup the bolt hole pattern. */
	}

	if (currentHole < 0)			/* If user hit <ESC> */
		return;

	currentIndex = currentHole;		/* Current index into the list of holes. */
	encoderX = 0;					/* Default to first two encoders for */
	encoderY = 1;					/*  X and Y coordinates. */
	pX = encoderList[encoderX];
	pY = encoderList[encoderY];
	incrMode = 1;					/* Default to Incremental Mode A offset. */

	if (lastHole<BOLTLIST)			/* Ending display row on screen. */
		endRow = lastHole;
	else
		endRow = BOLTLIST-1;

	/* startIndex in bolt hole list is first bolt hole pair displayed in window. */
	startIndex = currentIndex-(BOLTLIST/2); /* Show in center of list to start. */
	if (startIndex<0)
		startIndex = 0;

	while (answer!=ESC)
	{
		overlay_box (19, 60, "Bolt Hole Selection");

		s_outTextxyt ( 2,  3, BLACK, "Select a hole from list, then press Enter.");
		s_outTextxyt ( 3,  3, BLACK, "Press ");
		s_outCharxyt ( 3, -1, RED, 'N');
		s_printfxyt  ( 3, -1, BLACK, " for new list. Last hole selected was number %i", 
						currentHole+1);

		s_outTextxytb(14, 40, BRIGHT_WHITE, BROWN, " Use UP/DOWN cursor ");
		s_outTextxy  (15, 40, " arrows and/or PAGE ");
		s_outTextxy  (16, 40, " UP/DOWN keys to    ");
		s_outTextxy  (17, 40, " review list.       ");

		/* Display menu that allows user to change axis and offset mode. */
		s_outCharxytb(19,  2, RED, WHITE, 'X');
		s_outTextxyt (19, -1, BLACK, "= ");
		s_outTextxyt (19, -1, BLUE, pX->ed_name);
		s_outTextxyt (19, -1, RED, "  Y");
		s_outTextxyt (19, -1, BLACK, "= ");
		s_outTextxyt (19, -1, BLUE, pY->ed_name);
		s_outTextxyt (19, -1, RED, "  M");
		s_outTextxyt (19, -1, BLACK, "ode= ");
		s_outTextxyt (19, -1, BLUE, modeString[incrMode]);

		/* Print list of holes defined, show a window of BOLTLIST entries. */
		s_outTextxyt (5,  6, BLACK, "Hole");

		if (pX->ed_displayName) s_outTextxyt(5, 14,  BLUE, pX->ed_name);
		else s_outCharxyt(5, 18,  BLUE, pX->ed_label);

		if (pY->ed_displayName) s_outTextxy (5, 25, pY->ed_name);
		else s_outCharxy (5, 29, pY->ed_label);

		for (i=0,j=startIndex; i<=endRow; i++,j++)
		{
			if (j==currentIndex)	/* Mark current choice in list. */
			{
				s_printfxyt( 6+i, 2, RED, "-->  ", j+1);
				color = BLACK;
			}
			else
				color = DARK_GRAY;
			s_printfxyt( 6+i, 7, color,"%2i:  %10.4f,%10.4f", j+1,
						holePattern[j].x, holePattern[j].y);
			if (j==currentIndex)	/* Mark current choice in list. */
				s_printfxyt( -1, -1, RED, "  <--", j+1);
			if (j==lastHole)
				j = -1;
		}
	
		switch (answer=getUCchar())
		{
			case 'X':						/* Change X axis encoder. */
				for (i=0; i<5; i++)			/* Loop for max of 5 times. */
				{
					/* Step X encoder index. If end of list, reset to first. */
					if (encoderList[++encoderX]==NULL)
						encoderX = 0;
					/* If this encoder is active, end loop. */
					pX = encoderList[encoderX];
					if (pX->ed_displayRow!=0)
						break;
				}
				break;

			case 'Y':						/* Change Y axis encoder. */
				for (i=0; i<5; i++)			/* Loop for max of 5 times. */
				{
					/* Step Y encoder index. If end of list, reset to first. */
					if (encoderList[++encoderY]==NULL)
						encoderY = 0;
					/* If this encoder is active, end loop. */
					pY = encoderList[encoderY];
					if (pY->ed_displayRow!=0)
						break;
				}
				break;

			case 'N':						/* 'N'ew bolt hole list. */
				currentHole = -1;
				while (currentHole < 0)
				{
					drop_box ( 6, 50, 0, 0, "Bolt Hole Circle, Arc, or Line", WHITE);
					s_outTextxyt (3, 3, BLACK, "Do you want a Circle, Arc, or Line pattern");
					s_outTextxy  (4, 3,        " (C,A, or L) [C] ? ");
					answer = getUCchare(BLUE);
					if (answer==TAB || answer==HOME)
						longjmp(command, answer);		/* Jump to command loop, stuff a command. */
					if (answer==ENTER)
						answer = 'C';					/* Set default on ENTER */
					if (answer=='C' || answer=='A')
						mainCommand_G_bolt_circle(answer);	/* Setup the bolt hole pattern. */
					else if (answer=='L')
						mainCommand_G_bolt_line();		/* Setup the bolt hole pattern. */
					if (currentHole < 0)
					{
						/* Red box with yellow text prompt */
						drop_box ( 6, 50, 0, 0, "Bolt Hole Load Error", RED);
						s_outTextxyt (3, 3, YELLOW, "No bolt hole pattern has been set.");
						s_outTextxy  (4, 3,         "Do you want to define one now (Y or N) [Y] ? ");
						answer = getUCchare(BLUE);
						if (answer==TAB || answer==HOME)
							longjmp(command, answer);	/* Jump to command loop, stuff a command. */
						if (answer!='Y' && answer!=ENTER)
							return;
					}
				}

				currentIndex = currentHole;	/* Current index into the list of holes. */

				if (lastHole<BOLTLIST)		/* Ending display row on screen. */
					endRow = lastHole;
				else
					endRow = BOLTLIST-1;

				/* startIndex in bolt hole list is first bolt hole pair displayed in window. */
				startIndex = currentIndex-(BOLTLIST/2); /* Show in center of list to start. */
				if (startIndex<0)
					startIndex = 0;
				break;

			case SPACE:
			case 'M':
				if (++incrMode>=numberOfModes)/* Step forward through the modes. */
					incrMode = 0;			/* Wrap to zero at max */
				break;

			case BACKSPACE:
				if (--incrMode<0)			/* Step backwards through the modes. */
					incrMode = numberOfModes-1; /* Wrap to max at zero */
				break;

			case PAGEUP:
				if (lastHole<=BOLTLIST) break;
				startIndex -= BOLTLIST;
				if (startIndex<0)
					startIndex = 0;
				currentIndex = startIndex;
				break;

			case PAGEDOWN:
				if (lastHole<=BOLTLIST) break;
				startIndex += BOLTLIST;
				if (startIndex+endRow>lastHole)
					startIndex = lastHole-endRow;
				currentIndex = startIndex+endRow;
				break;

			case UPARROW:
				if (--currentIndex<0)
				{
					currentIndex = 0;
					break;
				}
				if (currentIndex<startIndex)	/* Is it still in window? */
					startIndex--;
				break;

			case DOWNARROW:
				if (++currentIndex>lastHole)
				{
					currentIndex = lastHole;
					break;
				}
				if (currentIndex>startIndex+endRow)	/* Is it still in window? */
					startIndex++;
				break;

			case ENTER:
				currentHole = currentIndex;	/* Set the desired hole index. */

				/* Store the offset values to get to the selected bolt hole location. */
				if (incrMode==0)			/* Absolute mode */
				{
					pX->ed_value = round2Units(pX, holePattern[currentHole].x) - pX->ed_value;
					pY->ed_value = round2Units(pY, holePattern[currentHole].y) - pY->ed_value;
				}
				else						/* Offset mode */
				{
					pX->ed_offset[incrMode] = round2Units(pX, holePattern[currentHole].x);
					pY->ed_offset[incrMode] = round2Units(pY, holePattern[currentHole].y);
				}
				currentMode = incrMode;		/* Place user in desired Mode */
				answer = ESC;				/* Exit selection loop. */
				break;

			case ESC:
				break;		

			case HOME:						/* <Home> go right to commandLoop */
			case TAB:						/* <tab> go right to encoder display mode */
				longjmp(command, answer);	/* Jump to command loop, stuff a command. */

			default:						/* Any other, Beep user. */
				Sound (ERRnote);
				break;
		}
	}
#endif /* BOLTLIST */
} /* End of loadBoltHole() */





/* Get a keyboard character, translate function keys, leave case alone. */

short getLCchar()

{
	register unsigned short c;

	c = getKeyBoard();				/* Get key. */
	if (c==0 || c==0xE0)			/* Reading a zero or 0xE0 indicates a function key press. */
		c = _getch() + 0x80;		/*  read second byte, add an offset. */
	return c;
}





/* As above, with echo if it will print. */

short getLCchare(short color)

{
	register short c;

	_settextcursor (CURSOR_ON); /* Always want cursor ON if getting input with echo */
	if ( isprint(c=getLCchar()) )
		if (graphicMode && encoderDisplayMode)	/* If encoder screen is up, do graphic display update. */
			colorGchar (-1, -1, color, -1, (unsigned char)c); 
		else
			s_outCharxyt (-1, -1, color, (unsigned char)c);
	_settextcursor (CURSOR_OFF);
	return c;
}





/* As above, with lower to upper case translation. */

short getUCchar()

{
	register unsigned short c;

	c = getLCchar();				/* Get key. */
	return toupper(c);				/* Make it UPPER, return. */
}





/* As above, with echo if it will print. */

short getUCchare(short color)

{
	register short c;

	_settextcursor (CURSOR_ON); /* Always want cursor ON if getting input with echo */
	if ( isprint(c=getUCchar()) )
		if (graphicMode && encoderDisplayMode)	/* If encoder screen is up, do graphic display update. */
			colorGchar (-1, -1, color, -1, (unsigned char)c); 
		else
			s_outCharxyt (-1, -1, color, (unsigned char)c);
	_settextcursor (CURSOR_OFF);
	return c;
}






/* Fetch data from buffer and decode optical encoder steps.  The data gets into the buffer
   either from interrupt level or from a poll to the parallel port.  Testing has shown
   the buffer can be 2-3,000 items (on a 486/33) if an axis is moved very fast. */

void decodeDataValue()

{
	register unsigned short theData;

	/* If there is data in the buffer, loop processing buffered input data until no more. */
	while (encoderDataBuffer.c_count>0)
	{
		theData = *encoderDataBuffer.c_remove;		/* Get the data */
		_disable ();								/* Buffer controls changed at interrupt level. */
		encoderDataBuffer.c_count--;				/* Count down data items taken out. */

		/* Compute the location to remove next input data, wrap when we go past the end. */
		if (++encoderDataBuffer.c_remove > encoderDataBuffer.c_bufferEnd)
			encoderDataBuffer.c_remove = encoderDataBuffer.c_buffer;
		_enable ();

/* Example of trace table use.
   codes are=TR_command TR_poll TR_decode TR_error TR_intrup TR_step */
//{char temp[50];
//sprintf(temp,"%02X",theData);
//Trace(TR_decode,temp);}

		/* If using the serial port with an external processor, otherwise decode bytes from
		   parallel port. */
		if (serialEnable)
			decodeValueMsg(theData);
		else
		{
			/* Look for a change in the encoder state, process value if so. */
			encoder1.ed_encoderNew = theData&0x03;
			if (encoder1.ed_encoderNew!=encoder1.ed_encoderOld)
				processStepValue(&encoder1);

			encoder2.ed_encoderNew = (theData>>2)&0x03;
			if (encoder2.ed_encoderNew!=encoder2.ed_encoderOld)
				processStepValue(&encoder2);

			encoder3.ed_encoderNew = (theData>>4)&0x03;
			if (encoder3.ed_encoderNew!=encoder3.ed_encoderOld)
				processStepValue(&encoder3);

			encoder4.ed_encoderNew = (theData>>6)&0x03;
			if (encoder4.ed_encoderNew!=encoder4.ed_encoderOld)
				processStepValue(&encoder4);

			/* While emptying buffer, keep trying to fill it. */
			pollParallelPort;

			/* The upper byte of the data value contains the parallel port status byte.
			   This contains the AutoZero input bit.  Process it after the step values
			   and if it is ON, terminate the auto zero function. */
			if ((theData & (AUTO_ZERO_INPUT<<8))!=0)
				if (autoZeroAxis!=NULL)
					processAutoZero();
				else if (autoCenterAxis!=NULL)
					processAutoCenter();
		}
	} /* End of while */
	return;
} /* End of decodeDataValue() */






/* An external micro processor can be used to interface the quadrature encoders and communicate
   across a serial link.  This routine decodes the commands sent on the serial link to the PC.

  The message format is:  <SYN><commamd>[<data1>...<datan>]<EOT>
	<SYN> is 0x10
	<EOT> is 0x03
	<data?> are the optional binary data values for this message.

  The defined command codes are:
	0x58	Command code byte for X encoder value, followed by 3 bytes of data
	0x59	code for Y encoder value
	0x5A	code for Z encoder value
	0x57	code for W encoder value
	 0x20	Bit added to command code for error flag (makes it LC).
	0x54	code for tachometer pulse interval count, followed by 2 bytes of data
	0x56	code for version info, two bytes of data.
	0x45	code for edge finder touch, no data.

  A message is not "properly formed" unless it has the <SYN> and <EOH> in the right
  place, message data is collected and buffered here until the header/trailer placement is
  confirmed.
 */

void decodeValueMsg (short theData)

{
/* Message decoder state values.  Starting from _syn state we step through required states
   as message bytes arrive. */
#define STATE_syn 0			/* Looking for <SYN>, once received look for command. */
#define STATE_cmd 1			/* Looking for <command>, once received execute or look for data. */
#define STATE_data1 2		/* Looking for three value data bytes for an axis value command. */
#define STATE_data2 3
#define STATE_data3 4
#define STATE_dataEnd 5		/* Got axis value, looking for <EOH> */
#define STATE_time1 6		/* Looking for two time data bytes for a tachometer interval command. */
#define STATE_time2 7
#define STATE_timeEnd 8		/* Got time value, looking for <EOH> */
#define STATE_edgeEnd 9		/* Looking for <EOH>. */
#define STATE_version1 10	/* Looking for two data bytes for a version command. */
#define STATE_version2 11	/* Looking for two data bytes for a version command. */
#define STATE_verEnd 12		/* Looking for <EOH>. */

	register ENCODER_DATA *p;
	static ENCODER_DATA *axis;
	static long int value, steps;
	static short command, state=STATE_syn;

	switch (state)
	{
		case STATE_syn:						/* Looking for <SYN> character */
			if (theData!=SYN)				/* Ignore bytes until <SYN> */
				break;
			state = STATE_cmd;				/* Now looking for command byte. */
			break;

		case STATE_cmd:							/* Got a <command> character */
			command = theData;					/* Remember the command code for later. */
			switch (theData)
			{
				case 'x':						/* Axis data msg with error indicator ON. */
				case 'X':						/* Axis data msg without error. */
					axis = &encoder1;			/* Pick up axis data block pointer. */
					state = STATE_data1;		/* Now looking for first data byte. */
					break;

				case 'y':
				case 'Y':
					axis = &encoder2;			/* Pick up axis data block pointer. */
					state = STATE_data1;		/* Now looking for first data byte. */
					break;

				case 'z':
				case 'Z':
					axis = &encoder3;			/* Pick up axis data block pointer. */
					state = STATE_data1;		/* Now looking for first data byte. */
					break;

				case 'w':
				case 'W':
					axis = &encoder4;			/* Pick up axis data block pointer. */
					state = STATE_data1;		/* Now looking for first data byte. */
					break;

				case 'E':						/* Edge finder touch msg. */
					state = STATE_edgeEnd;		/* Now looking for end byte. */
					break;

				case 'T':						/* Tachometer timer msg. */
					state = STATE_time1;		/* Now looking for a time byte. */
					break;

				case 'V':						/* Version msg. */
					state = STATE_version1;		/* Now looking for a two byte version number. */
					break;

				default:						/* Bad msg if not one of above. */
					state = STATE_syn;			/* Look for a new start. */
					break;
			}
			break;

		case STATE_data1:					/* Collect value high byte. */
			value = theData;				/* Store high byte. */
			if ((theData&0x80) != 0)		/* Look at sign bit */
				value |= 0xFFFFFF00;		/* Sign extend if required. */
			state = STATE_data2;			/* Now looking for next byte. */
			break;

		case STATE_data2:					/* Collect value mid byte. */
			value = value<<8 | theData;		/* Shift and store byte. */
			state = STATE_data3;			/* Now looking for next byte. */
			break;

		case STATE_data3:					/* Collect value low byte. */
			value = value<<8 | theData;		/* Shift and store byte. */
			state = STATE_dataEnd;			/* Expect end now. */
			break;

		case STATE_dataEnd:					/* Looking for <EOT> character */
			if (theData==EOT)				/* If got it, copy data value into encoder data. */
			{								/* Ignore entire msg if not properly formed. */
				if (axis->ed_value != value)/* Check if value changed (might be just error report). */
				{
					/* Are we in "wait for an axis move" state. If so, this axis just moved. */
					if (autoZeroAxisWait)
					{
						autoZeroStart = axis->ed_value; /* Rembember zero starting location. */
						autoZeroAxis = axis;			/* Remember the axis that moved. */
						autoZeroAxisWait = FALSE;		/* No longer waiting. */
						if (encoderDisplayMode)			/* If in "encoderDisplayMode" */
							draw_encode_screen();		/* Re-Draw the encoder display screen without autozero indicators.*/
					}
					if (autoCenterAxisWait)
					{
						autoCenterStart = axis->ed_value; /* Rembember center starting location. */
						autoCenterAxis = axis;			/* Remember the axis that moved. */
						autoCenterAxisWait = FALSE;		/* No longer waiting. */
						if (encoderDisplayMode)			/* If in "encoderDisplayMode" */
							draw_encode_screen();		/* Re-Draw the encoder display screen without autozero indicators.*/
					}
					steps = value - axis->ed_value;		/* Compute number of steps since last message. */
					if (axis->ed_count_reverse)			/* If counting backwards... reverse values. */
					{
						value = -(value);
						steps = -(steps);
					}
					axis->ed_value = value;				/* Assign new count to axis. */
					axis->ed_changed = TRUE;			/* Flag the change in encoder display values */
					if ((p=axis->ed_compoundX)!=NULL)	/* If there are companion axis, step them also. */
					{
						p->ed_compoundSteps[0] += steps;
						p->ed_changed = TRUE;
					}
					if ((p=axis->ed_compoundZ)!=NULL)
					{
						p->ed_compoundSteps[0] += steps;
						p->ed_changed = TRUE;
					}
				}
				if (islower(command))		/* If command code is lower case, means this axis errored. */
				{	
					axis->ed_possible_error_count += 1;	/* Count error. */
					axis->ed_error_changed = TRUE;
				}
			}
			state = STATE_syn;				/* Looking for <SYN> again. */
			break;

		case STATE_time1:					/* Collect timer high byte. */
			value = theData;
			state = STATE_time2;
			break;

		case STATE_time2:					/* Collect timer low byte. */
			value = value<<8 | theData;
			state = STATE_timeEnd;			/* Expect end now. */
			break;

		case STATE_timeEnd:					/* Looking for <EOT> character */
			if (theData==EOT)				/* If got it, update RPM value. */
			{	/* Tachometer time is ms between pulses. */
				if (value!=0)
					rpmCount = 1000l / value;	/* Store pulses per second. */
			}
			state = STATE_syn;				/* Looking for <SYN> again. */
			break;

		case STATE_edgeEnd:					/* Looking for <EOT> character */
			if (theData==EOT)				/* If got it, Process an edge touch. */
				if (autoZeroAxis!=NULL) processAutoZero();
				else if (autoCenterAxis!=NULL) processAutoCenter();
			state = STATE_syn;				/* Looking for <SYN> again. */
			break;

		case STATE_version1:				/* Collect version high byte. */
			value = theData;
			state = STATE_version2;
			break;

		case STATE_version2:				/* Collect version low byte. */
			value = value<<8 | theData;
			state = STATE_verEnd;
			break;

		case STATE_verEnd:					/* Looking for <EOT> character */
			if (theData==EOT)				/* If got it, Process version data. */
				outBoardVersion = (unsigned short)value;
			state = STATE_syn;				/* Looking for <SYN> again. */
			break;

		default:							/* Bad msg ?. */
			state = STATE_syn;				/* Look for a new start. */
			break;
	}
} /* End of decodeValueMsg() */








/* The two bit encoder value for the given axis is added to the recent history value
   to determine the direction of the encoder step. */

void processStepValue(register ENCODER_DATA *axis)

{
	/* The recent history of encoder values determines the direction of a step.
	   The history byte contains the last two bit value from an encoder. The following 
	   table is indexed by the history byte shifted left 2 plus the current value. 
	   A -1 means subtract, a +1 means add, zero means an encoder step error 
	   (we missed a step?), an error is counted and ignored.
			-0		;No change
			-1		;Step counter -1
			1		;Step counter +1
			0		;Invalid sequence */
	static short table[16] = {-0, -1, 1, 0, 1, -0, 0, -1, -1, 0, -0, 1, 0, 1, -1, -0};
	register short theStep;
	register ENCODER_DATA *p;

	/* Turn the old value and new value into a 4 bit index into step table. The new value then
	   becomes the old value. The table lookup will be 1, -1, or 0.  The 1 and -1 mean add or 
	   subtract a one to the encoder position value.  The new history is stored and we are done.
	   If zero, count a step error. */
	theStep = table[(axis->ed_encoderOld<<2)+axis->ed_encoderNew];
	axis->ed_encoderOld = axis->ed_encoderNew;

	if (theStep!=0)
	{
		if (axis->ed_count_reverse)			/* If user specified reverse counting...  */
			theStep = -theStep;				/*  reverse the counting step here by swapping 1 and -1. */
		
		/* Are in "wait for an axis to move" state. If so, this axis just moved. */
		if (autoZeroAxisWait)
		{
			autoZeroStart = axis->ed_value;	/* Save current position as starting location. */
			autoZeroAxis = axis;			/* Save as selected axis pointer. */
			autoZeroAxisWait = FALSE;		/* No longer waiting. */
			if (encoderDisplayMode)			/* If in "encoderDisplayMode" */
				draw_encode_screen();		/* Re-Draw the encoder display screen without autozero indicators.*/
		}
		if (autoCenterAxisWait)
		{
			autoCenterStart = axis->ed_value;/* Save current position as starting location. */
			autoCenterAxis = axis;			/* Save as selected axis pointer. */
			autoCenterAxisWait = FALSE;		/* No longer waiting. */
			if (encoderDisplayMode)			/* If in "encoderDisplayMode" */
				draw_encode_screen();		/* Re-Draw the encoder display screen without autozero indicators.*/
		}
		axis->ed_value += theStep;			/* Do the step */
		if ((p=axis->ed_compoundX)!=NULL)	/* If there are companion axis, step them also. */
		{
			p->ed_compoundSteps[0] += theStep;
			p->ed_changed = TRUE;
		}
		if ((p=axis->ed_compoundZ)!=NULL)
		{
			p->ed_compoundSteps[0] += theStep;
			p->ed_changed = TRUE;
		}
	}
	else
	{	
		/* Does not seem to match a valid pattern, count error. */
		axis->ed_possible_error_count += 1;
		axis->ed_error_changed = TRUE;
		return;
	}
	axis->ed_changed = TRUE;			/* Flag the change in encoder display values */
} /* End of processStepValue() */






/* The auto center feature "locks on" to the first axis that moves after entering auto center mode.
   We saved the current axis position then watched that axis until the probe input triggered, that 
   means we are at the edge.  Then come here and compute 1/2 the moved distance. */

void processAutoCenter()

{
	register ENCODER_DATA *axis;
	long int offset;

	axis = autoCenterAxis;			/* Get axis we were looking to center. */
	autoCenterAxis = NULL;			/* Clear AutoCenter state. */

	/* Compute total distance moved by this axis and divide by 2. */
	offset = labs(axis->ed_value - autoCenterStart) / 2;

	/* Determine whether offset is plus or minus. If coming from
	   a higher position count to a lower count, minus. */
	if (autoCenterStart > axis->ed_value)
		offset = -(offset);

	/* Subtract the offset from the current mode value. */
	if (currentMode==0)
	{
		axis->ed_value = offset;		/* Absolute */
		if (serialEnable)				/* If talking to an outboard processor. */
			sendSetCommand (axis->ed_encoder, offset);	/* Set counter in micro. */
	}
	else
		axis->ed_offset[currentMode] += offset; /* Relative mode */

	axis->ed_changed = TRUE;	/* Flag the change in encoder display values */

	if (encoderDisplayMode)		/* If in "encoderDisplayMode" */
		draw_encode_screen();	/* Re-Draw the encoder display screen without auto indicators.*/

	pollParallelPort;

	Sound (DONEnote);
} /* End of processAutoCenter() */







/* The auto zero feature "locks on" to the first axis that moves after entering autozero mode.
   We watch that axis until the probe input triggers, that means we are at the edge.  Then come
   here and zero then offset from the edge the radius of the probe. */

void processAutoZero()

{
	register ENCODER_DATA *axis;
	long int offset;

	axis = autoZeroAxis;				/* Get axis we were looking to zero. */
	autoZeroAxis = NULL;				/* Clear AutoZero state now. */

	/* Convert probe radius to units for this axis. */
	offset = round2Units(axis, autoZeroOffset);

	/* Determine whether to add or subtract the auto zero offset. If coming from
	   a lower position count to a higher count, subtract. */
	if (autoZeroStart < axis->ed_value)
		offset = -(offset);

	quick_rezero (axis);				/* Zero this axis. */

	/* After axis is zeroed, add the probe radius to offset the current 
	   position from the edge we just found. */
	if (currentMode==0)
	{
		axis->ed_value = offset;		/* Absolute */
		if (serialEnable)				/* If talking to an outboard processor. */
			sendSetCommand (axis->ed_encoder, offset);	/* Set counter in micro. */
	}
	else
		axis->ed_offset[currentMode] += offset; /* Relative */

	axis->ed_changed = TRUE;	/* Flag the change in encoder display values */

	if (encoderDisplayMode)		/* If in "encoderDisplayMode" */
		draw_encode_screen();	/* Re-Draw the encoder display screen without autozero indicators.*/

	pollParallelPort;

	Sound (DONEnote);
} /* End of processAutoZero() */







/* The input port polling routine.  If not using interrupts this routine is called
   as often as possible to look for data on the port.
   
   The port is controlled by just looking for a change in data by comparing
   to a lastDataByte variable.
 */

void pollPort (PARPORT *par)

{
	register unsigned short data;

	data = (unsigned short)inp (par->pp_data);
	if (data != par->pp_lastDataByte)	/* Input data changed? */
	{
		par->pp_lastDataByte = data;	/* Yes, store as new last seen. */
		/* The input data is both the data and status bytes. */
		*encoderDataBuffer.c_add = (unsigned short)inp (par->pp_status)<<8 | data;
		encoderDataBuffer.c_count++;
		if(++encoderDataBuffer.c_add > encoderDataBuffer.c_bufferEnd)
			encoderDataBuffer.c_add = encoderDataBuffer.c_buffer;
		timeCounter = timeDisplay;		/* Reset display time count. */
	}
	else
		timeCounter-=1;
} /* End of pollPort() */






/* Set counter in outboard micro processor.  The command contains a code for the axis
   and a 24 bit value to set the counter to 

   These commands have no data argument.
    ZERO=0x44     Zero counter, lower two bits is encoder#
    ALL=0xC1      Send all counters now
    VERSION=0xC2  Send version info now

   These commands have a data argument (24 bit value).
    SET=0x20      Set counter value, lower two bits is encoder#

*/

void sendSetCommand (unsigned char counter, long int value)

{
	static unsigned char string[6] = {SYN, 0x20, 0, 0, 0, EOT};

	string[1] = 0x20 + counter;		/* Store encoder counter number byte. */
	string[2] = (unsigned char) ((value>>16) & 0xFF);	/* Mask off upper byte of data. */
	string[3] = (unsigned char) ((value>>8) & 0xFF);	/* Mask off mid byte of data. */
	string[4] = (unsigned char) (value & 0xFF);		/* Mask off lower byte of data. */
	sendSerialData (string, sizeof(string));		/* Send command to micro. */
} /* End of sendSetCommand () */








/* Zero counter in micro. */

void sendZeroCommand (unsigned char counter)

{
	static unsigned char string[3] = {SYN, 0x44, EOT};

	string[1] = 0x44 + counter;		/* Store encoder counter number byte. */
	sendSerialData (string, sizeof(string));	/* Send command to micro. */
} /* End of sendZeroCommand () */






/* Tell outboard micro to send all counters and version info now.  Extra
   EOT sequence is to break a possible hung previous msg condition, like
   data or an EOT that was missed. */

void sendAllCommand()

{
	static unsigned char string[11] = {EOT, EOT, EOT, EOT, EOT, SYN, 0xC2, EOT, SYN, 0xC1, EOT};

	/* Force this to zero so we know it changes in response to this request. */
	outBoardVersion = 0;
	
	sendSerialData (string, sizeof(string));	/* Send command to micro. */
}






/* Copy a string of characters into the serial port output buffer.  Then
   send the first byte to start transmission. */

void sendSerialData (unsigned char *string, short count)

{
	while (count-- > 0)
	{
		*serialOutputBuffer.c_add = *string++;
		_disable ();						/* Buffer controls changed at interrupt level. */
		serialOutputBuffer.c_count++;		/* Count data stored. */
		_enable ();
		if (++serialOutputBuffer.c_add>serialOutputBuffer.c_bufferEnd)
			serialOutputBuffer.c_add = serialOutputBuffer.c_buffer;	/* Wrap at buffer end. */
	}

	/* If Transmitter Holding Register is empty, we need to start the first byte of msg
	   going.  Interrupts will handle the rest.  If THR is not empty, we must already be
	   sending and this msg will get handled in the interrupt routine. */
	if ((inp(sp.sp_data + SR_LINE_STATUS) & LSTS_TRANSMIT) && (serialOutputBuffer.c_count>0))
	{
		_disable ();						/* Buffer controls changed at interrupt level. */
		outp (sp.sp_data, *serialOutputBuffer.c_remove);
/* Example of trace table use.
   codes are=TR_command TR_poll TR_decode TR_error TR_intrup TR_step */
//{static char temp[5]={'T',0,0,0};
//static char hex[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
//temp[1]=hex[*serialOutputBuffer.c_remove>>4];
//temp[2]=hex[*serialOutputBuffer.c_remove&0x0F];
//Trace(TR_intrup,temp);}		/* Compute the location to remove next output data, wrap when we go past the end. */
		if (++serialOutputBuffer.c_remove > serialOutputBuffer.c_bufferEnd)
			serialOutputBuffer.c_remove = serialOutputBuffer.c_buffer;
		/* Count down data items taken out of buffer. */
		serialOutputBuffer.c_count--;
		_enable ();
	}
} /* End of sendSerialData() */







void interrupt far parallelPortHandler(void)

{
	/* Since we are at interrupt level, don't need to worry about disable/enable 
	   around the store to circular buffer. Read port data register and store in 
	   buffer, also read and store status port (will clear IRQ flag). */
	*encoderDataBuffer.c_add = inp(pp.pp_data) | inp(pp.pp_status)<<8;

	encoderDataBuffer.c_count++;		/* Count data stored. */
	if (++encoderDataBuffer.c_add>encoderDataBuffer.c_bufferEnd)
		encoderDataBuffer.c_add = encoderDataBuffer.c_buffer;	/* Wrap at buffer end. */

	/* Invert the state of the strobe line (pin 1 of control port) to prepare 
	   input hardware for next encoder step.  This bit "enables" the XNOR gate 
	   in the interface to toggle on the next encoder change (should really 
	   be a flip-flop in the hardware).  XOR the bit and write the port register. */
	outp (pp.pp_control, ((inp(pp.pp_control))^1));

	/* We tell the 8259 interrupt controller that we are done with this interrupt.
	 *** If we are on controller 2, must tell controller one also *** */
	outp (pp.pp_picaddr, END_OF_INTERRUPT);
	if (pp.pp_picaddr == 0xA0)			/* If this is controller #2 */
		outp(0x20, END_OF_INTERRUPT);	/* Tell controller #1 too */
} /* End of parallelPortHandler() */








/* This routine gets called by the BIOS timer interrupt.  It gets control
   every ~55 ms (.054945 seconds).  We count down  1/2 and 1 second intervals from this.
   The current encoder step count for each axis is stored in ed_slewLast and ed_slewLastLast 
   at each interval. The absolute difference between these two is the step count for the last 
   1/2 second.  The RPM counter is stored each second. */

void interrupt far intervalTimerHandler(void)

{
	static int tickCount = 0;		/* One tick = .054945 seconds */
	static int rpmTick = 0;			/* RPM is sampled every one second (actually 2*.494505 seconds) */

	if (++tickCount == 9)			/* approx .5 second (0.494505) interval */
	{
		/* Store step count since last interval for each axis, store current Last as LastLast
		   new value as Last value.  Used to compute Inches Per Minute. */
		encoder1.ed_slewLastLast = encoder1.ed_slewLast;	/* Previous Last is now LastLast */
		encoder1.ed_slewLast     = encoder1.ed_lastValue;	/* Current position is new Last */
		encoder1.ed_changed = TRUE;							/* Force a display update every .5 second. */

		encoder2.ed_slewLastLast = encoder2.ed_slewLast;
		encoder2.ed_slewLast     = encoder2.ed_lastValue;
		encoder2.ed_changed = TRUE;

		encoder3.ed_slewLastLast = encoder3.ed_slewLast;
		encoder3.ed_slewLast     = encoder3.ed_lastValue;
		encoder3.ed_changed = TRUE;

		encoder4.ed_slewLastLast = encoder4.ed_slewLast;
		encoder4.ed_slewLast     = encoder4.ed_lastValue;
		encoder4.ed_changed = TRUE;

		/* Count up 1/2 second interval in rpmTick, if not 0 one second has passed. 
		   Don't use this method if we have a serial outboard processor. */
		if (++rpmTick!=0)
		{
			rpmCurrent = rpmCount;	/* Save count as current interval count */
			rpmCount = 0;			/* Clear count of pulses for next interval */
			rpmTick = 0;			/* Clear for next one second cycle. */
		}
		tickCount = 0;				/* Reset for next timer interval cycle */
	}
} /* End of intervalTimerHandler() */







/* Routine to handle a serial port interrupt condition. */

void interrupt far serialPortHandler (void)
{
	register int id, status;

	/* Loop until all pending interrupts are servicesd. */
	while (!((id=inp(sp.sp_data + SR_INTERRUPT_ID)) & INTID_PENDING))
	{
		switch (id & INTID_MASK)
		{
			case INTID_MODEM:		/* Change in modem status */
			/* The serial port RI (Ring Indicator) input can be used to generate an interrupt 
			   for the tachometer pulse.  DTR and RTS supply power to the LED photosensor 
			   device (Omron EE-SV3-B is what I used).  Get here when the RPM sensor triggers 
			   an interrupt.  The RPM pulses are counted here and stored every second in the 
			   intervalTimerHandler() to compute RPM. */
				status = inp (sp.sp_data + SR_MODEM_STATUS); /* Read status to clear interrupt. */
				if (status & MSTS_RING_CNG)
					++rpmCount;			/* Count tach pulses this interval if this is RI change. */
				break;

			case INTID_TRANSMIT:	/* Transmitter data buffer empty. */
				/* Send a stream of data to fill 8250 output FIFO. */
				while ( (inp(sp.sp_data + SR_LINE_STATUS) & LSTS_TRANSMIT) &&
						 (serialOutputBuffer.c_count > 0) )	
				{
					outp (sp.sp_data, *serialOutputBuffer.c_remove);
/* Example of trace table use.
   codes are=TR_command TR_poll TR_decode TR_error TR_intrup TR_step */
//{static char temp[5]={'T',0,0,0};
//static char hex[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
//temp[1]=hex[*serialOutputBuffer.c_remove>>4];
//temp[2]=hex[*serialOutputBuffer.c_remove&0x0F];
//Trace(TR_intrup,temp);}

					/* Compute the location to remove next output data, wrap when we go past the end. */
					if (++serialOutputBuffer.c_remove > serialOutputBuffer.c_bufferEnd)
						serialOutputBuffer.c_remove = serialOutputBuffer.c_buffer;
					/* Count down data items taken out of buffer. */
					serialOutputBuffer.c_count--;
				}
				break;

			case INTID_RXFIFO:
			case INTID_RECEIVE:		/* Receive data buffer full. */
				/* A character is ready, add it to the input circular buffer */
				do {	/* Loop to empty the 8250 input FIFO. */
					/* Since we are at interrupt level, don't need to worry about disable/enable 
					   around the store to circular buffer. Read port data register and store in 
					   buffer. */
					*encoderDataBuffer.c_add = inp (sp.sp_data);
/* Example of trace table use.
   codes are=TR_command TR_poll TR_decode TR_error TR_intrup TR_step */
//{static char temp[5]={'R',0,0,0};
//static char hex[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
//temp[1]=hex[*encoderDataBuffer.c_add>>4];
//temp[2]=hex[*encoderDataBuffer.c_add&0x0F];
//Trace(TR_intrup,temp);}

					encoderDataBuffer.c_count++;		/* Count data stored. */
					if (++encoderDataBuffer.c_add>encoderDataBuffer.c_bufferEnd)
						encoderDataBuffer.c_add = encoderDataBuffer.c_buffer;	/* Wrap at buffer end. */
				} while (inp(sp.sp_data + SR_LINE_STATUS) & LSTS_RECEIVE);
 				break;

			case INTID_ERROR:		/* Error or break. */
				inp(sp.sp_data + SR_LINE_STATUS); /* Read status to clear interrupt. */
				break;
		} /* End of switch(id) */
	} /* End of while */
	/* We tell the 8259 interrupt controller that we are done with this interrupt.
	   *** If we are on controller 2, must tell controller one also *** */
	outp (sp.sp_picaddr, END_OF_INTERRUPT);	/* Clear interrupt in 8259 */
	if (sp.sp_picaddr == 0xA0)			/* If this is controller #2 */
		outp(0x20, END_OF_INTERRUPT);	/* Tell controller #1 too */
} /* End of serialPortHandler () */







/* Reads the config file contents into global data storage */

int readConfigFile(void)

{
	int i, inputMode;
	char configFile[sizeof(configName)+4];
	FILE *stream;
	ENCODER_DATA *axis;
	char string[255], fileVersion[255];		/* Used to read possible long strings */
	short encoderC, encoderX, encoderZ;
	double angle;

	strcpy (configFile, configName);
	if (strpbrk (configFile, ".")==NULL)	/* Look for a '.' in the name. */
		strcat (configFile, ".CFG");		/* Add the default type if no period. */

	/* Open for read */
	if( (stream = fopen (configFile, "r")) == NULL )
	{
		/* If at initialization time, prompt user and continue.  If after, just return error. */
		if (initializationComplete)
			return FALSE;
		else
		{
			printf( "\n%s: Was not able to read the file '%s'\n\nA default configuration will be created.", 
				    programName, configFile);
			printf("\n\npress any key to continue ...");
			getUCchar();
		}
	}
	else
	{
		fscanf(stream, "%[^\n]\n", &string);			/* First line read is an arbitrary name field (machine name). */
		fscanf(stream, "%s\n", &fileVersion);			/* Second line is program version that wrote the file. */
		
		/* If version string stored in config file does not match our version, warn user. */
		if (strcmp(fileVersion, progVersion)!=0)
		{
			printf("\nWarning: Configuration file '%s' was written by a different\n", configFile);
			printf(  "         version of DRO. You should delete it and create a new version.\n");
			printf("\ncurrent program version: %s", progVersion);
			printf("\n    config file version: %s\n\nContinue anyways (Y or N) [N] ? ", fileVersion);
			if (getUCchar()!='Y')
				return FALSE;
		}
		strncpy(machine, string, sizeof(machine)-1);	/* Copy machine name to proper area. */

		fscanf(stream, "%x %i %i %i %i\n", &pp.pp_data, &pp.pp_lpt, &pp.pp_irq, &pp.pp_use_irq, &timeDisplay);
		fscanf(stream, "%x %i %i %i %i %lf\n", &sp.sp_data, &sp.sp_com, &sp.sp_irq, &rpmEnable, &rpmFactor, &rpmDiameter);
		fscanf(stream, "%i %i %i %i\n", &graphicMode, &autoDisplay, &inputMode, &soundOff);
		fscanf(stream, "%i %lf %i %i\n", &encoderC, &angle, &encoderX, &encoderZ);
		fscanf(stream, "%lf\n", &autoZeroOffset);
		for (i=0; (axis = encoderList[i])!=NULL; i++)
		{
			memset (axis->ed_name, 0, sizeof(axis->ed_name)); /* Zero out the name string. */
			fscanf(stream, "%[^\n]\n", &string);		/* Read bytes til the end of line, can't use %s, may have blanks */
			strncpy(axis->ed_name, string, sizeof(axis->ed_name)-1);
			fscanf(stream, "%1c\n", &axis->ed_label);
			fscanf(stream, "%i\n", &axis->ed_displayName);
			fscanf(stream, "%lf\n", &axis->ed_units);
			fscanf(stream, "%i\n", &axis->ed_displayRow);
			fscanf(stream, "%i\n", &axis->ed_decimal_places_shown);
			fscanf(stream, "%i\n", &axis->ed_count_reverse);
			fscanf(stream, "%i\n", &axis->ed_displayError);
			fscanf(stream, "%i\n", &axis->ed_backlash);
			fscanf(stream, "%lf\n", &axis->ed_backlashDist);
			fscanf(stream, "%i\n", &axis->ed_tension);
			fscanf(stream, "%i\n", &axis->ed_rotaryOn);
			fscanf(stream, "%i\n", &axis->ed_number_of_blips);
			axis->ed_rotaryFactor = 360.0 / (double)axis->ed_number_of_blips; /* Conversion factor */
			fscanf(stream, "%i\n", &axis->ed_show_revolutions);
			fscanf(stream, "%i\n", &axis->ed_display_min);
			fscanf(stream, "%i\n", &axis->ed_conversion);
			fscanf(stream, "%i\n", &axis->ed_displaySlew);
			fscanf(stream, "%i\n", &axis->ed_displayDiam);
		}
		
		fclose (stream);	/* Close stream */

		/* If a compound axis exists, set axis pointer, the angle, and the companion axis encoders. */
		if (encoderC>-1 && encoderC<4)
		{
			register ENCODER_DATA *pX, *pZ;

			compoundAxis = encoderList[encoderC];
			compoundAxis->ed_compoundAngle = angle;
			if (encoderX>-1 && encoderX<4)
			{
				compoundAxis->ed_compoundX = pX = encoderList[encoderX];
				pX->ed_compoundUnits = sind(angle) * compoundAxis->ed_units;
			}
			if (encoderZ>-1 && encoderZ<4)
			{
				compoundAxis->ed_compoundZ = pZ = encoderList[encoderZ];
				pZ->ed_compoundUnits = cosd(angle) * compoundAxis->ed_units;
			}
		}

		parallelEnable = serialEnable = FALSE;		/* No mode set yet. */

		if (inputMode == 1)							/* Using parallel port */
		{
			parallelEnable = TRUE;
			serialEnable = FALSE;
		}
		if (inputMode == 2)							/* Using serial port (all others are illegal) */
		{
			serialEnable = TRUE;
			parallelEnable = FALSE;
		}

		/* The serial and parallel port may have changed, check it out. */
		detectSERport(&sp);
		detectPPort(&pp);

		rpmCircumference = (int)((PI*rpmDiameter)*100.0); /* Compute circumference now, scale up by 100. */
	}
	return TRUE;
} /* End of readConfigFile() */






/* Write or rewrite the config file. */

void saveConfigFile()

{
	int i, inputMode;
	char configFile[sizeof(configName)+4];
	FILE *stream;
	ENCODER_DATA *axis;
	short encoderC=-1, encoderX=-1, encoderZ=-1;
	double angle=0.0;
	
	if (configName[0]=='0')				/* Default filename if blank. */
		strcpy(configName, "ENCODER");
	strcpy(configFile, configName);
	if (strpbrk(configFile, ".")==NULL)	/* Look for a '.' in the name. */
		strcat(configFile, ".CFG");		/*  default filetype if no period. */
	
	inputMode = 0;						/* No mode set yet. */
	if (parallelEnable) inputMode = 1;	/* Using parallel port */
	if (serialEnable) inputMode = 2;	/* Using serial port */

	/* If a compound axis exists, write the encoder number, the angle, and the companion axis 
	   encoder numbers. */
	if (compoundAxis!=NULL)
	{
		register ENCODER_DATA *pX, *pZ;

		encoderC = (signed short)compoundAxis->ed_encoder;
		angle = compoundAxis->ed_compoundAngle;
		if ((pX=compoundAxis->ed_compoundX)!=NULL)
			encoderX = (signed short)pX->ed_encoder;
		else
			encoderX = -1;
		if ((pZ=compoundAxis->ed_compoundZ)!=NULL)
			encoderZ = (signed short)pZ->ed_encoder;
		else
			encoderZ = -1;
	}

	/* Open for write */
	if ((stream=fopen (configFile, "w+")) == NULL)
	{
		printf( "\n%s: The file '%s' open for write failed.\n", programName, configFile);
		printf("\npress any key to continue ...");
		getUCchar();
	}
	else
	{
		fprintf(stream, "%s\n", machine);
		fprintf(stream, "%s\n", progVersion);
		fprintf(stream, "%03X %i %i %i %i\n", pp.pp_data, pp.pp_lpt, pp.pp_irq, pp.pp_use_irq, timeDisplay); 
		fprintf(stream, "%03X %i %i %i %i %lf\n", sp.sp_data, sp.sp_com, sp.sp_irq, rpmEnable, rpmFactor, rpmDiameter);
		fprintf(stream, "%i %i %i %i\n", graphicMode, autoDisplay, inputMode, soundOff);
		fprintf(stream, "%i %.14lf %i %i\n", encoderC, angle, encoderX, encoderZ);
		
		fprintf(stream, "%lf\n", autoZeroOffset);
		
		for (i=0; (axis = encoderList[i])!=NULL; i++)
		{
			fprintf(stream, "%s\n", axis->ed_name);
			fprintf(stream, "%1c\n", axis->ed_label);
			fprintf(stream, "%i\n", axis->ed_displayName);
			fprintf(stream, "%.14lf\n", axis->ed_units);
			fprintf(stream, "%i\n", axis->ed_displayRow);
			fprintf(stream, "%i\n", axis->ed_decimal_places_shown);
			fprintf(stream, "%i\n", axis->ed_count_reverse);
			fprintf(stream, "%i\n", axis->ed_displayError);
			fprintf(stream, "%i\n", axis->ed_backlash);
			fprintf(stream, "%lf\n", axis->ed_backlashDist);
			fprintf(stream, "%i\n", axis->ed_tension);
			fprintf(stream, "%i\n", axis->ed_rotaryOn);
			fprintf(stream, "%i\n", axis->ed_number_of_blips);
			fprintf(stream, "%i\n", axis->ed_show_revolutions);
			fprintf(stream, "%i\n", axis->ed_display_min);
			fprintf(stream, "%i\n", axis->ed_conversion);
			fprintf(stream, "%i\n", axis->ed_displaySlew);
			fprintf(stream, "%i\n", axis->ed_displayDiam);
		}
		
		/* Close stream */
		if (fclose(stream))
		{
			printf( "\n%s: The file '%s' close failed. File not saved.\n", programName, configFile);
			printf("\npress any key to continue ...");
			getUCchar();
		}
	}
	return;
} /* End of saveConfigFile() */






/* Create a circular buffer of the specified size. */

int CreateBuf (CIRCULAR_BUFFER *cBuf, int bufsize)

{
	/* Compute length of the buffer in bytes. */
	cBuf->c_buflen = bufsize*sizeof(short);

	/* Allocate the actual buffer */
	cBuf->c_buffer = (unsigned short far *)_fmalloc(cBuf->c_buflen);
	if (cBuf->c_buffer == NULL)
		return (FALSE);

	/* Build a pointer to the end of the buffer. */
	cBuf->c_bufferEnd = cBuf->c_buffer + bufsize-1;

	/* The buffer now contains no data */
	cBuf->c_count = 0;

	/* Set the Add and Remove pointers to initial values. */
	cBuf->c_add = cBuf->c_buffer;
	cBuf->c_remove = cBuf->c_buffer;

	return (TRUE);
} /* End of CreateBuf() */







/* Get the DOS parallel port information by reading the table in low DOS 
   memory (0x408).  Init the parallel port table. Set default is to use
   DOS LPT1 port data as the default. */

void parallelPortScan(void)

{
	int i;
	PARPORT *p;

	/* Pointer to DOS location of Port Addresses */
#ifdef _WATCOMC_
	unsigned short *ptraddr = (unsigned short*)0x408;
#else /* _WATCOMC_ */
	unsigned short far *ptraddr = (unsigned short far *)0x00000408;
#endif /* _WATCOMC_ */

	/* Scan the LPT table in DOS low memory and load available info about
	   each port. */
	for (p=PPtable,i=1; p<PPtable+(sizeof(PPtable)/sizeof(PARPORT)); p++)
	{
		p->pp_data = *ptraddr++;	/* Base address from DOS table */
		p->pp_irq = 0;				/* No IRQ known yet. */
		p->pp_lpt = i++;			/* Set the LPT number. */
		p->pp_use_irq = 0;			/* Not using interrupts yet. */
		p->pp_saveHandler = NULL;	/* Clear handle save area. */
		if (p->pp_data!=0)
			detectPPort(p);			/* Auto detect port */
	}
	pp = PPtable[0];				/* Default to LPT1 for current */
}







/* Disable the parallel port interrupt system for the given port. */

void disablePPIRQ(PARPORT *par)

{
	if (!par->pp_use_irq || par->pp_irq==0)	/* If we are not using interrupts, return now */
		return;

	_disable ();					/* disable interrupts while we work on them */
	if (par->pp_saveHandler != NULL)	/* Restore the old interrrupt vector */
		_dos_setvect (par->pp_intno, par->pp_saveHandler);

	par->pp_saveHandler = NULL;		/* Clear out save area. */

	outp (par->pp_control, inp(par->pp_control)&~0x10);	/* Clear IRQ request bit. */

	/* Mask ON (to one) the IRQ we want to disable, write the register. */
	outp (par->pp_picaddr+1, inp(par->pp_picaddr+1)|par->pp_picmask);
	_enable ();						/* re-enable the interrupts */
}







/* Enable the parallel port interrupt system.
   Note: When interrupts are enabled, graphic output mode is usable */

void enablePPIRQ(PARPORT *par)

{
	if (!par->pp_use_irq || par->pp_irq==0)	/* If we are not using interrupts, return now */
		return;

	_disable ();						/* disable all interrupts while we work on them. */
	par->pp_saveHandler = _dos_getvect (par->pp_intno);		/* Save the old interrrupt vector address */
	_dos_setvect (par->pp_intno, &parallelPortHandler);		/* Set the new interrrupt vector */

	/* Mask OFF (to zero) the IRQ level we want to enable and write register. */
	outp (par->pp_picaddr+1, inp(par->pp_picaddr+1)&~par->pp_picmask);

	/* Mask ON the IRQ request bit to select LPT interrupt */
	outp (par->pp_control, inp(par->pp_control)|0x10);
	_enable ();							/* re-enable the interrupts.  We are done working on them */
}






/* Routine to configure the given parallel port.  Config is based
   on the identified port type. */

void parallelPortConfig(PARPORT *par)

{
	unsigned int theData;

	_disable ();						/* Control port is modified at interrupt level, */
										/*  so disable interrupts while we work on it. */
	switch (par->pp_type)
	{
	case lptN_A:						/* No port found, will error report later. */
		break;

	case lptECP:						/* ECP, set PS/2 mode. */
		theData = inp(par->pp_data+0x402);	/* Get current Extended Control Register image. */
		theData &= ~0xE0;				/* Mask off the current mode bits */
		theData |= 0x20;				/*  add in byte mode (001) select bits */
		outp (par->pp_data+0x402, theData); /* Write to ECR to enter byte mode, then handle like PS/2 mode */

	case lptSPP:						/* Not an input port, will error report later, try bit 5 anyhow. */
	case lptEPP:						/* Handle EPP like PS/2 */
	case lptPS2:						/* Set data direction (bit 5) ON in the control register. */
		theData = inp(par->pp_control);		/* Get image of control port register */
		theData |= 0x20;				/* OR ON bit 5 */
		outp (par->pp_control, theData); /* Store new image in port register */
		break;
	}

	theData = inp(par->pp_control);		/* Get image of control port register */
	theData &= ~0x01;					/* and OFF bit 1, HostClk (nSTROBE) */
	outp (par->pp_control, theData);	/* Store new image in port register */

	_enable ();							/* Re-enable the interrupts. */
	outp (par->pp_data, 0xFF);			/* Send all one's to the data port so they can be  */
										/*  "pulled down" if a true PS/2 style port. */
}






/* Given a PARPORT structure with the port address loaded, determine everything we can
   about the port. */

void detectPPort (PARPORT *par)
{
	unsigned short irq, data;
	unsigned char irqTable[8] = {0, 7, 9, 10, 11, 14, 15, 5}; /* ECP IRQ settings. */

	par->pp_type = detectPortType (par->pp_data); /* Store type code; SPP, EPP, ECP */
	par->pp_status = par->pp_data + 1;
	par->pp_control = par->pp_data + 2;

	/* Detect IRQ number. If one was not forced on us. */
	if (par->pp_irq == 0)
	{
		switch (par->pp_type)
		{
			case lptN_A:						/* No port found, will error report later. */
				irq = 0;
				break;

			case lptECP:						/* ECP, read IRQ setting. */
				data = inp(par->pp_data+0x402);	/* Get current Extended Control Register image. */
				data &= ~0xE0;					/* Mask off the current mode bits, want mode, but 111 must go */
				data |= 0x20;					/*  through mode 000 or 001 first. */
				outp(par->pp_data+0x402, data);	/* Write to ECR to enter 001 mode. */
				data |= 0xE0;					/*  add in config mode (111) select bits */
				outp(par->pp_data+0x402, data);	/* Write to ECR to enter 111 mode. */
				irq = inp(par->pp_data+0x401);	/* Get current Config B register image. */
				irq = (irq>>3)&0x07;			/* Isolate IRQ setting bits. */
				break;

			case lptSPP:						/* Can't lookup IRQ for these, use 7 or 5 */
			case lptEPP:
			case lptPS2:
				if (par->pp_data == 0x3BC)
					irq = 7;					/* We know this address is usually 5, load table index for 5. */
				else
					irq = 1;					/*  otherwise IRQ 7. */
				break;
		}
		par->pp_irq = irqTable[irq];			/* Lookup PC IRQ number in table. */
	}

	if (par->pp_irq >= 2 && par->pp_irq <= 7)
	{
		par->pp_intno = par->pp_irq + 0x08;
		par->pp_picaddr = 0x20;
		par->pp_picmask = 1 << par->pp_irq;
	}
	if (par->pp_irq >= 8 && par->pp_irq <= 15)
	{
		par->pp_intno = par->pp_irq + 0x68;
		par->pp_picaddr = 0xA0;
		par->pp_picmask = 1 << (par->pp_irq-8);
	}
}





/* Given a port address, determine the type of parallel port it is. */

short detectPortType(short thePort)

{
	short theType;

	/*	ECP test doesn't write to any active data registers, so no reset is needed.
		Test for an ECP */
	if ((theType = ECPdetect (thePort)) == lptN_A)
	{
		/* Not ECP, then test for an EPP */
		/* perform a printer reset to prevent printers from printing unusable stuff */
		ResetLPT (thePort);
		if ((theType = EPPdetect (thePort)) == lptN_A)
		{
			/* Not ECP or EPP, test for a SPP or PS/2 */
			theType = PPPdetect(thePort);
		}
	}

	outp (thePort+2,0xC4);			/* Reset bidirectional flag to output (normal mode) */
	return theType;
}






/* ECP port detection.  To test if a port is ECP, read the ECR at (base address + 402h).
   Then write 34h to the ECR and read it back.  Bits 0 and 1 are read-only, so if you read 
   35h, you almost certainly have an ECP. (This is the test described in Microsoft's ECP 
   document, on the MS developer's CD-ROM.) */

short ECPdetect(short thePort)

{
	int	ret = lptN_A;					/* Assume ECP is Not Available */
	unsigned char saveECR;

	if (thePort == 0x3BC)				/* Port 3BC cannot be ECP (extended port addresses conflict w/ video) */
		return lptN_A;

	saveECR = inp (thePort+0x402);		/* Save Extended Control Register */

	/* ECP test is write 0x34 (ECP mode '001') to ECR and then read back 0x35.
	   Lower bit is read only and should be zero, this checks for it. */
	outp (thePort+0x402, 0x34);			/* Write 0x34 (byte mode, no interrupt, no DMA) read back 0x35 */
	if (inp (thePort+0x402) == (unsigned char) 0x35) /* If ECR looks right, test data registers. */
	{
		outp (thePort+0x402, 0xD4);		/* Enter test mode (so we don't write to connected device) */
		outp (thePort+0x400, 0xAA);		/* Try to store a bit pattern. */
		if (inp(thePort+0x400) == (unsigned char) 0xAA)
		{
			outp (thePort+0x400, 0x55); /* Try another bit pattern. */
			if (inp(thePort+0x400) == (unsigned char) 0x55)
				ret = lptECP;			/* We have an ECP port */
		}
	}

	outp (thePort+0x402, 0x34);			/* Put back to ECP mode 001 before restore of ECR */
	outp (thePort+0x402, saveECR);		/* Restore ECR value */
	return ret;
} /* End of ECPdetect() */






/* EPP port detection without Control port initialisation
   In addition to the SPP's three registers, an EPP has four additional registers, at 
   base address + 3 through base address + 6. These additional registers provide a way 
   to test for the presence of an EPP, by writing a couple of values to one of the EPP 
   registers and reading them back, much like one would test for an SPP. If the reads 
   are successful, the registers exists and we probably have an EPP. */

short EPPdetect(short thePort)

{
	if (thePort == 0x3BC)			/* Port 3BC cannot be EPP (extended port addresses conflict w/ video) */
		return lptN_A;

	outp (thePort+2, 0x04);			/* EPP mode requires bits 0, 1, and 3 to be zero. */

	/* Found some EPP ports that mirror the SPP write, so write SPP first. */
	outp (thePort, 0xAA);			/* Write a test pattern to SPP data port. */
	outp (thePort+4, 0xAA);			/*  and to EPP data reg 0. */
	EPPclear (thePort);				/* Clear Timeout bit from the outp. */
	if(inp (thePort+4) != (unsigned char) 0xAA) /* See if EPP data reg 0 is 'AA' */
		return lptN_A;				/* Not EPP if 'AA' not found. */
                                    /* Timeout bit should be set from above _inp. */
	if(!(inp (thePort+1) & (unsigned char) 0x01)) /* Check status register for Timeout status. */
		return lptN_A;				/* No timeout, can't be EPP */
	EPPclear (thePort);				/* Clear the Timeout. */
	if(inp (thePort+1) & (unsigned char) 0x01) /* Confirm that it cleared. */
		return lptN_A;				/* Not EPP if it won't clear. */

	return lptEPP;					/*  else, it passed as an EPP device. */
} /* End of EPPdetect() */







/* Parallel Printer Port detection (SPP or PS/2).  Make sure the data register
   can be written to and check for the the ability to change the data direction.
   If we can change the data direction bit, assume it is PS/2 mode. */

short PPPdetect(short thePort)

{
	outp (thePort+2, 0xC4);		/* Set bidirectional flag in CTR to output direction */

	outp (thePort, 0xAA);		/* Try to store a bit pattern in the data register */
	if (inp (thePort) != (unsigned char) 0xAA)	/*  see if it reads back */
		return lptN_A;			/* Not a port if does not read back. */
	outp (thePort, 0x55);		/* Try alternate pattern also... */
	if (inp (thePort) != (unsigned char) 0x55)
		return lptN_A;

	outp (thePort+2, 0xE4);		/* Set bidirectional flag in CTR to input (reverse mode) */

	outp (thePort, 0xAA);		/* Should not read back now. */
	if (inp (thePort) != (unsigned char) 0xAA)
	{
		outp (thePort, 0x55);	/* Same with this pattern. */
		if (inp (thePort) != (unsigned char) 0x55)
			return lptPS2;		/* Is PS/2 mode if both patterns don't store. */
	}
	else
	{	/* If we can change the state of the direction bit, call it PS/2 */
		if ((inp (thePort+2) & 0x20) != 0)	/* If the bit is one now. */
		{
			outp (thePort+2, 0xC4);		/* Set bidirectional flag to zero. */
			if ((inp (thePort+2) & 0x20) == 0)	/* If the bit is zero now. */
				return lptPS2;	/* Call it PS/2 mode if bit changed. */
		}
	}
	return lptSPP;				/* Otherwise, a write only SPP mode device */
} /* End of PPPdetect() */









/* Clear EPP timeout.
   Semantics of clearing EPP timeout bit:
    PC87332	- reading status port does it...
    SMC		- write 1 to EPP timeout bit...
    Others	- (?) write 0 to EPP timeout bit
	still others - (?) read twice from status port */

void EPPclear(short thePort)

{
	register unsigned char val;

	val = inp (thePort+1);			/* Reset Timeout-Flag by reading */
	inp (thePort+1);				/* Have heard of ports that need two reads to clear... */
	outp (thePort+1, val | 0x01);	/*  or by writing 1 */
	outp (thePort+1, val & 0xFE);	/*  or by writing 0 to it */
}








/* Perform a parallel printer port reset */

void ResetLPT(short thePort)

{
	int i;

	/* since a port read/write command is delayed by ISA bus waitstates to
	   1,6 ęs, we can use it for a simple system independent delay routine. */
	for (i=10; i>0; i--)
		outp (thePort+2, 0xC0);	/* Drop reset bit (bit 2) to cause printer reset. */

	outp (thePort+2, 0xC4);		/* Clear reset state. */
}







/* Get the DOS serial port addresses by reading the table in low DOS 
   memory (0x400). */

void serialPortScan(void)

{
	int i;
	SERPORT *p;

	/* Pointer to DOS location of Port Address table */
#ifdef _WATCOMC_
	unsigned short *ptraddr = (unsigned short*)0x400;
#else /* _WATCOMC_ */
	unsigned short far *ptraddr = (unsigned short far *)0x0000400;
#endif /* _WATCOMC_ */

	/* Scan the COMn table in DOS low memory and load available info about
	   each port. */
	for (p=SPtable,i=1; p<SPtable+(sizeof(SPtable)/sizeof(SERPORT)); p++)
	{
		p->sp_data = *ptraddr++;	/* Base address from DOS table */
		p->sp_irq = 0;				/* No IRQ number yet. */
		p->sp_com = i++;			/* Set the COMn number. */
		p->sp_flags = 0;			/* Clear flags. */
		p->sp_saveHandler = NULL;	/* Clear handle save area. */
		if (p->sp_data!=0)
			detectSERport (p);		/* Auto detect port info */
	}
	sp = SPtable[0];				/* Default to COM1 for current port. */
} /* End of serialPortScan() */






/* Initialize the serial port. */

void serialPortConfig(SERPORT *ser)

{
	if (ser->sp_data == 0 || ser->sp_irq == 0)
		return;					/* Can't continue if no port address or no IRQ. */

	/* Flip to the 8250 divisor registers */
	outp(ser->sp_data + SR_LINE_CONTROL, LC_DIVISOR);

	/* Set the 8250 baud rate */
	outp(ser->sp_data + SR_BAUD_MSB, BD19200_MSB);
	outp(ser->sp_data + SR_BAUD_LSB, BD19200_LSB);

	/* Flip back to the 8250 data registers.  Set up for 8 bits, no parity. */
	outp(ser->sp_data + SR_LINE_CONTROL, LC_8BITS);

	/* Clear the character FIFOs and enable Tx and Rx FIFO, if there is one */
	if (ser->sp_flags & CF_FIFO)
	{
		outp(ser->sp_data + SR_FIFO_CONTROL, FIFO_ENABLE + FIFO_RX_RESET + FIFO_TX_RESET);
		outp(ser->sp_data + SR_FIFO_CONTROL, FIFO_1);
	}
} /* End of serialPortConfig() */







/* Disable the interrupt system for the given serial port. */

void disableSPIRQ(SERPORT *ser)

{
	if (ser->sp_irq==0)				/* If we are not using interrupts, return now */
		return;

	_disable ();					/* disable interrupts while we work on them */
	if (ser->sp_saveHandler != NULL)	/* Restore the old interrrupt vector */
		_dos_setvect (ser->sp_intno, ser->sp_saveHandler);

	ser->sp_saveHandler = NULL;		/* Clear out save area. */

    /* Turn OFF interrupt enable for receiver data ready and transmitter done. */
	inp (ser->sp_data + SR_LINE_STATUS);		/* Read status register to clear interrupt request. */
    outp(ser->sp_data + SR_INTERRUPT_ENABLE, 0);

	/* Turn OFF DTR, RTS, and interrupt enable for Modem status in serial port. */
    outp(ser->sp_data + SR_MODEM_CONTROL, 0);

	/* Mask ON (to one) the IRQ we want to disable, write the register. */
	outp (ser->sp_picaddr+1, inp(ser->sp_picaddr+1) | ser->sp_picmask);
	_enable ();						/* re-enable the interrupts */
} /* End of disableSPIRQ() */








/* Enable the interrupt system. */

void enableSPIRQ(SERPORT *ser)

{
	if (ser->sp_irq==0)					/* If we are not using interrupts, return now */
		return;

	_disable ();						/* disable all interrupts while we work on them. */
	ser->sp_saveHandler = _dos_getvect (ser->sp_intno);		/* Save the old interrrupt vector address */
	_dos_setvect (ser->sp_intno, &serialPortHandler);		/* Set the new interrrupt vector */

	/* Turn ON interrupt enable for receiver data ready. */
	inp (ser->sp_data + SR_LINE_STATUS);		/* Read status register to clear interrupt request. */
	outp(ser->sp_data + SR_INTERRUPT_ENABLE, INTR_RECEIVE|INTR_ERROR|INTR_STATUS|INTR_TRANSMIT);

	/* Turn ON DTR, RTS, and interrupt enable for Modem status in serial port. */
	outp(ser->sp_data + SR_MODEM_CONTROL, MODM_DTR + MODM_RTS + MODM_INTR_EN);

	/* Mask OFF (to zero) the IRQ level we want to enable and write register. */
	outp (ser->sp_picaddr+1, inp(ser->sp_picaddr+1) & ~ser->sp_picmask);
	_enable ();							/* re-enable the interrupts.  We are done working on them */
} /* End Of enableSPIRQ() */







/* Determine IRQ level of given serial port device.

   NOTE: This only works under true DOS. */


void detectSERport (SERPORT *ser)

{
	long delay=900000;
	unsigned char ier, mcr, imrm, imrs, maskm, masks, irqm, irqs;

	/* Detect type of UART. */
	ser->sp_type = detect_UART (ser->sp_data);

	if (ser->sp_type == 0)
		return;						/* Not a UART at all! */
	if (ser->sp_type > 3)
		ser->sp_flags |= CF_FIFO;	/* Type .GT. 3 means we have a FIFO */

	if (ser->sp_irq==0)				/* If given an IRQ, don't look for one. */
	{
		_disable ();					/*  disable all CPU interrupts */
		ier = inp (ser->sp_data + SR_INTERRUPT_ENABLE);	/*  read IER */
		outp (ser->sp_data + SR_INTERRUPT_ENABLE, 0);		/*  disable all UART ints */
		while (!(inp (ser->sp_data + SR_LINE_STATUS)&0x20))	/*  wait for Transmitter register empty */
			if (--delay == 0)
			{
				_enable ();
				return;					/* Don't wait too long */
			}
		mcr = inp(ser->sp_data + SR_MODEM_CONTROL);	/*  Read Modem Control Register */
		outp (ser->sp_data + SR_MODEM_CONTROL, 0x0F);	/*  Connect UART to irq line */
		imrm = inp (0x21);				/*  Read contents of master ICU mask register */
		imrs = inp (0xA1);				/*  Read contents of slave ICU mask register */
		outp (0xA0, 0x0A);				/*  Next read access to 0xA0 reads out Master IRR */
		outp (0x20, 0x0A);				/*  Next read access to 0x20 reads out Slave IRR */
		outp (ser->sp_data + SR_INTERRUPT_ENABLE, INTR_TRANSMIT); /*  Let's generate interrupts... */
		maskm = inp (0x20);				/*  This clears all bits except for the one */
		masks = inp (0xA0);				/*  That corresponds to the int */
		outp (ser->sp_data + SR_INTERRUPT_ENABLE, 0);	/*  Drop the int line */
		maskm &= ~inp (0x20);			/*  This clears all bits except for the one */
		masks &= ~inp (0xA0);			/*   that corresponds to the int */
		outp (ser->sp_data + SR_INTERRUPT_ENABLE, INTR_TRANSMIT); /*  and raise it again just to be sure... */
		maskm &= inp (0x20);			/*  This clears all bits except for the one */
		masks &= inp (0xA0);			/*   that corresponds to the int */
		outp(0xA1, ~masks);				/*  Now let us unmask this interrupt only */
		outp(0x21, ~maskm);
		outp(0xA0, 0x0C);				/*  Enter polled mode; Mike Surikov reported */
		outp(0x20, 0x0C);				/*   that order is important with Pentium/PCI systems */
		irqs = inp (0xA0);				/*   and accept the interrupt */
		irqm = inp (0x20);
		inp (ser->sp_data + SR_INTERRUPT_ID);		/*  Reset transmitter interrupt in UART */
		outp (ser->sp_data + SR_MODEM_CONTROL, mcr);	/*  Restore old value of MCR */
		outp (ser->sp_data + SR_INTERRUPT_ENABLE, ier);	/*  Restore old value of IER */
		if (masks)
			outp(0xA0,END_OF_INTERRUPT);/*  Send an EOI to slave */
		if (maskm)
			outp(0x20,END_OF_INTERRUPT);/*  send an EOI to master */
		outp (0x21, imrm);				/*  Restore old mask register contents */
		outp (0xA1, imrs);
		_enable ();

		if (irqm & 0x80)				/* Master interrupt occured */
			ser->sp_irq = irqm & 0x07;	/* Means it's IRQ1-7 */
		if (irqs & 0x80)				/* Slave interrupt occured */
			ser->sp_irq = (irqs & 0x07)+8;	/* Means it's IRQ8-15 */
	}
	
	setSerialIntNo (ser);
} /* End of detectSERport () */









void setSerialIntNo (SERPORT *ser)

{
	if (ser->sp_irq >= 2 && ser->sp_irq <= 7)
	{
		ser->sp_intno = ser->sp_irq + 0x08;
		ser->sp_picaddr = 0x20;
		ser->sp_picmask = 1 << ser->sp_irq;
	}
	if (ser->sp_irq >= 8 && ser->sp_irq <= 15)
	{
		ser->sp_intno = ser->sp_irq + 0x68;
		ser->sp_picaddr = 0xA0;
		ser->sp_picmask = 1 << (ser->sp_irq-8);
	}
} /* End of setSerialIntNo() */







/* Given an I/O base address, this function returns:
   0: no UART installed. 
   1: 8250
   2: 16450 or 8250 with scratch register
   3: 16550
   4: 16550A */

short detect_UART(short thePort)

{ 
    int olddata;

	/* Check if a UART is present at all, look at known zero bits */
	if (inp (thePort+SR_INTERRUPT_ID) & 0x30)	/* All UARTs have these bits zero */
		return 0;								/* Not a UART if not so... */
	if (inp (thePort+SR_INTERRUPT_ENABLE) & 0xF0) /* All UARTs have these bits zero */
		return 0;								/* Not a UART if not so... */
	if (inp (thePort+SR_MODEM_CONTROL) & 0xE0)	/* All UARTs have these bits zero */
		return 0;								/* Not a UART if not so... */

	/* Test for loopback feature */
	olddata = inp (thePort+SR_MODEM_CONTROL);	/* Save old state */

	/* Set loopback and some modem bits. */
	outp (thePort+SR_MODEM_CONTROL,	MODM_LOOP_BACK + MODM_DTR + MODM_RTS + MODM_OUT1);
	if ((inp (thePort+SR_MODEM_STATUS) & 0x70) != 0x70)
		return 0;								/* All bits must be ON */
	outp (thePort+SR_MODEM_CONTROL, MODM_LOOP_BACK); /* Set just loopback */
	if ((inp(thePort+SR_MODEM_STATUS) & 0x70) != 0)
		return 0;								/* All bits must be OFF */
	outp (thePort+SR_MODEM_CONTROL, olddata);	/* Restore */

	/* Next thing to do is look for the scratch register */
	olddata = inp (thePort+SR_SCRATCH);			/* Save current data */
	outp (thePort+SR_SCRATCH, 0x55);			/* Write a pattern */
	if (inp (thePort+SR_SCRATCH) != 0x55)		/* Check it */
		return 1;								/* No scratch */
	outp (thePort+SR_SCRATCH, 0xAA);			/* Change pattern */
	if (inp (thePort+SR_SCRATCH) != 0xAA)
		return 1;								/* No scratch */
	outp (thePort+SR_SCRATCH, olddata);			/* Restore it. */

	/* Then check if there's a FIFO */
	outp (thePort+SR_FIFO_CONTROL, FIFO_ENABLE); /* Enable FIFO */
	olddata = inp (thePort+SR_FIFO_CONTROL);	/* Save state of FIFO control word */
	outp (thePort+SR_FIFO_CONTROL, 0x00);		/* Disable FIFO, some old software relies on this! */
	if ((olddata & 0x80) == 0)					/* See if FIFO Enable bits came back... */
		return 2;								/* 8250 w/ scratch */
	if ((olddata & 0x40) == 0)
		return 3;								/* 16550 w/ FIFO */
	return 4;									/* 16550A w/ 16 word FIFO */
} /* End of detect_UART() */







/* This function rounds (up or down) the given floating point value to 1/2 of the
   axis count units. Then returns the long int of the value based on the ed_units.
 */

long int round2Units(ENCODER_DATA *axis, double theValue)

{
	double temp, round;

	temp = theValue / axis->ed_units;
	round = axis->ed_units/2;
	if (temp < 0)
		temp -= round;
	else
		temp += round;
	return (long int)temp;
}







/* Number input function, to input and check a double float number.  
   Max of 15 digits scanned.  Return FALSE if ESC was hit, nothing
   stored. */

int numbers_only(double *answer, short row, short colum, char *message, short color)

{
	char *numberString;

	numberString = collect_number_string(row, colum, message, color, 10);
	if (numberString[0]==ESC)
		return FALSE;
	if (numberString[0]!=ENTER)	/* Don't change value if null entry. */
		*answer = strtod(numberString, NULL);
	return TRUE;
}







/* Number input function, to input and check an int number.  The prompt message is
   optional (NULL if not wanted).  The row/col are required.  Column is the position
   that the input field begins (so colum-strlen(message) is start position of message.
   Max of 15 digits scanned.  Return FALSE if ESC was hit, nothing stored.*/

int intnumbers_only(short *answer, short row, short column, char *message, short color, short base)

{
	char *numberString;

	numberString = collect_number_string(row, column, message, color, base);
	if (numberString[0]==ESC)
		return FALSE;
	if (numberString[0]!=ENTER)	/* Don't change value if null entry. */
		*answer = strtoi(numberString, base);
	return TRUE;
}








/* Convert a string into an int number.  String is pointed to by nptr.
   Base can be 0, 2, 8, 10, or 16.  If base is 0, default is 10 unless
   string begins with a 0 for octal (base 8) or 0x for hex. 
*/

int strtoi(char *nptr, short base)

{
	int value, digit;
	int sign = 1;

	/* Skip leading white space */
	while( isspace(*nptr) )
		nptr++;

	/* Check if there is a sign */
	if (*nptr == '-')
	{
		sign = -1;
		nptr++;
	}
	else if (*nptr == '+')
	{
		sign = 1;
		nptr++;
	}

	/* Check the base, if undefined, determine it */
	if (base == 0)
	{
		if (*nptr == '0')
		{
			nptr++;
			if (tolower(*nptr) == 'x') 
			{
				nptr++;
				base = 16;
			}
			else
				base = 8;
		}
		else
			base = 10;
	 }

	/* Convert the characters to an int number */
	value = 0;
	digit = -1;
	while ( isalnum(*nptr) )
	{
		if ( isdigit(*nptr) )
			digit = *nptr - '0';
		else
			digit = tolower(*nptr) - 'a' + 10;

		if ( (digit >= base) || (digit < 0) )
			break;			/* Isn't a valid char, abort conversion */

		value = value * (int)base + digit;
		nptr++;
	}
	return sign * value;
}







/* Collect a number string from console input.  The prompt message is optional (NULL if 
   not wanted).  The row/col are required.  Color is the color given to the echoed
   input characters. Max of 15 characters scanned. '-' and '.' are ok, backspace is handled.
   All others are not allowed.  Terminate on <cr>. <ESC> will return flag value of ESC in 
   buffer[0] */

char *collect_number_string(short row, short col, char *message, short color, short base)

{
	static char buffer[16];
	short c, saveColor=_gettextcolor();
	int len, inp_col, period, i;

	/* Count the number of charchacters in the prompt string.  Only display if 
	   not zero length. */
	len = strlen(message);
	inp_col = col + len;			/* Input start screen location */
	if (len>0)
		s_outTextxy (row, col, message);	/* Prompt message. */

  	buffer[0] = '\0';				/* Terminate string with a null character to start. */		   
	period = 0;
	i = 0;
	while (TRUE)
	{
		s_setTextPosition (row, inp_col+i); 
		_settextcursor (CURSOR_ON);
		c = getLCchar();			/* Get an input character */
		if (c==TAB || c==HOME)
			longjmp(command, c);	/* Jump to command loop, stuff a command. */
		
		if (c==ESC)					/* Esc key-abort and return a flag to the calling function. */
		{
			buffer[0] = ESC;		/* Force this into buffer, and exit loop. */
			break;
		}   
		if (c==ENTER)				/* Enter--done processing input, exit loop and convert string */
		{
			if (i==0)
				buffer[0] = ENTER;	/* Flag null entry */
			break;					/*  stored in buffer. */
		}
		if (c==0x08)				/* Backspace -- unstore the last character. */
		{
			if (i==0)				/* Can't go back farther than begining. */
			{
				Sound (ERRnote);		/* At limit now, beep */
				continue;			/* Get next character. */
			}
			if (buffer[i--]=='.')	/* Watch for period, uncount character,  */
				period--;			/* Allow another period if we just deleted one */
			s_outCharxyt (row, inp_col+i, color,' ');	/* Blank it out on display */
			continue;				/* Get next character. */
		}
		if (i>=14)					/* Loop for max of 15 characters (0-14). */
		{
			Sound (ERRnote);			/* At limit now, only accept ESC, <cr>, or backspace */
			continue;
		}
		if (c=='.')					/* If a period, will add the char to the string. */
		{
			if (++period>1)			/* only allow 1 period */
			{
				period = 1;			/* Reset to max (so Backspace works). */
				Sound (ERRnote);		/* Beep */
				continue;			/* Get next character. */
			}
		}
		/* Check that it is a number, hex, X, or - sign. */
		else if (!(base!=10 ? isxdigit(c)||toupper(c)=='X' : isdigit(c)) && c!='-')
		{
			Sound (ERRnote);			/* Invalid char if not a digit, Beep */
			continue;				/* Get next character. */
		}
		/* If it got this far, the character is worth storing... */
		s_outCharxyt (row, inp_col+i, color, (unsigned char)c);	/* Echo character to display */
		buffer[i++] = (char)c;		/* Store and count character processed. */
	  	buffer[i] = '\0';			/* terminate string with null character	 */		   
	} /* end of while loop */

	_settextcolor (saveColor);
	_settextcursor (CURSOR_OFF);
	return buffer;
}









char *decimal_to_degrees(double decimal)   

{
    double fraction;
    double degrees, minutes, seconds;
    static char answer [20];

	/* Can do it like this... */
/*	modf( modf( modf( decimal, &degrees) * 60.0, &minutes) * 60.0, &seconds); */

	/* But, this is more readable... */
	fraction = modf( decimal, &degrees);		/* Seperate degrees and fraction part. */
	fraction = modf( fraction * 60.0, &minutes);/* Extract minutes from fraction. */
    fraction = modf( fraction * 60.0, &seconds);/* Extract seconds from fraction. */

	/* Degrees can be negitive, but the rest must be positive. */
    sprintf(answer, "% 5i\xF8%02i\'%02i\"", (int)degrees, abs((int)minutes), abs((int)seconds));

	return (answer);
}








/* Display the given prompt at the given display location and scan for input, 
   checking for valid characters.  The result is converted from deg-min-sec to 
   decimal and returned as a double. */

int auto_degrees_decimal(double *answer, char message[], short row_string, short colum_string,
						 short row_prompt, short colum_prompt, short color)  

{
	char buffer[15];
	static char *format[3] = {"%i", "%i,%i", "%i,%i,%i"};
	int degrees=0, minutes=0, seconds=0;
	int i, comma;
	short c;
	
	while (TRUE)				/* Repeat loop til valid numbers or escape */
	{
		i = 0;
		comma = 0;

		/* Position to prompt area on display and write prompt. */
		s_outTextxy (row_string, colum_string, message);
		
		s_setTextPosition (row_prompt, colum_prompt); 
		while (TRUE)
		{
			s_setTextPosition (row_prompt, colum_prompt+i); 
			_settextcursor (CURSOR_ON);
			c = getUCchar();			/* Get an input character */
			
			if (c==ESC)					/* The esc key -- abort and return a flag to the calling function. */
			{
				_settextcursor (CURSOR_OFF);
				return FALSE;
			}
			if (c==ENTER)				/* Enter -- done processing input, exit loop and convert string */
				break;					/*  stored in buffer. */
			if (c==0x08)				/* Backspace -- unstore the last character. */
			{
				if (i==0)				/* Can't go back farther than begining. */
					continue;			/* Get next character. */
				if (buffer[i--]==',')	/* Watch for comma, uncount character,  */
					comma--;			/* Allow another comma if we just deleted one */
				s_outCharxyt (row_prompt, colum_prompt+i, color, '_');	/* Blank it out on display */
				continue;				/* Get next character. */
			}
			if (i>=9)					/* Loop for max of 10 characters (0-9). */
			{
				Sound (ERRnote);			/* At limit now, only accept ESC, <cr>, or backspace */
				continue;
			}
			if (c==',')					/* If a comma, will add the char to the string. */
			{
				if (++comma>2)			/* only allow 2 commas (degrees,minutes,seconds) */
				{
					comma = 2;			/* Reset to max (so Backspace works). */
					Sound (ERRnote);		/* Beep */
					continue;			/* Get next character. */
				}
			}
			else if (!isdigit(c))		/* Check that it is a number. */
			{
				Sound (ERRnote);			/* Invalid char if not a digit, Beep */
				continue;				/* Get next character. */
			}

			/* If it got this far, the character is worth storing... */
			buffer[i++] = (char)c;		/* Store and count character processed. */
			buffer[i] = '\0';			/* Terminate string with null character */
			s_outCharxyt (row_prompt, colum_prompt, color, (unsigned char)c);	/* Echo character to display */
		} /* end of while loop */		    	
		
		/* Based on how many commas there are, scan the proper quantity of numbers */
		sscanf(buffer, format[comma], &degrees, &minutes, &seconds);

		/* Value check the given values, re display prompt and continue looping if in error. */
		if (degrees > 359 || degrees < 0)
			continue;
		if (minutes > 59 || minutes < 0)
			continue;
		if (seconds > 59 || seconds < 0)
			continue;
		break;							/* Good values, terminate loop. */
	}

	/* 1 minute is 1/60 degrees, 1 second is 1/3600 degrees */
	*answer = (double)degrees + ((double)minutes / 60.0) + ((double)seconds / 3600.0);
	_settextcursor (CURSOR_OFF);
	return TRUE;
}








/* Get a string of printable characters from console of max count bytes
   (including NULL).  Handles backspace, returns FALSE if ESC is pressed, 
   TRUE otherwise.  The color argument is used to "color" the input characters
   as they are echoed. */

int get_string(char *buffer, int count, short color)

{
	int c, i=0;
	struct _rccoord pos;

	while (TRUE)
	{
		_settextcursor (CURSOR_ON);
		c = getLCchar();			/* Get an input character (allow Lower Case) */
		if (c==ESC)					/* The esc key, abort and return a flag to calling function. */
		{
			_settextcursor (CURSOR_OFF);
			return FALSE;			/*  return FALSE flag. */
		}
		if (c==ENTER)				/* Enter -- done processing input, exit loop and return string */
			break;					/*  stored in buffer. */
		if (c==0x08)				/* Backspace -- unstore the last character. */
		{
			if (i==0)				/* Can't go back farther than begining. */
			{
				Sound (ERRnote);		/* Warn user. */
				continue;			/* Get next character. */
			}
			buffer[--i] = '\0';		/* Uncount character,  */
			pos = _gettextposition ();	/* Get current screen position */
			pos.col -= 1;			/* move back one col */
			s_outCharxyt (pos.row, pos.col, color, ' ');	/* Blank it out on display */
			s_setTextPosition (pos.row, pos.col); /* Backup again */
			continue;				/* Get next character. */
		}
		if (i>=(count-1) || !isprint(c))/* Check that it is a printable and i .LE. count. */
		{
			Sound (ERRnote);			/* Invalid char if not a printable, Beep */
			continue;				/* Get next character. */
		}
		/* If it got this far, the character is worth storing... */
		buffer[i++] = c;			/* Store and count character processed. */
		s_outCharxyt (-1, -1, color, (char)c);	/* Echo character to display */
	} /* end of while loop */
	buffer[i] = '\0';				/* terminate string with null character	*/
	_settextcursor (CURSOR_OFF);
	return TRUE;
}








/* Make a sound. */

void Sound (int noteType)

{
	enum NOTES      /* Enumeration of notes and frequencies     */
	{
		C_0 = 262, D_0 = 296, E_0 = 330, F_0 = 349, G_0 = 392, A_0 = 440, B_0 = 494,
		C_1 = 523, D_1 = 587, E_1 = 659, F_1 = 698, G_1 = 784, A_1 = 880, B_1 = 988,
		EIGHTH = 125, QUARTER = 250, HALF = 500, WHOLE = 1000
	};

	if (soundOff) return;

	switch (noteType)
	{
		case ERRnote:
			Note( C_0, EIGHTH );
			Note( C_1, EIGHTH );
			Note( C_0, QUARTER );
			break;

		case DONEnote:
			Note( B_1, EIGHTH );
			Note( B_0, EIGHTH );
			break;

		case QUESTnote:
			Note( B_0, EIGHTH );
			Note( B_1, EIGHTH );
			break;

		case BEEPnote:
			Note( A_0, EIGHTH );
			break;
	}
}







/* Sounds the speaker for a time specified in microseconds by duration
   at a pitch specified in hertz by frequency.
 */
void Note( int frequency, int duration )

{
	int control;

	/* If frequency is 0, don't try to make a sound.
	   Just sleeps for the duration.	*/
	if ( frequency )
	{
		/* 75 is about the shortest reliable duration of a sound. */
		if( duration < 75 )
			duration = 75;

		/* Prepare timer by sending 10111100 to port 43. */
		outp( 0x43, 0xb6 );

		/* Divide input frequency by timer ticks per second and
		   write (byte by byte) to timer.
		 */
		frequency = (unsigned)(1193180L / frequency);
		outp( 0x42, (char)frequency );
		outp( 0x42, (char)(frequency >> 8) );

		/* Save speaker control byte. */
		control = inp( 0x61 );

		/* Turn on the speaker (with bits 0 and 1). */
		outp( 0x61, control | 0x3 );
	}

	Sleep( (clock_t)duration );

	/* Turn speaker back on if necessary. */
	if ( frequency )
		outp( 0x61, control );
} /* End of Note() */






/* Pauses for a specified number of milliseconds. */
void Sleep( clock_t mSeconds )

{
	clock_t goal;

	goal = mSeconds + clock();
	while( goal > clock() )
		pollParallelPort;
}






void open_scroll_help(void)

{
	int lines_down=0;
	int row, cnt, i;
	FILE *stream;
	int c;
	char *status, text_line[80];

	s_init_outText(_TEXTC80);
	_settextcursor (CURSOR_OFF);

	/* Set window to max size, cursor OFF, and fill screen with WHITE background. */
	s_setTextWindow (1, 1, 25, 80, WHITE);

	/* WHITE banners top and bottom with BLACK text. */
	s_outTextxytb (1, 38, BLACK, WHITE, "HELP");
	s_outTextxy (25, 5, "Up & Down arrow keys    PgUp    PgDn    Home    End    Esc to return...");

	/* Fill center part of screen with BLUE */
	s_setTextWindow (2, 1, 24, 80, BLUE);

	s_setTextWindow (2, 10, 24, 70, -1);
	_settextcolor (YELLOW); 

	stream = fopen(helpFileName,"r");
	if (!stream) 
	{
		s_printfxy (11, 16, "File \"%s\" does not exist", helpFileName);
		s_outTextxy (13, 16, "Press any key to continue...");
		getUCchar();
		return;
	}

	c = 0;
	while (c != ESC)
	{
		fseek (stream, 0L, SEEK_SET);		/* 'Rewind' file */
		s_clearScreen(_GWINDOW, -1);
		if (lines_down < 23)				/* Must show at least 23 lines of text. */
			lines_down = 23;
		row = 1;							/* Start at display text row one. */
		for (cnt=1; cnt<=lines_down; cnt++)	/* Step through lines until where we want to be. */
		{
			if ((status=fgets (text_line, 75, stream)) == NULL)	/* Get a string, max of 75 bytes. */
				lines_down = cnt;			/* If at eof, reset lines_down now. */
			
			if(cnt > (lines_down-23))		/* Where we want to be yet? */
			{
				i = strlen(text_line)-1;	/* index to last char of string. */
				if (text_line[i] == '\n')
					text_line[i] = 0;		/* Trim off the trailing newline. */
				s_outTextxy (row++, 1, text_line);	/* Write one of the 23 rows. */
			} 
		}
		
		c = 0;
		while (c==0)
		{
			switch (c = getUCchar())
			{
			case HOME:
				lines_down = 0;					/* Go to top of file. */
				break;

			case END:
				lines_down = 9999;				/* Go to end of file. */
				break;

			case DOWNARROW:
				if (status == NULL)				/* Can't advance past end-of-file */
					Sound (ERRnote);
				else
					lines_down = lines_down+1;	/* Advance one line */
				break;

			case UPARROW:
				lines_down = lines_down-1;
				break;

			case PAGEDOWN:
				if (status == NULL)				/* Can't advance past end-of-file */
					Sound (ERRnote);
				else
					lines_down = lines_down+21;	/* Advance one page. */
				break;

			case PAGEUP:
				lines_down = lines_down-21;
				break;

			case ESC:
				break;							/* Leave char get loop. */

			default:
				Sound (ERRnote);					/* Error */
				c = 0;							/* Loop again. */
				break;
			}
		}
	}
	fclose (stream);
	return;
} /* End of open_scroll_help() */





/* getsymbol..------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** GetSymbol (char* s)                                                   **
**                                                                       **
** This routine obtains a value from the program's environment.          **
** This works for DOS and VMS (and other OS's???)
**                                                                       **
 ************************************************************************/

int GetSymbol (char *s, double *v)
{
	char* e;

	if (!(e = getenv(s)))
		return (FALSE);
	*v = atof(e);
	return (TRUE);
}

/* clearallvars..---------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** ClearAllVars()                                                        **
**                                                                       **
** Erases all user-defined variables from memory. Note that constants    **
** can not be erased or modified in any way by the user.                 **
**                                                                       **
** Returns nothing.                                                      **
**                                                                       **
 *************************************************************************/

void ClearAllVars ()
{
	int i;

	for (i=0; i < MAXVARS; i++)
	{
		*Vars[i].name = 0;
		Vars[i].value = 0;
	}
}

/* clearvar..-------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** ClearVar (char* name)                                                 **
**                                                                       **
** Erases the user-defined variable that is called NAME from memory.     **
** Note that constants are not affected.                                 **
**                                                                       **
** Returns TRUE if the variable was found and erased, or FALSE if it     **
** didn't exist.                                                         **
**                                                                       **
 *************************************************************************/

int ClearVar (char* name)
{
	int i;

	for (i=0; i < MAXVARS; i++)
	if (*Vars[i].name && ! strcmp(name, Vars[i].name))
	{
		*Vars[i].name = 0;
		Vars[i].value = 0;
		return (TRUE);
	}
	return (FALSE);
}

/* getvalue..-------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** GetValue (char* name, double* value)                                  **
**                                                                       **
** Looks up the specified variable (or constant) known as NAME and       **
** returns its contents in VALUE.                                        **
**                                                                       **
** First the user-defined variables are searched, then the constants are **
** searched.                                                             **
**                                                                       **
** Returns TRUE if the value was found, or FALSE if it wasn't.           **
**                                                                       **
 *************************************************************************/

int GetValue (char *name, double *value)
{
	int i;

	/* First check for an environment variable reference... */
	if (*name == '_')
		return (GetSymbol(name + 1, value));

	/* Now check the user-defined variables. */
	for (i=0; i < MAXVARS; i++)
		if (*Vars[i].name && ! strcmp(name, Vars[i].name))
		{
			*value = Vars[i].value;
			return (TRUE);
		}

	/* Now check the programmer-defined constants. */
	for (i=0; *Consts[i].name; i++)
		if (*Consts[i].name && ! strcmp(name, Consts[i].name))
		{
			*value = Consts[i].value;
			return (TRUE);
		}
	return (FALSE);
}

/* setvalue..-------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** SetValue (char* name, double* value)                                  **
**                                                                       **
** First, it erases any user-defined variable that is called NAME.  Then **
** it creates a new variable called NAME and gives it the value VALUE.   **
**                                                                       **
** Returns TRUE if the value was added, or FALSE if there was no more    **
** room.                                                                 **
 *************************************************************************/

int SetValue (char *name, double *value)
{
	int  i;

	ClearVar (name);
	for (i=0; i < MAXVARS; i++)
		if (! *Vars[i].name)
		{
			strcpy (Vars[i].name, name);
			Vars[i].name[VARLEN] = 0;
			Vars[i].value = *value;
			return (TRUE);
		}
	return (FALSE);
}

/* parse..----------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Parse ()  Internal use only                                           **
**                                                                       **
** This function is used to grab the next token from the expression that **
** is being evaluated.                                                   **
**                                                                       **
 *************************************************************************/

void Parse ()
{
	char* t;

	tokType = 0;
	t = token;
	while (iswhite (*expression))
		expression++;
	if (isdelim (*expression))
	{
		tokType = DEL;
		*t++ = *expression++;
	}
	else if (isnumer (*expression))
	{
		tokType = NUM;
		while (isnumer (*expression))
			*t++ = *expression++;
	}
	else if (isalpha (*expression))
	{
		tokType = VAR;
		while (isalpha (*expression))
			*t++ = *expression++;
		token[VARLEN]=0;
	}
	else if (*expression)
	{
		*t++ = *expression++;
		*t = 0;
		ERR(E_SYNTAX);
	}
	*t = 0;
	while (iswhite (*expression))
		expression++;
}

/* level1..---------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Level1 (double *r)   Internal use only                                **
**                                                                       **
** This function handles any variable assignment operations.             **
** It returns a value of 1 if it is a top-level assignment operation,    **
** otherwise it returns 0                                                **
**                                                                       **
 *************************************************************************/

int Level1 (double *r)
{
	char t[VARLEN + 1];

	if (tokType == VAR)
		if (*expression == '=')
		{
			strcpy (t, token);
			Parse();
			Parse();
			if (!*token)
			{
				ClearVar(t);
				return (1);
			}
			Level2(r);
			if (! SetValue(t, r))
				ERR(E_MAXVARS);
			return (1);
		}
	Level2(r);
	return (0);
}

/* level2..---------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Level2 (double* r)   Internal use only                                **
**                                                                       **
** This function handles any addition and subtraction operations.        **
**                                                                       **
 *************************************************************************/

void Level2 (double *r)
{
	double t=0;
	char o;

	Level3(r);
	while ((o=*token) == '+' || o == '-')
	{
		Parse();
		Level3(&t);
		if (o == '+')
			*r = *r + t;
		else if (o == '-')
			*r = *r - t;
	}
}

/* level3..---------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Level3 (double* r)   Internal use only                                **
**                                                                       **
** This function handles any multiplication, division, or modulo.        **
**                                                                       **
 *************************************************************************/

void Level3 (double *r)
{
	double t;
	char o;

	Level4(r);
	while ((o=*token) == '*' || o == '/' || o == '%')
	{
		Parse();
		Level4(&t);
		if (o == '*')
			*r = *r * t;
		else if (o == '/')
		{
			if (t == 0)
				ERR(E_DIVZERO);
			*r = *r / t;
		}
		else if (o == '%')
		{
			if (t == 0)
				ERR(E_DIVZERO);
			*r = fmod(*r, t);
		}
	}
}

/* level4..---------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Level4 (double* r)   Internal use only                                **
**                                                                       **
** This function handles any "to the power of" operations.               **
**                                                                       **
 *************************************************************************/

void Level4(double *r)
{
	double t;

	Level5(r);
	if (*token == '^')
	{
		Parse();
		Level5(&t);
		*r = pow(*r, t);
	}
}

/* level5..---------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Level5 (double* r)   Internal use only                                **
**                                                                       **
** This function handles any unary + or - signs.                         **
**                                                                       **
 *************************************************************************/

void Level5 (double *r)
{
	char o=0;

	if (*token == '+' || *token == '-')
	{
		o = *token;
		Parse();
	}
	Level6(r);
	if (o == '-')
		*r = -*r;
}

/* level6..---------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Level6 (double* r)   Internal use only                                **
**                                                                       **
** This function handles any literal numbers, variables, or functions.   **
**                                                                       **
 *************************************************************************/

void Level6 (double *r)
{
	int  i,n;
	double a[3];

	if (*token == '(')
	{
		Parse();
		if (*token == ')')
			ERR(E_NOARG);
		Level1(r);
		if (*token != ')')
			ERR(E_UNBALAN);
		Parse();
	}
	else
	{
		if (tokType == NUM)
		{
			*r = (double)atof(token);
			Parse();
		}
		else if (tokType == VAR)
		{
			if (*expression == '(')
			{
			for (i=0; *Funcs[i].name; i++)
				if (!strcmp(token, Funcs[i].name))
				{
					Parse();
					n = 0;
					do
					{
						Parse();
						if (*token == ')' || *token == ',')
						ERR(E_NOARG);
						a[n] = 0;
						Level1(&a[n]);
						n++;
					} while (n < 4 && *token == ',');
					Parse();
					if (n != Funcs[i].args)
					{
						strcpy (token, Funcs[i].name);
						ERR(E_NUMARGS);
					}
					*r = Funcs[i].func(a[0], a[1], a[2]);
					return;
				}
				if (! *Funcs[i].name)
					ERR(E_BADFUNC);
			}
			else if (! GetValue(token, r))
				ERR(E_UNKNOWN);
			Parse();
		}
		else
			ERR(E_SYNTAX);
	}
}

/* evaluate..-------------------------------------------------------------- */
/*************************************************************************
**                                                                       **
** Evaluate (char* e, double* result, int* a)                            **
**                                                                       **
** This function is called to evaluate the expression E and return the   **
** answer in RESULT.  If the expression was a top-level assignment, a    **
** value of 1 will be returned in A, otherwise it will contain 0.        **
**                                                                       **
** Returns E_OK if the expression is valid, or an error code.            **
**                                                                       **
 *************************************************************************/

int Evaluate (char *e, double *result, int *a)
{
	if (setjmp(jb))
		return (ERROR);
	expression = e;
	ERANC = e;
	strlwr(expression);
	*result = 0;
	Parse();
	if (!*token)
		ERR(E_EMPTY);
	*a = Level1(result);
	return (E_OK);
}

/*************************************************************************
**                                                                       **
** Some custom math functions...   Note that they must be prototyped     **
** above (if your compiler requires it)                                  **
**                                                                       **
 *************************************************************************/

double deg(double x)
{
	return (x*DPR);
}

double rad(double x)
{
	return (x*RPD);
}

double sind (double x)
{
	return sin(x*RPD);
}

double cosd (double x)
{
	return cos(x*RPD);
}

double tand (double x)
{
	return tan(x*RPD);
}

double asind (double x)
{
	return DPR*asin(x);
}

double acosd (double x)
{
	return DPR*acos(x);
}

double atand (double x)
{
	return DPR*atan(x);
}





/* *** Debugging features *** */
/* define _DEBUG to compile in these debug features. */

#ifdef _DEBUG

/*
 The 'I' command character within the Read Encoders screen will start a process that 
 injects an encoder step every n ticks (~18.2 ms). The 'I' command again stops it. 
 The commands '+' and '-' increase or decrease the time between injection steps 
 (doubles or halves time, minimum of zero ticks).
 The commands '<' and '>' switch direction of the injected stream.
 The command '^' switches encoder axis (only injects one axis at a time though). */

void inject_data (int flag)

{
	static int inject_index = 0;
	static long time_now, timer = 0;
	unsigned short theData;

    /* BIOS Timer period is 18.2065 counts per second, _bios_timeofday gives
       the current number of periods ("ticks") since midnight. */
    _bios_timeofday( _TIME_GETCLOCK, &time_now);

	if (time_now < timer)			/* If not yet time, don't inject. */
		return;

	timer = time_now + inject_time;	/* Compute future time to inject again (note-breaks if running  */
									/*  across midnight). So, don't be debugging across midnight! */

	/* Shift and store in buffer, add autozero status if flag=1 */
	theData = ( (unsigned short) (inject_table[inject_index]<<inject_shift) + (flag==1?(AUTO_ZERO_INPUT<<8):0) );

	*encoderDataBuffer.c_add = theData;	/* Store data. */
	encoderDataBuffer.c_count++;		/* Count data stored. */
	if (++encoderDataBuffer.c_add>encoderDataBuffer.c_bufferEnd)
		encoderDataBuffer.c_add = encoderDataBuffer.c_buffer;	/* Wrap at buffer end. */

	if (++inject_index >= sizeof(inject_table1))	/* Step to next table entry */
		inject_index = 0;							/*  Do a wrap around */
	return;
}





/*  trace table.  The table is built as an array.  It is controled
    by a series of base, top, and current entry pointers.

  Calls to Trace(n,str) will enter a timestamped trace entry in the table.
  A call to TraceDump("file", n) will dump 'n' entries to the file as text.

  This facility allows full speed execution while collecting debug data from
  calls placed in critical areas of the program.

  Control-P or "~" in most of the command scanners will print the full table 
  to the file "DRO.TRC".
*/

/* Trace table structure.  The tickCount is in the 18.2 counts per second
   format (number of counts since midnight).  Entry is 32 bytes. */
struct TRACETAB {
    long tickCount;	/* Time stamp of this entry. */
    char type,		/* type code */
		 data[27];	/* A 26 byte data string associated with this entry. */
} typedef TRACETAB;

static TRACETAB far *traceTable = NULL;
static TRACETAB far *traceTablePtr;
static TRACETAB far *traceTableEnd;
static int traceTableLength = 0;

int TRACE_FLAG = 1;

void TraceInit(int n)

{
    TRACETAB far *tp;
	
    /* If we already have a table, check it's size.  Reuse if ok. */
    if ((traceTableLength !=0 ) && (traceTableLength != n))
    {
		traceTableLength = 0;
		_ffree(traceTable);
    }
	
    /* Allocate trace table and pointer to first entry and last entry. */
    if (traceTableLength == 0)
		traceTable = (TRACETAB far *) _fmalloc( n*sizeof(TRACETAB));
	
    if (traceTable == NULL)
    {
		printf("TraceInit: Can't 'malloc' trace table.\n");
		TRACE_FLAG = 0;
		return;
    }
    else
		traceTableLength = n;
	
    traceTablePtr = traceTable;
    traceTableEnd = traceTable + (n-1);
	
    /* Zero all time stamps. */
    for (tp=traceTable;  tp<=traceTableEnd;  tp++)
		tp->tickCount = 0;
}








/*  Trace - Add an entry to the trace table.  The Type is just a number. The string 
	is a descriptor of this entry (like a keyword or buffer data).  The entries 
	are: 4 bytes of timestamp, one byte of type, and 26 bytes of string(plus a NULL).
*/

void Trace(char type, char *string)

{
    TRACETAB far *tp;
	long tick;
	
    if (!TRACE_FLAG || !traceTable)
		return;
    tp = traceTablePtr;
	
    /* Get the system timer clock count of periods since midnight.  Stuff 
	   timestamp, type code, and data string into current trace table entry. */
    _bios_timeofday(_TIME_GETCLOCK, &tick);
    tp->tickCount = tick;
	tp->type = type;
    _fmemccpy( tp->data, string, '\0', sizeof(tp->data)-2);
    tp->data[sizeof(tp->data)-1] = '\0';
	
    /* Step the trace table pointer, wrap when it goes beyond the end. */
    if (++traceTablePtr > traceTableEnd)
		traceTablePtr = traceTable;
}







/*  Write most recent 'n' trace entries to the file Tfile.
    If n is zero, full table will be dumped.
*/

int TraceDump(char *Tfile, int n)

{
	int i, hour, min, sec, tic;
    long t;
    FILE *traceFP;	/* The trace file FILE structure. */
    TRACETAB far *tp;
    static char *types[] = {
    "commnd", "poll  ", "decode", "error ", "intrup", "step  ",
    "_____6", "_____7", "_____8", "_____9", "____10", "____11",
    "____12", "____13", "____14", "____15", "____16", "____17",
    "____18", "____19", "____20", "____21", "____22", "____23",
    "____24", "____25", "____26", "____27", "____28", "____29",
    "____30", "____31", "____32", "____33", "____34", "ERROR ",
    };
	char t1[120];

	if (traceTableLength == 0)
		return(FALSE);

	if (n == 0)
		n = traceTableLength;

	/* Open file for write with commit and report if error. */
	if (NULL == (traceFP = fopen( Tfile, "wc") ))
	{
		printf("Couldn't open Trace file: %s\n", Tfile);
		return(FALSE);
	}

	tp = traceTablePtr - 1;

	/* Write the trace array. */
	for (i=0;  i<n;  i++,tp--)
	{
		if (tp < traceTable)
			tp = traceTableEnd;
		if (tp->tickCount == 0)
			break;
		hour = (int)(tp->tickCount>>16);/* Divide by 65536.00 */
		t = tp->tickCount & 0xFFFF;		/* Drop the hours off. */
		t = t * 100l;					/* Scale up by 100. */
		min = (int)(t / 109226l);		/* Divide by 1092.26 */
		t = (t % 109226l);				/* Mod by 1092.26 */
		sec = (int)(t / 1820l);			/* Divide by 18.20 */
		tic = (int)(t % 1820l);			/* Mod by 18.20 */
		_fstrcpy(t1, tp->data);
		fprintf(traceFP, "%02i:%02i:%02i.%04i %s %s\n",	
				hour, min, sec, tic, types[tp->type], t1);
		if (ferror(traceFP))
		{
			printf("Trace file write failed\n");
			return(FALSE);
		}
	}
	fclose(traceFP);
	return(TRUE);
}







/*** Debug hack to monitor buffer usage count ***/
void TrackbufferUsage()

{
	static unsigned int max=0;
	unsigned char string[40];
	
	if (max < encoderDataBuffer.c_count)
		max = encoderDataBuffer.c_count;
	if (pp.pp_use_irq||timeCounter<0)
	{
		sprintf(string, "max=%5i now=%5i", max, encoderDataBuffer.c_count);
		if (graphicMode)
		{
			_setcolor (BLACK);
			_rectangle (_GFILLINTERIOR, 1*pix_col, 16*pix_row, 25*pix_col+fi.pixwidth, 17*pix_row+fi.pixheight);
			colorGtext (1*pix_col, 16*pix_row, WHITE, string);
		}
		else
			s_outTextxytb (21, 1, WHITE, BLACK, string);
	}
}

#endif /* *** Debug *** */
