EPICS asyn测试程序--如何连接asynManager

这是一个如何连接asynManager的示例。这个示例放在/testApp中并且包含以下组件:

root@orangepi5:/usr/local/EPICS/program/asynTest/testApp# ls -R
.:
Db  Makefile  src

./Db:
Makefile  test.db

./src:
echoDriver.c  echoDriver.dbd  Makefile  testMain.cpp

echoDriver是一个端口驱动程序,它回应它接收的消息。
它实现了asynCommon和asynOctet。当asynOctet:write被调用时,它保存此条消息。
当asynOctet:read被调用时,保存的消息被返回并且这条小心被清空。
echoDriverInit有一个参数,它确定它行为是否像一个multiDevice或一个单设备端口驱动程序。
通过iocsh命令:
echoDriverInit(portName,delay,noAutoConnect,multiDevice)
启动一个echoDriver的实例。
此处:

  • portName:这个实例的端口名。
  • delay:在读或写后要延时的时间。如果delay为0,则echoDriver以一个同步设备驱动程序进行注册,即未设置ASYN_CANBLOCK属性。如果delay>0,则设置ASYN_CANBLOCK。
  • noAutoConnect:确定端口的初始设置。
  • multiDevice:如果true,则它支持地址0和1的两个设备。如果false,它不设置ASYN_MULTIDEVICE,即,它仅支持单个设备。 

echoDriver.c的源代码如下:

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#include 
#include 

#include 
#define BUFFERSIZE 4096
#define NUM_DEVICES 2

typedef struct deviceBuffer {
    char buffer[BUFFERSIZE];
    size_t  nchars;
}deviceBuffer;

typedef struct deviceInfo {
    deviceBuffer buffer;
    int          connected;
}deviceInfo;

/*
    typedef struct asynInterface{
        const char *interfaceType; 例如asynCommonType 
        void *pinterface;          例如pasynCommon 
        void *drvPvt;
    }asynInterface;
*/
typedef struct echoPvt {
    deviceInfo    device[NUM_DEVICES];
    const char    *portName;
    int           connected;
    int           multiDevice;
    double        delay;
    asynInterface common;
    asynInterface octet;
    char          eos[2];
    int           eoslen;
    void          *pasynPvt;   /*用于注册registerInterruptSource*/
}echoPvt;
    
/* init routine */
static int echoDriverInit(const char *dn, double delay, int noAutoConnect,int multiDevice);

/*
    asynUser:描述一个结构体,用户代码传递此结构体给大多数asynManager和驱动方法。
    代码必须调用asynManager:createAsynUser(或asynManager:duplicateAsynUser)和asynMananger:freeAsynUser分配和释放一个asynUser.


    typedef struct asynUser {
        char          *errorMessage;
        int            errorMessageSize;
        double         timeout;  // 必须由用户设置timeout,用于I/O操作的Timeout
        void          *userPvt; 
        void          *userData; 
        void          *drvUser;  // 驱动程序使用 
        int            reason;   
        // 以下通常由驱动程序通过asynDrvUser->create()设置 
        epicsTimeStamp timestamp;
        // 以下用于来自方法调用的更多信息
        int            auxStatus; 
        int            alarmStatus;   
        int            alarmSeverity; 
    }
*/

/*
    产生一个关于硬件设备的报告。
    这是唯一的asynCommon方法,它不是必须被queueRequest回调或者在lockPort/unlockPort调用之间调用
*/
static void report(void *drvPvt,FILE *fp,int details);
/*
    连接硬件设备或者通信路径。queueRequest必须指定优先级asynQueuePriorityConnect.
*/
static asynStatus connect(void *drvPvt,asynUser *pasynUser);
/*
    从硬件设备或者通信路径断开。queueRequest必须指定优先级asynQueuePriorityConnect
*/
static asynStatus disconnect(void *drvPvt,asynUser *pasynUser);
static asynCommon asyn = { report, connect, disconnect };



static asynStatus echoWrite(void *drvPvt,asynUser *pasynUser,
    const char *data,size_t numchars,size_t *nbytesTransfered);
