/**************************************************************************/
/*                                                                        */
/* FILE     :  TEST.C                                                     */
/*                                                                        */
/* PURPOSE  :  This program is intended to provide a convenient way for   */
/*             a user of the AT6400 to test I/O, motion, encoders, analog */
/*             inputs, ect.                                               */
/*                                                                        */
/*             The program provides a menu driven interface to test:      */
/*              1) Limits                                                 */
/*              2) Pulse Cutoff (P-CUT)                                   */
/*              3) Programmable Inputs                                    */
/*              4) Joystick Inputs (not for AUX2)                         */
/*              5) Programmable Outputs                                   */
/*              6) Encoders (not for AUX2)                                */
/*              7) Motion                                                 */
/*              8) Terminal Emulator                                      */
/*              9) Exit the program                                       */
/*                                                                        */
/* COMMENTS :  To compile and link with C library(small memory model),    */
/*             TCC -ms TEST.C                                             */
/*                                                                        */
/* REVISIONS:  November 4, 1991  - created.                               */
/*             December 6, 1991  - added arrow to point to selection of   */
/*                                 menu.  Reason = Monochrome monitors.   */
/*                               - added question asking if AT6400 should */
/*                                 be reset before entering terminal      */
/*                                 emulator.                              */
/*                               - fixed problem that showed motors still */
/*                                 moving after indexer was reset         */
/*             February 20, 1993 - added support for AUX2                 */                                                           
/*                                                                        */
/* COPYRIGHT : Copyright(c) 1991-1993, All Rights Reserved                */
/*             Parker Hannifin Corporation                                */
/*             Compumotor Corporation                                     */
/*             5500 Business Park Drive                                   */
/*             Rohnert Park, California 94928                             */
/*             Applications Engineering:  (800) 358-9070                  */
/*                                                                        */
/* DISCLAIMER: THIS SOFTWARE IS PROVIDED FREE OF CHARGE AND WITHOUT       */
/*             WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED.  IN NO  */
/*             EVENT WILL PARKER HANNIFIN CORPORATION BE LIABLE FOR ANY   */
/*             DAMAGES, INCLUDING ANY LOST PROFITS, LOST SAVINGS, OR OTHER*/
/*             INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE */
/*             OR INABILITY TO TO USE THIS SOFTWARE.                      */
/*                                                                        */
/**************************************************************************/

#include <stdio.h>
#include <conio.h>
#include <process.h>
#include <stdlib.h>
#include <dos.h>
#include "tc20lib.c"
#include "test.h"
#include "cursor.c"

/**********************************************************************
 * Global Variable Declarations                                       *
 **********************************************************************/

 int stop = 0;
 int key_code;
 int menu_choice = 1;
 int ok_to_continue;
 int Done = FALSE;              /* Flag used to exit program */
 int auxiliary_bd = 1;          /* AT6400-AUX1 */
 unsigned address = BASEP;      /* default address setting */
 char delimiter[] = "\x0D";     /* command delimiter - carriage return */
 int called_before = 0;         /* used to determine if motion routine has been called */
 char initial_str[MAXBUFFER];              /* used to initialize motor drive in meni item 7*/
 char * initial_ptr = &initial_str[0];


/*************************************************************************/
/* FUNCTION  : int download(int arg_count)                                        */
/* PURPOSE   : Downloads operating system, clear screen and prepare      */
/*           : for menu.                                                 */
/* REQUIRES  : Nothing                                                   */
/* RETURNS   : Integer to indicate failure status.                       */
/* FUNCTIONS : screen_blank()                                            */
/* GLOBALS   : address                                                   */
/*************************************************************************/
int download(int arg_count)
{
  int i;
  char buff[32];
  char szAddress[10];
  char szAux[10];
  char *ptr;
  char temp[32];
  int result = SUCCESS;
  int error_code = 0;
  char *pathname = "AT6400.EXE";
  char * args[] = {"AT6400.EXE",
		   "/port=768",
		   NULL
		  };

  screen_blank();

  /* identify program */
  cputs("\r\nTEST:  Test Program for AT6400\r\n\n");

  /* get auxiliary board type */
  cputs("Auxiliary board(AUX1=1, AUX2=2, etc.) = ? ");
  buff[0] = 2;        /* leave space for 2 characters */
  ptr = cgets(buff);  /* get base port address from user */
  strcpy(szAux, ptr);
  cputs("\r\n\n");    /* aesthetics */

  if(szAux[0] == 0)       /* test if someone hit <Enter> */
   {
    auxiliary_bd = AUX1;  /* default base port address */
    strcpy(szAux, "1");
   }
  else
    auxiliary_bd = atoi(szAux); /* convert ascii to integer */

  /* get base port address */
  cputs("Base port address(Default 768) = ? ");
  buff[0] = 5;        /* leave space for 5 characters */
  ptr = cgets(buff);  /* get base port address from user */
  strcpy(szAddress, ptr);
  cputs("\r\n\n");    /* aesthetics */

  if(szAddress[0] == 0)       /* test if someone hit <Enter> */
   {
    address = BASEP;  /* default base port address */
    strcpy(szAddress, "768");
   }
  else
    address = atoi(szAddress); /* convert ascii to integer */

  if((auxiliary_bd != AUX1) && (auxiliary_bd != AUX2))
    {
     putch(BELL);
     cputs("AT6400: ERROR -- support available only for AUX1 and AUX2.\r\n");
     result = FAILURE;
    }
    
  if(address > 0x3ff)
    {
     putch(BELL);
     cputs("AT6400: ERROR -- base port address must be less than 1024.\r\n");
     result = FAILURE;
    }
  if(address < 0x100)
    {
     putch(BELL);
     cputs("AT6400: ERROR -- base port address must be greater than 255.\r\n");
     result = FAILURE;
    }
  if( (address % 8) != 0)
    {
     putch(BELL);
     cputs("AT6400: ERROR -- base port address must be a multiple of 8.\r\n");
     result = FAILURE;
    }

  /* download the operating system */
if(arg_count==1){
  if(result == SUCCESS){
    sprintf(temp, "/port=%s", szAddress);
    sprintf(buff, "/aux%s", szAux);
    error_code=spawnl(P_WAIT, pathname, args[0], temp, buff, NULL);
    if(error_code == -1)
      {
       cputs("\r\n\r\nCould not locate AT6400.EXE.\r\n");
       result = FAILURE;
      }
    if(error_code > 0)
      {
       cputs("\r\n\r\nAT6400 operating system failed to download.\r\n");
       result = FAILURE;
      }
  }
  if(result == FAILURE) {
    cputs("\r\n\r\nPress any key to exit.");
    key_code = getch();
  }
  return(result);
}
else
  return(1);
}

/************************************************************************/
/* FUNCTION     : void screen_blank(void)                               */
/* PURPOSE      : Clears the screen                                     */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/************************************************************************/
void screen_blank(void)
{
  window(1,1,80,25);
  textattr(WHITE + (BLACK<<4));
  clrscr();
}

/************************************************************************/
/* FUNCTION     : void request_status(void)                             */
/* PURPOSE      : Tell the AT6400 to update fast status register        */
/*              : information.                                          */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/************************************************************************/
void request_status(void)
{
  /* request status update */
  outportb(address+STATUS, REQ_STATUS);
  /* wait until status has been updated */
  while( !(inportb(address+STATUS) & CS_UPDATED));
}

