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