static asynStatus echoRead(void *drvPvt,asynUser *pasynUser,
    char *data,size_t maxchars,size_t *nbytesTransfered,int *eomReason);
static asynStatus echoFlush(void *drvPvt,asynUser *pasynUser);
static asynStatus setEos(void *drvPvt,asynUser *pasynUser,
    const char *eos,int eoslen);
static asynStatus getEos(void *drvPvt,asynUser *pasynUser,
    char *eos, int eossize, int *eoslen);


static int echoDriverInit(const char *dn, double delay,
    int noAutoConnect,int multiDevice)
{
    echoPvt    *pechoPvt;
    char       *portName;
    asynStatus status;
    size_t     nbytes;
    int        attributes;
    asynOctet  *pasynOctet;

    //  计算所需空间:echoPvt结构体 + asynOctet结构体 + 用于存储传入字符串的字节,并且分配所需空间
    nbytes = sizeof(echoPvt) + sizeof(asynOctet) + strlen(dn) + 1;
    pechoPvt = callocMustSucceed(nbytes,sizeof(char),"echoDriverInit");
    // pasynOctet指向分配空间中asynOctet结构体首地址
    pasynOctet = (asynOctet *)(pechoPvt + 1);
    // 指向存储传入字符串的首地址
    portName = (char *)(pasynOctet + 1);
    // 把传入字符串复制到为其准备的空间
    strcpy(portName,dn);
    // 初始化分配空间中echoPvt结构体中的成员变量
    pechoPvt->portName = portName;
    pechoPvt->delay = delay;
    pechoPvt->multiDevice = multiDevice;
    /* 
        初始化echoPvt结构体中成员变量:
        common成员变量:asynInterface结构体,const char * interfaceType, void * pinterface和void * drvPvt
    
    */
    pechoPvt->common.interfaceType = asynCommonType;
    pechoPvt->common.pinterface  = (void *)&asyn;
    pechoPvt->common.drvPvt = pechoPvt;
    attributes = 0;
    if(multiDevice) attributes |= ASYN_MULTIDEVICE;
    if(delay>0.0) attributes|=ASYN_CANBLOCK;
    /*
    这个方法被驱动程序调用。为每个端口实例调用一次。
    属性是一个位集合。当前定义了两个属性:ASYN_MULTIDEVICE和ASYN_CANBLOCK.
    驱动程序必须合适地指定这些。autoConnect,其是(0,1)代表(no, yes),为这个端口和连接这个端口的所有设备提供初始值
    仅在ASYN_CANBLOCK=1时,优先级和栈大小才有作用,在这种情况下,
    在asynManager用epicsThreadCreate()创建这个端口驱动时,它使用这些值。
    如果priority是0,则将分配默认值epicsThreadPriorityMedium.
    如果stackSize是0,则将分配默认值epicsThreadGetStackSize(epicsThreadStackMedium).
    portName参数指定名称,asyn代码的上层通过其指向这个通信接口实例。
    registerPort方法进行这个字符串的内部复制到名称参数指向的位置。
    */
    status = pasynManager->registerPort(portName,attributes,!noAutoConnect,0,0);
    if(status!=asynSuccess) {
        printf("echoDriverInit registerDriver failed\n");
        return 0;
    }
    /*
        由端口驱动程序为每个支持的接口调用。
        这个方法不复制这个aysnInterface到pasynInterface参数指向的地方。
        调用者必须在一个为此端口存在期间保留的地方中存储这个asynInterface.
        常见通过'driver private'结构体中放置这个asynInterface做这件事。
        echoPvt结构体中类型为asynInterface的成员变量common
     */
    status = pasynManager->registerInterface(portName,&pechoPvt->common);
    if(status!=asynSuccess){
        printf("echoDriverInit registerInterface failed\n");
        return 0;
    }

    /* 初始化asynOctet结构体部分:指向echoPvt结构体中asynInterface类型成员变量octet*/
    pasynOctet->write = echoWrite;
    pasynOctet->read = echoRead;
    pasynOctet->flush = echoFlush;
    pasynOctet->setInputEos = setEos;
    pasynOctet->getInputEos = getEos;
    pechoPvt->octet.interfaceType = asynOctetType;
    pechoPvt->octet.pinterface  = pasynOctet;
    pechoPvt->octet.drvPvt = pechoPvt;
    /*
    在驱动程序调用regisgerPort后,它调用pasynOctetBase->initialize(...)
    在asynIntreface中任何null方法被默认实现替代。如果端口不是多设备的并且指定了processEosIn或processEosOut
    asynInterposeEosConfig被调用。如果端口不是多设备并且指定了interruptProcess,
    则在read被调用时,asynBase调用所有注册的中断用户。如果端口是多设备端口,asynOctetBase不能实现processEosIn
    ,processEosOut和interruptProcess.
    由于此方法仅在初始化过程中才被调用,它能够被直接调用而不是通过queueRequest.

        asynStatus (*initialize)(const char *portName,
            asynDriverasynInterface *pasynOctetInterface,
            int processEosIn,int processEosOut,int interruptProcess);
    asynOctetBase是一个对实现了接口asynOctet的驱动程序的接口和实现。
    asynOctetBase实现了registerInterruptUser和cancelInterruptUser.
    对于单设备支持,它能够可选地实现中断支持。实现了中断地驱动程序必须调用registerInterruptSource. 
    如果它请求asynOctetBase处理中断,当其有新数据时它调用synOctetBase:callInterruptUsers 。
    对于单设备支持, asynOctetBase能够可选地调用asynInterposeEosConfig来为输入和/或输出处理字符串结尾。
    在此接口中被传递去初始化地任何null方法被asynOctetBase提供地方法替代。
    */
    if(multiDevice) {
        status = pasynOctetBase->initialize(portName,&pechoPvt->octet,0,0,0);
    } else {
        status = pasynOctetBase->initialize(portName,&pechoPvt->octet,1,1,0);
    }
    if(status==asynSuccess)
        /*
            如果一个底层驱动程序支持中断,它必须为每个支持中断的接口调用这个方法。
            pasynPvt必须是一个void*的地址,其将由registerInterruptSource传给一个值。
            这个参数在interruptStart和interruptEnd中传递。
             asynStatus (*interruptStart)(void *pasynPvt,ELLLIST **plist);
             asynStatus (*interruptEnd)(void *pasynPvt);
        */
        status = pasynManager->registerInterruptSource(
            portName,&pechoPvt->octet,&pechoPvt->pasynPvt);
    if(status!=asynSuccess){
        printf("echoDriverInit registerInterface failed\n");
        return 0;
    }
    return(0);
}