/************************************************************************/
/* FUNCTION     : void set_pointer(int status_offset)                             */
/* PURPOSE      : Point to appropriate fast status information.         */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/************************************************************************/
void set_pointer(int status_offset)
{
  outportb(address+FASTSTATUS, status_offset);
}

/************************************************************************/
/* FUNCTION     : void read_status(unsigned int * status_high,          */
/*                unsigned int * status_low, unsigned long * status))   */
/* PURPOSE      : Read the status information from the fast status      */
/*              : register.  The information is pointed to by the       */
/*              : status_offset, set with subroutine set_pointer().     */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/************************************************************************/
void read_status(unsigned int * status_high, unsigned int * status_low,
		 unsigned long * status)
{
  /* read status info */
  * status_high = inport(address+FASTSTATUS);
  * status_low = inport(address+FASTSTATUS);

  /* build status because of low/high ordering */
  * status_high = (* status_high << 8) + (* status_high >> 8);
  * status_low = (* status_low << 8) + (* status_low >> 8);
  * status = * status_high;
  * status = (* status << 16) + * status_low;
}


/************************************************************************/
/* FUNCTION     : void primary_menu(void)                               */
/* PURPOSE      : Initializes main menu screen                          */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : screen_blank(), screen_update()                       */
/************************************************************************/
void primary_menu(void)
{
  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  window(5,8,70,19);
  cputs("Parker Compumotor's Motion and I/O Test Program\r\n\r\n");
  textattr(WHITE + (BLACK<<4));
  cputs("1.\r\n");
  cputs("2.  \r\n");
  cputs("3.  \r\n");
  cputs("4.  \r\n");
  cputs("5.  \r\n");
  cputs("6.  \r\n");
  cputs("7.  \r\n");
  cputs("8.  \r\n");
  cputs("9.  \r\n");
  screen_update(DOWN_ARROW,0,MAIN_MENU);

}

/************************************************************************/
/* FUNCTION     : void highlight_menu(int new_menu,int menu_choice)     */
/* PURPOSE      : Update video display when a up arrow or down arrow    */
/*              : key is pressed.                                       */
/* REQUIRES     : menu item to highlight (new_menu), and menu to use    */
/* RETURNS      : Nothing                                               */
/************************************************************************/
void highlight_menu(int new_menu, int menu_choice)
{
  int index;

  if(menu_choice == MAIN_MENU) {
    for(index=1; index<=MAX_MENU_NUM; index++) {
      window(7,9+index,50,10+index);
      if (index!=new_menu){
	textattr(WHITE + (BLACK <<4));
	cputs("  ");
      }
      else{
	textattr(BLACK + (CYAN<<4));
	cputs("* ");
      }
      switch(index){
	case 1: cputs(CHOICE_1); break;
	case 2: cputs(CHOICE_2); break;
	case 3: cputs(CHOICE_3); break;
	case 4: cputs(CHOICE_4); break;
	case 5: cputs(CHOICE_5); break;
	case 6: cputs(CHOICE_6); break;
	case 7: cputs(CHOICE_7); break;
	case 8: cputs(CHOICE_8); break;
	case 9: cputs(CHOICE_9);
      }
    }
  }
  else if(menu_choice==AUX_MENU){
    for(index=1; index<=(MAX_MENU_NUM-5); index++) {
      if (index!=new_menu) {
	textattr(WHITE + (BLACK <<4));
	window(2,12+index,50,15+index);
	cputs("   ");
      }
      else {
	window(2,12+index,50,15+index);
	textattr(WHITE + (BLACK <<4));
	cputs("->");
	window(5,12+index,50,15+index);
	textattr(BLACK + (CYAN<<4));
      }
      switch(index){
	case 1: cputs(AUX_0); break;
	case 2: cputs(AUX_1); break;
	case 3: cputs(AUX_2); break;
	case 4: cputs(AUX_3);
      }
    }
  }
}

/************************************************************************/
/* FUNCTION     : int screen_update(int kb_code, int menu_num,          */
/*              :                   int menu_select)                    */
/* PURPOSE      : Determines the key that was pressed, and highlights   */
/*              : the appropriate menu selection.                       */
/* REQUIRES     : Key code, current menu, current menu selection        */
/* RETURNS      : New menu selection to display                         */
/* FUNCTIONS    : highlight_menu()                                      */
/************************************************************************/
int screen_update(int kb_code, int menu_num, int menu_select)
{
  int new_menu_num;
  int upper;
  int upper_char;

  if(menu_select==MAIN_MENU) {
    upper = MAX_MENU_NUM;
    upper_char = NINE;
  }
  else if(menu_select==AUX_MENU) {
    upper = MAX_MENU_NUM - 5;
    upper_char = NINE - 5;
  }
  new_menu_num = menu_num;
  if(kb_code==UP_ARROW)
    if (menu_num == MIN_MENU_NUM)
      new_menu_num = upper;
    else
      new_menu_num = menu_num - 1;
  if(kb_code==DOWN_ARROW)
    if (menu_num == upper)
      new_menu_num = MIN_MENU_NUM;
    else
      new_menu_num = menu_num + 1;
  if ((kb_code > ZERO) && (kb_code <= upper_char))
    new_menu_num = kb_code - ZERO;
  highlight_menu(new_menu_num, menu_select);
  return(new_menu_num);
}

/************************************************************************/
/* FUNCTION     : int motion_routine(int initiate_motion, int moving,   */
/*              : int limit, int last_limit, int axis, int dir, int vel */
/*              : int accel)                                            */
/* PURPOSE      : To initiate motion on a corresponding axis (axis), or */
/*              : to stop motion on all axes.                           */
/* REQUIRES     : Value to specify if motion is to be started or stopped, */
/*              : current moving status, current limit status, axis     */
/*              : direction, velocity, and acceleration                 */
/* RETURNS      : integer that specifies moving/not moving              */
/* FUNCTIONS    : request_status(), read_status(), SendAT6400Block()    */
/* GLOBALS      : address                                               */
/************************************************************************/
int motion_routine(int initiate_motion, int * moving, int limit, int * last_limit,
		 int axis,int dir,int vel,int accel)
{
  static char command[MAXBUFFER];
  static char * ptr = command;
  int i;

  if(initiate_motion && !(* moving)) {
    /* set limit state */
    strcpy(ptr,"!LH");
    for(i=1;i<axis;i++)
      strcat(ptr,",");
    if(limit)
      strcat(ptr,"3");
    else
      strcat(ptr,"0");
    strcat(ptr,&delimiter[0]);

    /* set acceleration */
    strcat(ptr,"!A");
    for(i=1;i<axis;i++)
      strcat(ptr,",");
    if(accel==HIGH)
      strcat(ptr,"100");
    else if(accel==MEDIUM)
      strcat(ptr,"20");
    else
      strcat(ptr,"1");
    strcat(ptr,&delimiter[0]);

    /* set velocity */
    strcat(ptr,"!V");
    for(i=1;i<axis;i++)
      strcat(ptr,",");
    if(vel==HIGH)
      strcat(ptr,"20");
    else if(vel==MEDIUM)
      strcat(ptr,"5");
    else
      strcat(ptr,"0.5");
    strcat(ptr,&delimiter[0]);

    /* set direction */
    strcat(ptr,"!D");
    for(i=1;i<axis;i++)
      strcat(ptr,",");
    if(dir==CCW)
      strcat(ptr,"-");
    else
      strcat(ptr,"+");
    strcat(ptr,&delimiter[0]);

    /* initiate motion command */
    strcat(ptr,"!GO");
    for(i=1;i<axis;i++)
      strcat(ptr,"X");
    strcat(ptr,"1");
    strcat(ptr,&delimiter[0]);

    SendAT6400Block(address,command);
  }
  else if(!initiate_motion) {
    /* stop motion on all axes */
    SendAT6400Block(address,"!S1111\r");
  }

  textattr(YELLOW + (BLACK <<4));
  switch(axis){
    case 1: window(19,11,80,11); break;
    case 2: window(34,11,80,11); break;
    case 3: window(49,11,80,11); break;
    case 4: window(64,11,80,11);
  }
  if(initiate_motion) {
    textattr(LIGHTCYAN + (BLACK << 4));
    if((!limit) || (limit && (dir != * last_limit))) {
      textattr(LIGHTCYAN + (BLACK << 4));
      cputs("Moving        ");
      * last_limit = -1;
      * moving = 1;
    }
    else {
      textattr(LIGHTRED + (BLACK << 4) + BLINK);
      cputs("Press F6 or F7");
    }
  }
  else {
   window(19,11,80,11);
   cputs("Stopped        Stopped        Stopped        Stopped       ");
  }
  return(dir);
}

