在EPICS定义一个新的记录类型

1) 新建一个顶层目录exer13,进入这个目录并且创建一个IOC目录结构:

[blctrl@bjAli exer13]$ makeBaseApp.pl -t ioc xxxTest
[blctrl@bjAli exer13]$ makeBaseApp.pl -i -t ioc xxxTest
Using target architecture linux-x86_64 (only one available)
The following applications are available:
    xxxTest
What application should the IOC(s) boot?
The default uses the IOC's name, even if not listed above.
Application name?
[blctrl@bjAli exer13]$ ls
configure  iocBoot  Makefile  xxxTestApp

2)  进入exer13/xxxTestApp/src目录下,创建以下四个文件:

第一个文件xxxRecord.dbd:xxx记录的数据库定义文件,内容如下:

recordtype(xxx) {
        include "dbCommon.dbd"
        #  定义这个记录的值字段,类型为DBF_DOUBLE
        field(VAL,DBF_DOUBLE) {
                prompt("Current EGU Value")
                promptgroup("40 - Input")
                asl(ASL0)
                pp(TRUE)
        }
        # 定义这个记录的输入链接字段
        field(INP,DBF_INLINK) {
                prompt("Input Specification")
                promptgroup("40 - Input")
                special(SPC_NOMOD)
                interest(1)
        }
        # 定义值字段的精度
        field(PREC,DBF_SHORT) {
                prompt("Display Precision")
                promptgroup("80 - Display")
                interest(1)
        }
        # 定义单位
        field(EGU,DBF_STRING) {
                prompt("Engineering Units")
                promptgroup("80 - Display")
                interest(1)
                size(16)
        }
        # 定义操作的高限
        field(HOPR,DBF_FLOAT) {
                prompt("High Operating Range")
                promptgroup("80 - Display")
                interest(1)
        }
        # 定义操作的低限
        field(LOPR,DBF_FLOAT) {
                prompt("Low Operating Range")
                promptgroup("80 - Display")
                interest(1)
        }
        # 定义高高警报的值
        field(HIHI,DBF_FLOAT) {
                prompt("Hihi Alarm Limit")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
        }
        # 定义低低警报值
        field(LOLO,DBF_FLOAT) {
                prompt("Lolo Alarm Limit")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
        }
        # 定义高警报值
        field(HIGH,DBF_FLOAT) {
                prompt("High Alarm Limit")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
        }
        # 定义低警报值
        field(LOW,DBF_FLOAT) {
                prompt("Low Alarm Limit")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
        }
        # 定义高高警报的严重性
        field(HHSV,DBF_MENU) {
                prompt("Hihi Severity")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
                menu(menuAlarmSevr)
        }
        # 定义低低警报的严重性
        field(LLSV,DBF_MENU) {
                prompt("Lolo Severity")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
                menu(menuAlarmSevr)
        }
        # 定义高警报的严重性
        field(HSV,DBF_MENU) {
                prompt("High Severity")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
                menu(menuAlarmSevr)
        }
        # 定义低警报的严重性
        field(LSV,DBF_MENU) {
                prompt("Low Severity")
                promptgroup("70 - Alarm")
                pp(TRUE)
                interest(1)
                menu(menuAlarmSevr)
        }
        # 定义回滞因子
        field(HYST,DBF_DOUBLE) {
                prompt("Alarm Deadband")
                promptgroup("70 - Alarm")
                interest(1)
        }
        # 定义用于存档的差值
        field(ADEL,DBF_DOUBLE) {
                prompt("Archive Deadband")
                promptgroup("80 - Display")
                interest(1)
        }
        # 定义用于监视的差值
        field(MDEL,DBF_DOUBLE) {
                prompt("Monitor Deadband")
                promptgroup("80 - Display")
                interest(1)
        }
        # 上次产生警报的值
        field(LALM,DBF_DOUBLE) {
                prompt("Last Value Alarmed")
                special(SPC_NOMOD)
                interest(3)
        }
        # 上次存档的值
        field(ALST,DBF_DOUBLE) {
                prompt("Last Value Archived")
                special(SPC_NOMOD)
                interest(3)
        }
        # 上次监视的值
        field(MLST,DBF_DOUBLE) {
                prompt("Last Val Monitored")
                special(SPC_NOMOD)
                interest(3)
        }
}

第二个文件xxxRecord.c,xxx记录的记录支持例程源文件,内容如下:

