EPICS学习:记录支持


一、概述

本章的目的是详细描述对记录的支持,这样C程序员就可以编写新的记录支持模块。在尝试编写新的支持模块之前,应该仔细研究一些现有的支持模块。如果现有的支持模块与所需的模块相似,那么大部分工作已经完成。
从前面的章节中可以清楚地看到,许多事情都是作为记录处理的结果发生的。所发生的事情的细节取决于记录类型。为了在不影响核心IOC系统的情况下允许新记录类型和新设备类型,使用了记录支持和设备支持的概念。对于每种记录类型,都存在一个记录支持模块。它负责所有具体细节的记录。为了允许记录支持模块独立于设备特定细节,创建了设备支持的概念。

记录支持模块由一组标准例程组成,这些例程由数据库访问例程调用。这些例程实现特定于记录的代码。每个记录类型可以定义特定于该记录类型的一组标准设备支持例程。每个记录类型可以定义特定于该记录类型的一组标准设备支持例程。

到目前为止,最重要的记录支持例程是process, dbProcess在处理记录时调用它。这个例程负责记录处理的细节。在许多情况下,它调用一个设备支持I/O例程。下一节概述了为了处理一个记录必须做些什么。接下来是对记录和设备支持模块必须提供的条目表的描述。接下来是对记录和设备支持模块必须提供的条目表的描述。其余部分给出了示例记录和设备支持模块,并描述了一些用于记录支持模块的全局例程。记录及其设备支持模块是应该包含特定于记录的头文件的唯一源文件。因此,它们将是唯一访问记录特定字段而不进行数据库访问的例程。

二、记录处理概述

最重要的记录支持例程是进程。这个例程决定记录处理意味着什么。在调用特定于记录的“process”例程之前,已经完成了以下操作:

  • 决定处理一个例程
  • 检查记录是否有效,即pack必须是FALSE
  • 检查记录没有被禁用

处理例程及其相关设备支持,负责以下任务:

  • 将正在处理的记录设置为活动的
  • 执行I/O(借助设备支持)
  • 检查记录特定的报警条件
  • 产生数据库监控
  • 请求处理转发链接

记录处理的一个复杂之处在于,有些设备本质上是异步的。永远不允许等待速度较慢的设备完成。异步记录执行以下步骤:

  • 启动I/O操作并设置pact = TRUE
  • 确定在操作完成时再次调用进程的方法
  • 立即返回,无需完成记录处理
  • 当进程被调用后,I/O操作完成记录处理
  • Set pact = FALSE并返回

三、记录支持和设备支持入口表

每个记录类型都有一组相关联的记录支持例程。这些例程是通过recSup.h中声明并由特定记录类型定义的struct typed_rset数据结构定位的。这种记录支持向量表的使用将iocCore软件从每种记录类型的实现细节中分离出来。因此,可以在不修改IOC核心软件的情况下定义新的记录类型。每个记录类型还具有零个或多个设备支持例程。没有相关硬件的记录类型,例如计算记录,通常没有任何相关的设备支持。设备支持的概念将IOC核心软件甚至记录支持与设备特定细节隔离开来。对应于每个记录类型的是一组记录支持例程。对于每种记录类型,例程集都是相同的。这些例程通过记录支持入口表(RSET)定位,它有以下结构:

	/* record support entry table */
	struct typed_rset {
		long number; /* number of support routines */
		long (*report)(void *precord);
		long (*init)();
		long (*init_record)(struct dbCommon *precord, int pass);
		long (*process)(struct dbCommon *precord);
		long (*special)(struct dbAddr *paddr, int after);
		long (*get_value)(void); /* DEPRECATED set to NULL */
		long (*cvt_dbaddr)(struct dbAddr *paddr);
		long (*get_array_info)(struct dbAddr *paddr, long *no_elements, long *offset);
		long (*put_array_info)(struct dbAddr *paddr, long nNew);
		long (*get_units)(struct dbAddr *paddr, char *units);
		long (*get_precision)(const struct dbAddr *paddr, long *precision);
		long (*get_enum_str)(const struct dbAddr *paddr, char *pbuffer);
		long (*get_enum_strs)(const struct dbAddr *paddr, struct dbr_enumStrs *p);
		long (*put_enum_str)(const struct dbAddr *paddr, const char *pbuffer);
		long (*get_graphic_double)(struct dbAddr *paddr, struct dbr_grDouble *p);
		long (*get_control_double)(struct dbAddr *paddr, struct dbr_ctrlDouble *p);
		long (*get_alarm_double)(struct dbAddr *paddr, struct dbr_alDouble *p);
	};