/************************************************************************/
/* FUNCTION     : int change_axes(int current_axis)                     */
/* PURPOSE      : To set the current axis.                              */
/* REQUIRES     : Old axis number                                       */
/* RETURNS      : New axis number                                       */
/* GLOBALS      : key_code                                              */
/************************************************************************/
int change_axes(int current_axis)
{
  if(key_code==RIGHT_ARROW)
    if(current_axis==4)
      current_axis=1;
    else
      current_axis++;
  else  /* left arrow key */
    if(current_axis==1)
      current_axis=4;
    else
      current_axis--;
  window(1,2,80,2);
  clreol();
  switch(current_axis){
    case 1: window(19,2,80,3);
	    textattr(WHITE + (BLACK <<4));
	    cputs("******\r\nAXIS 1");
	    window(64,3,80,3);
	    textattr(CYAN + (BLACK <<4));
	    window(34,3,80,3);
	    cputs("AXIS 2");
	    window(64,3,80,3);
	    cputs("AXIS 4");
	    break;
    case 2: window(34,2,80,3);
	    textattr(WHITE + (BLACK <<4));
	    cputs("******\r\nAXIS 2");
	    textattr(CYAN + (BLACK <<4));
	    window(19,3,80,3);
	    cputs("AXIS 1");
	    window(49,3,80,3);
	    cputs("AXIS 3");
	    break;
    case 3: window(49,2,80,3);
	    textattr(WHITE + (BLACK <<4));
	    cputs("******\r\nAXIS 3");
	    textattr(CYAN + (BLACK <<4));
	    window(34,3,80,3);
	    cputs("AXIS 2");
	    window(64,3,80,3);
	    cputs("AXIS 4");
	    break;
    case 4: window(64,2,80,3);
	    textattr(WHITE + (BLACK <<4));
	    cputs("******\r\nAXIS 4");
	    textattr(CYAN + (BLACK <<4));
	    window(19,3,80,3);
	    cputs("AXIS 1");
	    window(49,3,80,3);
	    cputs("AXIS 3");
  }
  return(current_axis);
}

/************************************************************************/
/* FUNCTION     : int change_dir(int current_axis, int current_dir)     */
/* PURPOSE      : To change the direction of motion.                    */
/* REQUIRES     : Current axis number, current direction (CW or CCW)    */
/* RETURNS      : New direction                                         */
/************************************************************************/
int change_dir(int current_axis, int current_dir, int moving)
{
  textattr(YELLOW + (BLACK <<4));
  switch(current_axis){
    case 1: window(19,17,80,17); break;
    case 2: window(34,17,80,17); break;
    case 3: window(49,17,80,17); break;
    case 4: window(64,17,80,17);
  }
  if(current_dir == CW) {
    current_dir = CCW;
    cputs("CCW");
  }
  else   {
    current_dir = CW;
    cputs("CW ");
  }

  switch(current_axis){
    case 1: window(19,11,80,11); break;
    case 2: window(34,11,80,11); break;
    case 3: window(49,11,80,11); break;
    case 4: window(64,11,80,11);
  }
  if(!moving)
    cputs("Stopped       ");

  return(current_dir);
}

/************************************************************************/
/* FUNCTION     : int change_velocity(int current_axis,int current_vel) */
/* PURPOSE      : To set the current velocity.                          */
/* REQUIRES     : Current axis number, current velocity                 */
/* RETURNS      : New velocity                                          */
/************************************************************************/
int change_velocity(int current_axis, int current_velocity)
{
  textattr(YELLOW + (BLACK <<4));
  switch(current_axis){
    case 1: window(19,15,80,15); break;
    case 2: window(34,15,80,15); break;
    case 3: window(49,15,80,15); break;
    case 4: window(64,15,80,15);
  }
  switch(current_velocity) {
    case LOW: current_velocity = MEDIUM; cputs("5 rps  "); break;
    case MEDIUM: current_velocity = HIGH; cputs("20 rps "); break;
    case HIGH: current_velocity = LOW; cputs("0.5 rps");
  }
  return(current_velocity);
}

/************************************************************************/
/* FUNCTION     : int change_accel(int current_dir, int current_accel)  */
/* PURPOSE      : To set the current acceleration                       */
/* REQUIRES     : Current axis number, current acceleration             */
/* RETURNS      : New acceleration                                      */
/************************************************************************/
int change_accel(int current_axis, int current_accel)
{
  textattr(YELLOW + (BLACK <<4));
  switch(current_axis){
    case 1: window(19,16,80,16); break;
    case 2: window(34,16,80,16); break;
    case 3: window(49,16,80,16); break;
    case 4: window(64,16,80,16);
  }
  switch(current_accel) {
    case LOW: current_accel = MEDIUM; cputs("20 rps/s "); break;
    case MEDIUM: current_accel = HIGH; cputs("100 rps/s"); break;
    case HIGH: current_accel = LOW; cputs("1 rps/s  ");
  }
  return(current_accel);
}

/************************************************************************/
/* FUNCTION     : int change_limit(int current_axis, int limit)         */
/* PURPOSE      : To set the current limit state.                       */
/* REQUIRES     : Current axis number, current limit status             */
/* RETURNS      : New limit state (enabled, or disabled)                */
/************************************************************************/
int change_limit(int current_axis, int limit, int moving)
{
  textattr(YELLOW + (BLACK <<4));
  switch(current_axis){
    case 1: window(19,12,80,12); break;
    case 2: window(34,12,80,12); break;
    case 3: window(49,12,80,12); break;
    case 4: window(64,12,80,12);
  }
  if(limit) {
    textattr(LIGHTCYAN + (BLACK << 4));
    limit = 0;
    cputs("Disabled");
  }
  else {
    limit = 1;
    cputs("Enabled ");
  }

  textattr(YELLOW + (BLACK <<4));
  switch(current_axis){
    case 1: window(19,11,80,11); break;
    case 2: window(34,11,80,11); break;
    case 3: window(49,11,80,11); break;
    case 4: window(64,11,80,11);
  }
  if(!moving)
    cputs("Stopped       ");

  return(limit);
}