/* asynCommon 方法:drvPvt实际传递的是echoPvt的地址 */
static void report(void *drvPvt,FILE *fp,int details)
{
    echoPvt *pechoPvt = (echoPvt *)drvPvt;
    int i,n;
    // 向指定文件输出:是否多设备,端口是否连接,延时时间,
    // 然后输出端口下每个设备信息:是否连接,以及设备中内容
    fprintf(fp,"    echoDriver. "
        "multiDevice:%s connected:%s delay = %f\n",
        (pechoPvt->multiDevice ? "Yes" : "No"),
        (pechoPvt->connected ? "Yes" : "No"),
        pechoPvt->delay);
    n = (pechoPvt->multiDevice) ? NUM_DEVICES : 1;
    for(i=0;idevice[i].connected ? "Yes" : "No"),
            (int)pechoPvt->device[i].buffer.nchars);
    }
}

static asynStatus connect(void *drvPvt,asynUser *pasynUser)
{
    echoPvt    *pechoPvt = (echoPvt *)drvPvt;
    deviceInfo *pdeviceInfo;
    int        addr;
    asynStatus status;

    /*
        addr被设置为用户在调用connectDevice中指定的地址或者如果端口不支持多设备,为-1。      
    */
    status = pasynManager->getAddr(pasynUser,&addr);
    if(status!=asynSuccess) return status;//获取设备地址失败
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
        "%s echoDriver:connect addr %d\n",pechoPvt->portName,addr);
    if(!pechoPvt->multiDevice) {//端口非多设备
        if(pechoPvt->connected) {//端口已经连接
            asynPrint(pasynUser,ASYN_TRACE_ERROR,
               "%s echoDriver:connect port already connected\n",
               pechoPvt->portName);
            return asynError;
        }
        /* 模拟连接延时 */
        if(pechoPvt->delay>0.0) epicsThreadSleep(pechoPvt->delay*10.);
        pechoPvt->connected = 1; //设置端口连接
        pechoPvt->device[0].connected = 1; // 设置端口上第一个设备连接
        /*
            当前仅当驱动程序连接一个端口或设备时,它必须调用这个方法。
        */
        pasynManager->exceptionConnect(pasynUser);
        return asynSuccess;
    }
    if(addr<=-1) {//addr小于等于1,表示是单设备,测试这个端口是否已经连接
    // 已经连接,则报错,未连接,则设置其连接
        if(pechoPvt->connected) {
            asynPrint(pasynUser,ASYN_TRACE_ERROR,
               "%s echoDriver:connect port already connected\n",
               pechoPvt->portName);
            return asynError;
        }
        /* simulate connection delay */
        if(pechoPvt->delay>0.0) epicsThreadSleep(pechoPvt->delay*10);
        pechoPvt->connected = 1;
        pasynManager->exceptionConnect(pasynUser);
        return asynSuccess;
    }// addr已经超出了最大设备索引,直接报错
    if(addr>=NUM_DEVICES) {
        asynPrint(pasynUser,ASYN_TRACE_ERROR,
            "%s echoDriver:connect illegal addr %d\n",pechoPvt->portName,addr);
        return asynError;
    }
    // 由addr获取设备结构体deviceInfo,并且测试此设备其是否已经连接
    // 如果此设备已经连接,则报错,未连接,则设置此设备连接
    pdeviceInfo = &pechoPvt->device[addr];
    if(pdeviceInfo->connected) {
        asynPrint(pasynUser,ASYN_TRACE_ERROR,
            "%s echoDriver:connect device %d already connected\n",
            pechoPvt->portName,addr);
        return asynError;
    }
    /* 模拟连接延时 */
    if(pechoPvt->delay>0.0) epicsThreadSleep(pechoPvt->delay*10.);
    pdeviceInfo->connected = 1;
    pasynManager->exceptionConnect(pasynUser);
    return(asynSuccess);
}

