EPICS -- 使用asynPortDriver类编写示一个示例程序

本示例展示了如何使用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, &paramName);

        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, &paramName);

        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对应的两个框中数值不会变化。

 

猜你喜欢

转载自blog.csdn.net/yuyuyuliang00/article/details/128220307