/*-------------------------------------------------------------------------- L A K E S H O R E 3 4 0 U T I L A few utility functions for dealing with a LAKESHORE340 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 #include #include #include #include #include #include #include #include #include "lakeshore340util.h" /* -------------------------------------------------------------------------*/ int LAKESHORE340_Check_Status(pLAKESHORE340 self) /* Can be called to check for correct operation of the LAKESHORE340 */ { int iRet, iRetry, busy, notbusy; char pCommand[20]; char pReply[132]; /* Check the busy status. */ /* While busy, wait but not too long - set an upper limit of about 100 queries, */ /* should translate to about 1 or 2 seconds which is enough time for any commands */ /* to complete. Since we don't issue any time-consuming commands, this shouldn't */ /* be necessary anyway. */ /* Register a comms failure if a not-busy response isn't able to be received. */ iRetry=0; printf("Checking status..."); do { sprintf(pCommand,"BUSY?"); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=transactRS232(self->controller,pCommand,strlen(pCommand),pReply,79))<=0) { printf("Comms error!\n"); return iRet; // Comms problem } busy=(strncmp(pReply,"1",15)==0); notbusy=(strncmp(pReply,"0",15)==0); if (notbusy) { printf("Status OK.\n"); return 1; // Lakeshore 340 is ready to accept command } } while((++iRetry<100)&&busy); /* If we fell out of the loop, the Lakeshore 340 is either still busy */ /* or some bad response was received, log the response. */ sprintf(self->pAns,"BUSY response=%s",pReply); printf("Busy or bad response received!\n"); return LAKESHORE340__BADREAD; } int LAKESHORE340_ConfigureAndQueryGen(pLAKESHORE340 self, char *command, char *configandqueryparameters, char *configonlyparameters,char *diagnosis) /* Issue a command to the Lakeshore 340, if this works, */ /* issue the corresponding query and check the result is correct. */ /* Log and return any errors. */ { int iRet; char pCommand[20]; char pReply[132]; /* Issue a command to the Lakeshore 340. */ if (configandqueryparameters&&*configandqueryparameters) sprintf(pCommand,"%s %s,%s",command,configandqueryparameters,configonlyparameters); else sprintf(pCommand,"%s %s",command,configonlyparameters); printf("Issuing command: '%s'...\n",pCommand); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=writeRS232(self->controller,pCommand,strlen(pCommand)))!=1) return iRet; /* Issue the query corresponding to the command, */ /* then check the parameters match what was set. */ if (configandqueryparameters&&*configandqueryparameters) sprintf(pCommand,"%s? %s",command,configandqueryparameters); else sprintf(pCommand,"%s?",command); printf("Issuing query: '%s'...\n",pCommand); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=transactRS232(self->controller,pCommand,strlen(pCommand),pReply,79))<=0) { printf("transactRS232 error! Code=%d.\n",iRet); printf("DEBUG: pReply='%s' len=%d configonlyparameters='%s' len=%d\n", pReply,strlen(pReply),configonlyparameters,strlen(configonlyparameters)); return iRet; } /* Query should return the same data parameters set in the configuration (i.e. the configonlyparameters), the configandqueryparameters are used to index objects and are common to both configuration and query. */ printf("DEBUG: pReply='%s' len=%d configonlyparameters='%s' len=%d\n", pReply,strlen(pReply),configonlyparameters,strlen(configonlyparameters)); if (strcmp(pReply,configonlyparameters)!=0) { printf("Response was bad.\n"); if (diagnosis&&*diagnosis) sprintf(self->pAns,"%s response=%s (%s.)",command,pReply,diagnosis); else sprintf(self->pAns,"%s response=%s",command,pReply); return LAKESHORE340__BADREAD; } printf("Response was good.\n"); return 1; } int LAKESHORE340_ConfigureAndQuery(pLAKESHORE340 self, char *command, char *parameters, char *diagnosis) /* Use for config/query transactions that don't require index parameters in the query. */ { return LAKESHORE340_ConfigureAndQueryGen(self,command,"",parameters,diagnosis); } int LAKESHORE340_SetControl(pLAKESHORE340 self, int iControl) { /* Attempt to set the sensor used for temperature control. */ /* Only use control loop #1 to do the control, use Kelvin units, */ /* and leave control enabled at next power-on. */ if (iControl==1) return LAKESHORE340_ConfigureAndQueryGen(self,"CSET","1","A,1,1,1",""); else if (iControl==2) return LAKESHORE340_ConfigureAndQueryGen(self,"CSET","1","B,1,1,1",""); else if (iControl==3) return LAKESHORE340_ConfigureAndQueryGen(self,"CSET","1","C,1,1,1",""); else if (iControl==4) return LAKESHORE340_ConfigureAndQueryGen(self,"CSET","1","D,1,1,1",""); else return LAKESHORE340__BADPAR; } int LAKESHORE340_Setup(pLAKESHORE340 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 LAKESHORE340***\n");fflush(stdout); /* Setup the comms port (the baudrate etc. are probably just academic since */ /* they must be correct if we have established comms, but the termination */ /* character setting is relevant - use just \r. */ if ((iRet=LAKESHORE340_ConfigureAndQuery(self,"COMM","3,5,2","bad comms setup"))!=1) return iRet; /* Reset the controller to power-on state. */ sprintf(pCommand,"*RST"); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=writeRS232(self->controller, pCommand,strlen(pCommand)))!=1) return iRet; /* Clear the interface status (and buffer, but that's irrelevant) */ /* just in case we want to use the internal status flags later on. */ /* Actually, we do *RST above, probably this isn't necessary. */ sprintf(pCommand,"*CLS"); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=writeRS232(self->controller, pCommand,strlen(pCommand)))!=1) return iRet; /* Check the LAKESHORE340 status */ /* Basically this just makes sure comms is up. */ if ((iRet=LAKESHORE340_Check_Status(self))!=1) return iRet; /* Check the POST status, it should be 0 and not 1. */ sprintf(pCommand,"*TST?"); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=transactRS232(self->controller,pCommand,strlen(pCommand),pReply,79))<=0) return iRet; if (strcmp(pReply,"0")!=0) { if (strcmp(pReply,"1")==0) { strcpy(self->pAns,"*TST=1 - POST failure, Lakeshore 340 is faulty."); return LAKESHORE340__FAULT; } else // Unexpected response from *TST? query. { sprintf(self->pAns,"*TST response=%s",pReply); return LAKESHORE340__BADREAD; } } /* Check that the controller is a gen-new-wine Lakeshore 340 */ /* There's also the *REV command to check the firmware revision, but */ /* that would be going too far ;) */ sprintf(pCommand,"*IDN?"); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=transactRS232(self->controller,pCommand,strlen(pCommand),pReply,79))<=0) return iRet; if (strncmp(pReply,"LSCI,MODEL340",13)!=0) { strcpy(self->pAns,pReply); return LAKESHORE340__NOLAKESHORE340; } /* Switch to remote operation - but leave the keypad unlocked */ if ((iRet=LAKESHORE340_ConfigureAndQuery(self,"MODE","2",""))!=1) return iRet; /* Make sure the keypad isn't locked out via the LOCK setting too */ if ((iRet=LAKESHORE340_ConfigureAndQuery(self,"LOCK","0,000",""))!=1) return iRet; /* Clear any alarms. */ sprintf(pCommand,"ALMRST"); usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=writeRS232(self->controller, pCommand,strlen(pCommand)))!=1) return iRet; /* Set up the front panel display on the Lakeshore 340. */ /* Set 2 output fields, which we will use to display the set temp */ /* and the actual temp at the controlling sensor selected. */ /* Note: use 4,060,1 setting rather than just 4,60 so that the response */ /* matches exactly and we can use LAKESHORE340_ConfigureAndQuery ;) */ if ((iRet=LAKESHORE340_ConfigureAndQuery(self,"DISPLAY","4,060,1",""))!=1) return iRet; /* Set up the 4 fields on the front panel display to show both sensor temps. */ if ((iRet=LAKESHORE340_ConfigureAndQueryGen(self,"DISPFLD","1","A,1",""))!=1) return iRet; if ((iRet=LAKESHORE340_ConfigureAndQueryGen(self,"DISPFLD","2","B,1",""))!=1) return iRet; if ((iRet=LAKESHORE340_ConfigureAndQueryGen(self,"DISPFLD","3","C,1",""))!=1) return iRet; if ((iRet=LAKESHORE340_ConfigureAndQueryGen(self,"DISPFLD","4","D,1",""))!=1) return iRet; /* Set the sensor used for controlling temperature. */ /* Sets sensor A when iControl==1 and sensor B when iControl==2. */ if ((LAKESHORE340_SetControl(self,iControl))!=1) return iRet; /* Turn off the heater as a precaution. */ /* The heater gets turned on when we do any set operation. */ /* Also the heater will be turned off if SICS is shut down properly. */ if ((iRet=LAKESHORE340_ConfigureAndQuery(self,"RANGE","0",""))!=1) return iRet; /* Don't bother setting the controller's date & time (not easy to code and probably wouldn't be useful). */ /* Check the LAKESHORE340 operating status one last time */ if ((iRet=LAKESHORE340_Check_Status(self))!=1) return iRet; return 1; /* Success */ } int LAKESHORE340_Open(pLAKESHORE340 *pData, char *pRS232, int iSensor, int iCTRL, int iMode) { pLAKESHORE340 self = NULL; self = (pLAKESHORE340)malloc(sizeof(LAKESHORE340)); if(self == NULL) { return LAKESHORE340__BADMALLOC; } *pData = self; self->iControl = iCTRL; self->iRead = iSensor; self->iReadOnly = iMode; /* The LAKESHORE340 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 LAKESHORE340__BADCOM; } if(!self->iReadOnly) return LAKESHORE340_Setup(self, self->iControl); return 1; } /*--------------------------------------------------------------------------*/ void LAKESHORE340_Close(pLAKESHORE340 *pData) { pLAKESHORE340 self; self = *pData; if (!self) return; // Just in case /* Try to turn off the heater as a precaution. */ LAKESHORE340_ConfigureAndQuery(self,"RANGE","0",""); /* switch off remote operation */ /* Not sure if this is really necessary but do it just in case */ LAKESHORE340_ConfigureAndQuery(self,"MODE","1",""); return; } /*--------------------------------------------------------------------------*/ int LAKESHORE340_Config(pLAKESHORE340 *pData, int iTmo, int iRead, int iControl, float fDiv,float fMult) { pLAKESHORE340 self; self = *pData; return LAKESHORE340_Setup(self, iControl); } /* --------------------------------------------------------------------------*/ int LAKESHORE340_Send(pLAKESHORE340 *pData, char *pCommand, char *pReply, int iLen) { int iRet,i,commandlen,isquery; pLAKESHORE340 self; self = *pData; /* Send command direct to the LAKESHORE340 */ /* Because the LAKESHORE340 only provides a response for query commands (those ending in a '?'), just perform a write for others and not a full transaction. */ commandlen=strlen(pCommand); isquery=0; for(i=0;icontroller,pCommand,commandlen); *pReply='\0'; } else // LAKESHORE340 will send a response. { usleep(100000); // Required to meet Lakeshore340 spec. iRet=transactRS232(self->controller,pCommand,commandlen,pReply,iLen); } /* Check the LAKESHORE340 operating status after issuing the command, if it was successful */ if (iRet>=1) iRet=LAKESHORE340_Check_Status(self); return iRet; } /*--------------------------------------------------------------------------*/ int LAKESHORE340_Read(pLAKESHORE340 *pData, float *fVal) { char pCommand[20], pReply[132]; int iRet; float fRead = -9999999.; pLAKESHORE340 self; self = *pData; /* for the LAKESHORE340 there are three temp measurements available */ /* as 'A', 'B', 'C' and 'D'. (This is a bit different from the manual) */ /* We use the Kelvin readings (presumably the cryogenic 'furnace' */ /* operates at low temperatures, so it probably makes more sense) */ switch(self->iRead) { case 1: sprintf(pCommand,"KRDG? A"); break; case 2: sprintf(pCommand,"KRDG? B"); break; case 3: sprintf(pCommand,"KRDG? C"); break; case 4: sprintf(pCommand,"KRDG? D"); break; default: return LAKESHORE340__BADPAR; // But shouldn't happen } usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=transactRS232(self->controller,pCommand,strlen(pCommand),pReply,79))<=0) return iRet; iRet = sscanf(pReply,"%g",&fRead); // KRDG returns free-format exponentiated value if(iRet != 1) // Not a number, probably an error response { return LAKESHORE340__BADREAD; } *fVal = fRead; /* Check the LAKESHORE340 operating status after the read, and return */ iRet=LAKESHORE340_Check_Status(self); return iRet; } /* -------------------------------------------------------------------------*/ int LAKESHORE340_Set(pLAKESHORE340 *pData, float fVal) { char pCommand[20], pCommandRead[20], pReply[132]; int iRet, i; const float fPrecision = 0.1; float fDelta, fRead; pLAKESHORE340 self; self = *pData; if(self->iReadOnly) { return LAKESHORE340__READONLY; } /* Note we are using control loop #1 only for temperature control. */ sprintf(pCommand,"SETP 1,%1.1f",fVal); sprintf(pCommandRead,"SETP? 1"); // To read back and check the set value /* try three times: send, read, test, if OK return, else resend. */ /* MJL doesn't think this is necessary... left over from itc4 */ for(i = 0; i < 3; i++) { /* send SETP command, we don't get any response so use writeRS232 */ usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=writeRS232(self->controller,pCommand,strlen(pCommand)))!=1) return iRet; /* read the set value again using the SETP? command */ usleep(100000); // Required to meet Lakeshore340 spec. if ((iRet=transactRS232(self->controller,pCommandRead,strlen(pCommandRead),pReply,131))<=0) return iRet; printf("SETP: Response %d characters: '%s'\n",iRet,pReply); if(pReply[0] == '-'&&strlen(pReply)>7) { strcpy(self->pAns,pReply); return LAKESHORE340__BADCOM; } /* Convert the value read back. */ /* Note SETP? will return free-format exponentiated number, so use %g. */ if(sscanf(pReply,"%g",&fRead)!=1) return LAKESHORE340__BADREAD; printf(" Parsed response OK, value=%g\n",fRead); /* check the value read back */ printf(" Setpoint=%g actual=%g\n",fVal,fRead); fDelta = fRead - fVal; if(fDelta < 0) fDelta = -fDelta; printf(" delta=%g precision=%g\n",fDelta,fPrecision); if(fDelta < fPrecision) { /* Turn on the heater. (but it will already be on if there has been a previous set) */ /* Don't know what range is appropriate, but for the time being set to 50W */ /* which is setting 5 (maximum range). */ if ((iRet=LAKESHORE340_ConfigureAndQuery(self,"RANGE","5",""))!=1) return iRet; /* Success, but check the LAKESHORE340 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 LAKESHORE340, not a comms problem */ printf("SETP OK, checking status and returning.\n"); iRet=LAKESHORE340_Check_Status(self); return iRet; } } printf("SETP failed!\n"); return LAKESHORE340__BADSET; } /* -------------------------------------------------------------------------*/ void LAKESHORE340_ErrorTxt(pLAKESHORE340 *pData,int iCode, char *pError, int iLen) { char pBueffel[512]; pLAKESHORE340 self; self = *pData; switch(iCode) { case LAKESHORE340__BADCOM: sprintf(pBueffel,"LAKESHORE340: Invalid command or offline, got %s", self->pAns); strncpy(pError,pBueffel,iLen); break; case LAKESHORE340__BADPAR: strncpy(pError,"LAKESHORE340: Invalid parameter specified",iLen); break; case LAKESHORE340__BADMALLOC: strncpy(pError,"LAKESHORE340: Error allocating memory in LAKESHORE340",iLen); break; case LAKESHORE340__BADREAD: strncpy(pError,"LAKESHORE340: Badly formatted answer",iLen); break; case LAKESHORE340__BADSET: strncpy(pError,"LAKESHORE340: Failed three times to write new set value to LAKESHORE340",iLen); break; case LAKESHORE340__FAULT: // Covers various LAKESHORE340 self-diagnosed fault conditions sprintf(pBueffel,"LAKESHORE340: Internal fault condition detected: %s",self->pAns); strncpy(pError,pBueffel,iLen); break; case LAKESHORE340__NOLAKESHORE340: sprintf(pBueffel,"LAKESHORE340: Wrong model number (driver is for Model 340 only): %s",self->pAns); strncpy(pError,pBueffel,iLen); break; default: getRS232Error(iCode, pError,iLen); break; } }