static asynStatus disconnect(void *drvPvt,asynUser *pasynUser)
{
    echoPvt    *pechoPvt = (echoPvt *)drvPvt;
    deviceInfo *pdeviceInfo;
    int        addr;
    asynStatus status;
    // addr被设置为用户在调用connectDevice中指定的地址或者如果端口不支持多设备,为-1。
    status = pasynManager->getAddr(pasynUser,&addr);
    if(status!=asynSuccess) return status;
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
        "%s echoDriver:disconnect addr %d\n",pechoPvt->portName,addr);
     // 不是多设备端口
    if(!pechoPvt->multiDevice) {
        if(!pechoPvt->connected) {// 端口未连接,报错
            asynPrint(pasynUser,ASYN_TRACE_ERROR,
               "%s echoDriver:disconnect port not connected\n",
               pechoPvt->portName);
            return asynError;
        }
        // 端口连接,则设置其断开,设备也设置未断开
        pechoPvt->connected = 0;
        pechoPvt->device[0].connected = 0;
        /*
        当且仅当驱动程序从一个端口或设备断开时,它调用这个方法。
        */
        pasynManager->exceptionDisconnect(pasynUser);
        return asynSuccess;
    }
    if(addr<=-1) {//表示单设备端口,测试端口是否连接,如果未连接,则报错,否则设置端口断开
        if(!pechoPvt->connected) {
            asynPrint(pasynUser,ASYN_TRACE_ERROR,
               "%s echoDriver:disconnect port not connected\n",
               pechoPvt->portName);
            return asynError;
        }
        pechoPvt->connected = 0;
        /*
        当且仅当驱动程序从一个端口或设备断开时,它调用这个方法。
        */
        pasynManager->exceptionDisconnect(pasynUser);
        return asynSuccess;
    }
    if(addr>=NUM_DEVICES) {// 设备索引超过了最大设备索引,报错
        asynPrint(pasynUser,ASYN_TRACE_ERROR,
            "%s echoDriver:disconnect illegal addr %d\n",pechoPvt->portName,addr);
        return asynError;
    }
    pdeviceInfo = &pechoPvt->device[addr]; // 获取addr对应的设备结构体
    if(!pdeviceInfo->connected) {//测试这个设备是否连接,未连接则报错,否则设置此设备断开
        asynPrint(pasynUser,ASYN_TRACE_ERROR,
            "%s echoDriver:disconnect device %d not connected\n",
            pechoPvt->portName,addr);
        return asynError;
    }
    pdeviceInfo->connected = 0;
    pasynManager->exceptionDisconnect(pasynUser);
    return(asynSuccess);
}

