391 lines
13 KiB
C
391 lines
13 KiB
C
/*--------------------------------------------------------------------------
|
||
|
||
L H 4 5 U T I L
|
||
|
||
A few utility functions for dealing with a LH45 temperature controller
|
||
within the SINQ setup: host -- TCP/IP -- MAC --- RS-232.
|
||
|
||
Mark Koennecke, Juli 1997
|
||
Mark Lesha, January 2006 (based on ITC4 code)
|
||
|
||
Copyright:
|
||
|
||
Labor fuer Neutronenstreuung
|
||
Paul Scherrer Institut
|
||
CH-5423 Villigen-PSI
|
||
|
||
|
||
The authors hereby grant permission to use, copy, modify, distribute,
|
||
and license this software and its documentation for any purpose, provided
|
||
that existing copyright notices are retained in all copies and that this
|
||
notice is included verbatim in any distributions. No written agreement,
|
||
license, or royalty fee is required for any of the authorized uses.
|
||
Modifications to this software may be copyrighted by their authors
|
||
and need not follow the licensing terms described here, provided that
|
||
the new terms are clearly indicated on the first page of each file where
|
||
they apply.
|
||
|
||
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
|
||
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
|
||
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
|
||
POSSIBILITY OF SUCH DAMAGE.
|
||
|
||
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
|
||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
|
||
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
|
||
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
|
||
MODIFICATIONS.
|
||
----------------------------------------------------------------------------*/
|
||
#include <string.h>
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <assert.h>
|
||
#include <fortify.h>
|
||
#include <sics.h>
|
||
#include <modriv.h>
|
||
#include <rs232controller.h>
|
||
#include "serialsinq.h"
|
||
#include "lh45util.h"
|
||
/*-------------------------------------------------------------------------*/
|
||
|
||
int LH45_Check_Status(pLH45 self) /* Can be called to check for correct operation of the LH45 */
|
||
{
|
||
int iRet;
|
||
char pCommand[20];
|
||
char pReply[132];
|
||
/* Check the status. It should read '03 REMOTE START' or possibly '03 REMOTE START,DEGASING'. */
|
||
/* If there is any LH45 overload or other fault condition it will be detected here. */
|
||
// printf("Checking LH45 status...");fflush(stdout);
|
||
sprintf(pCommand,"status");
|
||
iRet = transactRS232(self->controller, pCommand,strlen(pCommand),
|
||
pReply,79);
|
||
if(iRet <= 0)
|
||
{
|
||
//transactRS232(self->controller,"\nDEBUG: RS232 transaction bad.\n",28,pReply,79);
|
||
return iRet;
|
||
}
|
||
if (strncmp(pReply,"03 REMOTE START",15)!=0)
|
||
{
|
||
//transactRS232(self->controller,"\nDEBUG: RS232 response bad\n",27,pReply,79);
|
||
//transactRS232(self->controller,pReply,strlen(pReply),pReply,79);
|
||
strcpy(self->pAns,pReply);
|
||
return LH45__FAULT;
|
||
}
|
||
//transactRS232(self->controller,"\nDEBUG: Status reply is good!\n",30,pReply,79);
|
||
return 1; // 1 = no fault
|
||
}
|
||
|
||
int LH45_Setup(pLH45 self, int iControl) /* Operations common to both Open and Config functions */
|
||
{
|
||
int iRet;
|
||
char pCommand[20];
|
||
//char pReply[132];
|
||
|
||
/* MJL enable RS232 debugging mode. */
|
||
//setRS232Debug(self->controller,1);
|
||
//printf("***RS232 debug mode enabled for LH45***\n");fflush(stdout);
|
||
|
||
/* switch to remote operation */
|
||
/* NOTE: The Julabo does not provide any response for 'out' commands,
|
||
so we just use writeRS232 not transactRS232 for these */
|
||
//printf("Issuing out_mode_05 1 command...");fflush(stdout);
|
||
sprintf(pCommand,"out_mode_05 1");
|
||
iRet = writeRS232(self->controller, pCommand,strlen(pCommand));
|
||
//printf("Response: '%s'\n",pReply);fflush(stdout);
|
||
if(iRet != 1)
|
||
{
|
||
return iRet;
|
||
}
|
||
/* if(pReply[0] == '-') // Probably an error response
|
||
{
|
||
strcpy(self->pAns,pReply);
|
||
return LH45__BADCOM;
|
||
} */
|
||
|
||
/* Check the LH45 operating status */
|
||
if ((iRet=LH45_Check_Status(self))!=1)
|
||
return iRet;
|
||
|
||
/* Set heater sensor */
|
||
/* For the LH45 there is a choice of internal or external sensor control,
|
||
set internal when iControl==1 and external when iControl==2 */
|
||
sprintf(pCommand,"out_mode_04 %1.1d",self->iControl - 1);
|
||
iRet = writeRS232(self->controller, pCommand,strlen(pCommand));
|
||
if(iRet != 1)
|
||
{
|
||
return LH45__BADCOM;
|
||
}
|
||
/* else if(pReply[0] == '-') // Probably an error response
|
||
{
|
||
strcpy(self->pAns,pReply);
|
||
return LH45__BADCOM;
|
||
} */
|
||
self->iControl=iControl; // Store control sensor setting since it was assigned to the LH45 successfully
|
||
|
||
/* reset timeout - currently not being used */
|
||
/* iRet = SerialConfig(&self->pData, 10);
|
||
if(iRet != 1)
|
||
{
|
||
return iRet;
|
||
} */
|
||
|
||
/* Check the LH45 operating status one last time */
|
||
if ((iRet=LH45_Check_Status(self))!=1)
|
||
return iRet;
|
||
|
||
return 1; /* Success */
|
||
}
|
||
|
||
int LH45_Open(pLH45 *pData, char *pRS232, int iSensor, int iCTRL, int iMode)
|
||
{
|
||
pLH45 self = NULL;
|
||
|
||
self = (pLH45)malloc(sizeof(LH45));
|
||
if(self == NULL)
|
||
{
|
||
return LH45__BADMALLOC;
|
||
}
|
||
*pData = self;
|
||
self->iControl = iCTRL;
|
||
self->iRead = iSensor;
|
||
self->iReadOnly = iMode;
|
||
|
||
/* The LH45 doesn't require divisors or multipliers
|
||
and they are always forced to 1.0 */
|
||
self->fDiv = 1.0;
|
||
self->fMult = 1.0;
|
||
|
||
self->controller = NULL;
|
||
|
||
self->controller = (prs232)FindCommandData(pServ->pSics,pRS232,
|
||
"RS232 Controller");
|
||
if(!self->controller){
|
||
/*SCWrite(pCon,"ERROR: motor controller not found",eError); */
|
||
return LH45__BADCOM;
|
||
}
|
||
|
||
if(!self->iReadOnly)
|
||
return LH45_Setup(self, self->iControl);
|
||
}
|
||
/*--------------------------------------------------------------------------*/
|
||
void LH45_Close(pLH45 *pData)
|
||
{
|
||
int iRet;
|
||
//char pReply[132];
|
||
char pCommand[20];
|
||
pLH45 self;
|
||
|
||
self = *pData;
|
||
if (!self)
|
||
return; // Just in case
|
||
|
||
/* switch off remote operation */
|
||
/* Not sure if this is really necessary but do it just in case */
|
||
sprintf(pCommand,"out_mode_05 0");
|
||
iRet = writeRS232(self->controller, pCommand,strlen(pCommand));
|
||
/* Don't bother checking the status but record any error reply */
|
||
/* if(pReply[0] == '-') // Probably an error response
|
||
strcpy(self->pAns,pReply); */
|
||
|
||
return;
|
||
}
|
||
/*--------------------------------------------------------------------------*/
|
||
int LH45_Config(pLH45 *pData, int iTmo, int iRead, int iControl,
|
||
float fDiv,float fMult)
|
||
{
|
||
pLH45 self;
|
||
|
||
self = *pData;
|
||
|
||
return LH45_Setup(self, iControl);
|
||
}
|
||
/*--------------------------------------------------------------------------*/
|
||
int LH45_Send(pLH45 *pData, char *pCommand, char *pReply, int iLen)
|
||
{
|
||
int iRet;
|
||
pLH45 self;
|
||
|
||
self = *pData;
|
||
|
||
/* make sure, that there is a \r at the end of the command */
|
||
/* if(strchr(pCommand,(int)'\r') == NULL)
|
||
{
|
||
strcat(pCommand,"\r");
|
||
} */
|
||
|
||
/* Send command direct to the LH45 */
|
||
/* Because the Julabo LH45 only provides a response for the 'version', 'status'
|
||
and 'in' commands and not for the 'out' commands, just perform a write
|
||
for those and not a full transaction. */
|
||
if (strncmp(pCommand,"out",3)==0) // 'out' command; LH45 does not send any response.
|
||
{
|
||
iRet=writeRS232(self->controller,pCommand,strlen(pCommand));
|
||
*pReply='\0';
|
||
}
|
||
else
|
||
iRet=transactRS232(self->controller,pCommand,strlen(pCommand),pReply,iLen);
|
||
if(iRet <= 0)
|
||
return iRet;
|
||
|
||
/* Check the LH45 operating status after issuing the command, and return */
|
||
iRet=LH45_Check_Status(self);
|
||
return iRet;
|
||
}
|
||
/*--------------------------------------------------------------------------*/
|
||
int LH45_Read(pLH45 *pData, float *fVal)
|
||
{
|
||
char pCommand[20], pReply[132];
|
||
int iRet;
|
||
float fRead = -9999999.;
|
||
pLH45 self;
|
||
|
||
self = *pData;
|
||
|
||
|
||
/* for the LH45 there are three temp measurements available */
|
||
switch(self->iRead)
|
||
{
|
||
case 1:
|
||
sprintf(pCommand,"in_pv_00");
|
||
break;
|
||
case 2:
|
||
sprintf(pCommand,"in_pv_02");
|
||
break;
|
||
case 3:
|
||
sprintf(pCommand,"in_pv_03");
|
||
break;
|
||
default:
|
||
return LH45__BADPAR; // But shouldn't happen
|
||
}
|
||
|
||
iRet = transactRS232(self->controller, pCommand,strlen(pCommand),
|
||
pReply,79);
|
||
|
||
if(iRet <= 0)
|
||
{
|
||
return iRet;
|
||
}
|
||
if(pReply[0] == '-'&&strlen(pReply)>7) // Not a number (-XXX.X\r), probably an error response
|
||
{
|
||
strcpy(self->pAns,pReply);
|
||
return LH45__BADCOM;
|
||
}
|
||
|
||
iRet = sscanf(pReply,"%f",&fRead);
|
||
if(iRet != 1)
|
||
{
|
||
return LH45__BADREAD;
|
||
}
|
||
|
||
*fVal = fRead;
|
||
|
||
/* Check the LH45 operating status after the read, and return */
|
||
iRet=LH45_Check_Status(self);
|
||
return iRet;
|
||
}
|
||
/*-------------------------------------------------------------------------*/
|
||
int LH45_Set(pLH45 *pData, float fVal)
|
||
{
|
||
char pCommand[20], pCommandRead[20], pReply[132];
|
||
int iRet, i;
|
||
const float fPrecision = 0.1;
|
||
float fDelta, fRead;
|
||
pLH45 self;
|
||
|
||
self = *pData;
|
||
|
||
if(self->iReadOnly)
|
||
{
|
||
return LH45__READONLY;
|
||
}
|
||
|
||
sprintf(pCommand,"out_sp_00 %1.1f",fVal);
|
||
sprintf(pCommandRead,"in_sp_00"); // To read back and check the set value
|
||
|
||
/* try three times: send, read, test, if OK return, else resend. */
|
||
for(i = 0; i < 3; i++)
|
||
{
|
||
/* send command, since it's an 'out' we don't get any response, so use writeRS232 */
|
||
iRet = writeRS232(self->controller,pCommand,strlen(pCommand));
|
||
//writeRS232(self->controller,pReply,strlen(pReply)); // MJL DEBUG
|
||
if(iRet != 1)
|
||
{
|
||
return iRet;
|
||
}
|
||
/* if(pReply[0] == '-') // Probably an error response
|
||
{
|
||
strcpy(self->pAns,pReply);
|
||
return LH45__BADCOM;
|
||
} */
|
||
/* read the set value again using the 'in' command */
|
||
iRet = transactRS232(self->controller,pCommandRead,strlen(pCommandRead),pReply,131);
|
||
//writeRS232(self->controller,pReply,strlen(pReply)); // MJL DEBUG
|
||
if(iRet <= 0)
|
||
{
|
||
return iRet;
|
||
}
|
||
if(pReply[0] == '-'&&strlen(pReply)>7) // Not a number (-XXX.X\r), probably an error response
|
||
{
|
||
strcpy(self->pAns,pReply);
|
||
return LH45__BADCOM;
|
||
}
|
||
/* Convert the value read back. */
|
||
iRet=sscanf(pReply,"%f",&fRead);
|
||
if(iRet != 1)
|
||
{
|
||
return LH45__BADREAD;
|
||
}
|
||
/* check the value read back */
|
||
fDelta = fRead - fVal;
|
||
if(fDelta < 0)
|
||
fDelta = -fDelta;
|
||
if(fDelta < fPrecision)
|
||
{
|
||
/* Success, but check the LH45 operating status afterwards, and return */
|
||
/* Don't bother to repeat the write if we get an error here as it would indicate
|
||
a fault or overload in the LH45, not a comms problem */
|
||
iRet=LH45_Check_Status(self);
|
||
return iRet;
|
||
}
|
||
}
|
||
return LH45__BADSET;
|
||
}
|
||
/*-------------------------------------------------------------------------*/
|
||
void LH45_ErrorTxt(pLH45 *pData,int iCode, char *pError, int iLen)
|
||
{
|
||
char pBueffel[512];
|
||
pLH45 self;
|
||
|
||
self = *pData;
|
||
|
||
switch(iCode)
|
||
{
|
||
case LH45__BADCOM:
|
||
sprintf(pBueffel,"LH45: Invalid command or offline, got %s",
|
||
self->pAns);
|
||
strncpy(pError,pBueffel,iLen);
|
||
break;
|
||
case LH45__BADPAR:
|
||
strncpy(pError,"LH45: Invalid parameter specified",iLen);
|
||
break;
|
||
case LH45__BADMALLOC:
|
||
strncpy(pError,"LH45: Error allocating memory in LH45",iLen);
|
||
break;
|
||
case LH45__BADREAD:
|
||
strncpy(pError,"LH45: Badly formatted answer",iLen);
|
||
break;
|
||
case LH45__BADSET:
|
||
strncpy(pError,"LH45: Failed three times to write new set value to LH45",iLen);
|
||
break;
|
||
case LH45__FAULT: // Covers various LH45 self-diagnosed fault conditions
|
||
sprintf(pBueffel,"LH45: Internal fault condition detected: %s",self->pAns);
|
||
strncpy(pError,pBueffel,iLen);
|
||
break;
|
||
default:
|
||
SerialError(iCode, pError,iLen);
|
||
break;
|
||
}
|
||
}
|