/*************************************************************************/
/* FUNCTION     : print_long(long status_info)                          */
/* PURPOSE      : To print long integer values on the screen.           */
/* REQUIRES     : Long integer value to print                           */
/* RETURNS      : Nothing                                               */
/*************************************************************************/
void print_long(long status_info)
{
  char long_storage[80];
  char * long_ptr = &long_storage[0];
  int j;

  ltoa(status_info,long_ptr,10);
  j = 15 - strlen(long_ptr);
  while(j) {
   strcat(long_ptr," ");
     j = j - 1;
  }
  cputs(long_ptr);
}

/************************************************************************/
/* FUNCTION     : void motion(void)                                     */
/* PURPOSE      : To provide motion menu for TEST program               */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : screen_blank(), SendAT6400Block(), change_axis(),     */
/*              : change_velocity(), change_accel(), change_dir(),      */
/*              : request_status(), read_status(), print_long(),        */
/*              : motion_routine()                                      */
/* GLOBALS      : called_before, key_code, address                      */
/************************************************************************/
void motion(void)
{
  long fast_status;
  unsigned int word_high, word_low;
  unsigned int lim_status, lim_mask, lim_bit, lim_garbage;
  unsigned long limit_garbage;
  unsigned int pulse_bit, pulse_mask, pulse_garbage;
  int change_output;
  char buff[20];
  char * ptr;
  char temp[20];
  char * temp_ptr = &temp[0];

  int i,j,tmp;
  int column;
  int current_axis = 4;
  int current_velocity[4];
  int current_accel[4];
  int current_dir[4];
  int motion_dir[4];
  static long axis_dres[4];
  double axis_pulse[4];
  int limits[4];
  int last_limit[4];
  int motion[4];

  static char command[MAXBUFFER];
  static char * com_ptr = &command[0];

  for(i=0;i<4;i++){
    current_velocity[i] = HIGH;
    current_accel[i] = HIGH;
    limits[i] = 1;
    last_limit[i] = -1;
    motion[i] = 0;
    motion_dir[i] = CW;
  }

  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  window(3,6,80,25);
  if(called_before) {
    cputs("The step output pulse width and motor/drive resolution have already\r\n");
    cputs("been defined.  If you do not want to change these values press the\r\n");
    cputs("ENTER key.  Otherwise, press any other key to change the values.\r\n");
    textattr(WHITE + (BLACK<<4));
    key_code = getch();
    if(key_code == RETURN)
      called_before = 1;
    else      /* any key, besides ENTER, to change values */
      called_before = 0;
  }

  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  gotoxy(1,2);
  if(!called_before){
    cputs("The motor step output pulse width is the amount of time the step\r\n");
    cputs("output pulse is active.  Different motor drives can have different\r\n");
    cputs("step input pulse width requirements.  A typical Compumotor Motor/Drive\r\n");
    cputs("requires a step pulse of 200 nanoseconds, or 0.2 microseconds.\r\n\r\n");
    cputs("The default step output pulse width for the AT6400 is 0.3 microseconds.\r\n");
    cputs("This is the smallest unit available.  The largest pulse width is 20\r\n");
    cputs("microseconds.  Valid pulse width values are 0.3, 0.5, 1.0, 2.0, 5.0,\r\n");
    cputs("10.0, 16.0, and 20.0 microseconds.  Refer to the PULSE command in the\r\n");
    cputs("6000 Series Software Reference Guide for more information.\r\n\r\n");
    cputs("Enter the pulse width value or press the ENTER key for the default value.\r\n\r\n");
    textattr(WHITE + (BLACK<<4));
    for(i=0;i<4;i++) {
      cputs("Motor step output pulse width for axis ");
      itoa(i+1,temp_ptr,10);
      cputs(temp_ptr);
      cputs(" (Default 0.3 microseconds) = ? ");
      buff[0] = 15;       /* leave space for 15 characters */
      ptr = cgets(buff);  /* get pulse width */
      cputs("\r\n\n");    /* aesthetics */
      if(*ptr == 0)       /* test if someone hit <Enter> */
	axis_pulse[i] = 0.3;  /* default pulse width */
      else {
	axis_pulse[i] = atof(ptr); /* convert ascii to float */
	if(axis_pulse[i] <= 0.3)
	  axis_pulse[i] = 0.3;
	else if((axis_pulse[i] > 0.3) && (axis_pulse[i] <= 0.5))
	  axis_pulse[i] = 0.5;
	else if((axis_pulse[i] > 0.5) && (axis_pulse[i] <= 1.0))
	  axis_pulse[i] = 1.0;
	else if((axis_pulse[i] > 1.0) && (axis_pulse[i] <= 2.0))
	  axis_pulse[i] = 2.0;
	else if((axis_pulse[i] > 2.0) && (axis_pulse[i] <= 5.0))
	  axis_pulse[i] = 5.0;
	else if((axis_pulse[i] > 5.0) && (axis_pulse[i] <= 10.0))
	  axis_pulse[i] = 10.0;
	else if((axis_pulse[i] > 10.0) && (axis_pulse[i] <= 16.0))
	  axis_pulse[i] = 16.0;
	else if(axis_pulse[i] > 16.0)
	  axis_pulse[i] = 20.0;
      }
    }

    /* create command to set pulse width */
    strcpy(com_ptr,"ERRLVL0");
    strcat(com_ptr,&delimiter[0]);
    strcat(com_ptr,"PULSE");
    gcvt(axis_pulse[0],10,temp_ptr);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,",");
    gcvt(axis_pulse[1],10,temp_ptr);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,",");
    gcvt(axis_pulse[2],10,temp_ptr);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,",");
    gcvt(axis_pulse[3],10,temp_ptr);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,&delimiter[0]);
    SendAT6400Block(address,command);  /* send pulse width */
    strcpy(initial_ptr,com_ptr);       /* copy command to initialization string */

    screen_blank();
    textattr(YELLOW + (BLACK<<4));
    gotoxy(1,6);
    cputs("Each motor/drive attached to the AT6400 requires a specific number\r\n");
    cputs("of steps in order to rotate the motor 1 revolution.  The AT6400 must\r\n");
    cputs("be programmed with the corresponding drive resolution for each axis.\r\n");
    cputs("Most Compumotor drives require 25000 steps to rotate a motor 1 revolution.\r\n");
    cputs("Drive resolution values between 200 and 1024000 are valid.\r\n\r\n\r\n");
    cputs("Enter the drive resolution value or press the ENTER key for the default\r\n");
    cputs("value.\r\n\r\n");
    textattr(WHITE + (BLACK<<4));
    for(i=0;i<4;i++) {
      cputs("Motor drive resolution for axis ");
      itoa(i+1,temp_ptr,10);
      cputs(temp_ptr);
      cputs(" (Default 25000) = ? ");
      buff[0] = 15;       /* leave space for 15 characters */
      ptr = cgets(buff);  /* get drive resolution */
      cputs("\r\n\n");    /* aesthetics */
      if(*ptr == 0)       /* test if someone hit <Enter> */
	axis_dres[i] = 25000;  /* default drive resolution */
      else {
	axis_dres[i] = atol(ptr); /* convert ascii to integer */
	if(axis_dres[i]<200)
	  axis_dres[i] = 200;
	if(axis_dres[i] > 1024000)
	  axis_dres[i] = 1024000;
      }
    }

    /* create command to set dres */
    strcpy(com_ptr,"DRES");
    ltoa(axis_dres[0],temp_ptr,10);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,",");
    ltoa(axis_dres[1],temp_ptr,10);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,",");
    ltoa(axis_dres[2],temp_ptr,10);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,",");
    ltoa(axis_dres[3],temp_ptr,10);
    strcat(com_ptr,temp_ptr);
    strcat(com_ptr,&delimiter[0]);
    strcat(com_ptr,"MC1111");
    strcat(com_ptr,&delimiter[0]);
    SendAT6400Block(address,command);  /* send drive resolution */
    strcat(initial_ptr,com_ptr);       /* concatinate command to initialization string */

    screen_blank();
    textattr(YELLOW + (BLACK<<4));
    gotoxy(1,6);
    cputs("The menu to follow will allow you to produce motion on axes 1-4.\r\n");
    cputs("Function keys F1 - F8 control the options within the menu.\r\n");
    cputs("Press the ESCAPE key or function key 8 (F8) when finished.\r\n\r\n\r\n");
    cputs("Press any key to continue.\r\n");
    key_code = getch();

    called_before=1;                   /* global variable */
  }
  screen_blank();
  window(1,3,70,25);
  textattr(WHITE + (BLACK<<4));
  cputs("Use Arrow Keys");
  textattr(CYAN + (BLACK<<4));
  cputs("    AXIS 1         AXIS 2         AXIS 3         AXIS 4\r\n");
  textattr(WHITE + (BLACK<<4));
  cputs("to Select Axis");
  cputs("    ---------------------------------------------------\r\n\r\n");
  cputs("MOTOR POSITION\r\n");
  cputs("ENCODER POSITON\r\n");
  cputs("MOTOR VELOCITY\r\n");
  cputs("                  ___________________________________________________\r\n\r\n");
  cputs("MOTION STATUS\r\n");
  cputs("LIMIT STATUS\r\n");
  cputs("                  ---------------------------------------------------\r\n");
  cputs("DRIVE RESOLUTION\r\n");
  cputs("VELOCITY\r\n");
  cputs("ACCELERATION\r\n");
  cputs("DIRECTION\r\n");
  cputs("                  ___________________________________________________\r\n");
  window(1,22,80,25);
  textattr(LIGHTCYAN + (BLACK<<4));
  cputs("   F1       F2        F3        F4        F5        F6        F7        F8\r\n");
  textattr(LIGHTRED + (BLACK<<4));
  cputs(" Start     Stop      Reset    Select    Select    Select     Limit     ");
  textattr(WHITE + (BLACK<<4));
  cputs("Main\r\n");
  textattr(LIGHTRED + (BLACK<<4));
  cputs(" Motion   Motion    Indexer  Velocity   Accel    Direction   ON/OFF    ");
  textattr(WHITE + (BLACK<<4));
  cputs("Menu\r\n");

  /* display axis, current direction, current velocity, and current acceleration */
  key_code = RIGHT_ARROW;
  current_axis = change_axes(current_axis);
  key_code = 0;
  for(i=0;i<4;i++){
    current_velocity[i] = change_velocity((i+1), current_velocity[i]);
    current_accel[i] = change_accel((i+1), current_accel[i]);
    current_dir[i] = change_dir((i+1), current_dir[i], motion[i]);
  }

  /* display drive resolution */
  window(19,14,80,14);
  ltoa(axis_dres[0],temp_ptr,10);
  cputs(temp_ptr);
  window(34,14,80,14);
  ltoa(axis_dres[1],temp_ptr,10);
  cputs(temp_ptr);
  window(49,14,80,14);
  ltoa(axis_dres[2],temp_ptr,10);
  cputs(temp_ptr);
  window(64,14,80,14);
  ltoa(axis_dres[3],temp_ptr,10);
  cputs(temp_ptr);

  /* initial motion and limit status */
  window(19,11,80,12);
  cputs("Stopped        Stopped        Stopped        Stopped\r\n");
  cputs("Enabled        Enabled        Enabled        Enabled");

  while(key_code != ESCAPE) {
    /* update motor position, encoder position, and velocity info */
    window(19,6,80,11);
    textattr(LIGHTCYAN + (BLACK<<4));
    request_status();

    /* get motor position information from fast status register */
    set_pointer(AXIS1_MOTOR);
    for(i=0;i<4;i++){
      read_status(&word_high, &word_low, (unsigned long *) &fast_status);
      print_long(fast_status);
    }
    cputs("\r\n");
    /* get encoder position information from fast status register */
    set_pointer(AXIS1_ENCODER);
    for(i=0;i<4;i++){
      read_status(&word_high, &word_low, (unsigned long *) &fast_status);
      print_long(fast_status);
    }
    cputs("\r\n");
    /* get motor velocity information from fast status register */
    set_pointer(AXIS1_VELOCITY);
    for(i=0;i<4;i++){
      read_status(&word_high, &word_low, (unsigned long *) &fast_status);
      print_long(fast_status);
    }

    /* get limit status to detemine if any axes are at a limit */
    set_pointer(LIMIT_STATUS);
    read_status(&lim_status, &lim_garbage, &limit_garbage);
    for(i=0;i<4;i++){
      switch (i){
	case 0: window(19,11,80,11); break;
	case 1: window(34,11,80,11); break;
	case 2: window(49,11,80,11); break;
	case 3: window(64,11,80,11);
      }
      if(motion_dir[i]==CCW)
	lim_mask = 0x0002;
      else
	lim_mask = 0x0001;
      for (j=0;j<i;j++)
	lim_mask = lim_mask << 2;
      lim_bit=lim_status & lim_mask;
      if((lim_bit == 0) && (motion[i]) && (limits[i])){
	motion[i] = 0;
	if(motion_dir[i]==CCW) {
	  last_limit[i] = CCW;
	  cputs("CCW Limit");
	}
	else {
	  last_limit[i] = CW;
	  cputs("CW Limit ");
	}
      }
    }

    /* determine if pulse cutoff is active */
    pulse_mask = 0x0020;              /* P-CUT 20, RSVD 40 */
    set_pointer(INO_STATUS);
    read_status(&pulse_bit, &pulse_garbage, (unsigned long *) &fast_status);
    pulse_bit = pulse_bit & pulse_mask;

    /* display pulse_cutoff info on the screen */
    window(19,19,80,19);
    if(pulse_bit)  {
	textattr(WHITE + (BLACK<<4));       /* use red for ones */
	cputs("Pulse cutoff input connected properly.  ");
    }
    else   {
     textattr(LIGHTRED + (BLACK<<4));     /* use white for zeros */
     cputs("Pulse cutoff input not connected to GND.");
    }

    if(kbhit()) {
      key_code = getch();
      tmp = current_axis -1;       /* in the array 0= axis 1, 1=axis 2, ect. */
      if(key_code != ESCAPE) {
	switch(key_code) {
	  case F1: motion_dir[tmp] = motion_routine(1,&motion[tmp],limits[tmp],&last_limit[tmp],current_axis,current_dir[tmp],current_velocity[tmp],current_accel[tmp]); break;
	  case F2: motion_dir[tmp] = motion_routine(0,&motion[tmp],limits[tmp],&last_limit[tmp],current_axis,current_dir[tmp],current_velocity[tmp],current_accel[tmp]); break;
	  case F3: motion_dir[tmp] = motion_routine(0,&motion[tmp],limits[tmp],&last_limit[tmp],current_axis,current_dir[tmp],current_velocity[tmp],current_accel[tmp]);
		   SendAT6400Block(address,"!RESET\r");
		   delay(200);
		   SendAT6400Block(address,initial_str);
		   break;
	  case F4: current_velocity[tmp] = change_velocity(current_axis, current_velocity[tmp]); break;
	  case F5: current_accel[tmp] = change_accel(current_axis, current_accel[tmp]); break;
	  case F6: current_dir[tmp] = change_dir(current_axis, current_dir[tmp], motion[tmp]); break;
	  case F7: limits[tmp] = change_limit(current_axis, limits[tmp], motion[tmp]); break;
	  case F8: key_code = ESCAPE; break;
	  case LEFT_ARROW:
	  case RIGHT_ARROW: current_axis = change_axes(current_axis);
	}
      }
      if((key_code==F2) || (key_code==F3))
	for(i=0;i<4;i++)
	  motion[i]=0;
      if(key_code == ESCAPE)
	/* stop motion before leaving motion menu */
	SendAT6400Block(address,"!K1111\r");  /* send kill command */
    }
  }
}