/* asynOctet 方法:将指定内容复制到指定设备内容缓存中 */
static asynStatus echoWrite(void *drvPvt,asynUser *pasynUser,
    const char *data,size_t nchars,size_t *nbytesTransfered)
{
    echoPvt      *pechoPvt = (echoPvt *)drvPvt;
    deviceInfo   *pdeviceInfo;   //存储设备信息的结构体
    deviceBuffer *pdeviceBuffer; //存储设备中内容的结构体
    int          addr;
    asynStatus   status;
    int i;

    // addr被设置为用户在调用connectDevice中指定的地址或者如果端口不支持多设备,为-1。
    status = pasynManager->getAddr(pasynUser,&addr);
    if(status!=asynSuccess) return status;
    // 如果非多设备端口,设置addr=0
    if(!pechoPvt->multiDevice) addr = 0;
    // 调试信息,端口名称 echoDriver:write addr 获取的设备索引
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
        "%s echoDriver:write addr %d\n",pechoPvt->portName,addr);
    // addr在有效范围外
    if(addr<0 || addr>=NUM_DEVICES) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "addr %d is illegal. Must be 0 or 1",addr);
        return asynError;
    }
    // 根据addr获取deviceInfo结构体
    pdeviceInfo = &pechoPvt->device[addr];
    // 设备未连接,则产生报错信息,并且返回
    if(!pdeviceInfo->connected) {
        asynPrint(pasynUser,ASYN_TRACE_ERROR,
            "%s echoDriver:write device %d not connected\n",
            pechoPvt->portName,addr);
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "%s echoDriver:write device %d not connected",
            pechoPvt->portName,addr);
        return asynError;
    }
    
    
    if(pechoPvt->delay>pasynUser->timeout) {
        if(pasynUser->timeout>0.0) epicsThreadSleep(pasynUser->timeout);
        asynPrint(pasynUser, ASYN_TRACE_ERROR,
            "%s echoDriver write timeout\n",pechoPvt->portName);
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "%s echoDriver write timeout",pechoPvt->portName);
        return asynTimeout;
    }
    // 获取设备中内容地址
    pdeviceBuffer = &pdeviceInfo->buffer;
    if(nchars>BUFFERSIZE) nchars = BUFFERSIZE;
    // 复制nchars个data中字节到设备内容缓存
    if(nchars>0) 
    {
	    memcpy(pdeviceBuffer->buffer,data,nchars);
	    printf("write to device: %d\n", addr);
	    for (i = 0; i < nchars; i++){
	    	printf("%c", data[i]);
	    }
	    printf("\n");
	    
    }
    asynPrintIO(pasynUser,ASYN_TRACEIO_DRIVER,data,nchars,
            "echoWrite nchars %lu\n",(unsigned long)nchars);
    pdeviceBuffer->nchars = nchars;//记录复制的字节数目
    if(pechoPvt->delay>0.0) epicsThreadSleep(pechoPvt->delay);
    *nbytesTransfered = nchars;
    return status;
}