/* xxxRecord.c */
/* 示例记录的支持模块 */
/* 以下是需要的头文件 */
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "epicsMath.h"
#include "alarm.h"
#include "dbAccess.h"
#include "recGbl.h"
#include "dbEvent.h"
#include "dbDefs.h"
#include "dbAccess.h"
#include "devSup.h"
#include "errMdef.h"
#include "recSup.h"
#include "special.h"
#define GEN_SIZE_OFFSET
#include "xxxRecord.h"    //必须在#define GEN_SIZE_OFFSE和#undef  GEN_SIZE_OFFSET之间
#undef  GEN_SIZE_OFFSET
#include "epicsExport.h"

/* 创建RSET - 记录支持入口表 */
/* 记录不支持的例程都应该被定义为NULL */
#define report NULL
#define initialize NULL
static long init_record(struct dbCommon *, int);
static long process(struct dbCommon *);
#define special NULL
#define get_value NULL
#define cvt_dbaddr NULL
#define get_array_info NULL
#define put_array_info NULL
static long get_units(DBADDR *, char *);
static long get_precision(const DBADDR *, long *);
#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
static long get_graphic_double(DBADDR *, struct dbr_grDouble *);
static long get_control_double(DBADDR *, struct dbr_ctrlDouble *);
static long get_alarm_double(DBADDR *, struct dbr_alDouble *);

rset xxxRSET={
        RSETNUMBER,
        report,
        initialize,
        init_record,
        process,
        special,
        get_value,
        cvt_dbaddr,
        get_array_info,
        put_array_info,
        get_units,
        get_precision,
        get_enum_str,
        get_enum_strs,
        put_enum_str,
        get_graphic_double,
        get_control_double,
        get_alarm_double
};
//导出记录支持模块,rest记录支持,xxxRSET为xxx记录的记录支持
//每种记录支持模块必须包含这样的预计
epicsExportAddress(rset,xxxRSET);
//设备支持模板
typedef struct xxxset { /* xxx记录输入dset */
        long            number;
        DEVSUPFUN       dev_report;
        DEVSUPFUN       init;
        DEVSUPFUN       init_record; /*returns: (-1,0)=>(failure,success)*/
        DEVSUPFUN       get_ioint_info;
        DEVSUPFUN       read_xxx;
}xxxdset;

static void checkAlarms(xxxRecord *prec);
static void monitor(xxxRecord *prec);

/* 初始化xxx记录,记录类型专用 */
static long init_record(struct dbCommon *pcommon, int pass)
{
    xxxRecord *prec = (xxxRecord *)pcommon;   //获取xxx类型的记录实例
    xxxdset     *pdset;
    long        status;

    if (pass==0) return(0);   //此例程第一次被调用

    /* 查看xxx记录的设备支持是否存在 */
    if(!(pdset = (xxxdset *)(prec->dset))) {
        // 状态信息, 记录名称, 正在调用的例程,此处错误表示记录不存在设备支持,记录支持中哪个例程调用中出错
        recGblRecordError(S_dev_noDSET,(void *)prec,"xxx: init_record");
        return(S_dev_noDSET);
    }
    /* 必须定义read_xxx函数 */
    if( (pdset->number < 5) || (pdset->read_xxx == NULL) ) {
    //状态信息,记录名称,调用的记录类型和记录支持例程,此处错误表示缺失必须的设备支持例程
        recGblRecordError(S_dev_missingSup,(void *)prec,"xxx: init_record");
        return(S_dev_missingSup);
    }
    
    // 如果设备支持例程init_record存在,则调用它。
    if( pdset->init_record ) {
        if((status=(*pdset->init_record)(prec))) return(status);
    }
    return(0);
}

static long process(struct dbCommon *pcommon)
{
        xxxRecord   *prec = (xxxRecord *)pcommon;
        xxxdset         *pdset = (xxxdset *)(prec->dset);  //获取记录的设备支持模块
        long             status;
        unsigned char    pact=prec->pact;//存储记录的PACT

        // 如果设备支持不存在,或者设备支持模块中read_xxx例程不存在,则报错
        if( (pdset==NULL) || (pdset->read_xxx==NULL) ) {
                prec->pact=TRUE;
                recGblRecordError(S_dev_missingSup,(void *)prec,"read_xxx missing");
                return(S_dev_missingSup);
        }

        /* 在调用设备支持前,pact一定不能被设置 */
        status=(*pdset->read_xxx)(prec);//调用设备支持的read_xxx例程
        /* 检查设备支持是否设置了pact */
        if ( !pact && prec->pact ) return(0);// 表示设备支持已经启动
        prec->pact = TRUE;

        recGblGetTimeStamp(prec); //获取时间戳
        /* 检查警报 */
        checkAlarms(prec);
        /* 检查事件列表 */
        monitor(prec);
        /* 运行forward扫描链接记录 */
        recGblFwdLink(prec);

        prec->pact=FALSE;  //到此记录运行结束
        return(status);
}