每个记录支持模块必须定义其RSET。外部名称必须是形式:

	<record_type>RSET

对于特定记录类型不需要的任何例程都应该初始化为NULL值。请看下面的示例以了解详细信息。
设备支持例程通过设备支持入口表(DSET)定位,它有如下结构:

	struct dset { /* device support entry table */
		long number; /* number of support routines */
		DEVSUPFUN report; /* print report */
		DEVSUPFUN init; /* init support */
		DEVSUPFUN init_record;/* init record instance*/
		DEVSUPFUN get_ioint_info; /* get io interrupt info*/
		/* other functions are record dependent*/
	};

每个设备支持模块必须定义其关联的DSET。外部名称必须与devsu .ascii中出现的名称相同。具有关联设备支持的任何记录支持模块还必须包括用于访问其关联设备支持模块的定义。在dbCommon中声明的字段dset包含dset的地址。它由iocInit提供一个值。

四、示例记录支持模块

这个部分包含了一个记录支持包的框架。该记录类型为xxx,除了dbCommon字段外,该记录还具有以下字段:VAL、PREC、EGU、HOPR、LOPR、HIHI、LOLO、HIGH、LOW、HHSV、LLSV,HSV, LSV, HYST, ADEL, MDEL, LALM, ALST, MLST。这些字段将具有与ai记录相同的含义。请参阅记录参考手册的描述。

4.1 声明

	#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 input 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);

以上声明定义了记录支持入口表(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 = (xxxdset *) prec->dset;
		if (pass == 0)
		return 0;
		//没有关联的设备支持例程
		if (!pdset) {
			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;
		}
		//设备支持例程的init_record必须存在
		if (pdset->init_record) {
			long status = pdset->init_record(prec);
			if (status)
			return(status);
		}
		return 0;
	}

此例程由iocInit为每个xxx类型的记录调用两次,检查它是否有一组正确的设备支持例程,如果存在,则调用DSET的init_record条目。

在第一次调用init_record(pass=0)期间,只能执行与此记录相关的初始化。在第二次调用(pass=1)期间,可以执行可能引用其他记录的初始化。另请注意,在第二次传递期间,其他记录可能引用此记录中的字段。一个很好的例子说明这些规则的重要性是波形记录。波形记录的VAL字段实际上是指数组。波形记录支持模块必须为数组分配存储空间。如果另一个记录具有引用波形值字段的数据库链接,则必须在解析链接之前分配存储。这是通过让波形记录支持在第一个过程(pass=0)期间分配数组,并在第二个过程(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;
		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,在此之前都没有进行置位操作 */
		if (!pact && prec->pact) return 0;
		prec->pact = TRUE;
		recGblGetTimeStamp(prec);
		/* 检查报警*/
		checkAlarms(prec);
		/* 检查事件表 */
		monitor(prec);
		/* 运行转发扫描链接记录 */
		recGblFwdLink(prec);
		prec->pact = FALSE;
		return status;
	}

记录处理程序是IOC软件的核心。每当dbProcess决定要处理一条记录时,它都会调用特定于记录的process例程。process决定了记录处理的真正含义。以上就是我们应该做的一个很好的例子。除了由dbProcess调用外,进程例程也可以由异步记录完成例程调用。上述模型支持同步和异步设备支持例程。

上述模型同时支持同步和异步设备支持例程。例如,如果read_xxx是一个异步例程,则会发生以下事件顺序:

  • 调用 process,path = FALSE
  • 调用 read_xxx。由于path = FALSE,开始 I/O,安排回调,并且设置path = TRUE
  • 返回 read_xxx
  • 由于 pact 从 FALSE 到 TRUE 的过程只是返回
  • 任何对dbProcess的新调用都会被忽略,因为它会发现结果为TRUE
  • 稍后这些回调会发生,并且 process 会被再次调用
  • 调用 read_xxx。由于pact为TRUE,它知道这是一个完成请求。
  • 返回 read_xxx
  • process 完成记录处理
  • pact 设置为 FALSE
  • 返回 process