static asynStatus echoRead(void *drvPvt,asynUser *pasynUser,
    char *data,size_t maxchars,size_t *nbytesTransfered,int *eomReason)
{
    echoPvt      *pechoPvt = (echoPvt *)drvPvt;
    deviceInfo   *pdeviceInfo;
    deviceBuffer *pdeviceBuffer;
    char         *pfrom,*pto;
    char         thisChar;
    size_t       nremaining;
    size_t       nout = 0;
    int          addr;
    asynStatus   status;

    if(eomReason) *eomReason=0; //emoReason不指向空,则初始化emoReaso指向位置上为0
    if(nbytesTransfered) *nbytesTransfered = 0; // nbytesTransfered不指向空,则初始化nbytesTransfered指向位置上为0
    // 获取设备地址
    status = pasynManager->getAddr(pasynUser,&addr);
    if(status!=asynSuccess) return status;
    if(!pechoPvt->multiDevice) addr = 0; //不是多设备端口,addr为0
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
        "%s echoDriver:read addr %d\n",pechoPvt->portName,addr);
    if(addr<0 || addr>=NUM_DEVICES) {//设备地址不再有效范围内,报错
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "addr %d is illegal. Must be 0 or 1",addr);
        return(0);
    }
    // 根据设备地址,获取设备信息结构体deviceInfo
    pdeviceInfo = &pechoPvt->device[addr];
    if(!pdeviceInfo->connected) {// 设备未连接,返回错误
        asynPrint(pasynUser,ASYN_TRACE_ERROR,
            "%s echoDriver:read device %d not connected\n",
            pechoPvt->portName,addr);
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "%s echoDriver:read device %d not connected",
            pechoPvt->portName,addr);
        return asynError;
    }
    if(pechoPvt->delay>pasynUser->timeout) {//设备延时大于用户指定延时,报超时错误
        if(pasynUser->timeout>0.0) epicsThreadSleep(pasynUser->timeout);
        asynPrint(pasynUser, ASYN_TRACE_ERROR,
            "%s echoDriver read timeout\n",pechoPvt->portName);
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "%s echoDriver read timeout",pechoPvt->portName);
        return asynTimeout;
    }
    // 延时设备指定的延时
    if(pechoPvt->delay>0.0) epicsThreadSleep(pechoPvt->delay);
    pdeviceBuffer = &pdeviceInfo->buffer;//设备内容缓冲区,为结构体deviceBuffer
    nremaining = pdeviceBuffer->nchars; //缓冲区中的字节数
    pdeviceBuffer->nchars = 0;
    pfrom = pdeviceBuffer->buffer; //字节源
    pto = data; //目标地址
    printf("read from device: %d\n", addr);
    while(nremaining>0 && nouteoslen>0) {
            if(thisChar==pechoPvt->eos[0]) {// 测试当前字节是否是结束字符
                if(pechoPvt->eoslen==1) {// 如果当前字节是结束字符,则结束原因位或ASYN_EOM_EOS,退出复制过程
                    if(eomReason) *eomReason |= ASYN_EOM_EOS;
                    break;
                }
                if(nremaining==0) {// 源地址中字节已经被全部复制
                    if(eomReason) *eomReason |= ASYN_EOM_CNT;
                    break;
                }
                if(*pfrom==pechoPvt->eos[1]) {//两个结束字节都符合
                    *pto++ = *pfrom++; nremaining--; nout++; //在从源地址复制一个字节到目标地址
                    if(eomReason) {// 结束原因按位或ASYN_EOM_EOS,如果剩余字节为0,再位或ASYN_EOM_CNT
                        *eomReason |= ASYN_EOM_EOS;
                        if(nremaining==0) *eomReason |= ASYN_EOM_CNT;
                        break;
                    }
                }
            }
       }
    }
    printf("\n");
    // 设置实际输出字节数目
    if(nbytesTransfered) *nbytesTransfered = nout;
    if(eomReason) {//输出字节数目大于等于设定输出的最大字节,则结束原因带上ASYN_EOM_CNT
        if(*nbytesTransfered>=maxchars) *eomReason |= ASYN_EOM_CNT;
        if(nremaining==0) *eomReason |= ASYN_EOM_END;//如果剩余字节数目为零,则结束原因带上ASYN_EOM_END
    }
    
    /*
       void (*callInterruptUsers)(asynUser *pasynUser,void *pasynPvt,
         char *data,size_t *nbytesTransfered,int *eomReason);
        调用通过registerInterruptUser注册的回调。
    */
    pasynOctetBase->callInterruptUsers(pasynUser,pechoPvt->pasynPvt,
        data,nbytesTransfered,eomReason);
    asynPrintIO(pasynUser,ASYN_TRACEIO_DRIVER,data,nout,
        "echoRead nbytesTransfered %lu\n",(unsigned long)*nbytesTransfered);
    return status;
}

