1 概要
除了记录支持模块,每个记录类型可以有任意数目的设备支持模块。设备支持的目的是从记录运行例程隐藏硬件特定的细节。因而,可以不更改记录支持例程为新设备开发支持。
设备支持例程了解记录定义。它也知道如何直接与硬件会话或者如何调用与硬件连接的设备驱动。因而,设备支持例程是数据库记录中硬件专用字段和设备驱动或硬件自身之间的接口。
发行版3.14.8引入扩展设备支持的概念,它提供了设备支持能够实现在运行时在记录的地址被改变时获取通知的可选接口。这运行记录被重新连接到一个不同种类的I/O设备,或者只到相同设备上一个不同信号。在以下第5部分详细地描述扩展设备支持。
数据库common包含了两个设备相关联字段:
- dtyp:设备类型
- dset:设备支持入口表地地址。
字段DTYP包含了由设备ASCII定义定义地菜单选项的索引。iocInit使用这个字段和在devSup.h中定义的设备支持例程来初始化字段DSET。因而记录支持可以通过DSET字段定位相关联的设备支持。
#ifdef __cplusplus
extern "C" {
typedef long (*DEVSUPFUN)(void *); /* ptr to device support function*/
#else
typedef long (*DEVSUPFUN)(); /* ptr to device support function*/
#endif
#ifndef USE_TYPED_DSET
typedef struct dset { /* device support entry table */
long number; /*number of support routines*/
DEVSUPFUN report; /*print report*/
DEVSUPFUN init; /*init support layer*/
DEVSUPFUN init_record; /*init device for particular record*/
DEVSUPFUN get_ioint_info; /* get io interrupt information*/
/*other functions are record dependent*/
} dset;
#else
typedef typed_dset dset;
#endif /* USE_TYPED_DSET */
设备支持模块可以分为两个基本类别:同步和异步。同步设备支持用于可以对I/O没有延时访问的硬件。很多基于寄存器的设备是同步设备。其它设备,例如GPIB设备,只能通过可能发生大量时间完成的I/O请求被访问。这样的设备必须有相关联的异步设备支持。异步设备支持使得创建有链接记录的数据库更加困难。
如果一个设备在少于几微秒的延时被访问,则同步设备支持是合适的。如果设备产生大于100微秒的延时,则异步设备支持是合适的。如果延时在这些值之间,你想用做什么,根据测试结果。可能你应该询问硬件设计者为什么创造这样一个设备。
如果一个设备花费很长时间接受请求,除异步设备支持外,还有一个选项。可以创建一个驱动程序,它周期地查询所有其链接的输入设备。设备支持仅返回最新查询的值。
对于输出,设备支持仅通知驱动程序一个新值必须被写。这个驱动程序,在其一个查询阶段,写这个新值。EPICS Allen Bradley设备/驱动支持是一个代表性示例。
2、示例同步设备支持模块
long init_record();
long read_ai();
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info; // 以上是必需有的,不用的声明为NULL
DEVSUPFUN read_ai; // 以下是记录专用的
DEVSUPFUN special_linconv;
}devAiSoft={
6,
NULL,
NULL,
init_record,
NULL,
read_ai,
NULL
};
epicsExportAddress(dset,devAiSoft);
static long init_record(void *precord)
{
aiRecord *pai = (aiRecord *)precord;
long status;
/* ai.inp必须是CONSTANT, PV_LINK, DB_LINK或CA_LINK */
switch (pai->inp.type) {
case (CONSTANT) ://常数的情况:从INP字段按DBF_DOUBLE类型获取值,存入VAL字段
if(recGblInitConstantLink(&pai->inp,DBF_DOUBLE,&pai->val))
pai->udf = FALSE; // 初始化完成
break;
case (PV_LINK) ://过程变量链接,数据库访问链接,通道访问链接
case (DB_LINK) :
case (CA_LINK) :
break;
default : // INP为其它情况,报错
recGblRecordError(S_db_badField, (void *)pai,
"devAiSoft (init_record) Illegal INP field");
return(S_db_badField);
}
/* 确认记录processing例程不执行任何转换*/
pai->linr=0;
return(0);
}
static long read_ai(void *precord)
{
aiRecord*pai =(aiRecord *)precord;
long status;
/*
从一个数据库链接引用字段获取值,格式:
long dbGetLink(
struct db_link *plink, // 指向链接字段的ptr
short dbrType, // DBR_xxx
void *pbuffer, // 返回数据的ptr
long *options, // 选项的ptr
long *nRequest); // 所需元素数目的ptr
• options:如果不需要选项,可以是NULL
• nRequest:对于标量,可以是NULL
*/
status=dbGetLink(&(pai->inp.value.db_link) ,DBR_DOUBLE,&(pai->val),0,1);
if (pai->inp.type!=CONSTANT && RTN_SUCCESS(status)) pai->udf = FALSE;
return(2); /* 不转换 */
}
这个示例是devAiSoft,它支持软模拟输入。INP字段可以是常数或者一个数据库链接或者通道访问链接。仅提供了两个例程(剩下都声明为NULL)。init_record例程首先检查这个链接类型是否有效。如果链接是一个常数,它初始化VAL。如果链接时一个过程变量链接,它调用dbCaGetLink将其变为通道访问链接。如果链接是数据库或者通道访问链接,read_ai例程获取一个输入值,否则它什么也不做。
3、示例异步设备支持模块
这个示例展示如何编写一个异步设备支持例程。它进行以下顺序的操作:
- 当首次调用时,PACT是FALSE。它安排一个回调例程(myCallback),在由DISV字段指定的秒数后被调用。
- 它打印一条声明processing以及开始的消息,设置PACT为TRUE,并且返回。记录没有结束processing,processing例程返回。
- 指定时间消耗时,myCallback被调用。它调用dbScanLock锁定这个记录,调用process,并且调用dbScanUnlock解锁这个记录。它直接调用记录支持模块的process,其通过dbCommon中RSET字段定位这个模块,而不是调用dbProcess。因为PACT为TRUE,dbProcess不调用process。
- 当process执行时,它再次调用read_ai。这次,PACT是TRUE。
- read_ai打印一条声明记录processing结束并且返回一个状态2的消息。通常将返回一个0值。值2告诉这个记录支持例程不尝试任何转换。这是模拟输入记录使用的规则(不好的规则)。
- 当read_ai返回,记录processing例程结束记录运行。
到此,记录已经完全被处理。下次调用process被调用时,一切从头开始。
注意:这某种程序上时一个人为示例,因为这种形式的真实代码更可能使用回调challbackRequestProcessCallbackDelayded函数来执行所需处理。
static void myCallback(CALLBACK *pcallback)
{
struct dbCommon *precord;
struct rset *prset;
// 从回调函数的参数中获取使用这个回调的记录
callbackGetUser(precord,pcallback);
// 从记录中获取记录支持例程集合
prset=(struct rset *)(precord->rset);
// 锁定这个记录
dbScanLock(precord);
// 调用设备支持支持process例程
(*prset->process)(precord);
// 解锁这个记录
dbScanUnlock(precord);
}
// 记录初始化
static long init_record(struct aiRecord *pai)
{
// 定义回调结构体指针
CALLBACK *pcallback;
switch (pai->inp.type) {
case (CONSTANT) : //如果记录INP字段中是常数,则分配一个回调结构体
pcallback = (CALLBACK *)(calloc(1,sizeof(CALLBACK)));
// 为回调结构体设置回调函数
callbackSetCallback(myCallback,pcallback);
// 将当前记录实例设置为这个回调结构体的使用者
callbackSetUser(pai,pcallback);
// 这个记录的私有字段设置位这个回调结构体
pai->dpvt = (void *)pcallback;
break;
default :// INP字段为其它类型时,设置为错误
recGblRecordError(S_db_badField,(void *)pai,
"devAiTestAsyn (init_record) Illegal INP field");
return(S_db_badField);
}
return(0);
}
static long read_ai(struct aiRecord *pai)
{
//获取这个记录的回调结构体
CALLBACK *pcallback = (CALLBACK *)pai->dpvt;
if(pai->pact) {
pai->val += 0.1; /* 更改VAL只为了显示我们做了一些事情 */
pai->udf = FALSE; /* 我们修改了VAL,因而我们也负责UDF */
printf("Completed asynchronous processing: %s\n",pai->name); /* 在终端打印,表示此记录实例已经结束运行了*/
return(2); /* 不需要转换 */
}
/* 在终端打印,表示此记录实例已经开始异步有运行 */
printf("Starting asynchronous processing: %s\n",pai->name);
pai->pact=TRUE;
// 延时DISV指定的时间后,调用回调结构体中回调函数
callbackRequestDelayed(pcallback,pai->disv);
return(0);
}
/* 为devAiTestAsyn创建设备支持 */
struct {
long number;
DEVSUPFUN report;
DEVSUPFUN init;
DEVSUPFUN init_record;
DEVSUPFUN get_ioint_info;
DEVSUPFUN read_ai;
DEVSUPFUN special_linconv;
}devAiTestAsyn={
6,
NULL,
NULL,
init_record,
NULL,
read_ai,
NULL
};
epicsExportAddress(dset,devAiTestAsyn);
4 设备支持例程
这个部分描述在DSET中定义的例程。任何未应用于一个特定记录类型的例程必须被声明为NUL。
4.1 生成设备报告
long report(int interest);
这个例程负责报告它找到的所有I/O板卡。提供interest值允许不同报告,或者控制显示多少详细。如果一个设备支持模块正在使用一个驱动,它可以选择不实现这个例程,因为驱动生成这个报告。
4.2 初始化设备运行
long init(int after);
这个例程在IOC初始化时被调用两次。任何操作是设备专用的。这个例程被调用两次:一次在任何数据库记录被初始化前,而另一次在所有记录被初始化后,但在扫描任务启动前。after值0在记录初始化前,而值1在记录初始化后。
4.3 初始化特定的记录
long init_record(void *precord); /* 记录地址 */
记录支持init_record例程调用这个例程。
4.4 获取I/O中断信息
long get_ioint_info(int cmd, struct dbCommon *precord, IOSCANPVT *ppvt)
这被I/O中断扫描任务调用。如果cmd是(0,1),则这个例程在相关联记录被放入一个I/O扫描列表或者被从此列表中移除时被调用。
4.5 其它设备支持例程
所有其它设备支持例程都是记录类型专用的。