/*----------------------------------------------------------------------** * file: devModtcp.c ** *-----------------------------------------------------------------------** * devModtcp.c - Implement the Epics device support layer for Modicon ** * PLCs using the NOE211 Ethernet options module. ** * ** * version: 1.0 ** * $Revision: 1.20 $ ** * created: 23-July-1998 R.Keitel ** * institution: TRIUMF Cylotron Facility ** * last edit: $Date: 2005/08/24 14:41:23 $ $Author: rolf $ ** *-----------------------------------------------------------------------** * ** history: ** $Log: devModtcp.c,v $ ** Revision 1.20 2005/08/24 14:41:23 rolf ** fix bug in mbbodirectInit ** ** Revision 1.19 2005/01/27 18:17:30 waters ** modify for epics R3.14 compatibility ** ** Revision 1.18 2004/12/09 16:01:48 rolf ** move from PVCS to CVS ** * * Rev 1.16 Mar 24 2004 04:24:04 R.Keitel * fix moddelay bugs * * Rev 1.15 Feb 04 2004 11:56:42 R.Keitel * include Revision directives in c and h files * * Rev 1.14 Feb 03 2004 15:39:00 R.Keitel * fix revision label * * Rev 1.13 Feb 03 2004 15:31:30 R.Keitel * add static string with PVCS revision * * Rev 1.12 Feb 03 2004 15:22:32 R.Keitel * test revision string * * Rev 1.11 Jan 28 2004 17:57:58 R.Keitel * add support for 2's complement ADCs * * Rev 1.10 12 Dec 2002 10:39:06 R.Keitel * add comments for PLC specific delays * * * Rev 1.9 28 Nov 2002 14:10:32 R.Keitel * update initial comments * * Rev 1.8 05 Mar 2002 03:27:38 R.Keitel * change print format * * * Rev 1.7 24 Jul 1999 12:35:12 R.Keitel * fix initial reads for output records * * Rev 1.6 22 Jul 1999 12:21:22 R.Keitel * ?? * * Rev 1.5 16 Mar 1999 17:26:56 R.Keitel * fixed pointer bug for dpvt * * Rev 1.4 23 Oct 1998 19:22:42 R.Keitel * set b.. fields * * Rev 1.3 30 Jul 1998 17:22:18 R.Keitel * all syntax errors fixed * * Rev 1.2 30 Jul 1998 16:36:36 R.Keitel * first cut at EPICS divice support * * Rev 1.1 30 Jul 1998 11:34:58 R.Keitel * version 0 driver */ /* devModtcp.c */ /* * Original Author: R. Keitel * * Experimental Physics and Industrial Control System (EPICS) * * Copyright 1996-1999, TRIUMF * * NOTE: All device suport resides in this file * Implementation and assumption: * - startup script uses * > modtcpDrvCreate to define a named PLC and create a data buffer for data * exchange with this PLC in VME memory * > modtcpDrvReadGroup to define "read groups" (groups of registers to be read * from the PLC) * > modtcpDrvWriteGroup to define "write groups" * > read groups may be flagged "read-once". This can be used to retrieve current * setpoint and command registers from the PLC on IOC boot. * > modtcpSetReadDelay to override the moddelay setting for read groups * > modtcpSetWriteDelay to override teh moddelay setting for write groups * - device support reads/writes from/to IOC memory buffer by calling the * driver functions modtcpRead and modtcpWrite * - driver support reads PLC "read groups" via TCP/IP from PLC and dumps them to * the IOC memory buffer. Note that read groups are limited by the modbus protocol * to 125 registers/group. * - driver support scans PLC "write groups" for changes and updates individual * PLC registers via a TCP/IP message. No length limit for write groups. * - for ai and ao records, both unipolar and bipolar (2's complement) data are * supported. * * We use the VME_IO hardware addressing scheme: * #Cc Ss @n [p] * where c is the PLC register address - 400000, i.e. C1 <==> 400001 * s is the bit offset into the PLC register * n is the PLC number (0 .. first PLC, 1 .. second PLC) * p is an optional range identifier for analog values in * hex notation of the format [-]. * - default for precision is 12 bits, unipolar * Examples for the p parameter: * - for a 16 bit unipolar signal set p to 0xffff * - for an 11 bit unipolar signal set p to 0x7ff * - for a 16 bit bipolar (2's complement) signal, set p to -0x7fff * - for a 12 bit bipolar (2's complement) signal, set p to -0x7ff * - for an 11 bit bipolar (2's complement) signal, set p to -0x3ff * * Note: bipolar signals in the EPICS database: * set EGUL, EGUF to 0, value at * Limitations: * - only register transfers are implemented, no coil transfers * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef EPICS3_14 #include /* new for EPICS 3.14 */ #include /* new for EPICS 3.14 */ #endif /* The following must match the definition in choiceGbl.ascii */ #define LINEAR 1 static char *devModtcp_c = "\ndevModtcp.c $Revision: 1.20 $ $Date: 2005/08/24 14:41:23 $\n"; typedef struct { int plc_no; unsigned long int range; unsigned short int bipolar; } devInfo_modtcp; static int common_init_record(struct dbCommon *prec, devInfo_modtcp **ppdevInfo, char *type); static void devModtcp_recDisable (struct dbCommon *prec); static int common_get_params(devInfo_modtcp *pdevInfo, struct vmeio *); /******** support for ao record *********************/ /* Create the dset for devAoModtcp */ static long ao_init_record(); static long write_ao(); static long ao_special_linconv(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN write_ao; DEVSUPFUN special_linconv; }devAoModtcp={ 6, NULL, NULL, ao_init_record, NULL, write_ao, ao_special_linconv}; #ifdef EPICS3_14 epicsExportAddress(dset,devAoModtcp); /* new for EPICS 3.14 */ #endif /* Device Support Routines for Modicon PLCs analog output*/ static long ao_init_record(struct aoRecord *pao) { struct vmeio *pvmeio; long err, status; unsigned short value; devInfo_modtcp *pdevInfo = NULL; /* ao.out must be an VME_IO */ switch (pao->out.type) { case (VME_IO) : pvmeio = (struct vmeio *) &(pao->out.value); break; default : recGblRecordError(S_db_badField,(void *)pao, "devAoModtcp (init_record) Illegal OUT field"); return(S_db_badField); } err = common_init_record((struct dbCommon *) pao, &pdevInfo, "ao"); if (err) { recGblRecordError(S_db_badField,(void *)pao, "devAoModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pao, "devAoModtcp (init_record) bad parameters"); return(S_db_badField); } /* set linear conversion slope*/ pao->eslo = ((double) (pao->eguf -pao->egul))/((double) pdevInfo->range); /* read the current value */ status = modtcpInitRead(pdevInfo->plc_no, pvmeio->card, 0, &value); if (status == 0 || status == -2) { logMsg("devModtcp: %s initial value: %u\n", pao->name, value); if (pdevInfo->bipolar) { pao->rval = (value > pdevInfo->range ? (int) (~pdevInfo->range | value) : (int) value ); pao->rbv = pao->rval; } else { pao->rval = (int) value; pao->rbv = (int) value; } } else logMsg("devModtcp: ** failed init read for %s\n", pao->name); return(0); } static long write_ao(struct aoRecord *pao) { struct vmeio *pvmeio; devInfo_modtcp *pdevInfo; long status; unsigned short value; pvmeio = (struct vmeio *) &(pao->out.value); pdevInfo = (devInfo_modtcp *) pao->dpvt; /* note that rval is long int, i.e. it has the proper sign for bipolar signals */ value = pao->rval; status = modtcpWrite(pdevInfo->plc_no, pvmeio->card, 0, &value); if(status==0 || status==-2) { /* note that value is short, rbv is long int, i.e. we must sign extend */ if (pdevInfo->bipolar) { pao->rbv = (value > pdevInfo->range ? (int) (~pdevInfo->range | value) : (int) value ); } else { pao->rbv = (int) value; } } if(status==-1) { if (recGblSetSevr(pao,WRITE_ALARM,INVALID_ALARM) && errVerbose && (pao->stat!=WRITE_ALARM || pao->sevr!=INVALID_ALARM)) recGblRecordError(-1,(void *)pao,"modtcp_driver address Error"); } else if(status==-2) { status=0; recGblSetSevr(pao,HW_LIMIT_ALARM,INVALID_ALARM); } return(status); } static long ao_special_linconv(struct aoRecord *pao, int after) { devInfo_modtcp *pdevInfo; if(!after) return(0); /* set linear conversion slope*/ pdevInfo = (devInfo_modtcp *) pao->dpvt; pao->eslo = ((double) (pao->eguf -pao->egul))/((double) pdevInfo->range); return(0); } /************** support for ai record *************************/ /* Create the dset for devAiModtcp */ static long ai_init_record(); static long read_ai(); static long ai_special_linconv(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_ai; DEVSUPFUN special_linconv; } devAiModtcp={ 6, NULL, NULL, ai_init_record, NULL, read_ai, ai_special_linconv}; #ifdef EPICS3_14 epicsExportAddress(dset,devAiModtcp); /* new for EPICS 3.14 */ #endif static long ai_init_record(struct aiRecord *pai) { struct vmeio *pvmeio; long err; devInfo_modtcp *pdevInfo = NULL; /* ai.inp must be an VME_IO */ switch (pai->inp.type) { case (VME_IO) : pvmeio = (struct vmeio *) &(pai->inp.value); break; default : recGblRecordError(S_db_badField,(void *)pai, "devAiModtcp (init_record) Illegal INP field"); return(S_db_badField); } err = common_init_record((struct dbCommon *) pai, &pdevInfo, "ai"); if (err) { recGblRecordError(S_db_badField,(void *)pai, "devAiModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pai, "devAiModtcp (init_record) bad parameters"); return(S_db_badField); } /* set linear conversion slope*/ pai->eslo = ((double) (pai->eguf -pai->egul)) / ((double) pdevInfo->range); return(0); } static long read_ai(struct aiRecord *pai) { unsigned short value; struct vmeio *pvmeio; devInfo_modtcp *pdevInfo; long status; pvmeio = (struct vmeio *) &(pai->inp.value); pdevInfo = (devInfo_modtcp *) pai->dpvt; status=modtcpRead(pdevInfo->plc_no, pvmeio->card, 0, &value); if(status==-1) { status = 2; /* don't convert*/ if(recGblSetSevr(pai,READ_ALARM,INVALID_ALARM) && errVerbose && (pai->stat!=READ_ALARM || pai->sevr!=INVALID_ALARM)) recGblRecordError(-1,(void *)pai,"modtcp_driver address Error"); return(status); } else if(status==-2) { status=0; recGblSetSevr(pai,HW_LIMIT_ALARM,INVALID_ALARM); } if(status!=0) return(status); if (pdevInfo->bipolar) { pai->rval = (value > pdevInfo->range ? (int) (~pdevInfo->range | value) : (int) value ); } else { pai->rval = (int) value; } return(status); } static long ai_special_linconv(struct aiRecord *pai, int after) { devInfo_modtcp *pdevInfo; if(!after) return(0); /* set linear conversion slope*/ pdevInfo = (devInfo_modtcp *) pai->dpvt; pai->eslo = ((double) (pai->eguf -pai->egul)) / ((double) pdevInfo->range); logMsg("%s - special linconv egul %f eguf %f eoff %f range %lx\n", pai->name, pai->egul, pai->eguf, pai->eoff, pdevInfo->range); return(0); } /************** support for mbbiDirect record *************************/ /* Create the dset for devMbbiDirectModtcp */ static long mbbidirect_init_record(); static long read_mbbidirect(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_mbbi; }devMbbiDirectModtcp={ 5, NULL, NULL, mbbidirect_init_record, NULL, read_mbbidirect}; #ifdef EPICS3_14 epicsExportAddress(dset,devMbbiDirectModtcp); /* new for EPICS 3.14 */ #endif static long mbbidirect_init_record(struct mbbiDirectRecord *pmbbi) { long err; devInfo_modtcp *pdevInfo = NULL; struct vmeio *pvmeio; /* mbbi.inp must be an VME_IO */ switch (pmbbi->inp.type) { case (VME_IO) : pvmeio = (struct vmeio *) &(pmbbi->inp.value); pmbbi->shft = pmbbi->inp.value.vmeio.signal; pmbbi->mask <<= pmbbi->shft; break; default : recGblRecordError(S_db_badField,(void *)pmbbi, "devMbbiDirectModtcp(init_record) Illegal INP field"); return(S_db_badField); } err = common_init_record((struct dbCommon *) pmbbi, &pdevInfo, "mbbi"); if (err) { recGblRecordError(S_db_badField,(void *)pmbbi, "devMbbiDirectModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pmbbi, "devMbbiDirectModtcp (init_record) bad parameters"); return(S_db_badField); } return(0); } static long read_mbbidirect(struct mbbiDirectRecord *pmbbi) { struct vmeio *pvmeio; devInfo_modtcp *pdevInfo; int status; unsigned short value; pdevInfo = (devInfo_modtcp *) pmbbi->dpvt; pvmeio = (struct vmeio *) &(pmbbi->inp.value); status = modtcpRead(pdevInfo->plc_no, pvmeio->card, 0, &value); if(status==0) { pmbbi->rval = value; } else { recGblSetSevr(pmbbi,READ_ALARM,INVALID_ALARM); } return(status); } /************** support for mbboDirect record *************************/ /* Create the dset for devMbboDirectModtcp */ static long mbbodirect_init_record(); static long write_mbbodirect(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN write_mbbo; }devMbboDirectModtcp={ 5, NULL, NULL, mbbodirect_init_record, NULL, write_mbbodirect}; #ifdef EPICS3_14 epicsExportAddress(dset,devMbboDirectModtcp); /* new for EPICS 3.14 */ #endif static long mbbodirect_init_record(struct mbboDirectRecord *pmbbo) { unsigned short value, set, i, temp; struct vmeio *pvmeio; int err, status=0; devInfo_modtcp *pdevInfo = NULL; /* mbbo.out must be an VME_IO */ switch (pmbbo->out.type) { case (VME_IO) : pvmeio = &(pmbbo->out.value.vmeio); pmbbo->shft = pvmeio->signal; pmbbo->mask <<= pmbbo->shft; /* printf("Init mbboDirect: ch %d parm %x --> shift %lx mask %lx", pvmeio->card, pvmeio->parm[0], pmbbo->shft, pmbbo->mask); */ break; default : status = S_db_badField; recGblRecordError(status,(void *)pmbbo, "devMbboDirectModtcp (init_record) Illegal OUT field"); return(S_db_badField); } err = common_init_record((struct dbCommon *) pmbbo, &pdevInfo, "mbbo"); if (err) { recGblRecordError(S_db_badField,(void *)pmbbo, "devMbboDirectModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pmbbo, "devMbboDirectModtcp (init_record) bad parameters"); return(S_db_badField); } status = modtcpInitRead(pdevInfo->plc_no, pvmeio->card, pmbbo->mask, &value); if(status==0) { pmbbo->rbv = pmbbo->rval = value; temp = value; for (i=0; i < 15; i++) { set = temp & 1; switch (i) { case 0: pmbbo->b0 = set; break; case 1: pmbbo->b1 = set; break; case 2: pmbbo->b2 = set; break; case 3: pmbbo->b3 = set; break; case 4: pmbbo->b4 = set; break; case 5: pmbbo->b5 = set; break; case 6: pmbbo->b6 = set; break; case 7: pmbbo->b7 = set; break; case 8: pmbbo->b8 = set; break; case 9: pmbbo->b9 = set; break; case 10: pmbbo->ba = set; break; case 11: pmbbo->bb = set; break; case 12: pmbbo->bc = set; break; case 13: pmbbo->bd = set; break; case 14: pmbbo->be = set; break; case 15: pmbbo->bf = set; break; } temp >>= 1; } } else if (status == -1) { logMsg("devModtcp: ** failed init read for %s\n", pmbbo->name); status = 2; } else status = 2; return(status); } static long write_mbbodirect(struct mbboDirectRecord *pmbbo) { struct vmeio *pvmeio; int plc_no, status; unsigned short value; devInfo_modtcp *pdevInfo; pvmeio = &(pmbbo->out.value.vmeio); pdevInfo = (devInfo_modtcp *) pmbbo->dpvt; value = pmbbo->rval; plc_no = pdevInfo->plc_no; status = modtcpWrite(plc_no, pvmeio->card, 0, &value); if(status==0) { status = modtcpRead(plc_no, pvmeio->card, 0, &value); if(status==0) pmbbo->rbv = value; else recGblSetSevr(pmbbo,READ_ALARM,INVALID_ALARM); } else { recGblSetSevr(pmbbo,WRITE_ALARM,INVALID_ALARM); } return(status); } /************** support for mbbi record *************************/ /* Create the dset for devMbbiModtcp */ static long mbbi_init_record(); static long read_mbbi(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_mbbi; }devMbbiModtcp={ 5, NULL, NULL, mbbi_init_record, NULL, read_mbbi}; #ifdef EPICS3_14 epicsExportAddress(dset,devMbbiModtcp); /* new for EPICS 3.14 */ #endif static long mbbi_init_record(struct mbbiRecord *pmbbi) { struct vmeio *pvmeio; devInfo_modtcp *pdevInfo = NULL; int err; /* mbbi.inp must be an VME_IO */ switch (pmbbi->inp.type) { case (VME_IO) : pvmeio = &(pmbbi->inp.value.vmeio); pmbbi->shft = pvmeio->signal; pmbbi->mask <<= pmbbi->shft; break; default : recGblRecordError(S_db_badField,(void *)pmbbi, "devMbbiModtcp(init_record) Illegal INP field"); return(S_db_badField); } err = common_init_record((struct dbCommon *) pmbbi, &pdevInfo, "mbbi"); if (err) { recGblRecordError(S_db_badField,(void *)pmbbi, "devMbbiModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pmbbi, "devMbbiModtcp (init_record) bad parameters"); return(S_db_badField); } return(0); } static long read_mbbi(struct mbbiRecord *pmbbi) { int status; unsigned short value; struct vmeio *pvmeio; devInfo_modtcp *pdevInfo; pvmeio = (struct vmeio *) &(pmbbi->inp.value); pdevInfo = (devInfo_modtcp *) pmbbi->dpvt; status = modtcpRead(pdevInfo->plc_no, pvmeio->card, pmbbi->mask, &value); if(status==0) { pmbbi->rval = value; } else { recGblSetSevr(pmbbi,READ_ALARM,INVALID_ALARM); } return(status); } /************** support for mbbo record *************************/ /* Create the dset for devMbboModtcp */ static long mbbo_init_record(); static long write_mbbo(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN write_mbbo; }devMbboModtcp={ 5, NULL, NULL, mbbo_init_record, NULL, write_mbbo}; #ifdef EPICS3_14 epicsExportAddress(dset,devMbboModtcp); /* new for EPICS 3.14 */ #endif static long mbbo_init_record(struct mbboRecord *pmbbo) { unsigned short value; int err, status=0; struct vmeio *pvmeio = NULL; devInfo_modtcp *pdevInfo = NULL; /* mbbo.out must be an VME_IO */ switch (pmbbo->out.type) { case (VME_IO) : pvmeio = &(pmbbo->out.value.vmeio); pmbbo->shft = pvmeio->signal; pmbbo->mask <<= pmbbo->shft; break; default : status = S_db_badField; recGblRecordError(status,(void *)pmbbo, "devMbboModtcp (init_record) Illegal OUT field"); } err = common_init_record((struct dbCommon *) pmbbo, &pdevInfo, "mbbo"); if (err) { recGblRecordError(S_db_badField,(void *)pmbbo, "devMbboModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pmbbo, "devMbboModtcp (init_record) bad parameters"); return(S_db_badField); } status = modtcpInitRead(pdevInfo->plc_no, pvmeio->card, pmbbo->mask, &value); if(status==0) pmbbo->rbv = pmbbo->rval = value; else if (status == -1) { logMsg("devModtcp: ** failed init read for %s\n", pmbbo->name); status = 2; } else status = 2; return(status); } static long write_mbbo(struct mbboRecord *pmbbo) { int plc_no, status; unsigned short value; struct vmeio *pvmeio; devInfo_modtcp *pdevInfo; pvmeio = &(pmbbo->out.value.vmeio); pdevInfo = (devInfo_modtcp *) pmbbo->dpvt; value = pmbbo->rval; plc_no = pdevInfo->plc_no; status = modtcpWrite(plc_no, pvmeio->card, pmbbo->mask, &value); if(status==0) { status = modtcpRead(plc_no, pvmeio->card, 0, &value); if(status==0) pmbbo->rbv = value; else recGblSetSevr(pmbbo,READ_ALARM,INVALID_ALARM); } else { recGblSetSevr(pmbbo,WRITE_ALARM,INVALID_ALARM); } return(status); } /************** support for bo record *************************/ /* Create the dset for devBoModtcp */ static long bo_init_record(); static long write_bo(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN write_bo; }devBoModtcp={ 5, NULL, NULL, bo_init_record, NULL, write_bo}; #ifdef EPICS3_14 epicsExportAddress(dset,devBoModtcp); /* new for EPICS 3.14 */ #endif static long bo_init_record(struct boRecord *pbo) { unsigned short value; int err, status=0; struct vmeio *pvmeio = NULL; devInfo_modtcp *pdevInfo = NULL; /* bo.out must be an VME_IO */ switch (pbo->out.type) { case (VME_IO) : pvmeio = (struct vmeio *) &(pbo->out.value); pbo->mask = 1; pbo->mask <<= pvmeio->signal; break; default : status = S_db_badField; recGblRecordError(status,(void *)pbo, "devBoModtcp (init_record) Illegal OUT field"); } err = common_init_record((struct dbCommon *) pbo, &pdevInfo, "bo"); if (err) { recGblRecordError(S_db_badField,(void *)pbo, "devBoModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pbo, "devBoModtcp (init_record) bad parameters"); return(S_db_badField); } status = modtcpInitRead(pdevInfo->plc_no, pvmeio->card, pbo->mask, &value); if(status == 0) pbo->rbv = pbo->rval = value; else if (status == -1) { logMsg("devModtcp: ** failed init read for %s\n", pbo->name); status = 2; } else status = 2; return(status); } static long write_bo(struct boRecord *pbo) { int plc_no, status; unsigned short value; struct vmeio *pvmeio; devInfo_modtcp *pdevInfo; value = pbo->rval; pvmeio = (struct vmeio *)&(pbo->out.value); pdevInfo = (devInfo_modtcp *) pbo->dpvt; plc_no = pdevInfo->plc_no; status = modtcpWrite(plc_no, pvmeio->card, pbo->mask, &value); if(status==0) { status = modtcpRead(plc_no, pvmeio->card, pbo->mask, &value); if(status==0) pbo->rbv = value; else recGblSetSevr(pbo,READ_ALARM,INVALID_ALARM); } if(status!=0) { if(recGblSetSevr(pbo,WRITE_ALARM,INVALID_ALARM) && errVerbose && (pbo->stat!=WRITE_ALARM || pbo->sevr!=INVALID_ALARM)) recGblRecordError(-1,(void *)pbo,"Modicon 984 driver Error"); } return(0); } /************** support for bi record *************************/ /* Create the dset for devBiModtcp */ static long bi_init_record(); static long read_bi(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_bi; }devBiModtcp={ 5, NULL, NULL, bi_init_record, NULL, read_bi}; #ifdef EPICS3_14 epicsExportAddress(dset,devBiModtcp); /* new for EPICS 3.14 */ #endif static long bi_init_record(struct biRecord *pbi) { struct vmeio *pvmeio; devInfo_modtcp *pdevInfo = NULL; int err; /* bi.inp must be an VME_IO */ switch (pbi->inp.type) { case (VME_IO) : pvmeio = (struct vmeio *)&(pbi->inp.value); pbi->mask=1; pbi->mask <<= pvmeio->signal; break; default : recGblRecordError(S_db_badField,(void *)pbi, "devBiModtcp (init_record) Illegal INP field"); return(S_db_badField); } err = common_init_record((struct dbCommon *) pbi, &pdevInfo, "bi"); if (err) { recGblRecordError(S_db_badField,(void *)pbi, "devBiModtcp (init_record) no memory"); return(S_db_badField); } err = common_get_params(pdevInfo, pvmeio); if (err) { recGblRecordError(S_db_badField,(void *)pbi, "devBiModtcp (init_record) bad parameters"); return(S_db_badField); } return(0); } static long read_bi(struct biRecord *pbi) { int status; unsigned short value; struct vmeio *pvmeio; devInfo_modtcp *pdevInfo; pvmeio = (struct vmeio *) &(pbi->inp.value); pdevInfo = (devInfo_modtcp *) pbi->dpvt; status = modtcpRead(pdevInfo->plc_no, pvmeio->card, pbi->mask, &value); if(status==0) { pbi->rval = value; return(0); } else { if(recGblSetSevr(pbi,READ_ALARM,INVALID_ALARM) && errVerbose && (pbi->stat!=READ_ALARM || pbi->sevr!=INVALID_ALARM)) recGblRecordError(-1,(void *)pbi,"Modicon 984 driver Error"); return(2); } return(status); } static int common_init_record(struct dbCommon *prec, devInfo_modtcp **ppdevInfo, char *type) { *ppdevInfo = (devInfo_modtcp *) calloc (1, sizeof(devInfo_modtcp)); prec->dpvt = (long *) (*ppdevInfo); if (*ppdevInfo == NULL) { devModtcp_recDisable (prec); return (ENOMEM); } return 0; } static void devModtcp_recDisable (struct dbCommon *prec) { prec->disa = TRUE; prec->disv = TRUE; /* Record disabled when DISA = DISV */ prec->disp = TRUE; /* Also disable putFields to this record*/ prec->stat = DISABLE_ALARM; /* Set the record status to DISABLE */ prec->sevr = INVALID_ALARM; /* Set severity to INVALID */ prec->diss = INVALID_ALARM; /* Also set DISS for completeness */ } /*end devModtcp_recDisable()*/ static int common_get_params(devInfo_modtcp *pdevInfo, struct vmeio *pvmeio) { int err, n, no; long int r; char *params; pdevInfo->plc_no = 0; /* default: PLC 0 */ pdevInfo->range = 0x0FFF; /* default: 12 bit precision */ pdevInfo->bipolar = 0; /* default: unipolar */ params = pvmeio->parm; n = sscanf(params, "%d %lx", &no, &r); if (n > 0) pdevInfo->plc_no = no; if (n > 1) { if (r >= 0) { pdevInfo->bipolar = 0; pdevInfo->range = r; } else { pdevInfo->bipolar = 1; pdevInfo->range = (unsigned long) (-r); } } err = modtcpCheckParams(pdevInfo->plc_no, pvmeio->card); return err; }