static long get_units(DBADDR *paddr, char *units)
{
    xxxRecord   *prec=(xxxRecord *)paddr->precord;

    strncpy(units,prec->egu,DB_UNITS_SIZE); //获取单位
    return(0);
}

static long get_precision(const DBADDR *paddr, long *precision)
{
    xxxRecord   *prec=(xxxRecord *)paddr->precord;

    *precision = prec->prec; //获取精度
    if(paddr->pfield == (void *)&prec->val) return(0);
    recGblGetPrec(paddr,precision);
    return(0);
}

static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd)
{
    xxxRecord   *prec=(xxxRecord *)paddr->precord;
    int         fieldIndex = dbGetFieldIndex(paddr);  //获取DBADDR结构体指向的字段的索引

    if(fieldIndex == xxxRecordVAL
    || fieldIndex == xxxRecordHIHI
    || fieldIndex == xxxRecordHIGH
    || fieldIndex == xxxRecordLOW
    || fieldIndex == xxxRecordLOLO
    || fieldIndex == xxxRecordHOPR
    || fieldIndex == xxxRecordLOPR) {
        pgd->upper_disp_limit = prec->hopr;
        pgd->lower_disp_limit = prec->lopr;
    } else recGblGetGraphicDouble(paddr,pgd);
    return(0);
}

static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd)
{
    xxxRecord   *prec=(xxxRecord *)paddr->precord;
    int         fieldIndex = dbGetFieldIndex(paddr);

    if(fieldIndex == xxxRecordVAL
    || fieldIndex == xxxRecordHIHI
    || fieldIndex == xxxRecordHIGH
    || fieldIndex == xxxRecordLOW
    || fieldIndex == xxxRecordLOLO) {
        pcd->upper_ctrl_limit = prec->hopr;
        pcd->lower_ctrl_limit = prec->lopr;
    } else recGblGetControlDouble(paddr,pcd);
    return(0);
}

static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad)
{
    xxxRecord   *prec=(xxxRecord *)paddr->precord;
    int         fieldIndex = dbGetFieldIndex(paddr);

    if(fieldIndex == xxxRecordVAL) {
        pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN;
        pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN;
        pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN;
        pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN;
    } else recGblGetAlarmDouble(paddr,pad);
    return(0);
}

static void checkAlarms(xxxRecord *prec)
{
        double              val, hyst, lalm;
        float               hihi, high, low, lolo;
        unsigned short  hhsv, llsv, hsv, lsv;
        // 如果记录的值字段未被定义,则设置记录的NSTA和NSEV字段为传入的UDF_ALARM和prec->udfs
        if(prec->udf == TRUE ){
                recGblSetSevr(prec,UDF_ALARM,prec->udfs);
                return;
        }
        // 获取记录的高高限,低低限,高限,低限,高高限严重性,
        // 低低限严重性,低限严重性,当前值以及回滞因子,上次警报值
        hihi = prec->hihi; lolo = prec->lolo; high = prec->high; low = prec->low;
        hhsv = prec->hhsv; llsv = prec->llsv; hsv = prec->hsv; lsv = prec->lsv;
        val = prec->val; hyst = prec->hyst; lalm = prec->lalm;

        /* 高高限警报条件:当前值大于hihi或者上次警报值是hihi,当前值回落小于回滞因子 */
        if (hhsv && (val >= hihi || ((lalm==hihi) && (val >= hihi-hyst)))){
                if (recGblSetSevr(prec,HIHI_ALARM,prec->hhsv)) prec->lalm = hihi;
                return;
        }

        /* 低低限警报状况 */
        if (llsv && (val <= lolo || ((lalm==lolo) && (val <= lolo+hyst)))){
                if (recGblSetSevr(prec,LOLO_ALARM,prec->llsv)) prec->lalm = lolo;
                return;
        }

        /* 高限警报状况 */
        if (hsv && (val >= high || ((lalm==high) && (val >= high-hyst)))){
                if (recGblSetSevr(prec,HIGH_ALARM,prec->hsv)) prec->lalm = high;
                return;
        }

        /* 低限警报状况 */
        if (lsv && (val <= low || ((lalm==low) && (val <= low+hyst)))){
                if (recGblSetSevr(prec,LOW_ALARM,prec->lsv)) prec->lalm = low;
                return;
        }

        /* 警报解除 */
        prec->lalm = val;
        return;
}