static asynStatus echoFlush(void *drvPvt,asynUser *pasynUser)
{
    echoPvt *pechoPvt = (echoPvt *)drvPvt;
    deviceInfo *pdeviceInfo;
    deviceBuffer *pdeviceBuffer;
    int          addr;
    asynStatus   status;
    // 获取设备地址 
    status = pasynManager->getAddr(pasynUser,&addr);
    if(status!=asynSuccess) return status;
    if(!pechoPvt->multiDevice) addr = 0;//端口为单设备
    asynPrint(pasynUser, ASYN_TRACE_FLOW,
        "%s echoDriver:flush addr %d\n",pechoPvt->portName,addr);
    if(addr<0 || addr>=NUM_DEVICES) {//设备索引在有效范围外
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "addr %d is illegal. Must be 0 or 1",addr);
        return(0);
    }
    //通过addr获取devcieInfo结构体
    pdeviceInfo = &pechoPvt->device[addr];
    if(!pdeviceInfo->connected) {
        asynPrint(pasynUser,ASYN_TRACE_ERROR,
            "%s echoDriver:flush device %d not connected\n",
            pechoPvt->portName,addr);
        return -1;
    }
    // 获取缓存deviceBuffer,并且设置其nchar=0
    pdeviceBuffer = &pdeviceInfo->buffer;
    asynPrint(pasynUser,ASYN_TRACE_FLOW,
        "%s echoFlush\n",pechoPvt->portName);
    pdeviceBuffer->nchars = 0;
    return(asynSuccess);
}

static asynStatus setEos(void *drvPvt,asynUser *pasynUser,
     const char *eos,int eoslen)
{
    echoPvt *pechoPvt = (echoPvt *)drvPvt;
    int     i;

    if(eoslen>2 || eoslen<0) {
        epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
            "setEos illegal eoslen %d",eoslen);
        return(asynError);
    }
    pechoPvt->eoslen = eoslen;
    for(i=0; ieos[i] = eos[i];
    asynPrint(pasynUser,ASYN_TRACE_FLOW, "%s setEos\n",pechoPvt->portName);
    return(asynSuccess);
}

static asynStatus getEos(void *drvPvt,asynUser *pasynUser,
    char *eos, int eossize, int *eoslen)
{
    echoPvt *pechoPvt = (echoPvt *)drvPvt;
    int     i;

    *eoslen = pechoPvt->eoslen;
    for(i=0; i<*eoslen; i++) eos[i] = pechoPvt->eos[i];
    asynPrint(pasynUser,ASYN_TRACE_FLOW, "%s setEos\n",pechoPvt->portName);
    return(asynSuccess);
}

/* 注册:echoDriverInit*/
/*
    echoDriverInit需要四个参数,定义参数名称以及参数类型
*/
static const iocshArg echoDriverInitArg0 = { "portName", iocshArgString };
static const iocshArg echoDriverInitArg1 = { "delay", iocshArgDouble };
static const iocshArg echoDriverInitArg2 = { "disable auto-connect", iocshArgInt };
static const iocshArg echoDriverInitArg3 = { "multiDevice", iocshArgInt };
static const iocshArg *echoDriverInitArgs[] = {
    &echoDriverInitArg0,&echoDriverInitArg1,
    &echoDriverInitArg2,&echoDriverInitArg3};

// 函数定义:初始化函数名称,需要的参数数目,参数数组    
static const iocshFuncDef echoDriverInitFuncDef = {
    "echoDriverInit", 4, echoDriverInitArgs};

// 函数调用    
static void echoDriverInitCallFunc(const iocshArgBuf *args)
{
    echoDriverInit(args[0].sval,args[1].dval,args[2].ival,args[3].ival);
}