此时,记录已被完全处理。下一次 process 被调用时一切从头开始。

4.4 各种实用程序例程

	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);
		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;
	}
/* similar routines would be provided for */

这些是由典型的记录支持包提供的各种例程的几个例子。必须由其余例程执行的函数将在下一节中进行描述。

4.5 报警处理

	static void checkAlarms(xxxRecord *prec)
	{
		double val;
		float hyst, lalm, hihi, high, low, lolo;	//报警阈值
		unsigned short hhsv, llsv, hsv, lsv;		//报警严重性
		//udf(未定义)字段:false-VAL字段有效, true-VAL字段无效。
		if (prec->udf) {
			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;
		 /* 最高高严重性产生 = 当前为最高高严重性 && (当前值>=最高高限 ||((上次报警值 == 最高高限)&&(当前值 >=(最高高限-报警死区))) */
		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;
	}

这是一组典型的代码,用于检查模拟类型记录的警报条件。实际的代码集可以是非常特定于记录的。还要注意,系统的其他部分也可以发出警报。该算法始终将警报严重性最大化,即报告的警报严重性最高。
上述算法也为报警提供了一个滞后因子。这是为了防止在电流值非常接近警报限值而噪声使其不断超过该限值的情况下警报风暴发
生。它只在值变为较低的报警严重度时才显示滞后。

测试:

	if (prec->udf) {
		recGblSetSevr(prec, UDF_ALARM, prec->udfs);
		return;
	}

数据库公共定义字段UDF,当字段VAL未定义时应设置该字段。字段UDFS控制处于未定义状态的记录的严重性。STAT和SEVR字段被初始化,就像调用recGblSetSevr(prec,UDF_ALARM,prec->udfs)一样。因此,如果从未处理过记录,则记录将处于未定义的报警状态,严重程度由记录的UDFS字段设置。字段UDF初始化为值1(true)。因此,上述代码将使记录保持报警状态,直到UDF重置为0(false)。

UDF字段非零表示记录未定义,即其VAL字段的内容不代表实际值。当记录加载到ioc时,这通常是记录的初始状态。当代码设置VAL字段时,它也应该设置UDF,通常为false。当VAL设置为NaN(非数字)值时,对于VAL字段为DBF FLOAT或DBF DOUBLE的记录,可以将UDF设置为true。

对于输入记录,设备支持负责获取输入值。如果无法获得输入值,记录支持和设备支持都不会将UDF设置为false。如果设备支持读取原始值,它将返回一个值,告诉记录支持人员执行转换。在记录支持将VAL设置为转换后的值之后,它将UDF设置为false。如果设备支持获得写入VAL的转换值,则将UDF设置为false。

对于输出记录,要么记录/设备支持之外的内容写入VAL字段,要么给VAL赋值,因为记录支持通过OMSL字段获取值。在这两种情况下,写入VAL字段的代码都将UDF设置为false。

当数据库访问写入VAL字段时,它将UDF设置为false。

调用例程recGblSetSevr来发出警报。它可以由iocCore、记录支持或设备支持调用。检测到警报的代码负责发出警报。

4.6 Raising Monitors

	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) {
	        /* 发送存档改变事件在值字段 */
	        monitor_mask |= DBE_LOG;
	        /* 更生一个监测的存档值 */
	        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 - 记录的地址
		pfiedld - 字段的地址
		monitor_mask - 一个位掩码,可以是下面的任意组合
			DBE ALARM: 报警状态发生变化。这是由recGblResetAlarms设置的。
			DBE LOG  : 存档值更新。
			DBE VAL  : 值更新。
			DBE PROPERTY: 值属性更新。

记录支持模块负责为任何因记录处理而更改的字段调用db_post_event。

五、记录支持例程

本节描述在RSET定义的例程。任何不适用于特定记录类型的例程都必须声明为NULL。

5.1 生成记录中每个字段的报告

	long report(void *precord);

大多数记录类型不使用此例程。任何操作都是特定于记录类型的。

5.2 初始化记录处理

	long init(void);

大多数记录类型不使用此例程。任何操作都是特定于记录类型的。大多数记录类型不需要这个例程。

5.3 初始化特定记录

	long (*init_record)(struct dbCommon *precord, int pass);

对于这个例程处理的每个类型的数据库记录,iocInit调用这个例程两次(pass=0和pass=1)。它必须执行以下功能:

  • 检查 and/or 发出相关设备支持例程的初始化调用。
  • 执行任何特定于记录类型的初始化。
  • 在第一次传递期间,它只能执行影响precord引用的记录的初始化。
  • 在第二次传递期间,它可以执行影响其他记录的初始化。

5.4 记录处理

	long (*process)(struct dbCommon *precord);

这个例程必须遵循前面指定的指导原则

5.5 特殊处理

	long special(struct dbAddr *paddr, int after);

此例程为dbAddr引用的字段实现特定于记录类型的特殊处理。当从记录外部写入字段时,它被调用两次,第一次是在对字段进行任何更改之前,使用after=0;第二次是在字段做了改变之后,after=1。例程可以通过从第一次调用(after=0)返回错误状态来防止任何更改。文件special.h定义特殊类型。这个例程只对用户特殊字段调用,即SPC_xxx >= 100的字段。字段在ASCII记录定义文件中被声明为特殊的。新值不应添加到special.h中,而应使用SPC_MOD。

数据库访问例程dbGetFieldIndex可用于确定修改了哪个字段。

5.6 获取值

	long (*get_value)(void); 

这个例程已不再使用。它应该作为一个NULL过程留在记录支持条目表中。

5.7 转换dbAddr定义

	long cvt_dbaddr(struct dbAddr *paddr);

如果字段的特殊设置为SPC_DBADDR,则dbNameToAddr调用这个例程。一个典型的用法是字段引用数组。这个例程可以更改dbAddr字段的任何组合:no_elements、field_type、field_size、special、pfield和dbr_type。例如,如果波形记录的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 *units);