static void monitor(xxxRecord *prec)
{
        unsigned short  monitor_mask;
        double          delta;
        // 如果记录存在警报,则返回DBE_ALARM,否则返回0,stat=nsta,sevr=nsev并且NSTA和NSEV字段都置0
        monitor_mask = recGblResetAlarms(prec);
        /* 检查值变化:上次发生值monitor时的值与现在的值之差的绝对值超过了prec_mdel */
        delta = prec->mlst - prec->val;
        if(delta<0.0) delta = -delta;
        if (delta > prec->mdel) {
                /* 提交值变化的事件 */
                monitor_mask |= DBE_VALUE;
                /* 更新上次被监视的值 */
                prec->mlst = prec->val;
        }

        /* 检查存档变化 */
        delta = prec->alst - prec->val;
        if(delta<0.0) delta = -delta;
        if (delta > prec->adel) {
                /* 提交对应存档的值变化的事件 */
                monitor_mask |= DBE_LOG;
                /* 更新上次被监视的存档值 */
                prec->alst = prec->val;
        }

        /* 发出连接了当前值字段的monitors */
        if (monitor_mask){
                db_post_events(prec,&prec->val,monitor_mask);
        }
        return;
}

第三个文件devXxxSoft.c ,xxx记录的设备支持例程源文件,内容如下:

/* devXxxSoft.c */
/* 示例设备支持模块 */

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "alarm.h"
#include "cvtTable.h"
#include "dbDefs.h"
#include "dbAccess.h"
#include "recGbl.h"
#include "recSup.h"
#include "devSup.h"
#include "link.h"
#include "xxxRecord.h"   //需要这个xxx记录头文件
#include "epicsExport.h"

/* 为devXxxSoft创建dset */
static long init_record();
static long read_xxx();
struct {
        long            number;
        DEVSUPFUN       report;
        DEVSUPFUN       init;
        DEVSUPFUN       init_record;
        DEVSUPFUN       get_ioint_info;
        DEVSUPFUN       read_xxx;
}devXxxSoft={
        5,
        NULL,
        NULL,
        init_record,
        NULL,
        read_xxx,
};
epicsExportAddress(dset,devXxxSoft);// 导出设备支持

// 如果不为空,在记录支持init_record例程中会被调用
// 初始化一个常量链接,把INP常量值按DBF_DOUBLE类型存入到到VAL字段,并且设置UDF字段为False
static long init_record(pxxx)
    struct xxxRecord    *pxxx;
{
    if(recGblInitConstantLink(&pxxx->inp,DBF_DOUBLE,&pxxx->val))
         pxxx->udf = FALSE;
    return(0);
}

/* 
    从由一个数据库链接引用的字段获取值,格式:
    long dbGetLink( 
    struct db_link ⋆plink, /⋆ 指向链接字段的指针 ⋆/ 
    short dbrType,         /⋆ DBR_xxx请求的数据类型⋆/ 
    void ⋆pbuffer,         /⋆ 指向返回的数据的指针 ⋆/ 
    long ⋆options,         /⋆ 指向选项的指针 ⋆/ 
    long ⋆nRequest);       /⋆ 指向所需元素数目的指针 ⋆/
注意:

如果不需要选项,options可以是NULL
对于标量,nRequest可以是NULL。
以一个调用dbGetLinkValue的宏实现了dbGetLink,并且其能够引用其参数多次。
宏跳过了对常数链接的调用。用户代码不应该调用dbGetLinkValue。
为了获取输入链接的值,这个例程是被数据库访问自身和被记录支持和/或设备支持例程调用。
可以从其它记录或者通过通道访问客户端获取这个值。这个例程遵守链接选项(process和最大化严重性)。
另外,它有优化没有options和标量情况的代码。
*/
static long read_xxx(pxxx)
    struct xxxRecord    *pxxx;
{
    long status;
    //从输入链接获取以DBF_DOUBLE一个值,存入VAL字段
    status = dbGetLink(&(pxxx->inp),DBF_DOUBLE, &(pxxx->val),0,0);
    /* 如果返回成功,则设置UDF为Fasle */
    if(!status) pxxx->udf = FALSE;
    return(0);
}

创建第四个文件,xxxSupport.dbd,内容如下:

include "xxxRecord.dbd"
device(xxx,CONSTANT,devXxxSoft,"SoftChannel")

 编辑相同目录下的Makefile文件:

TOP=../..

