/*--------------------------------------------------------------------------- ips2driv.c Driver for the Oxford Instruments IPS/PS magnet power supply. Version 2 (based on ease). Markus Zolliker, May 2005 ----------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "oxinst.h" #include "fsm.h" #include "initializer.h" typedef enum {NOTHING, SWITCH_OFF, RAMP_DOWN, RAMP_ALMOST, RAMP_ZERO, RAMPED_DOWN} DoThis; typedef struct { EaseDriv d; float current; /* current (in Tesla) */ float persfield; /* persistent field from IPS (in Tesla) */ float lastfield; /* persistent field from last drive */ float confirmfield; /* field confirmed in 1st step */ float ramp; /* actual ramp rate (Tesla/min) */ float maxlimit; /* hard field limit */ float perscurrent; /* current for persistent mode workaround (Tesla) */ float perslimit; /* field limit for activating persistent mode workaround (Tesla) */ float voltage; /* voltage */ float measured; /* measured current */ int persmode; /* 0: delayed persistant mode, 1: go to persistant mode, 2: leave switch on */ int persdelay; /* wait time for delayed persistant mode */ int perswitch; /* state of switch */ int remote; /* 0: local, 1: remote, do not check, 2: remote, check */ int nowait; /* 0: normal, 1: drive finishes immediately, ramp in background */ int perswait; /* wait time for persistent mode workaround (sec) */ int heaterFault; DoThis dothis; char *fmt; /* fmt for field */ int force; /* force = 2: put heater switch even when stored field does not match */ time_t swtim; /* time when last switching the heater */ time_t tim; /* outside IpsChangeField: time of last Field Change */ } Ips; static ParClass ipsClass = { "IPS", sizeof(Ips) }; static char *onOff[] = { "off", "on", NULL }; static char *persMode[] = { "auto", "on", "off", NULL }; /*----------------------------------------------------------------------------*/ static int IpsOk(Ips * drv) { float dif; if (drv->d.b.msg[0] != '\0') return 1; /* connection not yet confirmed */ if (drv->perswitch) return 1; if (drv->lastfield == PAR_NAN) { drv->lastfield = drv->persfield; } if (fabs(drv->persfield - drv->lastfield) < 1e-5) return 1; if (drv->force != 0) return 1; ParPrintf(drv, eWarning, "\nit is not sure which field is in the magnet\n" "value stored in power supply: %f\n" " in software: %f\n" "use command\n \n %s confirm ...\n \n" "to specify the persistent field\n \n", drv->persfield, drv->lastfield, drv->d.b.p.name); drv->force = 0; return 0; } /*----------------------------------------------------------------------------*/ static int IpsConfirm(void *object, void *userarg, int argc, char *argv[]) { Ips *drv = ParCast(&ipsClass, object); float fld; assert(drv); if (argc > 0) { if (argc > 1) { ParPrintf(object, eError, "Too many arguments"); return 0; } fld = atof(argv[0]); if (fld > drv->d.upperLimit || fld < drv->d.lowerLimit) { ParPrintf(object, eError, "Field outside limit"); return 0; } if (drv->perswitch) { ParPrintf(object, eWarning, "switch heater is on - field is %f", drv->current); return 0; } if (fabs(fld - drv->persfield) > 1e-5 && fabs(fld - drv->lastfield) > 1e-5) { ParPrintf(object, eWarning, "Be aware that this does neither match the field" " stored in software\nnor the field stored in power supply."); } if (drv->force && fld != drv->confirmfield) drv->force = 0; if (drv->force == 0) { ParPrintf(object, eWarning, "Please repeat this command, to confirm again" " the persistent field of\n %f Tesla.", fld); drv->confirmfield = fld; drv->force = 1; } else { drv->force = 2; drv->lastfield = fld; EaseParHasChanged(); ParPrintf(object, eValue, "%s confirm = %f", drv->d.b.p.name, fld); } } else { ParPrintf(object, eValue, "%s lastfield = %f", drv->d.b.p.name, drv->lastfield); } return 1; } /*----------------------------------------------------------------------------*/ void IpsParDef(void *object) { Ips *drv = ParCast(&ipsClass, object); ParName(""); ParFmt(drv->fmt); ParTail("Tesla"); ParFloat(&drv->persfield, PAR_NAN); ParName("persmode"); ParAccess(usUser); ParEnum(persMode); ParList(0); ParInt(&drv->persmode, 1); ParName("perswitch"); ParEnum(onOff); ParList(0); ParInt(&drv->perswitch, PAR_NAN); ParName("nowait"); ParAccess(usUser); ParEnum(onOff); ParList(0); ParInt(&drv->nowait, 0); ParName("maxlimit"); ParSave(1); ParFloat(&drv->maxlimit, 0.0); ParName("limit"); ParAccess(usUser); ParFmt(drv->fmt); ParTail("Tesla"); ParFloat(&drv->d.upperLimit, 0.0); if (ParActionIs(PAR_SET) > 0) { if (drv->maxlimit == 0) { /* first time: set maxlimit */ drv->maxlimit = drv->d.upperLimit; } else if (drv->d.upperLimit > drv->maxlimit) { drv->d.upperLimit = drv->maxlimit; ParPrintf(drv, eWarning, "limit is too high, set back to %.5g\n", drv->maxlimit); } drv->d.lowerLimit = -drv->d.upperLimit; } ParName("ramp"); ParAccess(usUser); ParFmt(drv->fmt); ParTail("Tesla/min"); ParFloat(&drv->ramp, 1.0); ParName("perscurrent"); ParAccess(usUser); ParFmt(drv->fmt); ParTail("Tesla"); ParFloat(&drv->perscurrent, 0.0); ParName("perslimit"); ParAccess(usUser); ParFmt(drv->fmt); ParTail("Tesla"); ParFloat(&drv->perslimit, 10.0); ParName("perswait"); ParAccess(usUser); ParTail("sec"); ParInt(&drv->perswait, 1800); ParName("persdelay"); ParAccess(usUser); ParTail("sec"); ParInt(&drv->persdelay, 1800); ParName("current"); ParFmt(drv->fmt); ParTail("Tesla equivalent"); ParFloat(&drv->current, PAR_NAN); ParName("measured"); ParFmt(drv->fmt); ParTail("Amps"); ParFloat(&drv->measured, PAR_NAN); ParName("voltage"); ParFmt(drv->fmt); ParTail("Volt"); ParFloat(&drv->voltage, PAR_NAN); ParName("lastfield"); ParSave(1); ParFloat(&drv->lastfield, PAR_NAN); ParName("confirm"); ParCmd(IpsConfirm, NULL); EaseBasePar(drv); EaseSendPar(drv); ParStdDef(); ParName("targetValue"); ParFmt(drv->fmt); ParTail("Tesla"); ParFloat(&drv->d.targetValue, PAR_NAN); if (ParActionIs(PAR_LIST) || ParActionIs(PAR_SET) || ParActionIs(PAR_SHOW)) { IpsOk(drv); } if (ParActionIs(PAR_KILL)) { EaseKillDriv(&drv->d); } EaseMsgPar(drv); } /*----------------------------------------------------------------------------*/ static void IpsStatus(Ips * drv) { char *ans; int *code; int swi; if (drv->d.b.state != EASE_read) return; ans = drv->d.b.ans; code = &drv->d.b.errCode; if (ans[0] != 'X' || ans[3] != 'A' || ans[5] != 'C' || ans[7] != 'H' || ans[9] != 'M') { ParPrintf(drv, eError, "illegal status response"); *code = EASE_FAULT; return; } switch (ans[1]) { case '0': break; case '1': ParPrintf(drv, eError, "magnet quenched"); drv->lastfield = PAR_NAN; *code = EASE_FAULT; return; case '2': ParPrintf(drv, eError, "IPS overheated"); *code = EASE_FAULT; return; case '4': ParPrintf(drv, eError, "IPS warming up"); *code = EASE_FAULT; return; case '8': ParPrintf(drv, eError, "IPS fault"); *code = EASE_FAULT; return; default: ParPrintf(drv, eError, "illegal status response"); *code = EASE_FAULT; return; } if (ans[6] != '3') { if (drv->remote == 2) { /* remote state monitoring local switch */ ParPrintf(drv, eError, "IPS switched to local"); *code = EASE_FAULT; drv->remote = 3; /* signal to switch back to C0 local locked */ return; } if (ans[6] == '1') { drv->remote = 3; /* signal to switch back to C0 local locked */ } if (drv->d.hwstate == HWBusy && drv->d.b.doit == NULL) { drv->d.hwstate = HWIdle; drv->d.stopped = 0; } } if (ans[8] == '5') { drv->heaterFault = 1; return; } drv->heaterFault = 0; if (ans[8] == '1') { swi = 1; } else { swi = 0; } if (swi != drv->perswitch || drv->swtim == 0) { drv->swtim = time(NULL); drv->perswitch = swi; if (drv->perswitch && drv->dothis == RAMP_ALMOST) { drv->dothis = NOTHING; } } } /*----------------------------------------------------------------------------*/ static void IpsSetField(Ips * drv, float val) { ParLog(drv); if (drv->perswitch) { drv->current = val; drv->lastfield = val; drv->persfield = val; EaseParHasChanged(); } else { drv->persfield = val; } } /*----------------------------------------------------------------------------*/ static long IpsRead(long pc, void *object) { Ips *drv = ParCast(&ipsClass, object); EaseBase *eab = object; time_t expire, now; switch (pc) { default: /* FSM BEGIN ****************************** */ EasePchk(drv); EaseWrite(eab, "X"); return __LINE__; case __LINE__: /**********************************/ IpsStatus(drv); EaseWrite(eab, "R7"); /* read current (in Tesla) */ return __LINE__; case __LINE__: /**********************************/ drv->current = OxiGet(eab, 3, NULL, drv->current); EaseWrite(eab, "R2"); /* read measured current (in amps) */ return __LINE__; case __LINE__: /**********************************/ drv->measured = OxiGet(eab, 3, NULL, drv->measured); EaseWrite(eab, "R1"); /* read measured voltage */ return __LINE__; case __LINE__: /**********************************/ drv->voltage = OxiGet(eab, 3, NULL, drv->voltage); if (drv->perswitch) { IpsSetField(drv, drv->current); goto checktodo; } EaseWrite(eab, "R18"); /* read persistant field (in Tesla) */ return __LINE__; case __LINE__: /**********************************/ IpsSetField(drv, OxiGet(eab, 3, NULL, drv->persfield)); checktodo: now = time(NULL); if (drv->persmode == 2) { /* persistent mode off */ drv->dothis = NOTHING; goto nothing; } else { if (drv->perswitch) { if (drv->persmode == 1 || now > drv->tim + drv->persdelay) { drv->dothis = SWITCH_OFF; } else { drv->dothis = NOTHING; goto nothing; } } else if (drv->dothis == RAMP_ALMOST) { if (now > drv->tim + drv->perswait) { drv->dothis = RAMP_ZERO; } else { goto nothing; } } else if (drv->dothis == SWITCH_OFF) { if (now > drv->swtim + 30) { drv->dothis = RAMP_DOWN; } else { goto nothing; } } else { goto nothing; } } EaseWrite(eab, "C3"); drv->remote = 1; /* remote state */ return __LINE__; case __LINE__: /**********************************/ switch (drv->dothis) { case SWITCH_OFF: goto switch_off; case RAMP_DOWN: goto ramp_down; case RAMP_ZERO: goto ramp_zero; } /* we should never get here */ goto quit; switch_off: EaseWrite(eab, "H0"); drv->perswitch = 0; drv->swtim = time(NULL); drv->lastfield = drv->current; EaseParHasChanged(); return __LINE__; case __LINE__: /**********************************/ ParPrintf(drv, -1, "IPS: go to persistent mode, wait 30 sec for switch"); goto localLocked; ramp_down: if (drv->current == 0) goto localLocked; if (drv->perscurrent == 0 || drv->persfield < drv->perslimit) { drv->dothis = RAMP_ZERO; goto ramp_zero; } OxiSet(eab, "J", drv->perscurrent, 3); /* put set point to a low value */ return __LINE__; case __LINE__: EaseWrite(eab, "A1"); /* goto set (almost zero) */ ParPrintf(drv, -1, "IPS: ramp current to %f (almost zero)", drv->perscurrent); return __LINE__; case __LINE__: /**********************************/ drv->tim = time(NULL); drv->dothis = RAMP_ALMOST; goto localLocked; ramp_zero: EaseWrite(eab, "A2"); /* goto zero */ ParPrintf(drv, -1, "IPS: ramp current to 0"); drv->dothis = RAMPED_DOWN; return __LINE__; case __LINE__: /**********************************/ goto localLocked; nothing: if (drv->remote != 3) /* NOT signal to switch back to local locked */ goto quit; localLocked: EaseWrite(eab, "C0"); drv->remote = 0; /* local state */ return __LINE__; case __LINE__: /**********************************/ quit: ParLog(drv); return 0; } /* FSM END ******************************************* */ } /*----------------------------------------------------------------------------*/ static long IpsMeas(long pc, void *object) { Ips *drv = ParCast(&ipsClass, object); EaseBase *eab = object; switch (pc) { default: /* FSM BEGIN ****************************** */ EasePchk(drv); EaseWrite(eab, "X"); return __LINE__; case __LINE__: /**********************************/ IpsStatus(drv); EaseWrite(eab, "R2"); /* read measured current (in amps) */ return __LINE__; case __LINE__: /**********************************/ drv->measured = OxiGet(eab, 3, NULL, drv->measured); EaseWrite(eab, "R1"); /* read measured voltage */ return __LINE__; case __LINE__: /**********************************/ drv->voltage = OxiGet(eab, 3, NULL, drv->voltage); quit: ParLog(drv); return 0; } /* FSM END ******************************************* */ } /*----------------------------------------------------------------------------*/ static long IpsStart(long pc, void *object) { Ips *drv = ParCast(&ipsClass, object); EaseBase *eab = object; switch (pc) { default: /* FSM BEGIN ****************************** */ EaseWrite(eab, "V"); return __LINE__; case __LINE__: /**********************************/ if (0 == strncmp(eab->version, "IPS120", 6)) { eab->syntax = 1; } else if (0 == strncmp(eab->version, "PS", 2)) { eab->syntax = 0; } else { snprintf(eab->msg, sizeof eab->msg, "unknown power supply version: %s", eab->version); ParPrintf(drv, eError, "ERROR: %s", eab->msg); EaseStop(eab); goto quit; } ParPrintf(drv, eLog, "connected to %s", eab->version); if (eab->syntax) { drv->fmt = "%.4f"; } else { drv->fmt = "%.3f"; } drv->tim = time(NULL); FsmCall(IpsRead); return __LINE__; case __LINE__: /**********************************/ drv->d.targetValue = drv->persfield; quit: return 0; } /* FSM END ******************************************* */ } /*----------------------------------------------------------------------------*/ static long IpsChangeField(long pc, void *object) { Ips *drv = ParCast(&ipsClass, object); EaseBase *eab = object; float fld; float step; float ramp; time_t delay; switch (pc) { default: /* FSM BEGIN ****************************** */ EaseSetUpdate(eab, EASE_RUN, 0); if (drv->nowait) { drv->d.hwstate = HWIdle; drv->d.eMode = EVMonitor; /* finish drive, continue in background */ } EaseWrite(eab, "C3"); drv->remote = 1; /* remote state */ return __LINE__; case __LINE__: /**********************************/ EaseWrite(eab, "F7"); /* switch to tesla on display */ return __LINE__; case __LINE__: /**********************************/ EaseWrite(eab, "A0"); /* hold */ return __LINE__; case __LINE__: /**********************************/ EaseWrite(eab, "R7"); /* read current (in Tesla) */ return __LINE__; case __LINE__: /**********************************/ drv->current = OxiGet(eab, 3, NULL, drv->current); if (drv->perswitch) { IpsSetField(drv, drv->current); goto skipR18; } EaseWrite(eab, "R18"); /* read persistant field (in Tesla) */ return __LINE__; case __LINE__: /**********************************/ fld = OxiGet(eab, 3, NULL, drv->persfield); IpsSetField(drv, fld); skipR18: FsmCall(IpsMeas); return __LINE__; case __LINE__: /**********************************/ drv->remote = 2; /* remote state monitoring local switch */ if (!IpsOk(drv)) goto finish; if (drv->lastfield == PAR_NAN) { drv->lastfield = drv->persfield; } if (fabs(drv->d.targetValue - drv->lastfield) < 1e-5) { ParPrintf(drv, -1, "IPS: we are already at field %f", drv->lastfield); if (drv->persmode == 1) { if (!drv->perswitch) goto finish; goto target_reached; } else { if (drv->perswitch) goto finish; } } if (fabs(drv->current - drv->lastfield) < 1e-5) { goto switch_on; } OxiSet(eab, "J", drv->lastfield, 3); /* set point */ return __LINE__; case __LINE__: /**********************************/ EaseWrite(eab, "A1"); ParPrintf(drv, -1, "IPS: ramp to current for %f Tesla", drv->lastfield); return __LINE__; case __LINE__: /**********************************/ drv->tim = time(NULL); stab1: EaseWrite(eab, "R7"); /* read current (in Tesla) */ return __LINE__; case __LINE__: /**********************************/ drv->current = OxiGet(eab, 3, NULL, drv->current); FsmCall(IpsMeas); return __LINE__; case __LINE__: /**********************************/ if (fabs(drv->current - drv->lastfield) > 1e-5) goto stab1; stab2: FsmWait(1); return __LINE__; case __LINE__: /**********************************/ FsmCall(IpsMeas); return __LINE__; case __LINE__: /**********************************/ if (time(NULL) < drv->tim + 10) goto stab2; /* stabilize */ switch_on: if (drv->perswitch) goto wait_open; if (drv->force == 2) { EaseWrite(eab, "H2"); } else { EaseWrite(eab, "H1"); } drv->force = 0; return __LINE__; case __LINE__: /**********************************/ drv->perswitch = 1; drv->dothis = NOTHING; drv->swtim = time(NULL); wait_open: delay = drv->swtim + 15 - time(NULL); if (delay > 0) ParPrintf(drv, -1, "IPS: wait %d sec to open switch", delay); ParLog(drv); start_ramp: FsmWait(1); return __LINE__; case __LINE__: /**********************************/ FsmCall(IpsMeas); return __LINE__; case __LINE__: /**********************************/ if (drv->heaterFault) { if (time(NULL) > drv->swtim + 3) { ParPrintf(drv, eError, "IPS: switch heater not connected"); eab->errCode = EASE_FAULT; goto off_finish; } } if (time(NULL) < drv->swtim + 15) goto start_ramp; /* wait */ OxiSet(eab, "T", drv->ramp, 3); return __LINE__; case __LINE__: /**********************************/ OxiSet(eab, "J", drv->current, 3); /* put set point to actual value */ return __LINE__; case __LINE__: /**********************************/ EaseWrite(eab, "A1"); /* go to setpoint (do not yet run) */ return __LINE__; case __LINE__: /**********************************/ ParPrintf(drv, -1, "IPS: ramp to %f Tesla", drv->d.targetValue); ramping: ParLog(drv); FsmWait(1); return __LINE__; case __LINE__: /**********************************/ EaseWrite(eab, "R7"); /* read "current" in Tesla */ return __LINE__; case __LINE__: /**********************************/ IpsSetField(drv, OxiGet(eab, 3, NULL, drv->current)); /* set drv->current and callback */ FsmCall(IpsMeas); return __LINE__; case __LINE__: /**********************************/ EaseWrite(eab, "R9"); /* read back ramp rate (may be sweep limited) */ return __LINE__; case __LINE__: /**********************************/ ramp = OxiGet(eab, 3, NULL, drv->ramp); step = ramp / 3; /* step = ramp * 20 sec */ if (step < 0.001) step = 0.001; if (drv->d.targetValue > drv->current + step) { fld = drv->current + step; } else if (drv->d.targetValue < drv->current - step) { fld = drv->current - step; } else { fld = drv->d.targetValue; if (fabs(drv->current - drv->d.targetValue) < 1e-5) goto target_reached; } OxiSet(eab, "J", fld, 3); return __LINE__; case __LINE__: /**********************************/ goto ramping; target_reached: drv->d.hwstate = HWIdle; drv->d.eMode = EVMonitor; /* we are at field, drive has finished */ if (drv->persmode != 1) goto hold_finish; /* but we continue in the background */ drv->tim = time(NULL); stab3: FsmWait(1); return __LINE__; case __LINE__: /**********************************/ FsmCall(IpsMeas); return __LINE__; case __LINE__: /**********************************/ if (time(NULL) < drv->tim + 10) goto stab3; /* stabilize */ EaseWrite(eab, "A0"); /* hold */ return __LINE__; case __LINE__: /**********************************/ goto finish; hold_finish: EaseWrite(eab, "A0"); return __LINE__; case __LINE__: /**********************************/ goto finish; off_finish: if (drv->perswitch) { drv->lastfield = drv->current; drv->swtim = time(NULL); } EaseWrite(eab, "H0"); return __LINE__; case __LINE__: /**********************************/ finish: EaseWrite(eab, "C0"); drv->remote = 0; /* local state */ drv->d.hwstate = HWIdle; drv->tim = time(NULL); /* time of last field change */ return __LINE__; case __LINE__: /**********************************/ return 0; } /* FSM END ******************************************* */ } /*----------------------------------------------------------------------------*/ static int IpsInit(SConnection * con, int argc, char *argv[], int dynamic) { /* args: MakeObject objectname ips */ Ips *drv; drv = EaseMakeDriv(con, &ipsClass, argc, argv, dynamic, 7, IpsParDef, OxiHandler, IpsStart, NULL, IpsRead, IpsChangeField); if (drv == NULL) return 0; drv->d.maxwait = 999999; drv->d.tolerance = 0.00011; drv->dothis = NOTHING; return 1; } /*----------------------------------------------------------------------------*/ void IpsStartup(void) { ParMakeClass(&ipsClass, EaseDrivClass()); MakeDriver("IPS", IpsInit, 0, "OI Power Supply"); }