这个例程设置工程单位。
数据库访问例程dbGetFieldIndex可用于确定修改了哪个字段。

5.11 获取精度

	long get_precision(const struct dbAddr *paddr, long *precision);

此例程获取精度,即小数位数,用于将字段值转换为ASCII字符串。对于与值字段不直接相关的字段,应调用recGblGetPrec。
数据库访问例程dbGetFieldIndex可用于确定要修改的字段。

5.12 获取枚举字符串

	long get_enum_str(const struct dbAddr *paddr, char *p);

这个例程设置*p等于字段值的ASCII字符串。字段的类型必须是DBF_ENUM。查看bi或mbbi记录的代码作为示例。数据库访问
例程dbGetFieldIndex可用于确定修改了哪个字段。

5.13 获取枚举字段的字符串

	//dbAccessDefs.h
	struct dbr_enumStrs     {DBRenumStrs};
	#define DBRenumStrs \
	        epicsUInt32     no_str;         /* 字符串长度 number of strings */\
	        epicsInt32      padenumStrs;    /* 填充强制8字节对齐 */\
	        char            strs[DB_MAX_CHOICES][MAX_STRING_SIZE];  /* 字符串值 */
	
	//xxxRecord.h
	long get_enum_strs(const struct dbAddr *paddr, struct dbr_enumStrs *p);

这个例程为结构dbr_enumStrs的所有字段提供值。
查看bi或mbbi记录的代码作为示例。
数据库访问例程,dbGetFieldIndex可用于确定修改了哪个字段。

5.14 放置枚举字符串

	long put_enum_str(const struct dbAddr *paddr, char *pbuffer);

给定一个ASCII字符串,这个例程更新数据库字段。将字符串与与每个枚举值关联的字符串值进行比较,如果找到匹配项,则将数据库字段设置为匹配字符串的索引。
查看bi或mbbi记录的代码作为示例。
数据库访问例程dbGetFieldIndex可用于确定要修改的字段。

5.15 获取Graphics Double信息

	long get_graphic_double(struct dbAddr *paddr, struct dbr_grDouble *p);

此例程填充结构dbr_grDouble的图形相关字段。对于与值字段不直接相关的字段,应调用recGblGetGraphicDouble。
数据库访问例程dbGetFieldIndex可用于确定要修改的字段。

5.16 获取Control Double信息

	long get_control_double(struct dbAddr *paddr, struct dbr_ctrlDouble *p);

此例程为dbr_ctrlDouble结构的所有字段提供值。对于与值字段不直接相关的字段,应调用recGblGetControlDouble。
数据库访问例程dbGetFieldIndex可用于确定修改了哪个字段。

