diff --git a/euro2kdriv.c b/euro2kdriv.c new file mode 100644 index 0000000..b85386a --- /dev/null +++ b/euro2kdriv.c @@ -0,0 +1,147 @@ +/*--------------------------------------------------------------------------- +euro2kdriv.c + +Driver for the EuroTherm 2000 controllers (ModBus protocoll) + +Markus Zolliker, August 2005 +----------------------------------------------------------------------------*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "modbus.h" +#include "fsm.h" +#include "initializer.h" + +typedef struct { + EaseDriv d; + float temperature; + float output; + float setpoint; + int adr; + float par; +} Euro2k; + +static ParClass euro2kClass = { "EURO2K", sizeof(Euro2k) }; + +/*----------------------------------------------------------------------------*/ +void Euro2kParDef(void *object) { + Euro2k *drv = ParCast(&euro2kClass, object); + + ParName(""); ParTail("C"); + ParFloat(&drv->temperature, PAR_NAN); + + ParName("output"); ParTail("%"); + ParFloat(&drv->output, PAR_NAN); + + ParName("setpoint"); ParTail("C"); + ParFloat(&drv->setpoint, PAR_NAN); + + ParName("adr"); ParAccess(usUser); ParList(NULL); + ParInt(&drv->adr, 0); + + if (drv->adr > 0) { + ParName("par"); ParList(NULL); + ParFloat(&drv->par, PAR_NAN); + } + + EaseBasePar(drv); + EaseDrivPar(drv, "%#.5g", "C"); + ParStdDef(); + EaseMsgPar(drv); +} +/*----------------------------------------------------------------------------*/ +static long Euro2kRead(long pc, void *object) { + Euro2k *drv = ParCast(&euro2kClass, object); + EaseBase *eab = object; + char *p; + int l; + char buf[4]; + + switch (pc) { default: /* FSM BEGIN *******************************/ + ModBusRequestFloats(eab, 1, 3); + return __LINE__; case __LINE__: /**********************************/ + drv->temperature = ModBusGetFloat(eab, 1); + drv->setpoint = ModBusGetFloat(eab, 2); + drv->output = ModBusGetFloat(eab, 3); + + if (drv->adr == 0) goto noPar; + ModBusRequestFloats(eab, drv->adr, 3); + return __LINE__; case __LINE__: /**********************************/ + drv->par = ModBusGetFloat(eab, drv->adr); + noPar: + + ParLog(drv); + fsm_quit: return 0; } /* FSM END *********************************/ +} +/*----------------------------------------------------------------------------*/ +static long Euro2kSet(long pc, void *object) { + Euro2k *drv = ParCast(&euro2kClass, object); + EaseBase *eab = object; + char *p; + int l; + char buf[4]; + + switch (pc) { default: /* FSM BEGIN *******************************/ + ModBusPutFloats(eab, 2, 1, &drv->d.targetValue); + return __LINE__; case __LINE__: /**********************************/ + + fsm_quit: return 0; } /* FSM END *********************************/ +} +/*----------------------------------------------------------------------------*/ +static long Euro2kStart(long pc, void *object) { + Euro2k *drv = ParCast(&euro2kClass, object); + EaseBase *eab = object; + + switch (pc) { default: /* FSM BEGIN *******************************/ + ModBusRequestFloats(eab, 1, 1); + return __LINE__; case __LINE__: /**********************************/ + if (0 == ModBusGetFloat(eab, 1)) { + ParPrintf(drv, eError, "bad or no response on ModBus"); + goto quit; + } + ParPrintf(drv, eStatus, "connected to euro2k"); + FsmCall(Euro2kRead); + return __LINE__; case __LINE__: /**********************************/ + + quit: + return 0; } /* FSM END ********************************************/ +} +/*----------------------------------------------------------------------------*/ +static int Euro2kInit(SConnection *con, int argc, char *argv[], int dynamic) { + /* args: + MakeObject objectname euro2k + + */ + Euro2k *drv; + + drv = EaseMakeDriv(con, &euro2kClass, argc, argv, dynamic, 7, + Euro2kParDef, ModBusHandler, Euro2kStart, NULL, Euro2kRead, + Euro2kSet); + if (drv == NULL) return 0; + setRS232ReplyTerminator(drv->d.b.ser,""); + setRS232SendTerminator(drv->d.b.ser,""); + return 1; +} +/*----------------------------------------------------------------------------*/ +void Euro2kStartup(void) { + ParMakeClass(&euro2kClass, EaseDrivClass()); + MakeDriver("EURO2K", Euro2kInit, 0); +} diff --git a/modbus.c b/modbus.c new file mode 100644 index 0000000..564f551 --- /dev/null +++ b/modbus.c @@ -0,0 +1,297 @@ +/*--------------------------------------------------------------------------- +modbus.c + +Communication routines for Eurotherm ModBus instruments + +Markus Zolliker, Aug 2005 +---------------------------------------------------------------------------- + +there is no error return value, eab->errCode is used. On success, eab->errCode +is not changed, i.e. an existing errCode is not overwritten. +*/ + +#include +#include +#include +#include +#include +#include +#include "sics.h" +#include "modbus.h" + + +/*-------------------------------------------------------------------------*/ +static void double2ieee(double input, char ieee[4]) { + +/* convert double to IEEE 32 bit floating number (denormalized numbers are considered as zero) */ + + long mantissa; + int exponent; + + if (input == 0) { + ieee[0]= 0; + ieee[1]= 0; + ieee[2]= 0; + ieee[3]= 0; + } else { + mantissa = 0x1000000 * (frexp(fabs(input), &exponent)); + exponent = exponent - 1 + 127; + if (exponent < 0) { + exponent = 0; + } else if (exponent > 0xFE) { + exponent = 0xFE; + } + if (input < 0) { + ieee[0] = 0x80 | (exponent >> 1); + } else { + ieee[0] = exponent >> 1; + } + ieee[1] = (exponent & 1) << 7 | ((mantissa & 0x7F0000) >> 16); + ieee[2] = (mantissa & 0xFF00) >> 8; + ieee[3] = mantissa & 0xFF; + } + return; +} + +/*-------------------------------------------------------------------------*/ +static double ieee2double(char ieee[4]) { + +/* IEEE 32 bit floating number to double (denormalized numbers are considered as zero) */ + + long mantissa; + double output; + int exponent; + + mantissa = ((ieee[1] << 16) & 0x7FFFFF) + | ((ieee[2] << 8) & 0xFF00) + | ((ieee[3] ) & 0xFF); + + exponent = (ieee[0] & 0x7F) * 2 + ((ieee[1] >> 7) & 1); /* raw exponent */ + if (exponent == 0 && mantissa == 0) { + return 0.0; + } + output = ldexp(mantissa, -23) + 1.0; + if (ieee[0] & 0x80) { + output = -output; + } + return output * ldexp(1, exponent - 127); +} +/*----------------------------------------------------------------------------*/ +static void uint2word(unsigned int adr, char word[2]) { + unsigned char *uword; + uword = (void *)word; + uword[0] = adr / 256; + uword[1] = adr % 256; +} +/*----------------------------------------------------------------------------*/ +static unsigned int word2uint(char word[2]) { + unsigned char *uword; + uword = (void *)word; + return uword[0]*256 + uword[1]; +} +/*----------------------------------------------------------------------------*/ +static void fadr2word(int adr, char word[2]) { + unsigned char *uword; + uword = (void *)word; + uword[0] = (adr*2) / 256 + 0x80; + uword[1] = (adr*2) % 256; +} +/*----------------------------------------------------------------------------*/ +static int word2fadr(char word[2]) { + unsigned char *uword; + uword = (void *)word; + return ((uword[0] & 0x7F)*256 + uword[1]) / 2; +} +/*----------------------------------------------------------------------------*/ +static int calc_crc(char *inp, int inpLen, int addCrc) { + +/* CRC runs cyclic Redundancy Check Algorithm on input inp */ +/* Returns value of 16 bit CRC after completion and */ +/* always adds 2 crc bytes to message */ +/* returns 0 if incoming message has correct CRC */ + + unsigned int crc= 0xffff; + unsigned int next; + int carry; + int n; + + while (inpLen--) { + next = *(unsigned char *)inp; + crc ^= next; + for (n = 0; n < 8; n++) { + carry = crc & 1; + crc >>= 1; + if (carry) { + crc ^= 0xA001; + } + } + inp++; + } + if (addCrc) { + inp[0] = crc % 256; + inp[1] = crc / 256; + } + return crc; +} +/*----------------------------------------------------------------------------*/ +void ModBusDump(EaseBase *eab, int iout, char *fmt, char *buffer, int length) { + char buf[50]; + int i; + + if (length > 16) length = 16; + for (i=0; icmd)); + + if (eab->errCode || eab->state == EASE_expect) return; + while (availableRS232(eab->ser) == 1) { + l=sizeof(trash); + iret = readRS232TillTerm(eab->ser, trash, &l); + if (iret < 0) break; + ModBusDump(eab, -2, "trash %s\n", trash, l); + } + calc_crc(eab->cmd, length, 1); + iret = writeRS232(eab->ser, eab->cmd, length+2); + if (iret < 0) { + eab->errCode = iret; + return; + } + ModBusDump(eab, -2, "cmd: %s", eab->cmd, length+2); + eab->state = EASE_expect; + eab->cmdtime = time(NULL); +} +/*----------------------------------------------------------------------------*/ +int ModBusHandler(void *object) { + int iret, l; + EaseBase *eab = EaseBaseCast(object); + + if (eab->state < EASE_idle) goto quit; + if (availableNetRS232(eab->ser) || availableRS232(eab->ser)) { + eab->msg[0] = '\0'; + l = 5; + iret = readRS232TillTerm(eab->ser, eab->ans, &l); + if (eab->state != EASE_expect && eab->state != EASE_lost) { + if (iret == 1) { + ModBusDump(eab, eError, "unexpected answer %s", eab->ans, l); + } + goto quit; + } + if (iret == 1) { + if (eab->cmd[0] != eab->ans[0] || eab->cmd[1] != (eab->ans[1] & 0x7F)) { + iret = EASE_ILL_ANS; + } + if (eab->ans[1] & 0x80) { + iret = EASE_ILL_ANS; + } else if (eab->ans[1] == 16) { /* answer from write n words */ + l=3; /* finish response */ + iret = readRS232TillTerm(eab->ser, eab->ans+5, &l); + l+=5; + } else if (eab->ans[1] == 3) { /* answer for read n words */ + l = eab->ans[2]; /* bytes to read */ + iret = readRS232TillTerm(eab->ser, eab->ans+5, &l); + l+=5; + } else if (eab->ans[1] == 8) { /* loopback info */ + l=1; + iret = readRS232TillTerm(eab->ser, eab->ans+5, &l); + l+=5; + if (eab->state == EASE_lost) { + if (eab->ans[4] == 44 && eab->ans[5] == 55) { + eab->state = EASE_idle; + } + goto quit; + } + } else { + iret = EASE_ILL_ANS; + } + if (eab->state == EASE_lost) { + goto quit; + } + ModBusDump(eab, -2, "ans: %s", eab->ans, l); + if (iret == 1) { + if (calc_crc(eab->ans, l, 0)) { + iret = MODBUS_BAD_CRC; + } + } + } + if (iret != 1) { + eab->errCode = iret; + eab->state = EASE_idle; + goto error; + } + eab->state = EASE_read; + } else if (eab->state == EASE_expect) { + if (time(NULL) > eab->cmdtime+20) { + eab->state = EASE_lost; + } + } else if (eab->state == EASE_lost) { + if (time(NULL) > eab->cmdtime) { + eab->cmd[1] = 8; + eab->cmd[2] = 0; + eab->cmd[3] = 0; + eab->cmd[4] = 44; + eab->cmd[5] = 55; + ModBusWrite(eab, 6); + eab->state = EASE_lost; + } + } + goto quit; +error: + /* EaseWriteError(eab); */ +quit: + return EaseHandler(eab); +} +/*----------------------------------------------------------------------------*/ +void ModBusPutFloats(EaseBase *eab, int adr, int npar, float val[]) { + int l, n; + + assert(npar <= 5); + + eab->cmd[0] = 1; /* device address */ + eab->cmd[1] = 16; /* write n words */ + fadr2word(adr, eab->cmd + 2); + uint2word(npar*2, eab->cmd + 4); + eab->cmd[6] = npar*4; /* number of bytes */ + l = 7; + for (n=0; ncmd+l); + l += 4; + } + ModBusWrite(eab, l); +} +/*----------------------------------------------------------------------------*/ +void ModBusRequestFloats(EaseBase *eab, int adr, int npar) { + int l; + + assert(npar <= 14); + + eab->cmd[0] = 1; /* device address */ + eab->cmd[1] = 3; /* read n words */ + fadr2word(adr, eab->cmd + 2); + uint2word(npar*2, eab->cmd + 4); + ModBusWrite(eab, 6); +} +/*----------------------------------------------------------------------------*/ +float ModBusGetFloat(EaseBase *eab, int adr) { + int startAdr; + int i; + + if (eab->state != EASE_read) { + return 0.0; + } + startAdr = word2fadr(eab->cmd + 2); + i = adr - startAdr; + if (i < 0 || i >= word2uint(eab->cmd + 4)) { + return 0.0; + } + return ieee2double(eab->ans + 3 + i * 4); +} diff --git a/modbus.h b/modbus.h new file mode 100644 index 0000000..ff89a4a --- /dev/null +++ b/modbus.h @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------- +modbus.h + +Communication routines for Eurotherm ModBus instruments + +Markus Zolliker, Aug 2005 +----------------------------------------------------------------------------*/ + +#ifndef MODBUS_H +#define MODBUS_H + +#include "ease.h" + +#define MODBUS_BAD_CRC -3090 + +int ModBusHandler(void *eab); +void ModBusPutFloats(EaseBase *eab, int adr, int npar, float val[]); +void ModBusRequestFloats(EaseBase *eab, int adr, int npar); +float ModBusGetFloat(EaseBase *eab, int adr); + +#endif diff --git a/oxinst.c b/oxinst.c new file mode 100644 index 0000000..5e59bec --- /dev/null +++ b/oxinst.c @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------- +oxinst.c + +Communication routines for Oxford Instruments equipment + +Markus Zolliker, March 2005 +---------------------------------------------------------------------------- + +there is no error return value, eab->errCode is used. On success, eab->errCode +is not changed, i.e. an existing errCode is not overwritten. +*/ + +#include +#include +#include +#include +#include +#include "sics.h" +#include "oxinst.h" + + +/*----------------------------------------------------------------------------*/ +int OxiHandler(void *object) { + int iret, l; + EaseBase *eab = EaseBaseCast(object); + + if (eab->state < EASE_idle) goto quit; + if (availableNetRS232(eab->ser) || availableRS232(eab->ser)) { + eab->msg[0] = '\0'; + l = sizeof(eab->ans); + iret = readRS232TillTerm(eab->ser, eab->ans, &l); + if (eab->state != EASE_expect && eab->state != EASE_lost) { + if (iret == 1) { + ParPrintf(eab, eError, "unexpected answer: %s", eab->ans); + } + goto quit; + } + if (iret == 1) { + ParPrintf(eab, -2, "ans: %s", eab->ans); + if (strcmp(eab->ans, "??ck") == 0) { + if (eab->state == EASE_lost) { + EaseWrite(eab, "V"); + goto quit; + } + } else if (eab->state == EASE_lost) { + goto quit; + } else if (eab->cmd[0] == 'V') { + if (strcmp(eab->ans, eab->version) == 0) { + /* we are still connected with the same device */ + } else if (*eab->version == '\0') { + strncat(eab->version, eab->ans, sizeof(eab->version)-1); + } else { /* version (and therefore device) changed */ + eab->errCode = EASE_DEV_CHANGED; + eab->state = EASE_idle; + goto error; + } + eab->state = EASE_idle; + goto quit; + } else if (eab->cmd[2] == 'k') { /* ?ck */ + } else if (eab->cmd[0] != eab->ans[0]) { + iret = EASE_ILL_ANS; + } + } + if (iret != 1) { + eab->errCode = iret; + eab->state = EASE_idle; + goto error; + } + eab->state = EASE_read; + } else if (eab->state == EASE_expect) { + if (time(NULL) > eab->cmdtime+20) { + eab->state = EASE_lost; + } + } else if (eab->state == EASE_lost) { + if (time(NULL) > eab->cmdtime) { + EaseWrite(eab, "?ck"); + eab->state = EASE_lost; + } + } + goto quit; +error: + /* EaseWriteError(eab); */ +quit: + return EaseHandler(eab); +} + +double OxiGet(EaseBase *eab, int dig, int *pdig) { + char *endp, *p; + double val; + + if (eab->state != EASE_read) { + /* eab->errCode = EASE_ILL_ANS; */ + return 0.0; + } + p = strchr(eab->ans, '.'); + if (p) { + if (pdig != NULL) { + *pdig = strlen(eab->ans) - (p - eab->ans) - 1; + } + val=strtod(eab->ans+1, &endp); + if (*endp != '\0') { + eab->errCode = EASE_ILL_ANS; + return 0.0; + } + } else { + val=strtol(eab->ans+1, &endp, 10); + if (*endp != '\0') { + eab->errCode = EASE_ILL_ANS; + return 0.0; + } + if (eab->syntax <= 0) { /* old style format */ + for (; dig > 0; dig--) val=val*0.1; + } + } + return val; +} + +void OxiSet(EaseBase *eab, char *cmd, double val, int dig) { + char buf[64]; + int l; + + if (eab->syntax <= 0) { + snprintf(buf, sizeof(buf), "%s%.*f", cmd, dig, val); + } else { + snprintf(buf, sizeof(buf), "%s%f", cmd, val); + } + EaseWrite(eab, buf); +} + diff --git a/oxinst.h b/oxinst.h new file mode 100644 index 0000000..9e92428 --- /dev/null +++ b/oxinst.h @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------- +oxinst.h + +Communication routines for Oxford Instruments equipment + +Markus Zolliker, March 2005 +----------------------------------------------------------------------------*/ + +#ifndef OXINST_H +#define OXINST_H + +#include "ease.h" + +int OxiHandler(void *eab); +double OxiGet(EaseBase *eab, int dig, int *pdig); +void OxiSet(EaseBase *eab, char *cmd, double val, int dig); + + +#endif