Skip to content. | Skip to navigation

Personal tools

cell.cpp

cell.cpp

cell.cpp

/////////////////////////////////////////////////////////////////////////////
// CELL.CPP
//
//  Last updated: 03/17/2002
//
//  Part of the source code for the Vane End Actuator Control System
//
//  This file contains all variables/constants/functions for updating
//  the mirror cell.
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// Calculation Functions -
//	Encoders -> Global Coordinates, Global Coordinates -> Encoder Values
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// mmult: Multiply a vector by a matrix
void mmult(matrix m,vector v)
// Multiplies the vector v by matrix m.
// The result is returned in vector v.
{
	int i,j;
	vector result;

	for (i=0;i<=2;i++)
	{
		result[i] = 0;
		for (j=0;j<=2;j++)
		result[i] += m[j][i] * v[j];
	}
	for (i=0;i<=2;i++)
		v[i] = result[i];
}

/////////////////////////////////////////////////////////////////////////////
// AEREtoXY: Convert encoder readings to local XY coordinates
void AEREtoXY(int AE,int RE,float* X,float* Y)
// Pass this encoder values in AE and RE (in microns).
// Note that the encoder values should have had their zero positions
// subtracted off, i.e. call AEREtoXY(AE[0]-ZAE[0],RE[0]-ZRE[0],X,Y).
// It will return the position of the vane end in the actuator's
// plane of motion in X and Y (in inches), X = radial, Y = axial,
// where X=0,Y=0 is the actuator's zeroed location (at ZAE[],ZRE[]).
{
	float R1 = 12.972,RS0 = 19.219,RAE = 20.032;
	float AAE = 15.228 * PI / 180;
	float TCAE = 110.264 * PI / 180;
	float DAERE = 30.682 * PI / 180;
	float R2 = 9.566,RRE = 16.033,RS3 = 20.658;
	float TCRE = 223.086 * PI / 180;
	float X2D = -8.649,Y2D = 0;
	float AEC0 =12.62579,REC0 =12.62865;
	float NAE,NRE;
	float X1,Y1,T1,TAE;
	float X2,Y2,XRE,YRE,T2,TRE,ARE;
	float temp; // Used to avoid divide-by-zeros

	NAE = (AE / 25400.0) + AEC0;
	NRE = (RE / 25400.0) + REC0;
	TAE = acos((RS0 * RS0 + RAE * RAE - NAE * NAE) / (2 * RS0 * RAE));
	T1 = TAE - AAE + TCAE;
	X1 = R1 * cos(T1);
	Y1 = R1 * sin(T1);
	XRE = RRE * cos(TAE - AAE + DAERE);
	YRE = RRE * sin(TAE - AAE + DAERE);
	temp = XRE - X1;
	if (temp == 0.0) temp = 0.000001;
	ARE = atan((YRE - Y1) / temp);
	TRE = acos((RRE * RRE + RS3 * RS3 - NRE * NRE) / (2 * RRE * RS3));
	T2 = TRE + ARE + TCRE;
	X2 = R2 * cos(T2) + X1;
	Y2 = R2 * sin(T2) + Y1;
	*X = (X2 - X2D) / FixXY;
	*Y = (Y2 - Y2D) / FixZ;
}

/////////////////////////////////////////////////////////////////////////////
// XYtoAERE: Convert local XY coordinates to encoder readings
void XYtoAERE(float X,float Y,int* R1,int* R2)
// Pass it the destination X and Y (in inches) in the actuators plane
// of motion, and it returns the needed values of AE and RE (encoder values)
// X is radial motion, and Y is axial motion.  Note that you'll have to add
// the zeroed encoder values to the AE and RE this routine returns.
// ie, call XYtoAERE(X,Y,AE[0],RE[0]) and then AE[0]+=ZAE[0],RE[0]+=ZRE[0].
{
	// Constants for polynomial that converts global coords to actuator coords
	float AECX = 0.025465,AECX2 = 0.115145,AECX3  = 0.013379,AECX4 =0.001707;
	float AECY =-2.14416 ,AECXY =-0.24737 ,AECX2Y =-0.02625, AECX3Y=-0.00277;
	float AECY2=-0.02165 ,AECXY2=-0.01846 ,AECX2Y2=-0.00496;
	float AECY3= 0.011084,AECXY3= 0.003498;
	float AECY4= 0.000514;

	float RECX = 1.675677,RECX2 = 0.003468,RECX3  =-0.00029, RECX4 =0.000549;
	float RECY = 0.000184,RECXY =-6.6E-06 ,RECX2Y = 1.08E-05,RECX3Y=-1.5E-05;
	float RECY2=-0.09679 ,RECXY2=-0.01166 ,RECX2Y2=-0.0014;
	float RECY3=-0.00018 ,RECXY3= 3.4E-05;
	float RECY4= 0.000365;

	*R1= 25400 * (
									AECX  *X      +AECX2 *X*X    +AECX3  *X*X*X  +AECX4*X*X*X*X
									+AECY *Y      +AECXY *X*Y    +AECX2Y *X*X*Y  +AECX3Y*X*X*X*Y
									+AECY2*Y*Y    +AECXY2*X*Y*Y  +AECX2Y2*X*X*Y*Y
									+AECY3*Y*Y*Y  +AECXY3*X*Y*Y*Y
									+AECY4*Y*Y*Y*Y);
	*R2= 25400 * (
									RECX  *X      +RECX2 *X*X    +RECX3  *X*X*X  +RECX4*X*X*X*X
									+RECY *Y      +RECXY *X*Y    +RECX2Y *X*X*Y  +RECX3Y*X*X*X*Y
									+RECY2*Y*Y    +RECXY2*X*Y*Y  +RECX2Y2*X*X*Y*Y
									+RECY3*Y*Y*Y  +RECXY3*X*Y*Y*Y
									+RECY4*Y*Y*Y*Y);
}

/////////////////////////////////////////////////////////////////////////////
// VaneCalc: Convert global xy coordinates to a local vane end x coordinate
float VaneCalc(float dx,float dy,int corner)
// Returns the necessary radial vane end position (X value) for the cell
// to move by dx,dy (in global coordinates, in microns).
// corner should be 0 = NW,1 = SW,2 = SE,3 = NE
{
	double slope,B,C,x,y,ox,oy;

	switch (corner)
	{
		case 0:
		{
			slope = 1.0;
			ox = VaneXY;
			oy = VaneXY;
		} break;
		case 1:
		{
			slope = -1.0;
			ox = VaneXY;
			oy = -VaneXY;
		} break;
		case 2:
		{
			slope = 1.0;
			ox = -VaneXY;
			oy = -VaneXY;
		} break;
		case 3:
		{
			slope = -1.0;
			ox = -VaneXY;
			oy = VaneXY;
		} break;
	}
	B = -2 * dy - 2 * dx * slope;
	C = dx * dx + dy * dy - VaneRadius2;
	if ((corner == 0) || (corner == 3))
		y = (-B+sqrt(B*B-8*C))/4;
	else
		y = (-B-sqrt(B*B-8*C))/4;
	x = slope * y;
	if ((x*x+y*y) > VaneRadius2)
		return(TensionAmount + FixXY * sqrt((x-ox)*(x-ox)+(y-oy)*(y-oy)));
	else
		return(TensionAmount + FixXY * -sqrt((x-ox)*(x-ox)+(y-oy)*(y-oy)));
}

/////////////////////////////////////////////////////////////////////////////
// CCell Functions
/////////////////////////////////////////////////////////////////////////////


/////////////////////////////////////////////////////////////////////////////
// CCell::Init Initializes the DGHs
void CCell::Init()
{
	int i,e;
	char s[255];

	// Set up the onscreen X and Y locations for the display of each vane end
	CornerX[0] = 44;
	CornerY[0] = 26;
	CornerX[1] = 44;
	CornerY[1] = 74;
	CornerX[2] = 300;
	CornerY[2] = 74;
	CornerX[3] = 300;
	CornerY[3] = 26;
	// Begin querying the DGHs with DGH 'a'
	DGHIndex = 0;
	// Configure all DGHs
	e = ConfigDGHs();
	if (e >= 1 && e <= 8) {
		Flags.SetFatal(FFTensions);
		EDSLog.Add(4,"Vane DGH %d init error",e);
	}
	else if (e >= 9 && e <= 11) {
		EDSLog.Add(4,"F11 not present",e);
		f11Present = 0;
	}
	else if (e >= 12 && e <= 17) {
		EDSLog.Add(4,"F5 not present",e);
		f5Present = 0;
	}
	else {
		EDSLog.Add(994,"DGH init success");
	}
	DisplayAll();
}