5.16 获取报警双重信息

	long get_alarm_double(struct dbAddr *paddr, struct dbr_alDouble *p);

这个例程为结构dbr_alDouble的所有字段提供值。
数据库访问例程dbGetFieldIndex可用于确定修改了哪个字段。

六、全局记录支持例程

可以使用许多全局记录支持例程。这些例程供特定于记录的处理例程使用,但是任何希望使用它们的服务的例程都可以调用这些例程。这些例程的名称都以“recGbl”开头。代码使用这些例程应该:

	#include <recGbl.h>

6.1 报警状态和严重性

在记录处理过程中,警报可能在许多不同的地方发出。该算法的目标是使告警级别最大化,即提出最高级别的未完成告警。如果发现多个相同级别的告警,则报告第一个告警。这意味着,每当代码片段想要发出警报时,只有当它声明的警报级别大于已经存在的警报级别时,它才会发出警报。4个字段(数据库通用)用于实现告警:serv、stat、nsev、nsta。前两个是记录完成处理后的状态和严重性。最后两个字段(nsta和nsev)是要在记录处理期间设置的状态和严重性值。有两个例程用于处理告警。每当一个例程想要发出警报时,它就调用recGblSetSevr。如果它会导致警报级别增加,则会改变nsta和nsev的值。在处理结束时,记录支持模块必须调用recGblResetAlarms。这个例程设置stat = nsta、sevr = nsev、nsta= 0和nsev = 0。如果stat或serv自上次调用以来改变了值,它为stat和serv调用db_post_event并返回DBE_ALARM值。如果没有发生任何更改,则返回0。因此,在调用recGblResetAlarms之后,一切都准备好在下次处理记录时发出警报。上面提供的示例记录支持模块展示了如何使用这些宏。

	int recGblSetSevr(void *precord, short nsta, short nsev)

如果改变了nsta 和/或 nsev返回TRUE,如果没有改变则返回FALSE。

	unsigned short recGblResetAlarms(void *precord);

返回:监视器掩码的初始值

6.2 报警确认

数据库Common包含两个额外的告警相关字段

  • acks - 最高严重性未确认警报
  • ackt - 瞬态报警是否需要确认
    这些字段由iocCorerecGblResetAlarms处理,不应该被记录支持使用。它为警报处理程序提供警报确认功能。

6.3 生成错误:过程变量名、调用者、消息

建议: 对于新代码,使用 errlogPrintf 来替代

	void recGblDbaddrError(long status,
						   struct dbAddr *paddr,
						   char *pcaller_name); /* 调用例程名称 */

这个例程与系统范围的错误处理系统接口,显示以下信息:状态信息、进程变量名称、调用例程。

6.4 生成错误:状态字符串、记录名称、调用者

建议: 对于新代码,使用 errlogPrintf 来替代

	void recGblRecordError(long status,
						   void *precord,
						   char *pcaller_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 获取 Graphics 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 获取 Alarm Double

	void recGblGetAlarmDouble(struct dbAddr *paddr, struct dbr_alDouble *pcd);

get_alarm_double记录支持例程可以使用这个例程来获取它不知道如何设置的字段的控制值。

6.9 获取精度

	void recGblGetPrec(struct dbAddr *paddr, long *pprecision);

get_precision记录支持例程可以使用这个例程来获取它不知道如何设置的字段的精度值。

6.10 获取时间戳

	void recGblGetTimeStamp(void *precord)

这个例程获取当前时间戳并将其放入记录中。它执行以下操作:

  • 如果TSEL不是一个常量链接,并且TSEL引用了记录的TIME字段,则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(或相关的设备支持)调用,以初始化与常量链接关联的字段。如果(did not, did)修改了目标,则返回(FALSE, TRUE)。

6.13 模拟值死区检查

	void recGblCheckDeadband(epicsFloat64 *poldval,
							 const epicsFloat64 newval,
							 const epicsFloat64 deadband,
							 unsigned *monitor_mask,
							 const unsigned add_mask);

检查模拟(double)值是否在指定的死带之外,并在监视器掩码中设置位。这个例程通常由模拟记录的监视器(作为处理的一部分)调用,以检查某个值是否在预定义的死区之外。它还根据检查结果设置监控掩码中的位。如果newval位于指定的死带之外,则将newval复制到*poldval中,并将add_mask OR 'ed到monitor_mask中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值