本示例展示了如何使用asynPortDriver类编写一个EPICS端口驱动程序的示例。 这个驱动程序参数库中一个有5个参数,分别支持5个EPICS记录。
如下是具体步骤:
1) 用makeBaseApp.pl脚本建立这个IOC应用程序的框架:
[blctrl@main-machine exer42]$ makeBaseApp.pl -t ioc testAsynPortDriver
[blctrl@main-machine exer42]$ makeBaseApp.pl -i -t ioc testAsynPortDriver
Using target architecture linux-x86_64 (only one available)
The following applications are available:
testAsynPortDriver
What application should the IOC(s) boot?
The default uses the IOC's name, even if not listed above.
Application name?
[blctrl@main-machine exer42]$ ls
configure iocBoot Makefile testAsynPortDriverApp
2)修改configure/RELEASE文件,增加一个SUPPORT和ASYN环境变量,它们分别指向support和asyn模块所在的路径:
SUPPORT=/usr/local/EPICS/synApps/support
ASYN=$(SUPPORT)/asyn
3) 进入到testAsynPortDriverApp/src目录,编写程序头文件以及源文件:
头文件:
#include "asynPortDriver.h"
/*
* 这些是drvInfo字符串,它们用于标识这些参数。包括标准asyn设备支持的asyn客户端使用它们。
* */
#define P_RunString "PROCESS_RUN" /*asynInt32, r/w*/
#define P_IntString "PROCESS_INT" /*asynInt32, r/w*/
#define P_FloatString "PROCESS_FLOAT" /*asynFloat64, r/w*/
#define P_IntRandString "PROCESS_INTRAND" /*asynInt32, r/o*/
#define P_FloatRandString "PROCESS_FLOATRAND" /*asynFloat64m r/o*/
/*
* 这个类演示了如何使用asynPortDriver基类来大大简化编写一个aysn端口驱动程序的任务
*/
class testAsynPortDriver : public asynPortDriver {//需要继承asynPortDriver
public:
testAsynPortDriver(const char * portName);
/* 我们重写从asynPortDriver继承的方法 */
virtual asynStatus writeInt32(asynUser * pasynUser, epicsInt32 value);
virtual asynStatus readInt32 (asynUser * pasynUser, epicsInt32 *value);
virtual asynStatus writeFloat64(asynUser * pasynUser, epicsFloat64 value);
virtual asynStatus readFloat64 (asynUser * pasynUser, epicsFloat64 * value);
/* 此方法是这个类的新方法 */
void simTask(void);
protected:
/* 用于pasynUser->reason的值, 并且是对参数库的索引 */
int P_Run;
int P_Int;
int P_FloatRand;
int P_Float;
int P_IntRand;
private:
epicsEventId eventId_; //用于线程同步的事件
int getIntRand(); //这个方法,返回一个0-100的随机数
float getFloatRand(); //这个方法,返回一个0.01-1的随机数
};
C++源文件:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <epicsTypes.h>
#include <epicsTime.h>
#include <epicsThread.h>
#include <epicsString.h>
#include <epicsTimer.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <iocsh.h>
#include "testAsynPortDriver.h"
#include <epicsExport.h>
static const char * driverName = "testAsynPortDriver";
void simTask(void * drvPvt);
/* testAsynPort类的构造函数 */
/* 调用asynPortDriver基类的构造器 */
/* param[in] : 要被创建的asyn驱动程序的名称 */
testAsynPortDriver::testAsynPortDriver(const char *portName)
:asynPortDriver(portName,
1, /* maxAddr */
asynInt32Mask | asynFloat64Mask | asynDrvUserMask, /* Interface mask */
asynInt32Mask | asynFloat64Mask, /* Interrupt mask */
0, /* asynFlags */
1, /* AutoConnect */
0, /* Default priority */
0) /* Default stack size */
{
asynStatus status;
int i;
const char * functionName = "testAsynPortDriver";
// 创建一个空时间
eventId_ = epicsEventCreate(epicsEventEmpty);
//根据参数名称,参数类型,在参数库中创建这个参数,并且返回其在参数库中的索引
createParam(P_RunString, asynParamInt32, &P_Run);
createParam(P_IntString, asynParamInt32, &P_Int);
createParam(P_FloatString, asynParamFloat64,&P_Float);
createParam(P_IntRandString, asynParamInt32, &P_IntRand);
createParam(P_FloatRandString, asynParamFloat64,&P_FloatRand);
// 此是调试代码,打印在参数库中的索引
printf("P_Run:%d,P_Int:%d,P_Float:%d,P_IntRand:%d,P_FloatRand:%d\n", P_Run, P_Int, P_Float, P_IntRand, P_FloatRand);
// 用索引将参数库中对应的变量初始化。
setIntegerParam(P_Run, 0);
setIntegerParam(P_Int, 0);
setIntegerParam(P_IntRand, 0);
setDoubleParam(P_Float, 0.0);
setDoubleParam(P_FloatRand, 0.0);
// 初始化随机数种子
srand(time(0));
/* 创建在后台计算这些随机数的线程 */
/* Create the thread that computes the random numbers in background */
status = (asynStatus)(epicsThreadCreate("testAsynPortDriverTask",
epicsThreadPriorityMedium,
epicsThreadGetStackSize(epicsThreadStackMedium),
(EPICSTHREADFUNC)::simTask,
this) == NULL);
// 如果线程创建失败,则打印产生错误的命令,以及驱动名,函数名
if (status){
printf("%s:%s: epicsThreadCreate failed\n", driverName, functionName);
return;
}
}
// 创建的新线程执行这个函数,此参数是指向testAsynPortDriver类实例的指针
void simTask(void * drvPvt)
{
testAsynPortDriver * pPvt = (testAsynPortDriver *)drvPvt;
pPvt->simTask();
}
/*
* 以单独线程运行仿真任务。当P_Run参数被设为1时,计算随机数。
* */
void testAsynPortDriver::simTask(void)
{
epicsInt32 run, int1, intrand;
epicsFloat64 float1, floatrand;
lock();
while(1){
getIntegerParam(P_Run, &run);
// 在等待启动命令或者等待更新时,释放锁
unlock();
if (run)
{//run为1,线程运行,计算随机数,并且设置到参数库中
epicsInt32 inum = (epicsInt32)getIntRand();
setIntegerParam(P_IntRand, inum);
epicsFloat64 fnum = (epicsFloat64)getFloatRand();
setDoubleParam(P_FloatRand, fnum);
// 带超时时间地进行等待
epicsEventWaitWithTimeout(eventId_, 1.0);
}
else //run为0,系统阻塞这个线程运行,直到等待的事件出现
(void)epicsEventWait(eventId_);
lock();
/* 在我们等待时,run可能发生变化,再次读取run */
getIntegerParam(P_Run, &run);
/* 如果run为0, 则从返回循环开始处执行 */
if (!run)
continue;
// 根据索引获取参数值,存入指定位置
getIntegerParam(P_Int, &int1);
getIntegerParam(P_IntRand, &intrand);
getDoubleParam(P_Float, &float1);
getDoubleParam(P_FloatRand, &floatrand);
// 初始为调试代码,查看这些参数值
printf("int1=%d, intrand=%d, float1=%f,floatrand=%f\n", int1, intrand,float1, floatrand);
// 更新时间戳
updateTimeStamp();
// 对上层进行回调
callParamCallbacks();
}
}
asynStatus testAsynPortDriver::writeInt32(asynUser * pasynUser, epicsInt32 value)
{
int function = pasynUser->reason;
asynStatus status = asynSuccess;
const char * paramName;
const char * functionName = "writeInt32";
// 调试代码:这个重写的方法被调用了。
printf("call writeInt32\n");
/* 在参数库中设置这个参数,此处reason就是参数索引 */
status = (asynStatus) setIntegerParam(function, value);
/* 根据索引获取参数字符串名称,在调试中可用 */
getParamName(function, ¶mName);
if (function == P_Run){//如果参数索引对应P_Run,并且传入值为1,表示启动线程
if (value) epicsEventSignal(eventId_);
}
else if (function == P_Int) //如果参数索引对应P_Int,则测试传入的value范围,并且将其写入参数库
{ if (value > 100)
value = 100;
else if (value < 0)
value = 0;
setIntegerParam(P_Int, value);
}
else if (function == P_IntRand)
{
}
// 通知客户端,参数库中参数发生变化了
status = (asynStatus)callParamCallbacks();
if (status)
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
"%s:%s: status=%d, function=%d, name=%s, value=%d",
driverName, functionName, status, function, paramName, value);
else
asynPrint(pasynUser, ASYN_TRACEIO_DRIVER,
"%s:%s: function=%d, name=%s, value=%d\n",
driverName, functionName, function, paramName, value);
return status;
}
asynStatus testAsynPortDriver::writeFloat64(asynUser *pasynUser, epicsFloat64 value)
{
int function = pasynUser->reason;
asynStatus status = asynSuccess;
epicsInt32 run;
const char * paramName;
const char * functionName = "writeFloat64";
printf("call writeFloat64\n");
status = (asynStatus) setDoubleParam(function, value);
getParamName(function, ¶mName);
if (function == P_Float)
{
if (value < -1000.0)
value = -1000.0;
else if (value > 1000.0)
value = 1000.0;
setDoubleParam(P_Float, value);
}
else if (function == P_FloatRand)
{
}
了
getIntegerParam(P_Run, &run);
if (run) epicsEventSignal(eventId_);
// 通知客户端,参数库中参数发生变化
status = (asynStatus) callParamCallbacks();
if (status)
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
"%s:%s: status=%d, function=%d, name=%s, value=%f",
driverName, functionName, status, function, paramName, value);
else
asynPrint(pasynUser, ASYN_TRACEIO_DRIVER,
"%s:%s: function=%d, name=%s, value=%f\n",
driverName, functionName, function, paramName, value);
return status;
}
asynStatus testAsynPortDriver::readFloat64(asynUser *pasynUser, epicsFloat64 * value)
{
int function = pasynUser->reason;
epicsFloat64 ftemp;
asynStatus status = asynSuccess;
epicsTimeStamp timeStamp;
const char * functionName = "readFloat64";
printf("call readFloat64\n");
getTimeStamp(&timeStamp);
pasynUser->timestamp = timeStamp;
getDoubleParam(function, &ftemp);
* value = ftemp;
printf("value = %f\n", ftemp);
if (status)
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
"%s:%s: status=%d, function=%d",
driverName, functionName, status, function);
else
asynPrint(pasynUser, ASYN_TRACEIO_DRIVER,
"%s:%s: function=%d\n",
driverName, functionName, function);
return status;
}
asynStatus testAsynPortDriver::readInt32(asynUser * pasynUser, epicsInt32 * value)
{
int function = pasynUser->reason;
epicsInt32 itemp;
asynStatus status = asynSuccess;
epicsTimeStamp timeStamp;
const char * functionName = "readInt32";
printf("call readInt32\n");
getTimeStamp(&timeStamp);
pasynUser->timestamp = timeStamp;
getIntegerParam(function, &itemp);
* value = itemp;
printf("value = %d\n", itemp);
if (status)
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
"%s:%s: status=%d, function=%d",
driverName, functionName, status, function);
else
asynPrint(pasynUser, ASYN_TRACEIO_DRIVER,
"%s:%s: function=%d\n",
driverName, functionName, function);
return status;
}
int testAsynPortDriver::getIntRand()
{
int num;
num = rand() % 100 + 1;
printf("call getIntRand() = %d\n", num);
return num;
}
float testAsynPortDriver::getFloatRand()
{
float num;
num = this->getIntRand();
return num / 100.0;
}
// 向EPICS注册端口驱动程序
extern "C" {
int testAsynPortDriverConfigure(const char *portName)
{
new testAsynPortDriver(portName);
return(asynSuccess);
}
/* EPICS iocsh shell commands */
static const iocshArg initArg0 = { "portName",iocshArgString};
static const iocshArg * const initArgs[] = {&initArg0,};
static const iocshFuncDef initFuncDef = {"testAsynPortDriverConfigure",1,initArgs};
static void initCallFunc(const iocshArgBuf *args)
{
testAsynPortDriverConfigure(args[0].sval);
}
void testAsynPortDriverRegister(void)
{
iocshRegister(&initFuncDef,initCallFunc);
}
epicsExportRegistrar(testAsynPortDriverRegister);
}
添加以下一个testAsynPortDriverSupport.dbd支持文件:
registrar("testAsynPortDriverRegister")
编辑相同目录下的Makefile文件:
TOP=../..
include $(TOP)/configure/CONFIG
#----------------------------------------
# ADD MACRO DEFINITIONS AFTER THIS LINE
#=============================
LIBRARY_IOC += testAsynPortDriverSupport
# Compile and add code to the support library
LIB_SRCS += testAsynPortDriver.cpp
LIB_LIBS += asyn
LIB_LIBS += $(EPICS_BASE_IOC_LIBS)
#
#=============================
# Build the IOC application
PROD_IOC = testAsynPortDriver
# testAsynPortDriver.dbd will be created and installed
DBD += testAsynPortDriver.dbd
# testAsynPortDriver.dbd will be made up from these files:
testAsynPortDriver_DBD += base.dbd
testAsynPortDriver_DBD += asyn.dbd
testAsynPortDriver_DBD += testAsynPortDriverSupport.dbd
# Include dbd files from all support applications:
#testAsynPortDriver_DBD += xxx.dbd
# Add all the support libraries needed by this IOC
testAsynPortDriver_LIBS += asyn
testAsynPortDriver_LIBS += testAsynPortDriverSupport
# testAsynPortDriver_registerRecordDeviceDriver.cpp derives from testAsynPortDriver.dbd
testAsynPortDriver_SRCS += testAsynPortDriver_registerRecordDeviceDriver.cpp
# Build the main IOC entry point on workstation OSs.
testAsynPortDriver_SRCS_DEFAULT += testAsynPortDriverMain.cpp
testAsynPortDriver_SRCS_vxWorks += -nil-
# Add support from base/src/vxWorks if needed
#testAsynPortDriver_OBJS_vxWorks += $(EPICS_BASE_BIN)/vxComLibrary
# Finally link to the EPICS Base libraries
testAsynPortDriver_LIBS += $(EPICS_BASE_IOC_LIBS)
#===========================
include $(TOP)/configure/RULES
4) 进入到 testAsynPortDriverApp/Db/目录,编辑一个名为testAsynPortDriver.db的数据库文件,内容如下:
###################################################################
# 这两个记录控制启动/停止,连接程序参数库中索引为P_RUN的变量 #
###################################################################
record(bo, "$(P)$(R)Run")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_RUN")
field(ZNAM, "Stop")
field(ONAM, "Run")
}
record(bi, "$(P)$(R)Run_RBV")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_RUN")
field(ZNAM, "Done")
field(ZSV, "NO_ALARM")
field(ONAM, "Running")
field(OSV, "MINOR")
field(SCAN, "I/O Intr")
}
###################################################################
# 这两个记录控制一个浮点数,连接程序参数库中索引为P_Float的参数 #
###################################################################
record(ao, "$(P)$(R)Float")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_FLOAT")
field(PREC, "3")
}
record(ai, "$(P)$(R)Float_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_FLOAT")
field(PREC, "3")
field(SCAN, "I/O Intr")
}
###################################################################
# 这个记录读取一个浮点数(0-1),连接程序参数库中索引为P_FloatRand的参数#
###################################################################
record(ai, "$(P)$(R)FloatRand_RBV")
{
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_FLOATRAND")
field(PREC, "2")
field(SCAN, "I/O Intr")
}
###################################################################
# 这些记录控制一个整数,连接程序参数库中索引为P_Int的参数 #
###################################################################
record(longin, "$(P)$(R)Int_RBV")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_INT")
field(SCAN, "I/O Intr")
}
record(longout, "$(P)$(R)Int")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_INT")
}
###################################################################
# 这个记录读取一个随机整数,连接程序参数库中索引为P_IntRand的参数 #
###################################################################
record(longin, "$(P)$(R)IntRand_RBV")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))PROCESS_INTRAND")
field(SCAN, "I/O Intr")
}
设置相同目录中Makefile文件,向文件中添加以下一行:
DB += testAsynPortDriver.db
5) 返回到这个IOC应用程序的顶层目录,执行make编译。
6) 进入到iocBoot/ioctestAsynPortDriver目录下,编译启动脚本st.cmd,内容如下:
#!../../bin/linux-x86_64/testAsynPortDriver
#- You may have to change testAsynPortDriver to something else
#- everywhere it appears in this file
< envPaths
cd "${TOP}"
## Register all support components
dbLoadDatabase "dbd/testAsynPortDriver.dbd"
testAsynPortDriver_registerRecordDeviceDriver pdbbase
## Load record instances
testAsynPortDriverConfigure("testAPD")
dbLoadRecords("db/testAsynPortDriver.db","P=BLCTRL:,R=Test:,PORT=testAPD,ADDR=0,TIMEOUT=1")
cd "${TOP}/iocBoot/${IOC}"
iocInit
7)启动这个IOC,用dbl查看,IOC中加载的记录:
[blctrl@main-machine ioctestAsynPortDriver]$ ../../bin/linux-x86_64/testAsynPortDriver st.cmd
#!../../bin/linux-x86_64/testAsynPortDriver
< envPaths
epicsEnvSet("IOC","ioctestAsynPortDriver")
epicsEnvSet("TOP","/home/blctrl/exer/exer42")
epicsEnvSet("SUPPORT","/usr/local/EPICS/synApps/support")
epicsEnvSet("ASYN","/usr/local/EPICS/synApps/support/asyn")
epicsEnvSet("EPICS_BASE","/usr/local/EPICS/base")
cd "/home/blctrl/exer/exer42"
## Register all support components
dbLoadDatabase "dbd/testAsynPortDriver.dbd"
testAsynPortDriver_registerRecordDeviceDriver pdbbase
## Load record instances
testAsynPortDriverConfigure("testAPD")
P_Run:0,P_Int:1,P_Float:2,P_IntRand:3,P_FloatRand:4
dbLoadRecords("db/testAsynPortDriver.db","P=BLCTRL:,R=Test:,PORT=testAPD,ADDR=0,TIMEOUT=1")
cd "/home/blctrl/exer/exer42/iocBoot/ioctestAsynPortDriver"
iocInit
Starting iocInit
############################################################################
## EPICS R7.0.3.1
## EPICS Base built Sep 8 2022
############################################################################
call readInt32
value = 0
call readInt32
value = 0
call readFloat64
value = 0.000000
call writeInt32
call readInt32
value = 0
call writeInt32
call readFloat64
value = 0.000000
call readInt32
value = 0
call readInt32
value = 0
call writeFloat64
iocRun: All initialization complete
## Start any sequence programs
#seq sncxxx,"user=blctrl"
epics> dbl
BLCTRL:Test:Int
BLCTRL:Test:Run_RBV
BLCTRL:Test:Run
BLCTRL:Test:Float_RBV
BLCTRL:Test:FloatRand_RBV
BLCTRL:Test:Int_RBV
BLCTRL:Test:IntRand_RBV
BLCTRL:Test:Float
epics>
8) 用CS Stdio设计一个客户端来查看这个IOC程序中的PV值,并且对这些PV值进行修改,测试这个程序的运行效果:
点击Run后,程序运行,IntRand和FloatRand对应的两个框中数值每秒钟变化一次,点击Stop后,程序运行停止,IntRand和FloatRand对应的两个框中数值不会变化。