/************************************************************************/
/* FUNCTION     : void limits(void)                                     */
/* PURPOSE      : Reads limit information from AT6400 Fast Status       */
/*              : Register and displays the information on the screen.  */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : request_status, read_status, screen_blank             */
/* GLOBALS      : key_code                                              */
/************************************************************************/
void limits(void)
{
  int i, column, row;
  unsigned int lim_status, lim_mask, lim_bit, lim_garbage;
  unsigned long fast_status;

  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  window(5,5,65,25);
  cputs("Toggle limit switches to see state transitions.\r\n");
  cputs("1 = Limit switch closed, 0 = Limit switch open.\r\n\r\n");
  textattr(WHITE + (BLACK<<4));
  cputs("\r\n");
  cputs("          Axis 1         Axis 2         Axis 3         Axis 4\r\n");
  cputs("HOME\r\n\r\n");
  cputs("CCW\r\n\r\n");
  cputs("CW\r\n\r\n\r\n\r\n");
  textattr(YELLOW + (BLACK<<4));
  cputs("Press any key to return to the main menu.\r\n");
  while(! kbhit()) {
    /* update limit status, using fast status area for limit info */
    request_status();
    set_pointer(LIMIT_STATUS);
    read_status(&lim_status, &lim_garbage, &fast_status);

    /* display limit info on the screen */
    lim_mask = 0x0800;
    for (i=0; i<12; i++) {
      column = 62 - ((i % 4) * 15);
      row = 11 + ((i / 4) * 2);
      window(column,row,65,25);
      lim_bit=lim_status & lim_mask;
      if(lim_bit)  {
	textattr(LIGHTRED + (BLACK<<4));  /* use red for ones */
	cputs("1");
      }
      else   {
	textattr(WHITE + (BLACK<<4));     /* use white for zeros */
	cputs("0");
      }
      if(i>3)                             /* only used in CW and CCW fast status */
	lim_mask = lim_mask >> 2;
      else
	lim_mask = lim_mask >> 1;
      if(i==3)                            /* used to grab CW limit info */
	lim_mask = 0x0080;
      if(i==7)                            /* used to grab CCW limit info */
	lim_mask = 0x0040;
    }
  }
  key_code = getch();
}

