这个记录类型通常用于从硬件输入获取一个模拟值并且转换它为工程单位。这个记录支持到工程单位的线性转换和断点转换,平滑,警报限制,警报过滤以及图形和控制限制。
在下面描述记录专用字段,按功能分组。
这些字段控制这个记录在其运行时将从何处读取数据:
DTYP字段选择哪个设备支持层应该负责提供输入数据给这个记录。EPICS Base提供的ai设备支持层在Device Support部分记载。外部支持模块可以为这个记录类型提供更多设备支持。如果没有显式地设置,DTYP值默认为此记录类型装载地第一个设备支持,其将通常是随Base提供地Soft Channel支持。
INP链接字段包含一个数据库或通道访问链接或者提供设备支持使用来确定输入数据应该来自哪里的硬件地址信息。INP字段的格式取决于DTYP字段选择的设备支持层。有关支持的各种硬件地址的描述见Address Specification。
这些字段控制raw输入值是否以及如何被转换成工程单位:
如果设备支持层读取以工程单位表示的值并且放置其到VAL字段时,不使用这些字段。这应用于Soft Channelk和Async Soft Channel设备支持,并且对于GPIB以及类似的高级设备接口是非常常见的。
如果设备支持设置RVAL字段,LINR字段根据如下控制着如何被转换到工程单位以及放置到VAL字段:
1) RVAL被转换成double并且向其加上ROFF。
2) 如果ASLO是非0,这个值乘上ASLO。
3) 加上AOFF。
4) 如果LINR是NO CONVERSION,在以上步骤后,结束单位转换。
5) 如果LINR是LINEAR或SLOPE,到第三步的值乘上ESLO,并且加上EOFF,完成单位转换过程。
6) 对于LINR的任何其它值,对以上第三步的值使用一个特定的断点表。
对于LINR字段LINEAR和SLOPE设置的差别是如何计算转换参数:
通常只在设备支持设置RVAL字段并且使用单位转换过程时才使用这种滤波。直接设置VAL字段的设备支持如果需要可以实现这种滤波。
由一个参数字段控制滤波:
SMOO字段应该被设置成一个0和1之间的数值。如果设成0,不适用这种滤波(不平滑),而如果设置成1,结果是无限平滑(VAL字段将不再变化)。执行计算是:
VAL = VAL * SMOO + (1 - SMOO) * New Data
此处New Data是来自以上单位转换的结果。这实现了z平面极点在SMOO的一阶无限脉冲响应(IIR)数字滤波器。等价的连续时间滤波时间常数=-T/ln(SMOO)
此处是记录运行之间的时间。
如果在适用了平滑滤波后,VAL字段包含了一个NaN(非数值)值,UDF字段被设置成非0,表明这个记录值是未定义的,这将触发一个严重性INVALID_ALARM的UDF_ALARM。
这些参数用于向操作者显示有意义数据。它们完全不影响这个记录的功能。
有关记录名称(NAME)和描述(DESC)字段的更多信息见Fields Common to All Record Types。
用户通过将数值写入HIHI,HIGH,LOW和LOLO字段配置限制警报,并且通过在对应的HHSV,HSV,LSV和LLSV菜单字段中设置关联警报严重性来配置限制警报。
HYST字段控制回滞来防止来自一个接近其中一个限制的输入信号并且遭受重要的读出噪声的警报抖动。
AFTC字段对低通滤波器设置时间常数,在这个低通滤波器在信号已经在警报范围内指定秒数前推迟报告限位警报(默认0的AFTC值保留先前行为)。为了滤波操作有效作用,必须足够频繁地扫描这个记录,并且警报严重性只能在运行这个记录时才能变化,但运行不是必须是定时的;这个滤波器在其计算中使用从上次运行这个记录以来的时间。设置AFTC为一个正秒数将在输入信号已经在那个警报范围那个秒数前延时这个记录进入或退出minor警报严重性或者从minor到major严重性。
记录警报和标准字段的完整解析见Alarm Specification。Alarms Fields列出了与所有记录类型共有的警报相关联的其它字段。
这些参数用于确定何时发送放在VAL字段上的monitors。当当前值超过了上次被传递的值何时的死区时,这些monitors被发送。如果这些字段被置成0时,在这个值每次变化时将触发一个monitor;如果被设置成-1,在这个记录每次运行时,将发送一个monitor。
ADEL字段为存档monitors(DEB_LOG事件),而MDEL字段控制值monitors(DBE_VALUE事件)。
以下字段由这个记录在运行时使用来实现这个记录进行监视的功能。
以下字段用于在仿真模式操作这个记录。
如果SIMM(通过SIML获取)是YES或RAW,记录被置入SIMS严重性并且这个值是通过SIOL被获取的(缓存在SVAL)中。如果SIMM是YES,SVAL不被转换地写入VAL,如果SIMM是RAW,SVAL被传递到RVAL并且被转换。SSCN设置在仿真模式中使用地不同SCAN机制。SDLY设置一个为异步模拟运行使用的延时(以秒为单位)。
有关仿真模式和其字段的更多信息见Input Simulation Fields。
记录需要设备支持提供一个入口表(dset),其定义以下成员:
- typedef struct {
- long number;
- long (*report)(int level);
- long (*init)(int after);
- long (*init_record)(aiRecord *prec);
- long (*get_ioint_info)(int cmd, aiRecord *prec, IOSCANPVT *piosl);
- long (*read_ai)(aiRecord *prec);
- long (*special_linconv)(aiRecord *prec, int after);
- } aidset;
这个模块必须设置number至少为6,并且提供一个指向read_ai()例程的指针;如果对应的支持层不需要其它函数指针的相关联功能,它们可以是NULL。大部分设备支持也提供一个init_record()例程来配置这个记录实例并且连接它到硬件或驱动支持层,并且如果使用记录的"Unit Conversion"特性,它们也设置special_linconv()。
在下面描述这些例程。
long report(int level)
这个可选的例程被IOC命令dbior调用,并且被传递用户请求的报告等级。如应该打印一个有关此设备支持状态的报告到stdout。level参数可以被用于用更高等级输出更详细地信息,或者选择用不同级别选项不同信息类型。等级0应该打印一小段概述。
long init(int after)
这个选项例程在IOC初始化时被调用两次。第一次调用用这个整数参数设置为0在进行任何init_record调用前发生。第二次调用用after设置为1在所有init_record()调用之后发生。
long init_record(aiRecord *prec)
记录初始化代码为每个具有其DTYP字段设为使用这个设备支持的ai记录实例调用这个可选的例程。它通常用于检查INP地址是期望的类型并且它指向一个有效地址;去分配任何记录专用的缓存空间和其它内存;以及去连接read_ai()例程正常工作所需的任何通信通道。
如果这个记录类型的单位转换特性被使用了,init_record()例程应该从EGUL和EGUF字段值为ESLO和EOFF字段计算合适的值。只在这个记录的LINR字段被设置成了LINEAR时,才需要执行这个计算,但首先检查那个条件是不必要的。这个相同计算在special_linconv()例程中发生,因此实现可以通常只调用那个例程来执行这个任务。
long get_ioint_info(int cmd, aiRecord *prec, IOSCANPVT *piosl)
当这个记录的SCAN字段正在被更改成值I/O Intr或者对值I/O Int修改来查找此记录应该被添加到哪个I/O Interrupt扫描列表或者被从哪个I/O Interrupt扫描列表删除时,这个可选例程被调用。
当记录被添加到扫描列表时,cmd参数是0,而它被从列表删除时,cmd是1。这个例程必须确定这个记录应该被连接到哪个中断源,它在返回前通过它用在*pisol的位置指向这个扫描列表表明这个中断源。通过返回一个非0值给其调用者,它能够完全防止SCAN字段被更改。
在大部分情况中,设备支持通过调用为每个单独的中断源调用一次void scanIoInit(IOSCANPVT *p piosl)创建它为自己返回的I/O中断扫描列表。此例程分配内存并且初始化这个列表,接着在位于*piosl的位置回传一个指向这个新列表的指针。
当设备支持接收中断已经发生的通知时,它通过调用void scanIoRequest(IOSCANPVT iosl)告诉IOC,上述调用将安排在合适线程中运行合适的记录。scanIoRequest()例程在嵌入架构上(vxWorks和RTEMS上)从一个中断服务例程调用是安全的。
long read_ai(aiRecord *prec)
当记录想要来自被寻址的设备的新值时,这个必需的例程被调用。它负责执行(或者至少初始化)一个读操作,并且*(最终)返回其值给这个记录。
...PACT和异步运行。
...返回值...
long special_linconv(aiRecord *prec, int after)
如果记录类型的单位转换特性被返回一个状态值0的设备支持read_ai()例程使用,应该提供这个可选的例程。当LINR,EGUL或EGUF中任何字段被修改并且LINR有值LINEAR时,记录代码调用它。这个例程必须根据EGUL和EGUF的新值,计算和设置合适的EOFF和ESLO。
这些计算可以被表述成read_ai()例程能够放入RVAL字段的最小和最大raw值。当RVAL设置为RVAL_max,VAL字段将被设置成EGUF,而当RVAL被设置成RVAL_min时,VAL字段将变成EGUL。
这个要使用的方程是:
- EOFF = (RVAL_max * EGUL − RVAL_min * EGUF) / (RVAL_max − RVAL_min)
- ESLO = (EGUF − EGUL) / (RVAL_max − RVAL_min)
注意:记录支持在调用这个例程前设置EOFF为EGUL,者是非常普遍的情况(当RVAL_min为0)。
以下数据库实例文件,是由5个记录实例组成:
1) $(USER):RawInput记录实例类型为longin,用作模拟ai记录的raw输入。
2) $(USER):InputHappen记录实例类型为event,其作用是在上一个记录引起其运行后,驱动下面三个记录运行。
3) $(USER):Voltage1, $(USER):Voltage2,$(USER):OrignalValue三个记录实例类型是ai,前两个记录使用软设备支持Raw Soft Channel, 对raw输入值进行转换后,放入VAL字段,它们转换使用了不同的字段。$(USER):OrignalValue记录使用Soft Channel,其输入值,不经过转换,直接放入了VAL字段。
- record(longin, "$(USER):RawInput")
- {
- field(SCAN, "Passive")
- field(DTYP, "Soft Channel")
- field(INP, "0")
- field(LOPR, "0")
- field(HOPR, "10000")
- field(FLNK, "$(USER):InputHappen.PROC")
- }
-
- record(event, "$(USER):InputHappen")
- {
- field(INP, "10")
- field(SCAN, "Passive")
- field(DTYP, "Soft Channel")
- }
-
- record(ai, "$(USER):Voltage1")
- {
- field(SCAN, "Event")
- field(EVNT, "10")
- field(DTYP, "Raw Soft Channel")
- field(INP, "$(USER):RawInput.VAL")
- field(ROFF, "0")
- field(ASLO, "1")
- field(AOFF, "0")
- field(LINR, "SLOPE")
- field(ESLO, "0.001")
- field(EOFF, "0")
- field(EGUL, "0")
- field(EGUF, "10")
- field(LOPR, "0")
- field(HOPR, "10")
- }
-
-
- record(ai, "$(USER):Voltage2")
- {
- field(SCAN, "Event")
- field(EVNT, "10")
- field(DTYP, "Raw Soft Channel")
- field(INP, "$(USER):RawInput.VAL")
- field(ROFF, "0")
- field(ASLO, "0.001")
- field(AOFF, "10")
- field(LINR, "NO CONVERSION")
- field(ESLO, "0.001")
- field(EOFF, "0")
- field(EGUL, "10")
- field(EGUF, "20")
- field(LOPR, "10")
- field(HOPR, "20")
- }
-
-
- record(ai, "$(USER):OriginValue")
- {
- field(SCAN, "Event")
- field(EVNT, "10")
- field(DTYP, "Soft Channel")
- field(INP, "$(USER):RawInput.VAL")
- field(LOPR, "0")
- field(HOPR, "10000")
- }
将上述数据库文件加载到IOC后,进行测试:
- [root@bjAli ~]# caget blctrl:RawInput blctrl:Voltage1 blctrl:Voltage2
- blctrl:RawInput 0
- blctrl:Voltage1 0
- blctrl:Voltage2 0
- [root@bjAli ~]# caput blctrl:RawInput 1
- Old : blctrl:RawInput 0
- New : blctrl:RawInput 1
- [root@bjAli ~]# caget blctrl:RawInput blctrl:Voltage1 blctrl:Voltage2
- blctrl:RawInput 1
- blctrl:Voltage1 0.001
- blctrl:Voltage2 10.001
- [root@bjAli ~]# caput blctrl:RawInput 20
- Old : blctrl:RawInput 1
- New : blctrl:RawInput 20
- [root@bjAli ~]# caget blctrl:RawInput blctrl:Voltage1 blctrl:Voltage2
- blctrl:RawInput 20
- blctrl:Voltage1 0.02
- blctrl:Voltage2 10.02
- [root@bjAli ~]# caput blctrl:RawInput 500
- Old : blctrl:RawInput 20
- New : blctrl:RawInput 500
- [root@bjAli ~]# caget blctrl:RawInput blctrl:Voltage1 blctrl:Voltage2
- blctrl:RawInput 500
- blctrl:Voltage1 0.5
- blctrl:Voltage2 10.5
- [root@bjAli ~]# caput blctrl:RawInput 8000
- Old : blctrl:RawInput 500
- New : blctrl:RawInput 8000
- [root@bjAli ~]# caget blctrl:RawInput blctrl:Voltage1 blctrl:Voltage2
- blctrl:RawInput 8000
- blctrl:Voltage1 8
- blctrl:Voltage2 18