static void echoDriverRegister(void)
{
    static int firstTime = 1;
    if (firstTime) {
        firstTime = 0;
        // 注册:函数定义,函数调用
        iocshRegister(&echoDriverInitFuncDef, echoDriverInitCallFunc);
    }
}
epicsExportRegistrar(echoDriverRegister);

需要加载的test.db文件内容如下:

record(calc,"$(P)$(R)_P$(PORT)_A$(A)_calc") {
    field(DESC, "Counter")
    field(SCAN,"Passive")
    field(CALC, "(A<99)?(A+1):0")
    field(INPA,"$(P)$(R)_P$(PORT)_A$(A)_calc NPP NMS")
    field(FLNK,"$(P)$(R)_P$(PORT)_A$(A)_so")
    field(EGU, "Counts")
    field(HOPR, "10")
    field(FLNK,"$(P)$(R)_P$(PORT)_A$(A)_so")
}

record(stringout,"$(P)$(R)_P$(PORT)_A$(A)_so") {
    field(DOL,"$(P)$(R)_P$(PORT)_A$(A)_calc NPP NMS")
    field(OMSL,"closed_loop")
    field(FLNK,"$(P)$(R)_P$(PORT)_A$(A)_si")
}

record(stringin,"$(P)$(R)_P$(PORT)_A$(A)_si") {
    field(DTYP,"asynOctetWriteRead")
    field(INP,"@asyn($(PORT),$(A)) $(P)$(R)_P$(PORT)_A$(A)_so")
}

record(stringout, "$(P)$(R)_P$(PORT)_A$(A)_writetodevice")
{
    field(OMSL, "supervisory")
    field(DTYP, "asynOctetWrite")
    field(OUT, "@asyn($(PORT),$(A)) $(P)$(R)_P$(PORT)_A$(A)_writestringtodevice")
    field(FLNK,"$(P)$(R)_P$(PORT)_A$(A)_readfromdevice")
}

record(stringin, "$(P)$(R)_P$(PORT)_A$(A)_readfromdevice")
{

    field(INP,"@asyn($(PORT),$(A)) $(P)$(R)_P$(PORT)_A$(A)_readfromdevice")
    field(DTYP, "asynOctetRead")
}

为了便于理解,画了一下这个程序中使用到结构体的示意图:

编译文件后,设置启动文件st.cmd,内容如下:

#!../../bin/linux-aarch64/test

#- You may have to change test to something else
#- everywhere it appears in this file

< envPaths

cd "${TOP}"

## Register all support components
dbLoadDatabase "dbd/test.dbd"
test_registerRecordDeviceDriver pdbbase

asynSetAutoConnectTimeout(.5)
echoDriverInit("A",0.05,0,0)

## Load record instances
#dbLoadRecords("db/test.db","user=orangepi")
dbLoadRecords("${ASYN}/db/asynRecord.db","P=asyn,R=Record_PA_A0,PORT=A,ADDR=0,OMAX=0,IMAX=0")
dbLoadRecords("db/test.db","P=test,R=Client,PORT=A,A=0")


cd "${TOP}/iocBoot/${IOC}"
iocInit

启动这个IOC,执行dbl查看这个IOC中加载的记录:

epics> dbl
asynRecord_PA_A0
testClient_PA_A0_calc
testClient_PA_A0_si
testClient_PA_A0_readfromdevice
testClient_PA_A0_so
testClient_PA_A0_writetodevice

进行测试,向设备写入一个字符数组:

orangepi@orangepi4-lts:/usr/local/EPICS/synApps/support/asyn/testApp/Db$ caput testClient_PA_A0_writetodevice "helloworld"
Old : testClient_PA_A0_writetodevice
New : testClient_PA_A0_writetodevice helloworld

ioc shell中显示如下:

epics> write to device: 0
helloworld
read from device: 0
helloworld

然后查看testClient_PA_A0_readfromdevice:

orangepi@orangepi4-lts:/usr/local/EPICS/synApps/support/asyn/testApp/Db$ caget testClient_PA_A0_readfromdevice
testClient_PA_A0_readfromdevice helloworld

你可能感兴趣的:(EPICS教程,EPICS,C语言)