/************************************************************************/
/* FUNCTION     : void pulse_cutoff(void)                               */
/* PURPOSE      : Reads Pulse-Cutoff information from AT6400 Fast       */
/*              : Status Register and displays on the screen.           */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : request_status, read_status, screen_blank             */
/* GLOBALS      : key_code                                              */
/************************************************************************/
void pulse_cutoff(void)
{
  unsigned int pulse_bit, pulse_mask, pulse_garbage;
  unsigned long fast_status;

  screen_blank();
  window(5,6,65,25);
  textattr(YELLOW + (BLACK<<4));
  cputs("Toggle pulse cutoff (P-CUT) input to see state transition.\r\n");
  cputs("1 = Pulse cutoff switch closed, 0 = Pulse cutoff switch open.\r\n");
  cputs("When switch is open, step pulses are inhibited.\r\n\r\n");
  textattr(WHITE + (BLACK<<4));
  cputs("Pulse Cutoff\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n");
  textattr(YELLOW + (BLACK<<4));
  cputs("Press any key to return to the main menu.\r\n");
  while(! kbhit()) {
    /* update pulse-cutoff status, using fast status area for limit info */
    pulse_mask = 0x0020;              /* P-CUT 20, RSVD 40 */
    request_status();
    set_pointer(INO_STATUS);
    read_status(&pulse_bit, &pulse_garbage, &fast_status);
    pulse_bit = pulse_bit & pulse_mask;

    /* display pulse_cutoff info on the screen */
    window(25,11,65,25);
    if(pulse_bit)  {
	textattr(LIGHTRED + (BLACK<<4));       /* use red for ones */
	cputs("1");
    }
    else   {
     textattr(WHITE + (BLACK<<4));     /* use white for zeros */
     cputs("0");
    }
  }
  key_code = getch();
}

/************************************************************************/
/* FUNCTION     : void programmable_in(void)                            */
/* PURPOSE      : Read programmable input information from Fast Status  */
/*              : Register and display information on the screen.       */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : request_status, read_status, screen_blank             */
/* GLOBALS      : auxiliary_bd, key_code                                */
/************************************************************************/
void programmable_in(void)
{
  unsigned int word_high, word_low;
  unsigned long in_status, in_mask, in_bit;
  int i;
  int nMaxInputs;

  screen_blank();
  window(2,6,80,25);
  if(auxiliary_bd == AUX1){
    nMaxInputs = 28;   /* 24 programmable + 4 triggers */
    textattr(YELLOW + (BLACK<<4));
    cputs("Toggle the programmable inputs to see state transitions.\r\n");
    cputs("1 = Input switch closed, 0 = Input switch open.\r\n\r\n");
    textattr(LIGHTCYAN + (BLACK<<4));
    cputs("1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24\r\n\r\n\r\n\r\n\r\n");
    textattr(YELLOW + (BLACK<<4));
    cputs("Toggle the trigger inputs (TRIGS connector) to see state transitions.\r\n\r\n");
    textattr(LIGHTCYAN + (BLACK<<4));
    cputs("1  2  3  4\r\n\r\n\r\n\r\n\r\n\r\n");
  }
  else{
    nMaxInputs = 20;  /* 8 programmable + 8 end-of-travel + 4 triggers */
    textattr(YELLOW + (BLACK<<4));
    cputs("Toggle the programmable inputs to see state transitions.\r\n");
    cputs("1 = Input switch closed, 0 = Input switch open.\r\n\r\n");
    textattr(LIGHTCYAN + (BLACK<<4));
    cputs("1  2  3  4  5  6  7  8\r\n\r\n\r\n\r\n\r\n");
    textattr(YELLOW + (BLACK<<4));
    cputs("Toggle the trigger inputs (TRIGS connector) to see state transitions.\r\n");
    cputs("1 = Input switch closed, 0 = Input switch open.\r\n\r\n");
    textattr(LIGHTCYAN + (BLACK<<4));
    cputs("1  2  3  4\r\n\r\n\r\n\r\n\r\n\r\n");
  }
  textattr(YELLOW + (BLACK<<4));
  cputs("Press any key to return to the main menu.\r\n");
  while(! kbhit()) 
   {
    /* update input status, using fast status area for limit info */
    request_status();
    set_pointer(INPUT_STATUS);
    read_status(&word_high, &word_low, &in_status);

    /* display input info on the screen */
    in_mask = 0x00000001;
    window(2,11,80,25);        /* window to display programmable inputs */
    for (i=0; i<nMaxInputs; i++) 
      {
       in_bit=in_status & in_mask;
       if( (auxiliary_bd == AUX2) && (i>7) && (i<16) )
	 goto skip_limits;
       if( i==(nMaxInputs-4) )
	  window(2,18,80,25);    /* window to display triggers */
       if(in_bit)  
	 {
	  textattr(LIGHTRED + (BLACK<<4));       /* use red for ones */
	  cputs("1  ");
	 }
       else   
	 {
	  textattr(WHITE + (BLACK<<4));     /* use white for zeros */
	  cputs("0  ");
	 }
       skip_limits:
       in_mask = in_mask << 1;
      } /* end for loop */
   } /* end while loop */
  key_code = getch();
}