include $(TOP)/configure/CONFIG
#----------------------------------------
#  ADD MACRO DEFINITIONS AFTER THIS LINE
#=============================
# Use typed rset structure (see 3.16.1 release notes)
USR_CPPFLAGS += -DUSE_TYPED_RSET
#
# 此规则将从xxxRecord.dbd创建xxxRecord.h
DBDINC += xxxRecord
#
# 安装xxxSupport.dbd到<top>/dbd
DBD += xxxSupport.dbd
#
# 构建一个IOC支持库,库名libxxxTestSupport.a和libxxxTestSupport.so
LIBRARY_IOC += xxxTestSupport
#
# 编译以下代码,并且添加到这个支持库
xxxTestSupport_SRCS += xxxRecord.c
xxxTestSupport_SRCS += devXxxSoft.c
#
# 链接本地提供的代码到这个支持库,而不是直接链接到IOC应用程序
xxxTestSupport_LIBS += $(EPICS_BASE_IOC_LIBS)

#=============================
# 构建IOC应用程序,名称为xxxTest
PROD_IOC = xxxTest
# 将创建和安装xxxTest.dbd
DBD += xxxTest.dbd

# xxxTest.dbd将从这些文件组成xxxTest.dbd
xxxTest_DBD += base.dbd

# 包含来自所有支持应用程序的dbd文件:
xxxTest_DBD += xxxSupport.dbd

# 添加这个IOC需要的所有支持库
xxxTest_LIBS += xxxTestSupport

# xxxTest_registerRecordDeviceDriver.cpp derives from xxxTest.dbd
xxxTest_SRCS += xxxTest_registerRecordDeviceDriver.cpp

# Build the main IOC entry point on workstation OSs.
xxxTest_SRCS_DEFAULT += xxxTestMain.cpp
xxxTest_SRCS_vxWorks += -nil-

# Add support from base/src/vxWorks if needed
#xxxTest_OBJS_vxWorks += $(EPICS_BASE_BIN)/vxComLibrary

# 最终链接EPICS base库
xxxTest_LIBS += $(EPICS_BASE_IOC_LIBS)

#===========================

include $(TOP)/configure/RULES
#----------------------------------------
#  ADD RULES AFTER THIS LINE

(3) 进入exer13/xxxTestApp/Db目录,编辑一个xxxTest.db文件,内容如下:

record(xxx, "$(user):xxxExample")
{
        field(DESC, "xxx record")
        field(EGU, "Counts")
        field(HOPR, "10")
        field(HIHI, "8")
        field(HIGH, "6")
        field(LOW, "4")
        field(LOLO, "2")
        field(HHSV, "MAJOR")
        field(HSV, "MINOR")
        field(LSV, "MINOR")
        field(LLSV, "MAJOR")
}

编辑相同目录下的Makefile文件,添加一行:

DB += xxxTest.db

4) 进入这个应用程序的顶层目录,执行make,进行编译。

5) 进入iocBoot/iocxxxTest目录,编辑st.cmd文件,添加如下一行:

dbLoadRecords("db/xxxTest.db","user=blctrl")

6) 用以下语句启动这个IOC应用程序,并且进行测试:

[blctrl@bjAli iocxxxTest]$ ../../bin/linux-x86_64/xxxTest st.cmd
#!../../bin/linux-x86_64/xxxTest
< envPaths
epicsEnvSet("IOC","iocxxxTest")
epicsEnvSet("TOP","/home/blctrl/exer/exer12")
epicsEnvSet("EPICS_BASE","/usr/local/EPICS/base")
cd "/home/blctrl/exer/exer12"
## Register all support components
dbLoadDatabase "dbd/xxxTest.dbd"
xxxTest_registerRecordDeviceDriver pdbbase
## Load record instances
dbLoadRecords("db/xxxTest.db","user=blctrl")
cd "/home/blctrl/exer/exer12/iocBoot/iocxxxTest"
iocInit
Starting iocInit
############################################################################
## EPICS R7.0.3.1
## EPICS Base built Oct  6 2022
############################################################################
iocRun: All initialization complete
## Start any sequence programs
#seq sncxxx,"user=blctrl"
epics> dbl
blctrl:xxxExample
epics>

测试:

[blctrl@bjAli exer]$ caget blctrl:xxxExample
blctrl:xxxExample              0
[blctrl@bjAli exer]$ caput blctrl:xxxExample 2
Old : blctrl:xxxExample              0
New : blctrl:xxxExample              2

猜你喜欢

转载自blog.csdn.net/yuyuyuliang00/article/details/127198121
今日推荐