1、概要
本章目的是足够详细地描述记录支持,使得C程序员可以编写新地记录支持模块。在尝试编写新的支持模块前,你应该仔细研究一些已有的支持模块。如果一个已有支持模块类似于所需模块,大部分工作已经完成了。
从前面的章节,应该明白很多事情作为记录运行的结果而发生。发生什么的细节是取决于记录类型。为了允许新的记录类型和新的设备类型而不影响核心IOC系统,使用了记录支持和设备支持的概念。对于每种记录类型,存在一个记录支持模块。它负责所有记录特定的细节。为了允许一个记录支持模块独立于设备专用的细节,创建了设备支持的概念。
一个记录支持模块由一个标准例程集合组成,它们被数据库访问例程调用。这些例程实现记录专用的代码。每种记录类型可以定义一个标准的专用于此记录类型的设备支持例程集合。
到目前为止,最重要的记录支持例程是process, 当dbProcess想要运行一个记录时,它调用这个例程。这个例程负责记录运行的细节。在很多请求下,它调用一个设备支持I/O例程。下一部分概述了为了运行一个记录必须做什么。接着描述由记录和设备支持模块必须提供的入口表。剩余部分给出了示例记录和设备支持模块并且描述了对记录支持模块有用的一些全局例程。
记录和其设备支持模块是应该包括这个记录专用头文件的唯一代码。因而,它们将是不通过数据库访问访问记录专有字段的唯一代码。
2 记录运行概要
最重要的记录支持例程是process。这个例程确定了记录运行意味着什么。在调用记录专用"process"例程前,已经做了以下事情:
- 决定运行一个记录
- 检查记录没有运行,即pact必须为FALSE
- 检查记录是未被禁用的。
process例程,和其相关联的设备支持一起负责以下任务:
- 在其被运行时,设置记录运行
- 执行I/O(在设备支持帮助下)
- 检查记录专用的警报条件
- 产生数据库monitors
- 请求转发链接的运行
记录运行的复杂性是某些设备本质上是异步的。等待一个慢设备结束是不可能的。异步记录执行以下步骤:
- 初始化I/O操作并且设置pact=TRUE
- 确定一个方法用于在操作结束时再次调用process。
- 在没有结束记录运行下立即返回
- 在I/O操作结束后process被调用时,结束记录运行
- 设置pact = FALSE并且返回
以下给出的示例显示如何做这件事。
3 记录支持和设备支持入口表
每种记录类型有一个相关联的记录支持例程集合。通过在recSup.h中声明和在特定记录类型定义的struct rset数据结构定位这些例程。记录支持矢量表的使用隔离了iocCore软件和每个记录类型的实现细节。因而新的记录类型可以被定义而不必修改IOC core软件。
每种记录类型也有0或多个设备支持例程集合。没有相关联硬件的记录类型,例如,计算记录,通常没有任何相关联的设备支持。有相关联硬件的记录类型通常有对应每种设备类型的设备支持模块。设备支持的概念隔离IOC核心软件甚至记录支持和设备专门细节。
对应每种记录类型是一个记录支持例程的集合。例程的集合对应每种记录类型是相同的。通过记录支持入口表(RSET)定位这些例程,其有以下结构体:
struct rset { /* 记录支持入口表 */
long number; /* 支持例程的数目 */
RECSUPFUN report; /* 打印报告 */
RECSUPFUN init; /* init支持 */
RECSUPFUN init_record; /* 初始化记录 */
RECSUPFUN process; /* 运行记录 */
RECSUPFUN special; /* 特殊处理 */
RECSUPFUN get_value; /* 弃用: 留NULL */
RECSUPFUN cvt_dbaddr; /* cvt dbAddr */
RECSUPFUN get_array_info;
RECSUPFUN put_array_info;
RECSUPFUN get_units;
RECSUPFUN get_precision;
RECSUPFUN get_enum_str; /* 从枚举获取字符串 */
RECSUPFUN get_enum_strs; /* 获取所有枚举字符串 */
RECSUPFUN put_enum_str; /* 从字符串写枚举 */
RECSUPFUN get_graphic_double;
RECSUPFUN get_control_double;
RECSUPFUN get_alarm_double;
}
每种记录支持模块必须定义它的RSET。外部名称格式必须是:
<record_type>RSET
特定记录类型不需要的任何例程应该被初始化为值NULL。详细查看以下示例。
通过设备支持入口表(DSET)定位设备支持例程,其有以下结构体:
struct dset { /* 设备支持入口表 */
long number; /* 支持例程的数目 */
DEVSUPFUN report; /* 打印报告 */
DEVSUPFUN init; /* 初始化支持 */
DEVSUPFUN init_record; /* 初始化记录实例 */
DEVSUPFUN get_ioint_info; /* 获取IO中断信息 */
/* 其它函数是取决于记录 */
};
每种设备支持模块必须定义其相关联的DSET。外部名称必须与在devSup.ascii中出现的名称相同。
有相关联设备支持的任何记录支持模块必须也包含访问其相关联设备支持模块的定义。字段dset,其在dbCommon中被声明,包含了DSET的地址。 它由iocInit传给一个值。
4 示例记录支持模块
这部分包含一个记录支持包的框架。记录类型是xxx和除了dbCommon字段,记录还有以下字段:VAL, PREC, EGU, HOPR, LOPR, HIHI,LOLO, HIGH, LOW, HHSV, LLSV, HSV, LSV, HYST, ADEL, MDEL, LALM, LALM, ALST, MLST。这些字段有与ai记录相同的含义。
4.1 声明
/* 创建RSET - 记录支持入口表 */
#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
};
epicsExportAddress(rset,xxxRSET);
typedef struct xxxset { /* xxx输入dset */
long number;
DEVSUPFUN dev_report;
DEVSUPFUN init;
DEVSUPFUN init_record; /*返回: (-1,0)=>(failure,success)*/
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_xxx;
}xxxdset;
static void checkAlarms(xxxRecord *prec);
static void monitor(xxxRecord *prec);
以上声明定义了记录支持入口表(RSET),一个相关联设备支持入口表(DSET)的模板,以及对私有例程的提前声明。
必须用一个xxxRSET的外部名称声明RSET。它定义了提供给这个类型的记录支持例程。注意:为所有支持的例程提供了提前声明,以及为任何不支持的例程提供了NULL声明。
为由这个模块使用声明了DSET的模板。
4.2 init_record
static long init_record(struct dbCommon *pcommon, int pass)
{
xxxRecord *prec = (xxxRecord *)pcommon;
xxxdset *pdset;
long status;
if (pass==0) return(0);
// prec->dset为空,表示此记录没有相关联的设备支持例程集合
if(!(pdset = (xxxdset *)(prec->dset))) {
recGblRecordError(S_dev_noDSET,(void *)prec,"xxx: init_record");
return(S_dev_noDSET);
}
/* 设备支持例程集合中例程数目不少于5,并且必须定义好read_xxx函数 */
if( (pdset->number < 5) || (pdset->read_xxx == NULL) ) {
recGblRecordError(S_dev_missingSup,(void *)prec,"xxx: init_record");
return(S_dev_missingSup);
}
// 设备支持例程集合存在,且read_xxx存在,且设备支持例程init_record存在
// 用这个记录结构体调用它
if( pdset->init_record ) {
if((status=(*pdset->init_record)(prec))) return(status);
}
return(0);
}
这个例程,被iocInit为每种类型xxx记录调用两次,检查它是否有一个合适的设备支持例程集合,并且如果存在,调用DSET的init_record函数。
在init_record(pass=0)的首次调用中,仅执行与这个记录相关的初始化。在第二次调用(pass=1)中,可以执行其它记录的初始化。也注意:在第二次pass,其它记录可以指向这个记录内的字段。体现这些规则重要的地方的示例是一个waveform记录。waveform记录VAL字段实际指向一个数组。waveform记录支持模块必须为这个数组分配存储区。如果零一个记录有一个指向这个waveform VAL字段的数据库链接,则存储区必须在这个链接被解析前被分配。通过使waveform记录支持在第一个pass(pass=0)过程中分配数组,而在第二个pass(pass=1)过程中解析链接引用拉拉做这件事。
4.3 process
static long process(struct dbCommon *pcommon)
{
xxxRecord *prec = (xxxRecord *)pcommon;
xxxdset *pdset = (xxxdset *)(prec->dset);
long status;
unsigned char pact=prec->pact;
// 设备支持例程集合为空或设备支持例程read_xxx为空,报缺少设备支持的错误
if( (pdset==NULL) || (pdset->read_xxx==NULL) ) {
prec->pact=TRUE;
recGblRecordError(S_dev_missingSup,(void *)prec,"read_xxx");
return(S_dev_missingSup);
}
/* 在调用设备支持前一定不能置位pact */
status=(*pdset->read_xxx)(prec);
/* 检查设备支持是否设置置位了pact:进入这个例程时,未置位pact,而此时置位了pact
表示是设备支持置位了pact
*/
if ( !pact && prec->pact ) return(0);
prec->pact = TRUE;
recGblGetTimeStamp(prec);
/* 检查警报 */
checkAlarms(prec);
/* 检查事件列表 */
monitor(prec);
/* 运行转发扫描链接记录 */
recGblFwdLink(prec);
prec->pact=FALSE;
return(status);
}
记录processing例程是IOC软件核心。当dbProcess决定一个记录应该被运行时,记录特定的process例程被它调用。process决定记录运行实际意味着什么。以上是一个应该做什么的代表性示例。除了被dbProcess调用,这个process例程也能被异步记录结束例程调用。
以上模型支持同步和异步设备支持例程。例如,如果read_xxx是一个异步例程,以下事件顺序将发生:
- 用pact为FALSE调用process
- read_xxx被调用。由于pact是FALSE,它启动I/O,安排回调,并且设置pact为TRUE。
- read_xxx返回。
- 因为pact从FALSE变为TRUE,process就返回。
- 一段时间后,回调发生并且process再次被调用。
- read_xxx被调用。由于pact是TRUE,它知道它是一个完成请求。
- read_xxx返回。
- process结束记录运行。
- pact设为FALSE。
- process返回。
到此,这个记录已经完全被运行。下次process被调用,一切从头开始。
4.4 各种工具例程
typedef struct dbAddr {
struct dbCommon *precord; /* 记录地址 */
void *pfield; /* 字段地址 */
struct dbFldDes *pfldDes; /* struct fldDes地址 */
long no_elements; /* 元素数目 */
short field_type; /* 数据库字段类型 */
short field_size; /* 被访问字段的大小 */
short special; /* 特殊处理 */
short dbr_field_type; /* 数据库请求见到的字段类型*/
/* DBR_STRING,...,DBR_ENUM,DBR_NOACCESS */
} dbAddr;
typedef dbAddr DBADDR;
// 获取记录实例使用的单位
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;
// paddr->pfield指向记录实例的val字段,否则返回这个字段数据类型的精度
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);
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) {
// 是否设置了高高,高,低,低低的警报严重性,如果设置了使用hihi,high,low,lolo
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);
}
这是由代表性记录支持包提供的各种例程的示例。这些必须由剩下例程执行的函数在下一部分描述。
4.5 警报处理
// 检查警报
static void checkAlarms(xxxRecord *prec)
{
double val, hyst, lalm;
float hihi, high, low, lolo;
unsigned short hhsv, llsv, hsv, lsv;
// 表示记录实例未定义,直接设置警报状态和严重性后返回
if(prec->udf == TRUE ){
recGblSetSevr(prec,UDF_ALARM,prec->udfs);
return;
}
// 获取记录实例中HIHI,LOLO,HIGH,LOW,HHSV,LLSV,HSV,LSV
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;
}
/* 警报条件lolo */
if (llsv && (val <= lolo || ((lalm==lolo) && (val <= lolo+hyst)))){
if (recGblSetSevr(prec,LOLO_ALARM,prec->llsv)) prec->lalm = lolo;
return;
}
/* 警报条件high */
if (hsv && (val >= high || ((lalm==high) && (val >= high-hyst)))){
if (recGblSetSevr(prec,HIGH_ALARM,prec->hsv)) prec->lalm = high;
return;
}
/* 警报条件low */
if (lsv && (val <= low || ((lalm==low) && (val <= low+hyst)))){
if (recGblSetSevr(prec,LOW_ALARM,prec->lsv)) prec->lalm = low;
return;
}
/*只在val超出警报至少hyst时,我们才到这里*/
prec->lalm = val;
return;
}
这是一个用于检测一个模拟类型记录的警报条件代码的代表性集合。代码的实际集合是记录专用的。注意:系统的其它部分也可以产生警报。算法就是最大化警报严重性,即:将报告最高严重性待处理警报。
以上算法总是遵守这个警报的回滞因子。这为了防止在当前值非常接近警报限制并且噪声使其持续跨过这个限制的情况下发生警报风暴。只在值变得一个更低警报严重性时,它才遵守回滞。
注意测试:
/*记录未定义时,设置警报状态为UDF_ALARM,严重性为prec->udfs*/
if (prec->udf) {
recGblSetSevr(prec, UDF_ALARM, prec->udfs);
return;
}
数据库common定义了字段UDF,当字段VAL是未定义时,应该置位它。字段UDFS控制这个记录处于未定义状态时的严重性。STAT和SEVR字段被初始化,就像recGblSetSevr(prec, UDF_ALARM, prec->udfs)被调用。因而,如果记录未被运行,记录处于UNDEFINED警报状态,而严重性由记录的UDFS字段设置。字段UDF被初始化为值1(真)。因而,以上代码将保持这个记录处于警报状态,直到UDF被重置为0(假)。
UDF字段非零表明记录是未定义,即:其VAL字段的内容不表示一个实际值。当纪录被装载到一个ioc时,这通常是这个记录的初始状态。当代码设置VAL字段时,它也应该通常设置UDF为假。当记录的VAL字段是DBF_FLOAT或DBF_DOUBLE时,当VAL被设置为一个NaN(非数值)值时,UDF可以被设置为真。
对于输入记录,设备支持负责获取输入值。如果不能获取输入值,记录支持和设备支持都不会设置UDF为假。如果设备支持读取一个原始值,他返回一个告诉记录支持执行转换的值。在记录支持设置VAL为转换好的值后,它设置UDF假。如果设备支持获取一个转换好的值,它写入VAL,它设置UDF假。
对于输出记录,记录/设备支持外的东西写入VAL字段,或者传给VAL一个值,因为记录支持通过OMSL字段获取一个值。在任何一种情况,写入VAL字段的代码设置UDF为假。
当数据库访问写入VAL字段时,它设置UDF假。
调用例程recGblSetSevr产生警报。它可以被iocCore,记录支持或设备支持调用。发现一个警报的代码负责产生这个警报。
4.6 产生监控
static void monitor(xxxRecord *prec)
{
unsigned short monitor_mask;
double delta;
/* 重置记录警报,获取警报条件 */
monitor_mask = recGblResetAlarms(prec);
/* 检测值变化:上次监控的数值和当前值之间的差别 */
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) {
/* post events on value field for archive change */
monitor_mask |= DBE_LOG;
/* update last archive value monitored */
prec->alst = prec->val;
}
/* 发送出链接到值字段的监视程序 */
if (monitor_mask){
db_post_events(prec,&prec->val,monitor_mask);
}
return;
}
所有记录类型应该调用如上所示的recGblResetAlarms。注意:nsta和nsev在这个例程结束后值将为0。确保警报检测在运行结束后重新开始是必要的。此代码也管理在记录从一种警报状态变成无警报状态时产生警报监视。记录支持例程遵守以上模型是必要的,否则警报运行将不遵守这些规则。
模拟类型记录也应该提供由此示例展示的监控和存档回滞字段。
db_post_events导致通道访问为连接到这个记录和字段的客户端发送监视事件。调用为
int db_post_events(void *precord, void *pfield, unsigned int monitor_mask)
此处:
- precord:记录的地址。
- pfield:字段的地址。
- monitor_mask:未掩码,可以是以下任意组合:
- DBE_ALARM:警报状态变化发生。通过recGblResetAlarms设置这个。
- DBE_LOG:存档状态的变化
- DBE_VAL:值状态的变化
重要:记录支持模块负责为随记录运行结果变化的任何字段调用db_post_event。它不应该为不发生变化的字段调用db_post_event。
5 记录支持例程
这部分描述在RSET中定义的例程。不应用于特定记录类型的任何例程必须被声明为NULL。
5.1 产生记录中每个字段的报告
long report(void *precord);
大部分记录类型不使用这个例程。任何操作都是记录类型特定的。
5.2 初始化记录运行
long initialize(void);
这个例程在IOC初始化时被调用一次。任何操作都是记录类型专用的。大部分记录类型不需要这个例程。
5.3 初始化特定的记录
long init_record(void *precord, int pass);
iocInit为由这个例程处理的这种类型的每个数据库记录调用这个例程两次(pass=0和pass=1) 。它必须执行以下功能:
- 检查和/或发出相关联设备支持例程的初始化调用。
- 执行任何记录类型特定的初始化。
- 在第一个pass过程中,它只执行影响由precord引用记录的初始化。
- 在第二次pass过程中,它可以执行影响其它记录的初始化。
5.4 运行记录
long process(void *precord);
这个例程必须遵守先前指定的指导方法。
5.5 特殊运行
long special(struct dbAddr *paddr, int after);
这个例程为由dbrAddr指向字段实现了记录类型专用的special运行。当从记录外面写入一个字段时,它被调用两次,一次用after=0在对这个字段做任何更改前,第二次用after=1在完成更改后。这个例程通过从第一次调用(after=0)返回一个错误状态防止进行任何修改。文件special.h定义了special类型。只为用户speical字段调用这个例程,SPC_xxx>=100的字段。在ASCII记录定义文件中被声明为special的字段。新值不应该通过添加到spcical.h,而是使用SPC_MOD。
数据库访问例程dbGetFieldIndex可以用于获取哪个字段被修改。
5.6 获取值
这个例程不再被使用。它应该在记录支持入口表中被留为NULL过程。
5.7 转换dbAddr定义
long cvt_dbaddr(struct dbAddr *paddr);
如果这里字段设置special为SPC_DBADDR,这个例程被称为dbNameToAddr。一个代表性用法是在一个字段指向一个数组时。这个例程可以修改dbAddr字段的任意组合:no_elements, field_type, field_size special, pfield和dbr_type。例如,如果waveform记录的VAL字段被传递给dbNameToAddr,cvt_dbaddr将更改dbAddr使其指向实际数组而非VAL。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段被修改。
注意:
- 通道访问调用db_name_to_addr,其时老数据库访问的组成。db_name_to_addr调用dbNameToAddr。当一个客户端连接这个记录时,做这件事。
- no_elements必须被设置为将存储在这个数组中元素最大数目。
5.8 获取数组信息
long get_array_info(struct dbAddr *paddr, long *no_elements, long *offset);
这个例程返回当前元素数目以及指定数组第一个值的偏移。如果数组实际是一个环形缓存,偏移是有意义的。
数据库访问dbGetFieldIndex可以用于确定哪个字段正在被修改。get_array_info更改pfield是可能的。这个特定可以用于实现双缓存。
当数组字段被写时,在字段值被修改前,调用get_array_info。
5.9 写数组信息
long put_array_info(struct dbAddr *paddr, long nNew);
在新值被放入指定数组后,调用这个例程。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段被修改。
5.10 获取单位
long get_units(struct dbAddr *paddr, char *punits);
这个例程为这个字段设置工程单位。
5.11 获取精度
long get_precision(struct dbAddr *paddr, long *precision);
这个例程获取精度,即,十进制位数目,其应该被用于转换字段值为一个ASCII字符串。应该为不于这个值字段直接相关联的字段调用recGblGetPrec。
5.12 获取枚举字符串
long get_enum_str(struct dbAddr *paddr, char *p);
这个例程设置*p为这个字段值的ASCII字符串。这个字段具有类型DBF_ENUM。数据库访问例程dbGetFieldIndex可以用于获取哪个字段正在被修改。
5.13 获取枚举字段的字符串
#define DBRenumStrs \
epicsUInt32 no_str; /* number of strings*/\
epicsInt32 padenumStrs; /*padding to force 8 byte align*/\
char strs[DB_MAX_CHOICES][MAX_STRING_SIZE]; /* string values */
struct dbr_enumStrs {DBRenumStrs};
long get_enum_strs(struct dbAddr *paddr, struct dbr_enumStrs *p);
这个例程传值给结构体dbr_enumStrs的所有字段。数据库访问例程dbGetFieldIndex可以用于获取哪个字段正在被修改。
5.14 写枚举字符串
long put_enum_str(struct dbAddr *paddr, char *p);
传给一个ASCII字符串,这个例程更新数据库字段。它比较这个字符串与每个枚举值相关联的字符串,并且如果它找到一个匹配,设置这个数据库字段为匹配字符串的索引。
5.15 获取Graphic Double信息
#define DBRalDouble \
epicsFloat64 upper_alarm_limit;\
epicsFloat64 upper_warning_limit;\
epicsFloat64 lower_warning_limit;\
epicsFloat64 lower_alarm_limit;
struct dbr_grDouble {DBRgrDouble};
long get_graphic_double(struct dbAddr *paddr, struct dbr_grDouble *p);
这个例程填充结构体dbr_grDouble图形相关联字段,为不于值字段相关联的字段调用recGblGetGraphicDouble。
5.16 获取Control Double信息
#define DBRctrlDouble \
epicsFloat64 upper_ctrl_limit; /*upper limit of graph*/\
epicsFloat64 lower_ctrl_limit; /*lower limit of graph*/
struct dbr_ctrlDouble {DBRctrlDouble};
long get_control_double(struct dbAddr *paddr, struct dbr_ctrlDouble *p);
这个例程赋值给结构体dbr_ctrlDouble的所有字段。为不与值字段直接相关联的字段调用recGblGetControlDouble。
数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被修改。
5.17 获取alarm double信息
struct dbr_alDouble {DBRalDouble};
long get_alarm_double(struct dbAddr *paddr, struct dbr_alDouble *p);
这个例程传值给结构体dbr_double的所有字段。数据库访问例程dbGetFieldIndex可以用于确定哪个字段正在被修改。
6、全局记录支持例程
很多全局记录支持例程是可用的。这些例程用于由记录特定运行例程使用,也能够被想要使用它们服务的任何例程调用。
这些例程中的每个例程名称以"recGbl"开始。使用这些例程的代码应该:
#include <recGbl.h>
6.1 警报状态和严重性
在记录运行过程中在很多不同地方可能产生警报。此处算法是最大化警报严重性,即:产生最高严重性待处理警报。如果发现了相同严重性的多个警报,则第一个警报被报告。这表示当一个代码端想要产生一个警报,它只在它将声明的警报严重性高于已有的严重性时它才做这件事。(在数据库common中)四个字段用于实现警报:sevr,stat,nsev和nsta。前两个是记录结束运行后的状态和严重性。后两个字段(nsta和nsev)是在记录运行过程中的状态和严重性。两个例程用于处理警报。当一个例程想要产生一个警报时,它调用recGblSetSevr。这个例程将导致警报严重性被增加时,它才更改nsta和nsev。在运行结束时,记录支持模块必须调用recGblResetAlarms。这个例程设置stat=nsta,sevr=nsev,nsta=0和nsev=0。如果从上次调用以来stat或sevr已经更改了值,它为stat和sevr调用调用db_post_event并且返回一个DBE_ALARM的值。如果没有发生变化,它返回0。因而在调用recGblResetAlarms后,为了下次运行这个记录产生警报做了一切准备。以上展现的示例记录支持模块展示了如何使用这些宏。
int recGblSetSevr(void *precord, short nsta, short nsevr);
如果它更改nsta和/或nsev,返回TRUE,如果它不更改它们,返回FALSE。
unsigned short recGblResetAlarms(void *precord);
返回:monitor_mask的初始值。
6.2 警报确认
数据库common包含了两个其它警报相关的字段:
- acks:最高严重性未确认警报
- ackt:瞬态警报需要被确认吗?
这些字段被iocCore和recGblResetAlarms处理,并且不应该被记录支持使用。它提供的警报确认工具被警报处理程序使用。
6.3 产生错误:过程变量名,调用者,消息
建议:新代码使用errlogPrintf替代这个。
void recGblDbaddrError(long status, struct dbAddr *paddr, char *pcaller_name); /* calling routine name */
这个例程连接系统范围错误处理系统来显示以下信息:状态信息,过程变量名,调用例程。
6.4 产生错误:状态字符串,记录名,调用者
建议:新代码使用errlogPrintf替代这个。
void recGblRecordError(long status, void *precord, char *pcaller_name); /* calling routine name */
这个例程连接系统范围错误处理系统来显示以下信息:状态信息,记录名,调用例程,记录支持入口名称。
6.5 产生错误:记录名,调用者,记录支持消息
建议:新代码使用errlogPrintf替代这个。
void recGblRecsupError(long status, struct dbAddr *paddr,
char *pcaller_name, /* calling routine name */
char *psupport_name); /* support routine name*/
这个例程连接系统范围错误处理系统来显示以下信息:状态信息,记录名,调用例程,记录支持入口名。
6.6 获取Graphic Double
void recGblGetGraphicDouble(struct dbAddr *paddr, struct dbr_grDouble *pgd);
这个例程可以被get_graphic_double记录支持例程使用,获取它不知道如何设置字段的图形值。
6.7 获取Control Double
void recGblGetControlDouble(struct dbAddr *paddr, struct dbr_ctrlDouble *pcd);
这个例程被get_control_double记录支持例程使用,获取它不知道如何设置字段的控制值。
6.8 获取警报double
void recGblGetAlarmDouble(struct dbAddr *paddr, struct dbr_alDouble *pcd);
这个例程可以被get_alarm_double记录支持例程使用,来获取其不知道如何设置字段的警报值。
6.9 获取精度
void recGblGetPrec(struct dbAddr *paddr, long *pprecision);
这个例程可以被get_precisioin记录支持例程使用,获取它不知道如何设置精度的字段的精度。
6.10 获取时间戳
void recGblGetTimeStamp(void *precord)
这个例程获取当前时间戳并且放它到记录中,它做以下事情:
- 如果TSEL不是一个常数链接,TSEL指向一个记录的TSEL,从由TSEL引用的记录获取时间,并且被放入字段TIME。这个例程接着返回。
- 如果TSEL不是一个常数链接,调用dbGetLink并且值被放入字段TSE。
- 如果TSE等于epicsTimeEventDeviceTime(-2),接着什么也不做,即:例程仅返回。
- epicsTimeGetEvent被调用。
6.11 转发链接
void recGblFwdLink(void *precord);
这个例程可以被process调用,请求转发链接的运行。
6.12 初始化常数链接
int recGblInitConstantLink(struct link *plink, short dbfType, void *pdest);
初始化一个常数链接。这个例程通常被init_record调用(或者被相关联的设备支持)来用初始化与一个常数链接相关联的字段。根据它(没有,有)修改这个目标,它返回(FALSE, TRUE)。
6.13 模拟值死区检查
void recGblCheckDeadband(epicsFloat64 *poldval, const epicsFloat64 newval,
const epicsFloat64 deadband, unsigned *monitor_mask, const unsigned add_mask);
检查模拟(double)值是否超出指定的死区,并且在监视掩码中设置位。这个例程通常被模拟记录的monitor调用来检查一个值是否超出预定义的死区。它也根据检查结果设置monitor mask中的位。如果newval在指定deadband外,newval被复制到*poldval,并且add_mask被按位或到monitor_mask中。