/************************************************************************/
/* FUNCTION     : void programmable_out(void)                           */
/* PURPOSE      : Read output status from Fast Status Register and      */
/*              : display information on the screen. In addition, any   */
/*              : of the outputs can be turned on or off.               */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : request_status, read_status, screen_blank,            */
/*              : SendAT6400Block                                       */
/* GLOBALS      : auxiliary_bd                                          */
/************************************************************************/
void programmable_out(void)
{
  unsigned int word_high, word_low;
  unsigned long out_status, out_mask, out_bit;
  int i,j=0;
  char output_num[80];
  char * out_ptr = &output_num[0];
  int change_output;
  int io_num;

  char command[MAXBUFFER];
  char * command_ptr = &command[0];

  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  window(2,6,80,25);
  cputs("Programmable outputs - current status.\r\n");
  cputs("1 = Output On (sinking current), 0 = Output Off (not sinking current)\r\n\r\n");
  textattr(LIGHTCYAN + (BLACK<<4));
  cputs("\r\n");
  cputs("1  2  3  4");
  if(auxiliary_bd == AUX1)
    cputs("  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24");
  cputs("\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n");
  textattr(YELLOW + (BLACK<<4));
  cputs("Enter the output number followed by ENTER to toggle the output state.\r\n");
  cputs("\r\n\r\n\r\nPress ESC to return to main menu.\r\n");
  output_num[0]=0;
  while(output_num[j]!=ESCAPE) {
    /* update output status, using fast status area for limit info */
    request_status();
    set_pointer(OUTPUT_STATUS);
    read_status(&word_high, &word_low, &out_status);

    /* display state of outputs on the screen */
    out_mask = 0x00000001;
    window(2,12,80,25);
    io_num = 4;
    if(auxiliary_bd == AUX1)
      io_num = 24;
    for (i=0; i<io_num; i++) {
      out_bit=out_status & out_mask;
      if(out_bit)  {
	textattr(LIGHTRED + (BLACK<<4));  /* use red for ones */
	cputs("1  ");
      }
      else   {
	textattr(WHITE + (BLACK<<4));     /* use white for zeros */
	cputs("0  ");
      }
      out_mask = out_mask << 1;
    }

    /* change state of outputs if keyboard pressed, output state is changed
	only after the ENTER key is pressed */
    if(kbhit()) {
      output_num[j] = getch();
      if(output_num[j]!=ESCAPE) {
	if(output_num[j]==RETURN) {
	  output_num[j+1] = NULL;        /* Place null character at end */
	  change_output = atoi(out_ptr);
	  if((change_output>0) && (change_output<(io_num+1))) {
	    /* change output state */
	    strcpy(command_ptr,"OUT");
	    out_mask = 0x00000001;
	    for (i=0; i<io_num; i++) {
	      out_bit=out_status & out_mask;
	      if(i==(change_output-1)) {
		if(out_bit)
		  strcat(command_ptr,"0");
		else
		  strcat(command_ptr,"1");
	      }
	      else {
		if(out_bit)
		  strcat(command_ptr,"1");
		else
		  strcat(command_ptr,"0");
	      }
	      out_mask = out_mask << 1;
	    }
	    strcat(command_ptr,&delimiter[0]);    /* attach command delimiter - carriage return */
	    SendAT6400Block(address,command);   /* send output command */
	  }
	  j = 0;
	  output_num[j] = NULL;
	}
	else
	  j++;
      }
    }
  }
}

/************************************************************************/
/* FUNCTION     : void joystick(void)                                   */
/* PURPOSE      : Read the status of the analog inputs, and other       */
/*              : joystick inputs from the fast status register and     */
/*              : display the information on the screen.                */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : request_status, read_status, screen_blank             */
/* GLOBALS      : auxiliary_bd, key_code                                */
/************************************************************************/
void joystick(void)
{
  unsigned int word_high, word_low;
  unsigned int ino_status, ino_mask, ino_bit, ino_garbage;
  unsigned long fast_status, anv_status;
  int i;
  char * anv_channel;
  unsigned char voltage;
  unsigned int volts;
  float real_volts;

  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  if(auxiliary_bd == AUX1){
    window(5,4,78,25);
    cputs("Apply a voltage to analog channels 1-4 to see voltage level changes.\r\n");
    cputs("Voltage Range = 0 - 2.5 VDC\r\n\r\n");
    textattr(WHITE + (BLACK<<4));
    cputs("Analog Channel 1 (Pin 1)            VDC\r\n");
    cputs("Analog Channel 2 (Pin 2)            VDC\r\n");
    cputs("Analog Channel 3 (Pin 3)            VDC\r\n");
    cputs("Analog Channel 4 (Pin 4)            VDC\r\n\r\n");
    textattr(YELLOW + (BLACK<<4));
    cputs("Toggle joystick inputs to see state transitions.\r\n\r\n");
    textattr(WHITE + (BLACK<<4));
    cputs("Joystick Auxiliary (Pin 19)\r\n");
    cputs("Joystick Trigger   (Pin 18)\r\n");
    cputs("Axes Select        (Pin 15)\r\n");
    cputs("Velocity Select    (Pin 16)\r\n");
    cputs("Joystick Release   (Pin 17)\r\n\r\n");
    textattr(YELLOW + (BLACK<<4));
    cputs("(1 = Switch closed, 0 = Switch open)\r\n\r\n\r\n");
    cputs("Press any key to return to the main menu.\r\n");
    while(! kbhit()) {
      /* update analog input status, using fast status area for joystick info */
      request_status();
      set_pointer(ANALOG_STATUS);
      read_status(&word_high, &word_low, &anv_status);

      /* display analog channel info on the screen */
      anv_channel = (char *) &anv_status;
      for(i=0;i<4;i++){
	window(36,10-i,65,25);
	voltage =  * anv_channel++;
	volts = 0;
	volts = volts + (int) voltage;
	real_volts = (float) volts;
	real_volts = real_volts / 255 * 2.5;   /* 8 bit DAC = 256 divisions */
	printf("%1.2f",real_volts);
      }
      /* update joystick input status, using fast status area for joystick info */
      request_status();
      set_pointer(INO_STATUS);
      read_status(&ino_status, &ino_garbage, &fast_status);

      /* display joystick input info on the screen */
      ino_mask = 0x0001;
      window(36,14,65,25);
      for (i=0; i<5; i++) {
	ino_bit=ino_status & ino_mask;
	if(ino_bit)  {
	  textattr(LIGHTRED + (BLACK<<4));       /* use red for ones */
	  cputs("1\r\n");
	}
	else   {
	  textattr(WHITE + (BLACK<<4));     /* use white for zeros */
	  cputs("0\r\n");
	}
	ino_mask = ino_mask << 1;
      }
    }
  }
  else {
    window(5,8,80,25);
    cputs("There are no joystick inputs available.  Only available with AT6400-AUX1.\r\n\r\n\r\n\r\n\r\n");
    cputs("Press any key to return to the main menu.\r\n");
  }
  key_code = getch();
}