/////////////////////////////////////////////////////////////////////////////
// CCell::Update: Update fatal flags, tensions, encoder readings,movement
void CCell::Update()
//
// CCell::Update performs a normal update of the mirror cell.
// It checks for fatal errors, updates the encoders, and
// performs a move if a move is in progress.
{
	int i,e;
	int MotionComplete; // Set to 1 when a move is done
	char s[255];

	// Read the tensions
	UpdateTensions();

	// Check for fatals
	if (UpdateStep == 0)
	{
		PauseOK = 0;
		// if no fatal flags are set, perform normal operations
		e = CheckFatal(); // Check for fatal errors
		if (e == -1) return;
		if (e != 0)
		{
    	// Only display the fatal error once
    	if (Flags.Fatal == 0)
				EDSLog.Add(16,"Check fatal error %d",e);
			Flags.SetFatal(FFCheckFatal);
		}
		UpdateStep = 1;
		return;
	}
  //Update M2 Support
	else if (UpdateStep == 1) {
		if (f11Present && !f5Present) {
			e = PresVac[0].PresVacComm();
			if (e == -1) return;
		}
		UpdateStep = 2;
		return;
	}
	// Update encoders
	else if (UpdateStep == 2)
	{
		e = UpdateEncoders();
		if (e == -1) return;
		if (e != 0)
		{
			UpdateStep = 0;
			PauseOK = 1;
			return;
		}
		// Convert encoder readings to current mirror location
		GetXYZHVFromEncoders();
		// Calculate and display destination encoder readings (DAE[] and DRE[])
		DisplayDestination();
		UpdateStep = 3;
		return;
	}
	else if (UpdateStep == 3)
	{
		// Check to see if a move is in progress
		if (Flags.CheckStatus(SFMoveInProgress))
		{
			// Verify that destination locations are valid (don't exceed soft limits) //
			for (i=0;i<=3;i++)
			{
				if ((DAE[i] < MinAE[i]) || (DAE[i] > MaxAE[i]) ||
						(DRE[i] < MinRE[i]) || (DRE[i] > MaxRE[i]))
				{
					Flags.SetFatal(FFEncoderLimit);
					UpdateStep = 0;
					PauseOK = 1;
					EDSLog.Add(5,"Destination exceeds soft limits");
					return;
				}
			}
			// Execute a move to get closer to the destination if necessary
			if (Flags.Fatal != 0)
			{
				Flags.SetMoveStage(MSIdle);
				EDSLog.Add(6,"Move aborted: A fatal flag is set");
			}
			else
			{
				e = Move();
				if (e == -1) return;
				if (e != 0)
				{
					EDSLog.Add(6,"Move aborted: Move error %d",e);
					Flags.SetFatal(FFNoMotorResponse);
				}
			}
		}
		PauseOK = 1;
		UpdateStep = 0;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CCell::AllVacOffPresOn turns vacuum off/pressure on on all four vane ends
void CCell::AllVacOffPresOn()
// This routine is called after the homing routines, so that if an error
// occurred, vacuum will be turned off anyway (if possible),
// and pressure on.
{
	char temp[255];

  // Vaccuum off
	BDMCCommand(Axial,"SB1;SB3;SB5;SB7\r",temp,DMCSlowCommand);
  Delay(1000);
	BUpdateEncoders();
	// Pressure on
  BDMCCommand(Axial,"CB2;CB4;CB6;CB8\r",temp,DMCSlowCommand);
  Delay(1000);
	BUpdateEncoders();
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayAll displays all encoders,tensions,and global coordinates
void CCell::DisplayAll()
{
	int i;
	int zero[4] = {0,0,0,0};
	char s[255];
	int x0,y0;
	byte color;

	// Display all encoder and tension values
	for (i = 0;i <= 3;i++)
	{
		DisplayAxialEncoder(i);
		DisplayRadialEncoder(i);
		DisplayAxialSwitch(i);
		DisplayRadialSwitch(i);
		DGHs[i].Display();
		DGHs[i+4].Display();
	}
	if (f11Present && !f5Present) {
		for (i = 8;i <= 10;i++) {
			DGHs[i].Display();
		}
	}

	if (f5Present) F5PresVacDisplay();

	DisplayAxialMove(zero);
	DisplayRadialMove(zero);

	// Display current global coordinates
	DisplayGlobal();
	// Display all destination values (global and encoder)
	DisplayDestination();
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayAxialEncoder: Display the axial encoder reading of Corner onscreen
void CCell::DisplayAxialEncoder(int Corner)
// Corner = 0 (NW),1 (SW),2 (SE),3 (NE)
{
	byte color;
	char s[255];

	// Display axial encoder reading
	sprintf(s,"% 06d",AE[Corner]);
	// Color-code numeric display (Green = OK, Yellow = Close to limits,
	// Red = Outside of limits)
	if ((AE[Corner] < MinAE[Corner]) || (AE[Corner] > MaxAE[Corner]))
	{
		color = SHECRED;
		AEOK[Corner] = 2;	// Encoder beyond limits
	}
	else if ((AE[Corner] - MinAE[Corner] < EncoderWarningRange) ||
					 (MaxAE[Corner] - AE[Corner] < EncoderWarningRange))
	{
		color = YELLOW;
		AEOK[Corner] = 1;	// Encoder near limits
	}
	else
	{
		color = BLACK;
		AEOK[Corner] = 0;	// Encoder within limits
	}
	PutString16(s,CornerX[Corner]+56,CornerX[Corner]+56+48,CornerY[Corner],color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayRadialEncoder: Display the radial encoder reading of Corner onscreen
void CCell::DisplayRadialEncoder(int Corner)
// Corner = 0 (NW),1 (SW),2 (SE),3 (NE)
{
	byte color;
	char s[255];

	// Display radial encoder reading
	sprintf(s,"% 06d",RE[Corner]);
	// Color-code numeric display (Green = OK, Yellow = Close to limits,
	// Red = Outside of limits)
	if ((RE[Corner] < MinRE[Corner]) || (RE[Corner] > MaxRE[Corner]))
	{
		color = SHECRED;
		REOK[Corner] = 2;	// Encoder beyond limits
	}
	else if ((RE[Corner] - MinRE[Corner] < EncoderWarningRange) ||
					 (MaxRE[Corner] - RE[Corner] < EncoderWarningRange))
	{
		color = YELLOW;
		REOK[Corner] = 1; // Encoder near limits
	}
	else
	{
		color = BLACK;
		REOK[Corner] = 0;	// Encoder within limits
	}
	PutString16(s,CornerX[Corner]+56,CornerX[Corner]+56+48,CornerY[Corner]+16,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayAxialSwitch: Display the axial limit switches of Corner onscreen
void CCell::DisplayAxialSwitch(int Corner)
// Corner = 0 (NW),1 (SW),2 (SE),3 (NE)
{
	int Color,Char;

	// Display axial limit switches
	if ((AS[Corner] & 12) == 12)
	{
		Color = BLACK;
		Char = 18;
	}
	// Down limit
	else if ((AS[Corner] & 12) == 4)
	{
		Color = SHECRED;
		Char = 31;
	}
	// Up limit
	else if ((AS[Corner] & 12) == 8)
	{
		Color = SHECRED;
		Char = 30;
	}
	else
	{
		Color = SHECRED;
		Char = 18;
	}
	PutChar16(Char,CornerX[Corner]+128,CornerY[Corner],Color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayRadialSwitch: Display the radial limit switches of Corner onscreen
void CCell::DisplayRadialSwitch(int Corner)
// Corner = 0 (NW),1 (SW),2 (SE),3 (NE)
{
	int Color,Char;

	// Display radial limit switches
	if ((RS[Corner] & 12) == 12)
	{
		Color = BLACK;
		Char = 18;
	}
	else if ((RS[Corner] & 12) == 4)
	{
		Color = SHECRED;
		Char = 31;
	}
	else if ((RS[Corner] & 12) == 8)
	{
		Color = SHECRED;
		Char = 30;
	}
	else
	{
		Color = SHECRED;
		Char = 18;
	}
	PutChar16(Char,CornerX[Corner]+128,CornerY[Corner]+16,Color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayGlobal: Display current global coordinates
void CCell::DisplayGlobal()
{
	int x0,y0,color;
	char s[20];
	int temp;

	x0 = 588;
	y0 = 26;
	color = BLACK;
	temp = round(x);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0,color,SHECMEDGRAY);
	temp = round(y);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+16,color,SHECMEDGRAY);
	temp = round(z);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+32,color,SHECMEDGRAY);
	temp = round(h);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+48,color,SHECMEDGRAY);
	temp = round(v);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+64,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayDestination: Display all destination encoder readings and global coords
void CCell::DisplayDestination()
{
	int Corner,x0,y0;
	byte color;
	char s[255];
	int temp;

  // Limit global coords to reasonable numbers (this avoids math errors)
  dx = __min(dx,99999.0);
  dx = __max(dx,-99999.0);
  dy = __min(dy,99999.0);
  dy = __max(dy,-99999.0);
  dz = __min(dz,99999.0);
  dz = __max(dz,-99999.0);
  dh = __min(dh,99999.0);
  dh = __max(dh,-99999.0);
  dv = __min(dv,99999.0);
  dv = __max(dv,-99999.0);

  // Convert to encoder readings
	GetDestination(dx,dy,dz,dh,dv,DAE,DRE);
	for (Corner=0;Corner<=3;Corner++)
	{
		// Display destination axial encoder reading
		sprintf(s,"% 06d",DAE[Corner]);
		if ((DAE[Corner] < MinAE[Corner]) || (DAE[Corner] > MaxAE[Corner]))
		{
			color = SHECRED;
			DAEOK[Corner] = 2;	// Destination beyond limits
		}
		else if ((DAE[Corner] - MinAE[Corner] < EncoderWarningRange) ||
						 (MaxAE[Corner] - DAE[Corner] < EncoderWarningRange))
		{
			color = YELLOW;
			DAEOK[Corner] = 1;	// Destination near limits
		}
		else
		{
			color = BLACK;
			DAEOK[Corner] = 0;	// Destination within limits
		}
		PutString16(s,CornerX[Corner],CornerX[Corner]+48,CornerY[Corner],color,SHECMEDGRAY);

		// Display destination radial encoder reading
		sprintf(s,"% 06d",DRE[Corner]);
		if ((DRE[Corner] < MinRE[Corner]) || (DRE[Corner] > MaxRE[Corner]))
		{
			color = SHECRED;
			DAEOK[Corner] = 2;	// Destination beyond limits
		}
		else if ((DRE[Corner] - MinRE[Corner] < EncoderWarningRange) ||
						 (MaxRE[Corner] - DRE[Corner] < EncoderWarningRange))
		{
			color = YELLOW;
			DAEOK[Corner] = 1;	// Destination near limits
		}
		else
		{
			color = BLACK;
			DAEOK[Corner] = 0;	// Destination within limits
		}
		PutString16(s,CornerX[Corner],CornerX[Corner]+48,CornerY[Corner]+16,color,SHECMEDGRAY);
	}

	// Display dx,dy,dz,dh,dv
	x0 = 540;
	y0 = 26;
	color = BLACK;
	temp = round(dx);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0,color,SHECMEDGRAY);
	temp = round(dy);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+16,color,SHECMEDGRAY);
	temp = round(dz);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+32,color,SHECMEDGRAY);
	temp = round(dh);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+48,color,SHECMEDGRAY);
	temp = round(dv);
	sprintf(s,"%5d",temp);
	PutString16(s,x0,x0+40,y0+64,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayAxialMove: Display the axial movement flags
void CCell::DisplayAxialMove(int Moves[4])
// Displays axial movement flags onscreen for all four corners
{
	int i,Char,Color;

	for (i=0;i<=3;i++)
	{
		// No motion
		if (Moves[i] == 0)
		{
			Char = 18;
			Color = BLACK;
			// Update motion flag
			AM[i] = 0;
		}
		// Down motion
		else if (Moves[i] < 0)
		{
			Char = 31;
			Color = SHECGREEN;
			// Update motion flag
			AM[i] = 2;
		}
		// Up motion
		else
		{
			Char = 30;
			Color = SHECGREEN;
			// Update motion flag
			AM[i] = 1;
		}
		PutChar16(Char,CornerX[i]+112,CornerY[i],Color,SHECMEDGRAY);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CCell::DisplayRadialMove: Display the axial movement flags
void CCell::DisplayRadialMove(int Moves[4])
// Displays radial movement flags onscreen for all four corners
{
	int i,Char,Color;

	for (i=0;i<=3;i++)
	{
		// No motion
		if (Moves[i] == 0)
		{
			Char = 18;
			Color = BLACK;
			// Update motion flag
			RM[i] = 0;
		}
		// Negative motion
		else if (Moves[i] < 0)
		{
			Char = 31;
			Color = SHECGREEN;
			// Update motion flag
			RM[i] = 2;
		}
		// Positive motion
		else
		{
			Char = 30;
			Color = SHECGREEN;
			// Update motion flag
			RM[i] = 1;
		}
		PutChar16(Char,CornerX[i]+112,CornerY[i]+16,Color,SHECMEDGRAY);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CCell::UpdateEncoders gets the current encoder readings and displays them
int CCell::UpdateEncoders()
{
	int i,e;
	char s[255];
	static int NewAE[4],NewRE[4],NewAS[4],NewRS[4];

	// Read the encoders into NewAE[] and NewRE[]
	if (UpdateEncodersStep == 0)
	{
		e = GetEncoders(NewAE,NewRE);
		if (e == -1) return(-1);
		if (e != 0)
		{
			EncoderErrors++;
			if (EncoderErrors < MaxEncoderErrors)
				EDSLog.Add(7,"Encoder read error %d",e);
			if (EncoderErrors == MaxEncoderErrors)
				EDSLog.Add(17,"Encoder read errors suspended");
			if (EncoderErrors == FatalEncoderErrors)
				Flags.SetFatal(FFEncoders);
			return(1);
		}
		if (EncoderErrors >= MaxEncoderErrors)
			EDSLog.Add(987,"Encoder read errors resumed");
		EncoderErrors = 0;
		// Display all changed encoder values
		for (i = 0;i <= 3;i++)
		{
			if (NewAE[i] != AE[i])
			{
				AE[i] = NewAE[i];
				DisplayAxialEncoder(i);
			}
			if (NewRE[i] != RE[i])
			{
				RE[i] = NewRE[i];
				DisplayRadialEncoder(i);
			}
		}
		UpdateEncodersStep = 1;
		return(-1);
	}
	// Read the limit switches into NewAS[] and NewRS[]
	else if (UpdateEncodersStep == 1)
	{
		e = GetSwitches(NewAS,NewRS);
		if (e == -1) return(-1);
		if (e != 0)
		{
			EncoderErrors++;
			if (EncoderErrors < MaxEncoderErrors)
				EDSLog.Add(7,"Encoder read error %d",e);
			if (EncoderErrors == MaxEncoderErrors)
				EDSLog.Add(17,"Encoder read errors suspended");
			if (EncoderErrors == FatalEncoderErrors)
				Flags.SetFatal(FFEncoders);
			return(1);
		}
		if (EncoderErrors >= MaxEncoderErrors)
			EDSLog.Add(987,"Encoder read errors resumed");
		EncoderErrors = 0;
		// Display all changed limit switches
		for (i = 0;i <= 3;i++)
		{
			if (NewAS[i] != AS[i])
			{
				AS[i] = NewAS[i];
				DisplayAxialSwitch(i);
			}
			if (NewRS[i] != RS[i])
			{
				RS[i] = NewRS[i];
				DisplayRadialSwitch(i);
			}
		}
	}
	UpdateEncodersStep = 0;
	return(0);
}

int CCell::BUpdateEncoders()
{
	int e;

	do
		e = UpdateEncoders();
	while (e == -1);
	return(e);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::GetXYZHVFromEncoders: Converts encoder readings to global coordinates
void CCell::GetXYZHVFromEncoders()
// This function takes the current values of the encoders (AE[] and RE[]),
// and calculates the global coordinates x,y,z,h, and v.
{
	int i;
	char s[255];
	int iterations; // Used to count the number of iterations. If it gets too big, stop
	float X[4],Y[4],Z[4]; // These hold the global coords of the vane ends
	float X2[4],Y2[4],Z2[4]; // These hold the global coords - Cage corner Coords
	float Dist[4]; // This holds the distance from the mirror center to each vane end
	float DeltaX,DeltaY,DeltaZ; // Used to converge on the correct values of x,y,z
	float newx,newy,newz,newh,newv; // Stores the newly calculated values
	int x0,y0,color; // Used during display of values

	// First, calculate the vane end positions in global coordinates
	// from the encoder values
	for (i=0;i<=3;i++)
	{
		AEREtoXY(AE[i]-ZAE[i],RE[i]-ZRE[i],&X[i],&Z[i]);
		X[i] *= 25400;
		X[i] -= TensionAmount;
		Z[i] *= 25400;
		Z2[i] = Z[i];
		Y2[i] = (VaneRadius + X[i]) * sin(Angle);
		Y[i] = Y2[i] + CageY;
		X2[i] = (VaneRadius + X[i]) * cos(Angle);
		X[i] = CageX + X2[i];
		if (i == 1)
		{
			Y[i] *= -1;
			Y2[i] *= -1;
		}
		if (i == 2)
		{
			X[i] *= -1;
			Y[i] *= -1;
			X2[i] *= -1;
			Y2[i] *= -1;
		}
		if (i == 3)
		{
			X[i] *= -1;
			X2[i] *= -1;
		}
	}
	// Now do a numerical iteration to find the mirror's x,y,and z coordinates
	// Just use the origin as the first guess for x,y,z
	newx = 0;
	newy = 0;
	newz = 0;
	iterations = 0;
	do
	{
		for (i=0;i<=3;i++)
			Dist[i] = sqrt((X2[i]-newx)*(X2[i]-newx)+
										 (Y2[i]-newy)*(Y2[i]-newy)+
										 (Z2[i]-newz)*(Z2[i]-newz)) - VaneRadius;
		// These deltas are just a good guess (~10%) of how much each variable has to change
		DeltaX = (Dist[0] + Dist[1] - Dist[2] - Dist[3]) / 4;
		DeltaY = (Dist[0] - Dist[1] - Dist[2] + Dist[3]) / 4;
		DeltaZ = (Z[0]+Z[1]+Z[2]+Z[3] - 4*newz) / 4;
		newx += DeltaX;
		newy += DeltaY;
		newz += DeltaZ;
		iterations++;
	}
	while (((fabs(DeltaX) > 0.1) || (fabs(DeltaY) > 0.1) ||
					(fabs(DeltaZ) > 0.1)) && (iterations < 10));
	// Now calculate h and v
	if ((X[0]+X[1]-2*x) < 0.1)
		newh = 0;
	else
		newh = -FixH * (3600.0*180.0/PI)*atan(((Z[0]+Z[3])/2-newz)/((Y[0]+Y[3])/2-newy));
	if ((Y[0]-Y[1]) < 0.1)
		newv = 0;
	else
		newv = -FixV * (3600.0*180.0/PI)*atan(((Z[0]+Z[1])/2-newz)/((X[0]+X[1])/2-newx));
	// Display any changes on-screen
	x0 = 580;
	y0 = 36;
	color = LIGHTBLUE;
	if ((x != newx) || (y != newy) || (z != newz) || (h != newh) || (v != newv))
	{
		x = newx;
		y = newy;
		z = newz;
		h = newh;
		v = newv;
		DisplayGlobal();
	}
}

/////////////////////////////////////////////////////////////////////////////
// CCell::GetDestination: Converts global coordinates to encoder readings
void CCell::GetDestination(float newx,float newy,float newz,
													 float newh,float newv,
													 int NewDAE[4],int NewDRE[4])
// This function takes newx,newy,newz ,newh, and newv as input.
// These are the global coordinates you want to move the mirror to.
// It returns the necesary values of the encoders (in microns)
// in NewAE[] and NewRE[].
{
	matrix R;     // Rotation matrix
	vector V[4];  // 4 vane end positions in global coords
	vector Vo[4]; // 4 vane end positions in global coords, originally
	float deltax[4],deltaz[4]; // Vane end motions due to h/v rotations
	float X,Y;    // actuator coords
	float hr,vr;  // h and v in radians
	int i,j;

	// Initialize hr and vr (convert h and v to radians)
	hr = newh * PI / 180 / 60 / 60;
	vr = newv * PI / 180 / 60 / 60;
	// Initialize vane end positions relative to mirror center. These are
	// the zeroed global coordinates of the vane ends.
	Vo[0][0] = VaneX;
	Vo[0][1] = VaneY;
	Vo[0][2] = 0;
	Vo[1][0] = VaneX;
	Vo[1][1] = -VaneY;
	Vo[1][2] = 0;
	Vo[2][0] = -VaneX;
	Vo[2][1] = -VaneY;
	Vo[2][2] = 0;
	Vo[3][0] = -VaneX;
	Vo[3][1] = VaneY;
	Vo[3][2] = 0;
	// Make a copy of these original positions for transformation use
	for (i=0;i<=3;i++)
		for (j=0;j<=2;j++)
			V[i][j] = Vo[i][j];
	// Generate rotation matrix
	// NOTE: I chose to rotate around x-axis, and THEN the y-axis
	// First,the matrix for rotation about the x-axis
	R[0][0] = 1;
	R[0][1] = 0;
	R[0][2] = 0;
	R[1][0] = 0;
	R[1][1] = cos(hr);
	R[1][2] = -sin(hr);
	R[2][0] = 0;
	R[2][1] = sin(hr);
	R[2][2] = cos(hr);
	for (i=0;i<=3;i++)
		mmult(R,V[i]);
	// Then the matrix for rotation about the y-axis
	R[0][0] = cos(vr);
	R[0][1] = 0;
	R[0][2] = -sin(vr);
	R[1][0] = 0;
	R[1][1] = 1;
	R[1][2] = 0;
	R[2][0] = sin(vr);
	R[2][1] = 0;
	R[2][2] = cos(vr);
	for (i=0;i<=3;i++)
		mmult(R,V[i]);
	// The two rotations are NOT commutative, but oh well...
	// Calculate the deltax and deltaz's this rotation caused
	for (i=0;i<=3;i++)
	{
		deltax[i] = sqrt((V[i][0] - Vo[i][0]) * (V[i][0] - Vo[i][0]) +
										 (V[i][1] - Vo[i][1]) * (V[i][1] - Vo[i][1]));
		deltaz[i] = V[i][2] - Vo[i][2];
	}
	// Now convert these positions into local actuator coords
	// (also convert from microns to inches)
	// Vane end 1 (NW)
	X = (VaneCalc(newx,newy,0) + deltax[0]) / 25400;
	Y = FixZ * (newz + deltaz[0]) / (25400);
	XYtoAERE(X,Y,&NewDAE[0],&NewDRE[0]);
	// Vane end 2 (SW)
	X = (VaneCalc(newx,newy,1) + deltax[1]) / 25400;
	Y = FixZ * (newz + deltaz[1]) / (25400);
	XYtoAERE(X,Y,&NewDAE[1],&NewDRE[1]);
	// Vane end 3 (SE)
	X = (VaneCalc(newx,newy,2) + deltax[2]) / 25400;
	Y = FixZ * (newz + deltaz[2]) / (25400);
	XYtoAERE(X,Y,&NewDAE[2],&NewDRE[2]);
	// Vane end 4 (NE)
	X = (VaneCalc(newx,newy,3) + deltax[3]) / 25400;
	Y = FixZ * (newz + deltaz[3]) / (25400);
	XYtoAERE(X,Y,&NewDAE[3],&NewDRE[3]);
	// Adjust the destination values with the zero locations
	for (i=0;i<=3;i++)
	{
		NewDAE[i] += ZAE[i];
		NewDRE[i] += ZRE[i];
	}
}

/////////////////////////////////////////////////////////////////////////////
// CCell::RemoveBacklash: Give commands to remove backlash on flagged axes
int CCell::RemoveBacklash(int A[4],int R[4])
// This routine gives the necessary commands to remove backlash on
// the corners passed in A and R.
// To remove backlash in the + direction, pass a 1 in an array slot,
// pass a 0 to do nothing and pass a -1 to remove backlash in the - direction.
{
	char s[255];
	int i,e;
	static int BA[4],BR[4];   // Stores backlash motor step amounts
	static int BAE[4],BRE[4]; // Encoder readings after backlash removed
	static int BacklashGone;       // Set to 1 if all backlash has been removed
	static int BacklashAttempts;   // Counts the number of attempts at removing backlash
	int MaxBacklashAttempts = 50; // = maximum number of microns of backlash at encoders
	int DoRemoval;

	DoRemoval = 0;
	if (BacklashStep == 0)
	{
		// Eliminate backlash.
		//
		// The overall idea is this: Figure out which direction each motor is
		// going to be moving, if at all.  Then, fire off a series of small
		// step values to each motor, and check after each to see if the encoder
		// value has changed (which implies that the backlash has been removed
		// for that motor). After too many tries (MaxBacklashAttempts), give up.
		Log.Add("CCell::RemoveBacklash(%d): Eliminating backlash",VERBOSE,__LINE__);
		for (i=0;i<=3;i++)
		{
			// Setup backlash movement amounts (BA[i],BR[i]) and
			// target encoder values (BAE[i],BRE[i]) after backlash has been removed.
			if (A[i] == 1)
			{
				BA[i] = (float)BacklashMove / AxialEncoderMicsPerStep; // Remove about 8 microns of backlash per try
				BAE[i] = AE[i] + BacklashTarget; // Set target location (after backlash removed) 3 microns past current location
				DoRemoval = 1;
			}
			else if (A[i] == -1)
			{
				BA[i] = -(float)BacklashMove / AxialEncoderMicsPerStep;
				BAE[i] = AE[i] - BacklashTarget;
				DoRemoval = 1;
			}
			else
				BA[i] = 0;
			if (R[i] == 1)
			{
				BR[i] = (float)BacklashMove / RadialEncoderMicsPerStep;
				BRE[i] = RE[i] + BacklashTarget;
				DoRemoval = 1;
			}
			else if (R[i] == -1)
			{
				BR[i] = -(float)BacklashMove / RadialEncoderMicsPerStep;
				BRE[i] = RE[i] - BacklashTarget;
				DoRemoval = 1;
			}
			else
				BR[i] = 0;
		}
		if (!DoRemoval) return(0);
		BacklashAttempts = 0;
		BacklashStep = 1;
		return(-1);
	}
	if (BacklashStep == 1)
	{
		// Move all motors that are going to move a small step in the
		// right direction.
		e = MoveAllMotors(BA,BR,BacklashTime,BacklashTimeout,BacklashDelay);
		if (e == -1) return(-1);
		if (e != 0)
		{
			Log.Add("!CCell::RemoveBacklash(%d): Move command failed",VERBOSE,__LINE__,i);
			BacklashStep = 0;
			return(1);
		}
		BacklashAttempts++;
		BacklashStep = 2;
		return(-1);
	}
	if (BacklashStep == 2)
	{
		BacklashGone = 1;
		// If an encoder value changed, its associated motor has eliminated
		// its backlash, so set Bx[i] to 0
		for (i=0;i<=3;i++)
		{
			if (BA[i] > 0)
				if (BAE[i] <= AE[i])
					BA[i] = 0;
				else
					BacklashGone = 0;
			if (BA[i] < 0)
				if (BAE[i] >= AE[i])
					BA[i] = 0;
				else
					BacklashGone = 0;
			if (BR[i] > 0)
				if (BRE[i] <= RE[i])
					BR[i] = 0;
				else
					BacklashGone = 0;
			if (BR[i] < 0)
				if (BRE[i] >= RE[i])
					BR[i] = 0;
				else
					BacklashGone = 0;
		}
		// If BacklashAttempts exceeds MaxBacklashAttempts, abort with an error.
		// If the target encoder readings BAE and BRE were reached, then
		// backlash has been eliminated.
		if ((BacklashAttempts < MaxBacklashAttempts) && (!BacklashGone))
		{
			BacklashStep = 1;
			return(-1);
		}
		if (!BacklashGone)
		{
			BacklashStep = 0;
			return(1);
		}
	}
	BacklashStep = 0;
	return(0);
}


/////////////////////////////////////////////////////////////////////////////
// CCell::Move: Give commands to move towards current destination
int CCell::Move()
// This routine sends the motors the commands necessary to get them closer
// to the destination we're headed for.  If the destination location requires
// a move of more than MacroMove microns (at an encoder), the move is scaled down
// so that it does take < MacroMove microns.
{
	int i,j,e;
	char s[255];
	static int BiggestMove;   // Saves the values of the longest move found
	static int BA[4],BR[4];   // Stores backlash motion flags, 0 =done
	static int EAE[4],ERE[4]; // Store the destination encoder values
	static int MAE[4],MRE[4]; // Store the number of steps we want the motors to move to
	static int CAE[4],CRE[4]; // Compare encoders at end of move
	int MoveDone;           	// Set to 1 if microscopic movement is complete
	static int MoveAttempts;  // Counts the number of attempts at microscopic movement
	int MaxMoveAttempts = 500;// maximum number of microscopic moves
	char temp[80];
	static float tx,ty,tz,th,tv; // Temporary destination storage

	if (MoveStep == 0)
	{
		Flags.SetMoveStage(MSBacklash);
		// Get the encoder values necessary for the target location dx,dy,dz,dh,dv
		GetDestination(dx,dy,dz,dh,dv,EAE,ERE);
		tx = dx;
		ty = dy;
		tz = dz;
		th = dh;
		tv = dv;
		BiggestMove = 0;
		// Find the biggest move necessary
		for (i=0;i<=3;i++)
		{
			if (abs(AE[i] - EAE[i]) > BiggestMove)
				BiggestMove = abs(AE[i] - EAE[i]);
			if (3*abs(RE[i] - ERE[i]) > BiggestMove)
				BiggestMove = 3*abs(RE[i] - ERE[i]);
		}
		// Don't move more than MacroMove microns (at encoders) in one chunk
		// So, scale all moves back to make sure the move is safe
		if (BiggestMove > MacroMove)
		{
			tx = x + (dx - x) * MacroMove / BiggestMove;
			ty = y + (dy - y) * MacroMove / BiggestMove;
			tz = z + (dz - z) * MacroMove / BiggestMove;
			th = h + (dh - h) * MacroMove / BiggestMove;
			tv = v + (dv - v) * MacroMove / BiggestMove;
			GetDestination(tx,ty,tz,th,tv,EAE,ERE);
			BiggestMove = 0;
			// Find the biggest move necessary again
			for (i=0;i<=3;i++)
			{
				if (abs(AE[i] - EAE[i]) > BiggestMove)
					BiggestMove = abs(AE[i] - EAE[i]);
				if (3*abs(RE[i] - ERE[i]) > BiggestMove)
					BiggestMove = 3*abs(RE[i] - ERE[i]);
			}
			if (BiggestMove > MacroMove)
				for (i=0;i<=3;i++)
				{
					EAE[i] = AE[i] + (EAE[i] - AE[i]) * MacroMove / BiggestMove;
					ERE[i] = RE[i] + (ERE[i] - RE[i]) * MacroMove / BiggestMove;
				}
		}
		// Setup backlash movement flags (BA[i],BR[i])
		for (i=0;i<=3;i++)
		{
			if (abs(EAE[i] - AE[i]) < BacklashThreshold) // Don't remove backlash for movements under 20 microns
				BA[i] = 0;
			else if (EAE[i] > AE[i])
				BA[i] = 1;
			else
				BA[i] = -1;
			if (3*abs(ERE[i] - RE[i]) < BacklashThreshold)
				BR[i] = 0;
			else if (ERE[i] > RE[i])
				BR[i] = 1;
			else
				BR[i] = -1;
		}
		MoveStep = 1;
		return(-1);
	}
	if (MoveStep == 1)
	{
		e = RemoveBacklash(BA,BR);
		if (e == -1) return(0);
		if (e != 0)
		{
			Log.Add("!CCell::Move(%d): Error removing backlash!",VERBOSE,__LINE__);
			MoveStep = 0;
			return(1);
		}
		MoveStep = 2;
		return(0);
	}
	if (MoveStep == 2)
	{
		Flags.SetMoveStage(MSMacroMove);
		Log.Add("CCell::Move(%d): Performing macroscopic movement",VERBOSE,__LINE__);
		Log.Add(" Current: X: %8.2f Y:%8.2f Z:%8.2f H:%8.2f V:%8.2f",VERBOSE,x,y,z,h,v);
		Log.Add("Waypoint: X: %8.2f Y:%8.2f Z:%8.2f H:%8.2f V:%8.2f",VERBOSE,tx,ty,tz,th,tv);
		Log.Add("   Final: X: %8.2f Y:%8.2f Z:%8.2f H:%8.2f V:%8.2f",VERBOSE,dx,dy,dz,dh,dv);
		// Now begin the actual move.
		// Set the number of steps to give the motors to achieve the move.
		// Make this number a little smaller than the total necessary
		// to avoid overshooting our target. This will get us within
		// MacroThreshold microns of our target location.
		for (i=0;i<=3;i++)
		{
			if (abs(EAE[i] - AE[i]) > MacroThreshold)
				MAE[i] = 0.85 * (float)(EAE[i] - AE[i]) / AxialEncoderMicsPerStep;
			else
				MAE[i] = 0;
			if (3*abs(ERE[i] - RE[i]) > MacroThreshold)
				MRE[i] = 0.85 * (float)(ERE[i] - RE[i]) / RadialEncoderMicsPerStep;
			else
				MRE[i] = 0;
		}
		Log.Add(" AEs: %d %d %d %d  REs: %d %d %d %d",VERBOSE,AE[0],AE[1],AE[2],AE[3],RE[0],RE[1],RE[2],RE[3]);
		Log.Add("MAEs: %d %d %d %d MREs: %d %d %d %d",VERBOSE,MAE[0],MAE[1],MAE[2],MAE[3],MRE[0],MRE[1],MRE[2],MRE[3]);
		Log.Add("EAEs: %d %d %d %d EREs: %d %d %d %d",VERBOSE,EAE[0],EAE[1],EAE[2],EAE[3],ERE[0],ERE[1],ERE[2],ERE[3]);
		// When all encoders are within MacroThreshold microns of their
		// required location switch to the MicroMove stage.
		if (!(MAE[0]||MAE[1]||MAE[2]||MAE[3]||MRE[0]||MRE[1]||MRE[2]||MRE[3]))
		{
			MoveStep = 5;
			return(-1);
		}
		// Save current encoder readings
		for (i=0;i<=3;i++)
		{
			CAE[i] = AE[i];
			CRE[i] = RE[i];
		}
		MoveStep = 3;
		return(0);
	}
	if (MoveStep == 3)
	{
		e = MoveAllMotors(MAE,MRE,MacroTime,MacroTimeout,MacroDelay);
		if (e == -1) return(0);
		if (e != 0)
		{
			Log.Add("!CCell::Move(%d): Move command failed",VERBOSE,__LINE__,i);
			MoveStep = 0;
			return(2);
		}
		MoveStep = 4;
		return(0);
	}
	if (MoveStep == 4)
	{
		for (i=0;i<=3;i++)
		{
			// Compare axial position with requested position
			if (MAE[i] != 0)
			{
				if (abs(AE[i] - CAE[i]) < MacroError * abs(CAE[i] - EAE[i]))
				{
					Log.Add("!CCell::Move(%d): Axial motor (%d) exceeded allowable error",VERBOSE,__LINE__,i);
					Log.Add("!Expected encoder reading: %8ld Actual reading: %8ld",VERBOSE,EAE[i],AE[i]);
					MoveStep = 0;
					return(3);
				}
			}
			// Compare radial position with requested position
			if (MRE[i] != 0)
			{
				if (abs(RE[i] - CRE[i]) < MacroError * abs(CRE[i] - ERE[i]))
				{
					Log.Add("!CCell::Move(%d): Radial motor (%d) exceeded allowable error",VERBOSE,__LINE__,i);
					Log.Add("!Expected encoder reading: %8ld Actual reading: %8ld",VERBOSE,ERE[i],RE[i]);
					MoveStep = 0;
					return(4);
				}
			}
		}
		MoveStep = 0;
		return(0);
	}
	if (MoveStep == 5)
	{
		Flags.SetMoveStage(MSMicroMove);
		Log.Add("CCell::Move(%d): Performing microscopic movement",VERBOSE,__LINE__);
		MoveAttempts = 0;
		MoveStep = 6;
		return(-1);
	}
	if (MoveStep == 6)
	{
		for (i=0;i<=3;i++)
		{
			// Setup microscopic movement amounts (BA[i],BR[i]).
			// These movement amounts should move each encoder
			// 1 micron closer to the target position.
			//if ((EAE[i] - AE[i]) == 0)
			if (abs(EAE[i] - AE[i]) < 2)
				BA[i] = 0;
			else if (EAE[i] > AE[i])
				BA[i] = 1;
			else
				BA[i] = -1;
			//if ((ERE[i] - RE[i]) == 0)
			if (abs(ERE[i] - RE[i]) < 2)
				BR[i] = 0;
			else if (ERE[i] > RE[i])
				BR[i] = 3;
			else
				BR[i] = -3;
		}
		MoveAttempts++;
		MoveStep = 7;
		return(0);
	}
	if (MoveStep == 7)
	{
		// Move all motors that are going to move a small step in the
		// right direction.
		e = MoveAllMotors(BA,BR,MicroTime,MicroTimeout,MicroDelay);
		if (e == -1) return(0);
		if (e != 0)
		{
			Log.Add("!CCell::Move(%d): Move command failed",VERBOSE,__LINE__,i);
			MoveStep = 0;
			return(5);
		}
		MoveDone = 1;
		// Check to see if the move is complete
		for (i=0;i<=3;i++)
		{
			if (abs(EAE[i] - AE[i]) > AxialError)
				MoveDone = 0;
			if (abs(ERE[i] - RE[i]) > RadialError)
				MoveDone = 0;
		}
		// If MoveAttempts exceeds MaxMoveAttempts, abort with an error.
		// Because the MacroMove stage should have gotten us within
		// 50 microns of the destination position, only 50 move attempts
		// should be necessary.
		if ((MoveAttempts < MaxMoveAttempts) && (!MoveDone))
		{
			MoveStep = 6;
			return(0);
		}
		// Turn the motors off when done to avoid over-heating
		if (BDMCCommand(Axial,"MO\r",temp,DMCSlowCommand))
		{
			Log.Add("!CCell::Move(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			MoveStep = 0;
			return(6);
		}
		if (BDMCCommand(Radial,"MO\r",temp,DMCSlowCommand))
		{
			Log.Add("!CCell::Move(%d): Communication error with Radial DMC",VERBOSE,__LINE__);
			MoveStep = 0;
			return(7);
		}
		if (!MoveDone)
		{
			Log.Add("!CCell::Move(%d): Unable to complete move",VERBOSE,__LINE__);
			MoveStep = 0;
			return(8);
		}
		// Check to see if we've reached the final destination
		MoveDone = 1;
		for (i=0;i<=3;i++)
		{
			if (abs(DAE[i] - AE[i]) > AxialError)
				MoveDone = 0;
			if (abs(DRE[i] - RE[i]) > RadialError)
				MoveDone = 0;
		}
		if (MoveDone)
		{
			Flags.SetMoveStage(MSIdle);
			MoveStep = 0;
		}
	}
	MoveStep = 0;
	return(0);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::UpdateTensions: Read and display tensions
void CCell::UpdateTensions()
{
	int e = 0;
	char s[255];

	// Read the tensions
	if ((DGHIndex >= 0 && DGHIndex <=7) ||
		((DGHIndex >= 8 && DGHIndex <= 10) && (f11Present && !f5Present)) ||
		((DGHIndex >= 11 && DGHIndex <= 16) && f5Present)) {

		e = DGHs[DGHIndex].Update();
	}
	if (e == -1) return;
	if (e == 0)
	{
		// Read the next DGH the next time UpdateTensions is called
		DGHIndex++;
		if (DGHIndex > 16)
			DGHIndex = 0;
		return;
	}
	// A DGH read error occurred
  if ((DGHIndex >=0) && (DGHIndex <= 7))
	{
	  if (DGHs[DGHIndex].ErrorCount >= FatalDGHErrors)
		  Flags.SetFatal(FFTensions);
	  Log.Add("!CCell::Update(%d): Error with the tensions",VERBOSE,__LINE__);
  }
  else
	  Log.Add("!CCell::Update(%d): Error reading DGH %c",VERBOSE,__LINE__,DGHIndex+'a');
	// Read the next DGH the next time UpdateTensions is called
	DGHIndex++;
	if (DGHIndex > 16)
		DGHIndex = 0;
}

/////////////////////////////////////////////////////////////////////////////
// CCell::CheckFatal: Checks tensions, overheats, etc, and sets fatal flags
int CCell::CheckFatal()
{
// This checks for excessive tensions, motor controller overheats,
// and emergency stop card failure, and sets the FatalFlags flags appropriately.
// Returns a 0 on OK, -1 on not done, >0 on com error
	int i,e;
	char s[255];
	static char line[80]; // Buffer for DMC responses
	int Check; // Temp variable for checking input bits

	Log.Add("CCell::CheckFatal(%d): Checking for fatal errors",VERBOSE,__LINE__);

	// Check encoder limits
	for (i=0;i<=3;i++)
		if ((AE[i] < MinAE[i]) || (AE[i] > MaxAE[i]) ||
			 (RE[i] < MinRE[i]) || (RE[i] > MaxRE[i]))
		{
			Flags.SetFatal(FFEncoderLimit);
			Log.Add("!CCell::CheckFatal(%d): Encoders are out of bounds, fatal flag set",VERBOSE,__LINE__);
		}
	// Check tension limits
	for (i=0;i<=7;i++)
		if (!DGHs[i].Disabled)
			if ((DGHs[i].Tension < DGHs[i].MinTension) ||
					(DGHs[i].Tension > DGHs[i].MaxTension))
			{
				Flags.SetFatal(FFTensionLimit);
				Log.Add("!CCell::CheckFatal(%d): Tensions are out of bounds, fatal flag set",VERBOSE,__LINE__);
			}
	if (CheckFatalStep == 0)
	{
		// The TI command reads the DMCs input bits
		e = DMCCommand(Radial,"TI\r",line,DMCSlowCommand);
		if (e == -1) return(-1);
		if (e != 0)
		{
			Log.Add("!CCell::CheckFatal(%d): Error reading Radial DMC",VERBOSE,__LINE__);
			return(2);
		}
		// Read the DMC response into Check
		if (sscanf(line,"%d",&Check) == 1)
		{
			// Do a binary not of Check (makes things simpler)
			Check ^= 255;
			// If (Check & 16) = 0, the emergency stop card has disabled the motors
			// due to an error condition
			if (Check & 16)
				Flags.ClearFatal(FFEmergencyStop);
			else
			{
				Flags.SetFatal(FFEmergencyStop);
				Log.Add("!CCell::CheckFatal(%d): Emergency Stop Card triggered, fatal flag set",VERBOSE,__LINE__);
			}
			// If (Check & 15) = 1, one or more of the four radial motor controllers
			// is overheating
			if (Check & 15)
			{
				Flags.SetFatal(FFOverheat);
				Log.Add("!CCell::CheckFatal(%d): Motor controller overheating, fatal flag set",VERBOSE,__LINE__);
			}
			else
				Flags.ClearFatal(FFOverheat);
		}
		else
		{
			Log.Add("!CCell::CheckFatal(%d): Error reading Radial DMC",VERBOSE,__LINE__);
			return(3);
		}
		CheckFatalStep = 1;
	}
	else if (CheckFatalStep == 1)
	{
		// Repeat all of the above steps for the Axial controller
		// The TI command reads the DMCs input bits
		e = DMCCommand(Axial,"TI\r",line,DMCSlowCommand);
		if (e == -1) return(-1);
		if (e != 0)
		{
			Log.Add("!CCell::CheckFatal(%d): Error reading Axial DMC",VERBOSE,__LINE__);
			return(3);
		}
		// Read the DMC response into Check
		if (sscanf(line,"%d",&Check) == 1)
		{
			// Do a binary not of Check (makes things simpler)
			Check ^= 255;
			// If (Check & 15) = 1, one or more of the four axial motor controllers
			// is overheating
			if (Check & 15)
			{
				Flags.SetFatal(FFOverheat);
				Log.Add("!CCell::CheckFatal(%d): Motor controller overheating, fatal flag set",VERBOSE,__LINE__);
			}
			else
				Flags.ClearFatal(FFOverheat);
		}
		else
		{
			Log.Add("!CCell::CheckFatal(%d): Error reading Axial DMC",VERBOSE,__LINE__);
			return(4);
		}
		CheckFatalStep = 0;
	}
	return(0);
}

/////////////////////////////////////////////////////////////////////////////
// Homes one of the four pairs of encoders
int CCell::HomeEncoders()
// Set home (zero) positions of encoders by retracting them.
// Returns a 0 on success, non-0 on a failure
{
	char s[255];
  int i,Corner;												// Used to step through all 4 corners
	int AECompare[4],RECompare[4]; 			// Used to check for motion
	int AERef[4],RERef[4]; 						  // Used to make sure encoders are moving
	int Difference; 										// Used to find largest error in homing
	int savetime;				 								// Used for to check for timing out
	int pausetime;											// Used for pausing
	char line[80];						 					// string buffer
	int MotionDelay = 300;	 						// Time to wait before checking for motion (ms)
	int Timeout = 10000; 								// Time before Home has failed (ms)
	int MinimumMotion = 10; 						// number in microns encoder MUST move to ensure
																			//	it is actually there and moving
	int MaxEncoderError = 1; 						// Maximum error (in microns) during Home operation


	Log.Add("CCell::HomeEncoders(%d): Beginning Home operation",VERBOSE,__LINE__);
	savetime = Timer;
	// Make sure pressure to encoders is off
	Log.Add("CCell::HomeEncoders(%d): Extension pressure off",VERBOSE,__LINE__);
  for (Corner=0;Corner<=3;Corner++)
  {
  	sprintf(s,"SB%d\r",(Corner+1)*2);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
		{
			Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			return(1);
		}
  }
  for (Corner=0;Corner<=3;Corner++)
  {
		AECompare[Corner] = 999;
		RECompare[Corner] = 999;
  }
	Log.Add("CCell::HomeEncoders(%d): Retraction pressure on",VERBOSE,__LINE__);
	Log.Add("Retracting encoders...",BRIEF);
  for (Corner=0;Corner<=3;Corner++)
  {
  	sprintf(s,"CB%d\r",(Corner+1)*2-1);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
		{
			Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			return(2);
		}
  }
	do
	{
		// Wait for movement to occur
		pausetime = Timer;
		while (Timer - pausetime < MotionDelay)
		{
			Watchdog();
			BUpdateEncoders();
		}
		Difference = 0;
		// Read the encoders
		if (BUpdateEncoders())
		{
			Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
			return(3);
		}
		// Check for a change in the reading
	  for (Corner=0;Corner<=3;Corner++)
    {
			if ((abs(AE[Corner] - AECompare[Corner]) > Difference))
				Difference = abs(AE[Corner] - AECompare[Corner]);
			AECompare[Corner] = AE[Corner];
			if ((abs(RE[Corner] - RECompare[Corner]) > Difference))
				Difference = abs(RE[Corner] - RECompare[Corner]);
			RECompare[Corner] = RE[Corner];
    }
		if ((Timer - savetime) > Timeout)
		{
			Log.Add("!CCell::HomeEncoders(%d): Timed out waiting for motion to stop",VERBOSE,__LINE__);
			return(4);
		}
	} while (Difference > 0);	// Repeat until motion has stopped
	Log.Add("CCell::HomeEncoders(%d): Zeroing encoders",VERBOSE,__LINE__);
	Log.Add("Zeroing encoders...",BRIEF);
	// Once encoders have stabilized, zero appropriate encoder values
	if (BDMCCommand(Radial,"DE0,0,0,0\r",line,DMCSlowCommand))
	{
		Log.Add("!CCell::HomeEncoders(%d): Communication error with Radial DMC",VERBOSE,__LINE__);
		return(5);
	}
	if (BDMCCommand(Axial,"DE0,0,0,0\r",line,DMCSlowCommand))
	{
		Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
		return(6);
	}
	// Check that the readings are zeroed
	if (BUpdateEncoders())
	{
		Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
		return(13);
	}
  for (Corner=0;Corner<=3;Corner++)
		if ((AE[Corner] != 0) || (RE[Corner] != 0))
		{
			Log.Add("!CCell::HomeEncoders(%d): Encoders failed to zero properly",VERBOSE,__LINE__);
			return(14);
		}
	// Now release retraction pressure and add extension pressure
	Log.Add("CCell::HomeEncoders(%d): Retraction pressure off",VERBOSE,__LINE__);
  for (Corner=0;Corner<=3;Corner++)
  {
  	sprintf(s,"SB%d\r",(Corner+1)*2-1);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
		{
			Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			return(15);
		}
  }
	// Once all encoders have stabilized, apply pressure and wait AGAIN
  for (Corner=0;Corner<=3;Corner++)
  {
		AECompare[Corner] = 999;
		RECompare[Corner] = 999;
  }
	Log.Add("Extending encoders...",BRIEF);
	Log.Add("CCell::HomeEncoders(%d): Extension pressure on",VERBOSE,__LINE__);
	// Turn on pressure
  for (Corner=0;Corner<=3;Corner++)
  {
  	sprintf(s,"CB%d\r",(Corner+1)*2);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
		{
			Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			return(18);
		}
  }
	do
	{
		// Wait for motion to occur
		pausetime = Timer;
		while (Timer - pausetime < MotionDelay)
		{
			Watchdog();
			BUpdateEncoders();
		}
		Difference = 0;
		// Read the encoders
		if (BUpdateEncoders())
		{
			Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
			return(19);
		}
		// Check for a change in the reading
	  for (Corner=0;Corner<=3;Corner++)
    {
			if ((abs(AE[Corner] - AECompare[Corner]) > Difference))
				Difference = abs(AE[Corner] - AECompare[Corner]);
			AECompare[Corner] = AE[Corner];
			if ((abs(RE[Corner] - RECompare[Corner]) > Difference))
				Difference = abs(RE[Corner] - RECompare[Corner]);
			RECompare[Corner] = RE[Corner];
    }
		if ((Timer - savetime) > Timeout)
		{
			Log.Add("!CCell::HomeEncoders(%d): Timed out waiting for motion to stop",VERBOSE,__LINE__);
			return(20);
		}
	}
	while (Difference > 0);
	// Check that minimum motion was achieved
	Log.Add("Checking for minimum motion...",BRIEF);
	if (BUpdateEncoders())
	{
		Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
		return(21);
	}
	// Save current position for later checking
  for (Corner=0;Corner<=3;Corner++)
  {
		AERef[Corner] = AE[Corner];
		RERef[Corner] = RE[Corner];
		// Check that the encoders moved the minimum distance...
		// If not, they're stuck or don't have vacuum/pressure
		if ((AE[Corner] < MinimumMotion) || (RE[Corner] < MinimumMotion))
		{
			Log.Add("!CCell::HomeEncoders(%d): Minimum motion was not achieved",VERBOSE,__LINE__);
			return(22);
		}
  }
	Log.Add("This completes one home operation.",BRIEF);
//*****************************************************************
//	This completes one Home operation; now repeat and insure that
//	you get the same results, and that there was motion
//*****************************************************************
	Log.Add("Checking our results (repeating Home operation)...",BRIEF);
	// Make sure pressure is off
	Log.Add("CCell::HomeEncoders(%d): Extension pressure off",VERBOSE,__LINE__);
  for (Corner=0;Corner<=3;Corner++)
  {
  	sprintf(s,"SB%d\r",(Corner+1)*2);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
	  {
		  Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
		  return(23);
	  }
  }
  for (Corner=0;Corner<=3;Corner++)
  {
		AECompare[Corner] = 999;
		RECompare[Corner] = 999;
  }
	// Turn on retraction pressure
	Log.Add("CCell::HomeEncoders(%d): Retraction pressure on",VERBOSE,__LINE__);
	Log.Add("Retracting encoders...",BRIEF);
  for (Corner=0;Corner<=3;Corner++)
  {
  	sprintf(s,"CB%d\r",(Corner+1)*2-1);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
		{
			Log.Add("!HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			return(24);
		}
  }
	do
	{
		// Wait for motion to occur
		pausetime = Timer;
		while (Timer - pausetime < MotionDelay)
		{
			Watchdog();
			BUpdateEncoders();
		}
		Difference = 0;
		// Read the encoders and check for a change in the reading
		if (BUpdateEncoders())
		{
			Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
			return(25);
		}
	  for (Corner=0;Corner<=3;Corner++)
    {
			if ((abs(AE[Corner] - AECompare[Corner]) > Difference))
				Difference = abs(AE[Corner] - AECompare[Corner]);
			AECompare[Corner] = AE[Corner];
			if ((abs(RE[Corner] - RECompare[Corner]) > Difference))
				Difference = abs(RE[Corner] - RECompare[Corner]);
			RECompare[Corner] = RE[Corner];
    }
		if ((Timer - savetime) > Timeout)
		{
			Log.Add("!CCell::HomeEncoders(%d): Timed out waiting for motion to stop",VERBOSE,__LINE__);
			return(26);
		}
	}
	while (Difference > 0);
	// Once all encoders have stabilized, check for the minimum amount of motion
	Log.Add("Checking for proper zeroing...",BRIEF);
	if (BUpdateEncoders())
	{
		Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
		return(27);
	}
  for (Corner=0;Corner<=3;Corner++)
		if ((abs(AE[Corner]) > MaxEncoderError) || (abs(RE[Corner]) > MaxEncoderError))
		{
			Log.Add("!CCell::HomeEncoders(%d): Encoders failed to re-zero properly",VERBOSE,__LINE__);
			return(28);
		}
	// Extend encoders
	Log.Add("Extending encoders...",BRIEF);
	// Release retraction pressure
	Log.Add("CCell::HomeEncoders(%d): Retraction pressure off",VERBOSE,__LINE__);
  for (Corner=0;Corner<=3;Corner++)
  {
  	sprintf(s,"SB%d\r",(Corner+1)*2-1);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
		{
			Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			return(29);
		}
  }
  for (Corner=0;Corner<=3;Corner++)
  {
		AECompare[Corner] = 999;
		RECompare[Corner] = 999;
  }
	// Apply pressure
	Log.Add("CCell::HomeEncoders(%d): Extension pressure on",VERBOSE,__LINE__);
  for (Corner=0;Corner<=3;Corner++)
  {
	 	sprintf(s,"CB%d\r",(Corner+1)*2);
	  if (BDMCCommand(Axial,s,line,DMCFastCommand))
		{
			Log.Add("!CCell::HomeEncoders(%d): Communication error with Axial DMC",VERBOSE,__LINE__);
			return(32);
		}
  }
	do
	{
		// Wait for motion to occur
		pausetime = Timer;
		while (Timer - pausetime < MotionDelay)
		{
			Watchdog();
			BUpdateEncoders();
		}
		Difference = 0;
		// Read the encoders and check for a change in the reading
		if (BUpdateEncoders())
		{
			Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
			return(33);
		}
	  for (Corner=0;Corner<=3;Corner++)
    {
			if ((abs(AE[Corner] - AECompare[Corner]) > Difference))
				Difference = abs(AE[Corner] - AECompare[Corner]);
			AECompare[Corner] = AE[Corner];
			if ((abs(RE[Corner] - RECompare[Corner]) > Difference))
				Difference = abs(RE[Corner] - RECompare[Corner]);
			RECompare[Corner] = RE[Corner];
    }
		if ((Timer - savetime) > Timeout)
		{
			Log.Add("!CCell::HomeEncoders(%d): Timed out waiting for motion to stop",VERBOSE,__LINE__);
			return(34);
		}
	}
	while (Difference > 0);
	// Make sure all encoders have returned to zero
	Log.Add("Checking that encoders returned to original position...",BRIEF);
	if (BUpdateEncoders())
	{
		Log.Add("!CCell::HomeEncoders(%d): Error reading encoders",VERBOSE,__LINE__);
		return(35);
	}
  for (Corner=0;Corner<=3;Corner++)
		if ((abs(AE[Corner] - AERef[Corner]) > MaxEncoderError) ||
				(abs(RE[Corner] - RERef[Corner]) > MaxEncoderError))
		{
			Log.Add("!CCell::HomeEncoders(%d): Encoders failed to re-extend properly",VERBOSE,__LINE__);
			return(36);
		}
	Flags.ClearFatal(FFHome);
	return(0);
}

/////////////////////////////////////////////////////////////////////////////
// CCell::Homes all 8 encoders
int CCell::Home()
// This routine homes the 8 encoders (sets their zero reading
// at fully retracted, using the vacuum system).
// It also configures the DMC and DGH modules, and resets the
// emergency stop card.
// It returns a 0 on success, nonzero on a failure.
{
	int i,e,retry;
	char s[255];
	char c;
	int savetime;

	// Configure DMCs
	if (ConfigDMCs())
		if (e = ConfigDMCs())
		{
			EDSLog.Add(8,"DMC config error %d",e);
			return(40);
		}
	// Home the encoders on all four corners
  do
  {
  	retry = 0;
	  if (e = HomeEncoders())
	  {
		  Log.Add("!CCell::Home(%d): Error homing",VERBOSE,__LINE__);
		  AllVacOffPresOn();
		  InputLog.Add("  Retry home (Y/N/A)?",BRIEF);
      InputLog.Add("     (Yes/No/Accept)",BRIEF);
		  savetime = Timer;
		  // Give a local user 15 seconds to retry the home
		  while ((Timer - savetime < 15000) && !kbhit())
			  Watchdog();
		  if (kbhit())
		  {
			  c = toupper(getch());
			  InputLog.Add("* %c",BRIEF,c);
        // No, don't retry.. Home failed
			  if (c == 'N')
				  return(e);
        // Yes, try the Home again
        else if (c == 'Y')
        	retry = 1;
        // Accept home values, even though Home failed
        else
        	retry = 0;
		  }
		  else
		  {
			  InputLog.Add("* N",BRIEF);
			  return(e);
		  }
	  }
  } while(retry);
	// Convert the current position of the mirror to global coordinates
	// (x,y,z,h,v) and store this as the current destination.
	GetXYZHVFromEncoders();
	dx = x;
	dy = y;
	dz = z;
	dh = h;
	dv = v;
	DisplayAll();
	return(0);
}

/////////////////////////////////////////////////////////////////////////////
// PauseUpdate - waits until Update has reached a safe pausing point
void PauseUpdate()
{
	int e;

	while (!PauseOK)
	{
		Watchdog();
		Cell.Update();
	}
	do
		if ((Cell.DGHIndex >= 0 && Cell.DGHIndex <=7) ||
			((Cell.DGHIndex >= 8 && Cell.DGHIndex <= 10) && (f11Present && !f5Present)) ||
			((Cell.DGHIndex >= 11 && Cell.DGHIndex <= 16) && f5Present)) {
				e = Cell.DGHs[Cell.DGHIndex].Update();
		}
	while (e == -1);
  PauseIR();
}

Generated by GNU Enscript 1.6.5.2.
Document Actions