/************************************************************************/
/* FUNCTION     : void encoder(void)                                    */
/* PURPOSE      : Reads current encoder position for all axes from fast */
/*              : status area and displays the infomation.              */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : request_status, read_status, screen_blank             */
/* GLOBALS      : auxiliary_bd, key_code                                */
/************************************************************************/
void encoder(void)
{
  unsigned int word_high, word_low;
  long enc_position;
  int change_output;
  char long_storage[80];
  char * long_ptr = &long_storage[0];

  int i,j;
  int column;

  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  if(auxiliary_bd != AUX2){
    window(15,6,70,25);
    cputs("                 Encoder Position\r\n");
    textattr(WHITE + (BLACK<<4));
    cputs("---------------------------------------------------\r\n");
    cputs("Axis 1         Axis 2         Axis 3         Axis 4\r\n");
    cputs("\r\n\r\n___________________________________________________");
    cputs("\r\n\r\n\r\n\r\n\r\n\r\n\r\n");
    textattr(YELLOW + (BLACK<<4));
    cputs("\r\nRotate the encoder by hand.\r\n");
    cputs("\r\n\r\nPress any key to return to the main menu.\r\n");
    while(!kbhit()) {
      /* update encoder position info using fast status area */
      request_status();
      set_pointer(AXIS1_ENCODER);

      /* display position info on the screen */
      for(i=0;i<4;i++) {
	read_status(&word_high, &word_low, (unsigned long *) &enc_position);
	textattr(LIGHTRED + (BLACK<<4));       /* use red for position */
	column = 15 + (i*15);
	window(column,10,80,25);
	ltoa(enc_position,long_ptr,10);
	j = 15 - strlen(long_ptr);
	while(j) {
	  strcat(long_ptr," ");
	  j = j - 1;
	}
	cputs(long_ptr);
      }
    }
  }
  else {
    window(5,8,80,25);
    cputs("There are no encoder inputs available with AT6400-AUX2.\r\n\r\n\r\n\r\n\r\n");
    cputs("Press any key to return to the main menu.\r\n");
  }
  key_code = getch();
}

/************************************************************************/
/* FUNCTION     : void emulate(void)                                    */
/* PURPOSE      : Calls terminal emulator FASTTERM.COM from FASTTERM    */
/*              : directory.                                            */
/* REQUIRES     : Nothing                                               */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : SendAT6400Block(), cursor_off(), screen_blank()       */
/************************************************************************/
void emulate(void)
{
  int error_code = 0;

  screen_blank();
  textattr(YELLOW + (BLACK<<4));
  window(3,6,80,25);
  cputs("The AT6400 indexer will be reset to factory defaults upon entering\r\n");
  cputs("and returning from this terminal emulator.\r\n\r\n");
  cputs("Press any key to continue.\r\n");
  key_code = getch();

  window(1,1,80,25);
  SendAT6400Block(address,"!RESET\r");  /* send reset command */
  cursor_off(0);
  error_code = spawnl(P_WAIT,"FASTTERM.COM","FASTTERM.COM",NULL);
  if(error_code == -1) {
    screen_blank();
    window(8,10,80,25);
    textattr(LIGHTRED + (BLACK<<4));
    cputs("Cannot locate FASTTERM.COM\r\n\r\n");
    textattr(YELLOW + (BLACK<<4));
    cputs("FASTTERM.COM should be located in the same directory as TEST.EXE.\r\n\r\n");
    cputs("Press any key to return to main menu.\r\n");
  }
  else {
    printf("\n\nPress any key to return to main menu.\n");
  }
  key_code = getch();
  SendAT6400Block(address,"!RESET\r");  /* send reset command */
  delay(200);
  if(called_before)
    SendAT6400Block(address,initial_str);
  cursor_off(1);
}


/************************************************************************/
/* FUNCTION     : int run_task(int menu_num)                            */
/* PURPOSE      : Runs the corresponding task depending on the menu     */
/*              : selection.                                            */
/* REQUIRES     : Current menu selection number displayed on screen     */
/* RETURNS      : Nothing                                               */
/* FUNCTIONS    : motion(), limits(), pulse_cutoff(), emulate(),        */
/*              : programmable_in(), programmable_out(), joystick()     */
/*              : encoder(), primary_menu(), screen_update()            */
/************************************************************************/
int run_task(int menu_num)
{
 int stop_flag = 0;

 switch(menu_num){
   case 1: limits(); break;
   case 2: pulse_cutoff(); break;
   case 3: programmable_in(); break;
   case 4: joystick(); break;
   case 5: programmable_out(); break;
   case 6: encoder(); break;
   case 7: motion(); break;
   case 8: emulate(); break;
   case 9: stop_flag=1;
 }
 primary_menu();
 screen_update(RETURN,menu_choice,MAIN_MENU);
 return(stop_flag);
}

/************************************************************************/
/* FUNCTION     : void main(void)                                       */
/* PURPOSE      : Downloads AT6400 operating system and proceeds to     */
/*                process keystrokes.                                   */
/* FUNCTIONS    : primary_menu(), screen_blank, run_task(),             */
/*              : screen_update(), SendAT6400Block(), download(),       */
/*              : cursor_off(), aux_board()                             */
/* GLOBALS      : key_code, stop, menu_choice                           */
/************************************************************************/
void main(int argc)
{
 textmode(3);                       /* color, 80 columns */
 cursor_off(1);
 if(download(argc)){
   primary_menu();                 /* initialize main menu screen */
   while (!stop) {                 /* wait until EXIT menu choice selected */
     key_code = getch();           /* get keyboard data */
     if (key_code == ESCAPE){      /* if key = ESCAPE, return to set auxiliary board */
       primary_menu();
       key_code=ESCAPE;
     }
     if (key_code == RETURN)          /* if key = carriage return key, run application */
       stop = run_task(menu_choice);
     else                            /* else go to appropriate menu choice */
       menu_choice = screen_update(key_code,menu_choice,MAIN_MENU);
   }
   SendAT6400Block(address,"!RESET\r");
 }
 screen_blank();
 cursor_off(0);
}
