1# @Language: Markdown
2# @Software: VS Code/MacDown/Typora/Vim
3# @Author : Di Wang
4# @Email : [email protected]
缘由: 最近看到了Gabriel创建的项目
感觉是应用费曼学习法的良机. 于是决定创建此文, 记录相关内容.
正如Gabriel所写:
This repository aims to collect examples of EPICS Databases to help newcomers (or not so new) get an idea on how to use different records.
即便已使用epics多年, 对一些record type依然不熟悉, 或源于需求少或源于版本更新带来的功能变更. 往往导致有简单方法却使用了更复杂的实现方式.
此文分为三部分, 一部分是epics database的示例, 另一部分是常用epics module的示例, 还有一些其它内容的示例. 一些epics module (如busy), 会创建了新的record type, 依然归入module. 而一些复杂的module, 如asyn, stream device则难以简单完成示例, 因此会另起专文记述.
由于内容较多, 会在闲暇时陆续更新, 此文大概会花费数年才能完成.
Module
autosave
caputlog
iocstats
TODO
recsync
calc
TODO
std
TODO
sscan
TODO
busy
sequencer
Others
Access Security
record patch
可以把record定义在一个db file中, 然后在另一个db file中添加这个record的一些field (比如逻辑处理相关field). 但是要注意, dbLoadRecords
的先后顺序.
也可以不写record的type, 用*
来代替.
最近在GitHub issue上看到有人在提议添加功能来反向patch(即删除某些record), 估计不久后会加入主线.
field modifiers
历史包袱又一例, 具体因果暂时未能厘清, 但大致源于以下一些限制:
- epics record name 长度限制 40 -> 60
- epicsString 大小限制为
MAX_STRING_SIZEP
(40) - CA 使用的
DBR_STRING
大小限制为MAX_STRING_SIZEP
(40)
而record name又会被使用在db link中, (INP, DOL, OUT, FWD), 所以就出现了一个问题, 当某个link field使用了大于40字节的record name时, 无法通过caget caput来读写. 于是引入了$
作为field modifier. 当在link field后面加上$
后, 就不会使用string, 而是使用char数组.
1$ caput -S long_string.INP$ SomeReallyLongRecordNameThatExceeds40Characters
2Old : long_string.INP$ SomeReallyLongRecordNameThatExceeds40Ch NPP NMS
3New : long_string.INP$ SomeReallyLongRecordNameThatExceeds40Characters NPP NMS
4
5$ caput -S long_string.INP SomeReallyLongRecordNameThatExceeds40Characters
6Old : long_string.INP SomeReallyLongRecordNameThatExceeds40Ch
7Error from put operation: Invalid element count requested
8
9$ caget -S long_string.INP$
10long_string.INP$ SomeReallyLongRecordNameThatExceeds40Characters NPP NMS
11
12$ caget -S long_string.INP
13long_string.INP SomeReallyLongRecordNameThatExceeds40Ch
类似的, lsi
和lso
record的VAL field也可以使用$
来修改CA的行为.
Channel Filters and IOC Database Link types
breakpoint table
TODO
Echoless IOC Comments
启动ioc时, 可以在st.cmd文件中使用#-
来不让注释行输出. 非常有用.
示例
1$ cat st.cmd
2# print this comment
3#- not print this comment
4dbLoadRecords a.db
5
6$ softIocPVA st.cmd
7# print this comment
8dbLoadRecords a.db
9iocInit
10Starting iocInit
11############################################################################
12## EPICS R7.0.8
13## Rev. 2024-03-27T10:23+0100
14## Rev. Date build date/time:
15############################################################################
16iocRun: All initialization complete
17epics>
18iocInit
ioc shell commands
Database (record type)
dbCommon
field | information |
---|---|
NAME | 60 characters |
DESC | 40 characters |
ASG | 29 characters |
SCAN | menuScan |
PINI | menuPini |
PHAS | 不建议使用 |
EVNT | Event scan类型 |
DTYP | Device Type |
TSE | Time Stamp Event, 0, -1, -2, [1-255] |
TSEL | Time Stamp Link |
TIME | epicsTimeStamp time |
DISS | Disable Alarm Sevrty, menuAlarmSevr |
DISV | Disable Value, 只是不process, value依然可以改变, 与DISA 比较, 相等的话disable |
DISA | Disable, DBF_SHORT |
SDIS | Scanning Disable, INLINK, 读取后放入DISA |
MLOK | Monitor lock, epicsMutexId |
MLIS | Monitor List, ELLLIST |
BKLNK | Backwards link tracking, ELLLIST |
DISP | Disable putField, 非0时不可通过CA或PVA修改field(除DISP) |
PROC | Force Processing, 写入时process record, 写0也可 |
STAT | Alarm Status, menuAlarmStat |
SEVR | Alarm Severity, menuAlarmSevr |
AMSG | Alarm Message |
NSTA | New Alarm Status, 用于内部处理过程, 处理完成后将最严重的级别更新到STAT |
NSEV | New Alarm Severity |
NAMSG | New Alarm Message |
ACKS | Alarm Ack Severity, highest unacknowledged alarm severity |
ACKT | Alarm Ack Transient, Alarm Ack Transient, menuYesNo |
LCNT | Lock Count, 记录PACT 为TRUE 时的数量, 超过10, 那就触发SCAN_ALARM |
PACT | Record active, 为TRUE 时代表record正被process, 类型是DBF_UCHAR 而非menuYesNo |
PUTF | dbPutField process, 不详 |
RPRO | Reprocess, 不详 |
ASP | Access Security Pvt, struct asgMember |
PPN | pprocessNotify, putNotify callback, struct processNotify |
PPNR | pprocessNotifyRecord, next record for PutNotify, struct processNotifyRecord |
SPVT | Scan Private, struct scan_element |
RSET | struct typed_rset |
DSET | unambiguous_dset |
DPVT | Device Private, void *dpvt |
RDES | Address of dbRecordType, struct dbRecordType |
LSET | Lock Set, struct lockRecord |
PRIO | Scheduling Priority, menuPriority |
TPRO | Trace Processing, 非0时打印trace信息 |
BKPT | Break Point, 调试用断点, epicsUInt8 bkpt |
UDF | Undefined |
UDFS | Undefined Alarm Severity, menuAlarmSevr |
UTAG | Time Tag |
FLNK | Forward Process Link |
对于TSE
与TSEL
:
- 当
TSEL
指向另一个record的TIME
field时, 使用获取到的TIME
作为本record的timestamp. 如果是同一个ioc,UTAG
也被复用. - 当
TSEL
指向另一个record其它field时, 那个field的值被放到TSE
中.- 当
TSE
为0, 会从current time providers中找到优先级最高的, 获取timestamp. - 当
TSE
为-1, 会从event time providers中找到优先级最高的, 然后由这个provider来提供event=-1
的timestamp. 需要调用generalTimeEventTpRegister
来注册event provider. - 当
TSE
为-2, 从device support中获取timestamp和user tag, 由device support来决定读取time provider还是event provider还是不注册provider直接读硬件. - 当
TSE
为[1, 255], 从event providers中找到优先级最高的, 然后由这个 provider提供对应event发生时的timestamp.
- 当
相关函数
1// base中的一些macro
2#define generalTimeCurrentTpName generalTimeCurrentProviderName
3#define generalTimeEventTpName generalTimeEventProviderName
4
5#define epicsTimeEventCurrentTime 0
6#define epicsTimeEventBestTime -1
7#define epicsTimeEventDeviceTime -2
8
9int epicsShareAPI epicsTimeGetEvent(epicsTimeStamp *pDest, int eventNumber)
10{
11 if (eventNumber == epicsTimeEventCurrentTime) {
12 // TSE为0的情况
13 return epicsTimeGetCurrent(pDest);
14 } else {
15 // TSE为-1或[1-255]的情况
16 return generalTimeGetEventPriority(pDest, eventNumber, NULL);
17 }
18}
19
20// mrfioc2 处理longout record中TSE=-2的情况
21static long process_longout(longoutRecord *prec)
22{
23 priv *p=static_cast<priv*>(prec->dpvt);
24 long ret=0;
25
26 if (prec->val>=0 && prec->val<=255)
27 post_event(prec->val);
28
29 if(prec->tse==epicsTimeEventDeviceTime){
30 p->evr->getTimeStamp(&prec->time,p->event);
31 }
32}
对于disable record, 使用Access security可以禁止record被读写, 也可以用DISV
和SDIS
禁止record被process.
1record(ai, ai){
2 field(DISV, "1")
3 field(DISS, "MAJOR")
4 field(VAL, "311")
5 field(SDIS, "bi PP")
6}
7
8record(bi, bi){
9 field(INP, "1")
10 field(PINI, "YES")
11}
Mostly Common
field | information |
---|---|
RVAL | 对于input, 从hardware中读取的原始值, 待转换; 对于output, 转换后发送给hardware的值. |
OVAL | Output Value |
RBV | Readback Value |
DOL | Desired Output Link |
OMSL | supervisory or closed_loop |
ORAW | Previous Raw Value |
PVAL | Previous value |
IVOA | INVALID output action, 三个选项Continue normally , Don't drive outputs 和Set output to IVOV |
IVOV | INVALID output value |
NORD | Number elements read, 用于waveform和aai |
Simulation
开启simulation mode时, 会忽略device support. 不知道有什么实际用处.
field | information |
---|---|
SIOL | Simulation Input/Output Link |
SVAL | Simulation Value |
SIML | Simulation Mode Link |
SIMM | Simulation Mode |
SIMS | Simulation Mode Severity |
OLDSIMM | Prev. Simulation Mode |
SSCN | Sim. Mode Scan |
SDLY | Sim. Mode Async Delay |
SIMPVT | Sim. Mode Private, epicsCallback |
1 -- (SIMM = NO?)
2 / (if supported and directed by device support,
3 / INP -> RVAL -- convert -> VAL),
4 (else INP -> VAL)
5 SIML -> SIMM
6
7 \
8 -- (SIMM = YES?) SIOL -> SVAL -> VAL
9 \
10 -- (SIMM = RAW?) SIOL -> SVAL -> RVAL -- convert -> VAL
Analog Input Record (ai)
Unit conversion
当device support为Raw Soft Channel
时才会转换.
field | information |
---|---|
RVAL | Current Raw Value |
ROFF | Raw Offset |
ASLO | Adjustment Slope |
AOFF | Adjustment Offset |
LINR | Linearization MENU, menuConvert |
ESLO | Raw to EGU Slope |
EOFF | Raw to EGU Offset |
EGUL | Engineer Units Low |
EGUF | Engineer Units Full |
PBRK | Pointer to brkTable |
LBRK | Last brkTable |
- (
RVAL
+ROFF
) - 如果ASLO不为0, 乘以
ASLO
- 再加上
AOFF
- 判断
LINR
的值:- 如果为
NO CONVERSION
, 结束转换 - 如果为
SLOPE
, 乘以ESLO
, 再加上EOFF
- 如果为
LINEAR
, 由special_linconv
函数根据用户设置的EGUL
和EGUF
, 以及hardware的实际量程来计算出ESLO
和EOFF
, 具体计算规则根据不详. - 其它选项, 使用breakpoint table.
- 如果为
Smoothing Filter
SMOO
值为0-1, 0的话代表不使用filter, 1的话代表infinite smoothing, 计算公式如下.
VAL = VAL * SMOO + (1 - SMOO) * New Data
Display
field | information |
---|---|
PREC | Display Precision |
EGU | Engineering Units, 16 characters |
HOPR | High Operating Range, 设置一些控件的上限 |
LOPR | Low Operating Range, 设置一些控件的下限 |
HIHI | |
LOLO | |
HIGH | |
LOW | |
HHSV | |
LLSV | |
HSV | |
LSV | |
HYST | 加入hysteresis, 比如low limit是10, HYST 为1, 当val从11降为10.5时无alarm, 但当val从9升为10.5时, 依然触发alarm, 直到vale > 10 + 1时, alarm才消失. |
AFTC | Alarm Filter Time Constant, 类似与HYST , 不过是当val持续一定时间后再触发alarm. |
AFVL | Alarm Filter Value, AFTC 计算过程中使用的变量. |
MDEL | Monitor Deadband, value monitors, 当val变化超过deadband时才会发送monitor. 0的话每次val改变都触发, -1时record process时触发. |
ADEL | Archive Deadband, archive monitors |
LALM | Last Value Alarmed |
ALST | Last Value Archived |
MLST | Last Val Monitored |
INIT | Initialized? |
示例
1record(ai, ai){
2 field(VAL, "13")
3 field(HOPR, "0")
4 field(LOPR, "100")
5 field(PREC, "3")
6 field(HIHI, "90")
7 field(HIGH, "80")
8 field(LOW, "20")
9 field(LOLO, "10")
10 field(HHSV, "MAJOR")
11 field(HSV, "MINOR")
12 field(LSV, "MINOR")
13 field(LLSV, "MAJOR")
14 field(PINI, "RUNNING")
15 field(HYST, "1")
16}
以下为HSYT
的示例, 可以看到VAL从13变为21时, 依然有low alarm.
Analog Output Record (ao)
ao的field和ai基本相同. 一些特殊的field:
OIF
: 可选项为Incremental
和Full
, 也就是output之前加上PVAL
的值.DRVL
: Drive Low Limit, 输出的下限DRVH
: Drive High Limit 输出的上限OROC
: Output Rate of Change, 设置与上次输出的OVAL
field相比能改变的最大值.
对于record中的一些preview value, 命名有些奇怪, 有时候是PVAL
, 有时候又是OVAL
, 即Old value.
在ao record中, OVAL
代表Output value, 所以不称呼为Old value可以理解.
但又把ORAW
叫Previous Raw Value, 把ORBV
叫Previous Readback Value. 真是很让人困惑
Analog Array Input Record (aai)
初始化时可以用支持JSON的link. 引号在使用Relaxed JSON时可以省略, 但规则不详.
MPST
与APST
控制何时触发monitor, 他们使用了HASH
field来判断值是否发生变化.
但鉴于可能的哈希碰撞(Hash Collision), 所以最好使用Always
, 也就是每次process就会触发monitor.
1record(aai, aai){
2 field(NELM, "10")
3 field(FTVL, "SHORT")
4 field(APST, "On Change")
5 field(MPST, "Always")
6# field(INP, "[3,1,1]")
7 field(INP, "{const: [315, 10, 0, 0, 1]}")
8}
9
10record(aao, aao){
11 field(OMSL, "closed_loop")
12 field(DOL, "aai")
13 field(PINI, "RUNNING")
14 field(FTVL, "SHORT")
15 field(NELM, "10")
16 field(OUT, "wf")
17}
18record(waveform, wf){
19 field(FTVL, "LONG")
20 field(NELM, "20")
21}
Analog Array Output Record (aao)
示例见aai
Waveform Record (waveform)
示例见aai
比aai
多的field: 基本都不再使用了.
RARM
: Rearm the waveformBUSY
: Busy Indicator
Binary Input Record (bi)
COSV
可以用来设置value改变时的alarm, 可以设置为MINOR or MAJOR
.
如下例, 当value为0时, 触发MAJOR, value从0变1时, 触发MINOR, value从1变1时, 触发NO_ALARM.
device support可以使用MASK
field. 不详.
1record(bi, bi){
2 field(VAL, "0")
3 field(ZNAM, "OFF")
4 field(ONAM, "ON")
5 field(ZSV, "MAJOR")
6 field(OSV, "NO_ALARM")
7 field(COSV, "MINOR")
8 field(PINI, "YES")
9}
Binary Output Record (bo)
可以用HIGH
field来判断ioc是否存活. 如下例, i_am_alive
持续写入deadIfZero
, 如果使用hardware interrupt来processi_am_alive
, 当持续5秒未收到interrupt, 则deadIfZero
会因为HIGH
field, 在5秒后自动归零.
1record(bo, "i_am_alive") {
2 field(DTYP, "Soft Channel")
3 field(SCAN, "1 second")
4 field(DOL, "1")
5 field(UDF, "0")
6 field(ZNAM, "Dead")
7 field(ONAM, "Alive")
8 field(OUT, "deadIfZero.VAL PP")
9}
10
11record(bo, "deadIfZero") {
12 field(DTYP, "Soft Channel")
13 field(ZNAM, "Dead")
14 field(ONAM, "Alive")
15 field(HIGH, "$(DEAD_SECONDS=5)")
16}
Calculation Record (calc)
有12个input links.
1# 简单的例子, heartbeat
2record(calc, "calc"){
3 field(VAL, "0")
4 field(CALC, "VAL?0:1")
5 field(SCAN, "1 second")
6}
7# 复杂的例子, 产生连续正弦波, 初始相位可调
8record(calc, "calc"){
9 field(INPA, "1.57079632679489661923") # 初始相位为pi/2
10 field(CALC, "A:=(A+D2R)>(2*PI)?0:(A+D2R); sin(A)")
11 field(SCAN, ".1 second")
12}
Calculation Output Record (calcout)
相比与calc
, 增加了一些output选项.
OOPT
: Output Execute Opt, 可选Every Time
,On Change
,When Zero
,When Non-zero
,Transition To Zero
,Transition To Non-zero
DOPT
: Output Data Opt, 可选Use CALC
,Use OCAL
OCAL
: Output Calculation, 和CALC
一样包含运算表达式OVAL
: Output Value,OCAL
的输出结果OEVT
: Event To Issue, 要触发的eventODLY
: Output Execute Delay, 触发event和输出前的delayDLYA
: Output Delay Active, delay过程中这个field会被设为1INAV
-INIV
: Input PV Status, 会被设为Ext PV NC
,Ext PV OK
,Local PV
,Constant
OUTV
: Output PV Status, 同上CLCV
: CALC Valid,OCLV
: OCAL Valid,
- 首先计算
CALC
, 得到的结果放到VAL
里; - 然后判断
OOPT
- 不满足什么也不做;
- 如果满足再判断
DOPT
,- 如果用
CALC
, 那就把VAL
赋值给OVAL
- 如果要用
OCAL
, 那就再计算得到OVAL
- 如果用
- 将
OVAL
输出到OUT
1record(calcout, "co"){
2 field(CALC, "VAL<10?VAL+1:0")
3 field(OCAL, "90-89")
4 field(OOPT, "When Zero")
5 field(DOPT, "Use OCAL")
6 field(OUT, "res PP")
7 field(SCAN, "1 second")
8}
9
10record(ao, "res") {
11 field(TPRO, "1")
12}
Compression Record (compress)
两种工作模式, 当input link是数组时, 取所有数据然后计算. 如果不是, 那就每次process一次就把数据加入环形缓冲区进行计算.
使用的field, 其中关键的就是NSAM
和N
, 前者决定compress中存多少数据, 后者决定每多少个输入进行一次计算. ILIL IHIL OFF
都只在N to 1
类的算法中使用.
NSAM
: Number of ValuesN
: N to 1 CompressionILIL
: Init Low Interest Lim, 过滤数据的下限IHIL
: Init High Interest Lim, 过滤数据的上限OFF
: Offset, 从哪个数据开始算,RES
: Reset
支持的compression algorithm, 也就是ALG
field的值:
N to 1 Low Value
N to 1 High Value
N to 1 Average
Average
Circular Buffer
, 保持一个大小为NSAM
的缓冲区.N to 1 Median
compress
的input link支持array field, 但只支持N to 1
类的算法.
BLAG
又可选缓冲区的类型, FIFO Buffer
或者 LIFO Buffer
calc
每秒自增1, compress每隔10输入计算一次平均值, 最多存两个平均值.
1record(calc, calc){
2 field(CALC, "VAL+1")
3 field(FLNK, "cp PP")
4 field(SCAN, "1 second")
5}
6record(compress, cp) {
7 field(INP, "calc NPP")
8 field(ALG, "Average")
9 field(BALG, "LIFO Buffer")
10 field(NSAM, "2")
11 field(N, "10")
12 field(PINI, "YES")
13}
下例的效果, 计算10个输入的平均值, 然后放到compress
的VAL field中. 使用BALG
使得新值放到数组最前面.
1$ camonitor cp
2cp <undefined> UDF INVALID
3cp 2024-05-17 16:03:37.379709 4.5
4cp 2024-05-17 16:03:47.379616 2 14.5 4.5
5cp 2024-05-17 16:03:57.379640 2 24.5 14.5
Event Record (event)
用来触发一个epics event, 比如下例会触发event 14.
1record(event, event){
2 field(INP, "14")
3 field(TPRO, "1")
4}
5
6record(calc, calc){
7 field(CALC, "VAL+1")
8 field(SCAN, "Event")
9 field(EVNT, "14")
10 field(TPRO, "1")
11 field(PRIO, "HIGH")
12}
13record(ai, ai){
14 field(INP, "calc")
15 field(SCAN, "Event")
16 field(EVNT, "14")
17 field(TPRO, "1")
18}
如下所示ai
和calc
都被触发了, 但ai
先触发, 所以calc
变为1, 而ai
依然为0.
即使加上PRIO
也不影响, 可能与record load时的先后顺序有关, 不详.
所以event适用于无数据流关系的一些record触发, 而强数据流关系的还是用link合适.
1epics> dbpf event.PROC 0
2_main_: dbProcess of 'event'
3DBF_UCHAR: 0 = 0x0
4cbLow: dbProcess of 'ai'
5cbHigh: dbProcess of 'calc'
6# caput event.PROC 0
7epics> ca:sdcswd@zephyrus: dbProcess of 'event'
8cbLow: dbProcess of 'ai'
9cbHigh: dbProcess of 'calc'
Fanout Record (fanout)
fanout
有16个输出link.
对于fanout类的record, 有三种SELM
, 即Select Mechanism
All
:Specified
: 从SELL
得到值, 放到SELN
, 然后加上OFFS
, 然后触发对应的link. e.g., 1 -> LNK1.Mask
: 把SELN
右移SHFT
位, 对应bit为1的会触发. e.g., 0x01 -> LNK0. 注意SHFT
默认值为-1, 也就是默认会把SELN
左移一位. 当SELN
为2时, bit 3为1,LNK2
被触发.
Q: 为什么SHFT
默认值为-1?
A: 历史兼容性. 早期fanout的link是从1开始的, 而且只有6个输出link, 当新增LNK0
后, 需要增加SHFT
来兼容旧的record.
1record(fanout, fo){
2 # Specified, LNK1被触发
3 field(SELM, "Specified")
4 field(SELN, "0")
5 field(OFFS, "1")
6
7 # Mask, LNK1和LNK2被触发
8 # field(SELM, "Mask")
9 # field(SELN, "6")
10 # field(SHFT, "0")
11
12 field(LNK0, "calc0")
13 field(LNK1, "calc1")
14 field(LNK2, "calc2")
15 field(LNK3, "calc3")
16 field(LNK4, "calc4")
17}
18record(calc, calc0){
19 field(CALC, "VAL+1")
20}
21record(calc, calc1){
22 field(CALC, "VAL+1")
23}
24record(calc, calc2){
25 field(CALC, "VAL+1")
26}
27record(calc, calc3){
28 field(CALC, "VAL+1")
29}
30record(calc, calc4){
31 field(CALC, "VAL+1")
32}
Data Fanout Record (dfanout)
有8个输出link.
对比fanout
, 多了数据转发, 但是少了bit shift和offset field. 而且link field的命名也变为了OUTA
-> OUTH
.
- 对于
All
, 用于把一个value写到多个record. - 对于
Specified
, 当SELN
为0时, 不会输出, 为1时, 输出到OUTA
, 和fanout
不同. - 对于
Mask
, LSB为1时, 输出到OUTA
1record(dfanout, dfo){
2 field(OMSL, "closed_loop")
3 field(DOL, "19")
4
5 # Specified, SELN为0时无输出
6 # field(SELM, "Specified")
7 # field(SELN, "0")
8
9 # Mask, 二进制为`101`, 所以输出到OUTA和OUTC
10 field(SELM, "Mask")
11 field(SELN, "5")
12
13 field(OUTA, "calc0")
14 field(OUTB, "calc1")
15 field(OUTC, "calc2")
16 field(OUTD, "calc3")
17 field(OUTE, "calc4")
18}
Histogram Record (histogram)
计算histogram, 用到的field:
NELM
: Num of Array ElementsSVL
: Signal Value Location, 数据源SGNL
: Signal Value, 数据ULIM
: Upper Signal Limit, 低于上限才会被收录LLIM
: Lower Signal Limit, 高于下限才会被收录MDEL
: Monitor Count Deadband, 何时触发monitor, 与MCNT
进行比较,MCNT
大于MDEL
时才触发monitor, 比如每收集100个数据触发一次monitor.SDEL
: Monitor Seconds Dband, 定时检测MCNT
, 当MCNT
大于0时就触发monitor, 应该是用于数据稀少或非周期性到来的场景.MCNT
: Counts Since Monitor, 每次有新数据就加1, 触发monitor之后就置0.CMD
: Collection Control, 控制histogram, 清空或者暂停或者恢复读取.Read
,Clear
,Start
,Stop
CSTA
: Collection Status, 内部状态位, 当它为TRUE
时才会采集数据. 只有Start
会设置它为TRUE
. 而Read
和Clear
都只会清除已采集的数据, 也就是使用Stop
的话必须使用Start
才能开启采集. 当然也可以只用Clear
来重新采集.Read
命令和Clear
没区别.WDTH
: Element Width, 由(ULIM - LLIM) / NELM
计算width, 比如下限为4, 上限12, 四个bin, 那每个bin的WDTH
就是2.
1record(calc, "calc"){
2 field(INPA, "1.57079632679489661923") # Phase
3 field(CALC, "A:=(A+D2R)>(2*PI)?0:(A+D2R); sin(A)")
4 field(SCAN, ".1 second")
5 field(FLNK, "hist")
6}
7
8record(histogram, hist){
9 field(SVL, "calc NPP")
10 field(NELM, "10")
11 field(ULIM, "1")
12 field(LLIM, "-1")
13 field(MDEL, "20")
14 field(SDEL, "0")
15 field(CMD, "Read")
16}
以下为一个正弦波的直方图示例, 横坐标应该是从-1到1的十组, 但我不知道怎么修改Phoebus的横轴😞. 也许需要一个单独的waveform作为横轴吧.
另外, 发现了histogram的设计问题, 写入Stop
后, 虽然的确停止采集了, 但返回值却依然是Read
. 这样就无法使用CMD
判断当前状态了. 必须要读取CSTA
.
Long Input Record (longin)
32位整数, 支持AFTC
.
Long Output Record (longout)
32为整数, 支持DRVH
和DRVL
.
支持OOPT
. 不知为什么, ao
, int64out
都不支持OOPT
.
64bit Integer Input Record (int64in)
和longin
相似, 只不过支持64位长度.
允许超过32位上限
1$ caput li 2147483647
2Old : li 111
3New : li 2147483647
4sdcswd @ zephyrus in ~/epics/R7.0.8/base [11:47]
5$ caput li 2147483649
6Old : li 2147483647
7CA.Client.Exception...............................................
8New : li 2147483647
9 Warning: "Channel write request failed"
10 Context: "op=1, channel=li, type=DBR_STRING, count=1, ctx="li""
11 Source File: ../oldChannelNotify.cpp line 159
12 Current Time: Thu May 16 2024 11:47:58.193643892
13..................................................................
14$ caput i64 2147483647
15Old : i64 0
16New : i64 2.14748e+09
17sdcswd @ zephyrus in ~/epics/R7.0.8/base [11:48]
18$ caput i64 2147483999
19Old : i64 2.14748e+09
20New : i64 2.14748e+09
64bit Integer Output Record (int64out)
类似longout
, 但R7.0.8
依然不支持OOPT
, 也许将来会加上.
Multi-Bit Binary Input Record (mbbi)
读取32位无符号数或者字符串, 然后匹配对应的值.
因为是从hardware中读取值, 所以类似ai
, 也要raw value和value的区分, raw value经过转换然后放入value. 具体转换规则要依据device support而定.
对于16个匹配项, 每个都有一个值, 一个string, 一个severity.
1-16的前缀分别为: ZR, ON, TW, TH, FR, FV, SX, SV, EI, NI, TE, EL, TV, TT, FT, FF
. 分别加上VL
, ST
, SV
就可以.
使用的field:
NOBT
: Number of Bits, 由此得到MASK
,((1 << NOBT) - 1)
SHFT
: 移位, 对于raw value是右移位, 也会把MASK
左移SHFT
位RVAL
: 原始值&MASK
的值UNSV
: Unknown State Severity, 无匹配时的severityCOSV
: Change of State Svr
如下例, mbbi的值为VAL&0xf0
, 因为设置了SHFT
, 所以MASK
移位为0xf0
. 然后raw value就是原始值与MASK
与操作的结果, 也就是0x30.
. 然后把RVAL右移4位后进行匹配, 也就是0x3
, 与ONVL
匹配成功.
可以看到同时使用SHFT
和NOBT
的逻辑显得令人困惑. 所以感觉应该谨慎使用SHFT
.
1record(mbbi, mbbi){
2 field(NOBT, "4")
3 field(DTYP, "Raw Soft Channel")
4 field(INP, "0x1234")
5 field(SHFT, "4")
6 field(ZRVL, "0x4")
7 field(ZRST, "VAL&0xf")
8 field(ZRSV, "MINOR")
9 field(ONVL, "0x3")
10 field(ONST, "VAL&0xf0")
11 field(ONSV, "MINOR")
12 field(FFVL, "0x1234")
13 field(FFST, "All")
14 field(PINI, "RUNNING")
15}
Multi-Bit Binary Output Record (mbbo)
类似于mbbi
, 输出32位无符号数. 但是多了output相关的一些field, 比如DOL, OMSL, IVOA, IVOV
例子: 用DOL
来决定输出哪个匹配项, 也可以直接caput mbbo 1
来选择.
1record(mbbo, mbbo){
2 field(DTYP, "Raw Soft Channel")
3 field(DOL, "15")
4 field(OMSL, "closed_loop")
5 field(PINI, "RUNNING")
6 field(OUT, "li")
7 field(ZRVL, "111")
8 field(ZRST, "aaa")
9 field(ZRSV, "MINOR")
10 field(ONVL, "222")
11 field(ONST, "bbb")
12 field(ONSV, "MINOR")
13 field(FFVL, "32768")
14 field(FFST, "fff")
15}
16record(longin, li){}
Multi-Bit Binary Input Direct Record (mbbiDirect)
mbbi
是匹配, mbbiDirect
则是直接映射/操作bit.
把一个32位 有符号数 的每一位放到B0-B1F
中. 当然mbbiDirect主要用于寄存器操作, 也无所谓有无符号.
同样也支持NOBT
, MASK
, SHFT
.
1record(mbbiDirect, mbbidir){
2 #field(NOBT, "4")
3 field(DTYP, "Raw Soft Channel")
4 field(INP, "0xaaaa")
5 #field(SHFT, "4")
6 field(PINI, "RUNNING")
7}
Multi-Bit Binary Output Direct Record (mbboDirect)
从DOL中取数, 然后输出.
从R7.0.6.1
开始, 当OMSL
为closed_loop
时, 禁止直接caput mbbodir.BO 1
. 需要先修改成supervisory
. 然后直接写B0-B1F
这些field. 直接caput mbbo 0xffff
无效.
1record(longout, lo){}
2record(mbboDirect, mbbodir){
3 field(PINI, "YES")
4 field(DOL, "0xaaaa")
5 field(OMSL, "closed_loop")
6 field(DTYP, "Raw Soft Channel")
7 field(OUT, "lo PP")
8}
Permissive Record (permissive)
deprecated
Printf Record (printf)
用printf来输出格式化字符串. 可以输出到别的string record, 或者使用base提供的device support来输出到stream. 可用@stdout, @stderr or @errlog
SIZV 不能超过32767, 不然会导致segmentation fault. 修复于202405.
1record(printf, pr){
2 field(DTYP, "stdio")
3 field(SIZV, "80")
4 field(FMT, "0: %s, 1: %f, 2: %f, 3: %s")
5 field(INP0, {const: hello})
6 field(INP1, {const: "3.14159"})
7 field(INP2, {const: "pi:3.14159"})
8 field(INP3, "non-exist-link")
9 field(IVLS, "Whoops")
10 field(PINI, "YES")
11 field(OUT, "@stdout")
12}
Select Record (sel)
12个输入link.
从INPA-INPL
中得到数据放到fieldA-L
中, 然后依据SELM
来决定VAL
.
Specified
: 使用SELN
中的值(0-11), 决定使用哪个input link的值. 也可以使用NVL
这个link来获得SELN
. 此处为什么不继续使用SELL
这个名称而要改为NVL
呢?High Signal
: 选择A-L
中的最大值Low Signal
: 选择A-L
中的最小值Median Signal
: 选择A-L
中的中位数
Sequence Record (seq)
16个输入输出link, 相当于16个ao
集成在一起.
从DOL0-DOLF
得到value放到DO0-DOF
中, 然后输出到LNK0-LNKF
. 支持SHFT
和OFFS
. 支持delay field, DLY0-DLYF
, 在取数之前等待一段时间.
如下例, 会输出10到LNK0
, 11到LNK1
.
1record(seq, seq){
2 field(SELM, "Mask")
3 field(SELN, "3")
4 field(SHFT, "0")
5 field(OFFS, "0")
6
7 field(DOL0, "10")
8 field(DOL1, "11")
9 field(DOL2, "12")
10 field(DOL3, "13")
11 field(DOL4, "14")
12 field(LNK0, "calc0")
13 field(LNK1, "calc1")
14 field(LNK2, "calc2")
15 field(LNK3, "calc3")
16 field(LNK4, "calc4")
17}
State Record (state)
deprecated
String Input Record (stringin)
40 characters
String Output Record (stringout)
40 characters
Long String Input Record (lsi)
65535 characters
SIZV
: 默认41, 不能超过65535
有bug, 当设置SIZV
大于32767时, 使用CA读取时会导致ioc segmentation fault.
修复于202405.
1record(lsi, lsi){
2 field(SIZV, "32768")
3 field(INP, {const: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa})
4 field(PINI, "YES")
5}
Long String Output Record (lso)
65535 characters
同样, SIZV大于32767时候就出问题. 修复于202405.
1record(lso, lso){
2 field(OMSL, "closed_loop")
3 field(DOL, {const: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb})
4 field(SIZV, "32768")
5 field(PINI, "YES")
6}
Sub-Array Record (subArray)
对于subArray
record, 和waveform
类似, 都有FTVL, NELM, NORD
这些field.
额外的field包括
MALM
: 需要设置为waveform
的size, 分配subArray
元素的内存时会用MALM
的大小INDX
: index, 必须小于MALM
.
注意它的实现只能获得前MALM
个元素, 也就是说当MALM
为1时, index只能为0. 如果要从一个size为1000的waveform中获得最后一个元素, 那subArray
的大小也必须为1000, 即使它只关心那最后一个元素.
下例中, 当设置MALM
为5, 那waveform中后五个元素就无法得到了.
1record(subArray,"subarr") {
2 field(INP, "wf.VAL NPP NMS")
3 field(MALM, "5")
4 field(NELM, "2")
5 field(INDX, "4")
6 field(FTVL, "ULONG")
7}
8record(waveform,"wf") {
9 field(PINI, "YES")
10 field(INP, "[3,1,4,1,5,9,2,6]")
11 field(NELM, "10")
12 field(FTVL, "ULONG")
13 field(FLNK, "subarr")
14}
Subroutine Record (sub)
sub有12个input link.
用法如下, 很简单, INAM
函数用来初始化一些设置, SNAM
函数会在每次process时调用.
Array Subroutine Record (aSub)
aSub就复杂多了, 有20个input和20个output.
使用的field:
LFLG
: Subr. Input Enable, 是否在每次record process时从SUBL
读取新的值, 默认IGNORE
, 可选READ
. ! 这时候选项又不是首字母大写了. (┬┬﹏┬┬)SUBL
: Subroutine Name Link, input link, 可以切换subroutineEFLG
: Output Event Flag, 是否触发输出事件, 默认为ON CHANGE
, 可选NEVER
,ALWAYS
VAL
: subroutine的返回值, 状态码, 用于判断是否output, 0代表无故障.OVAL
: Old return valueBRSV
: Bad Return Severity, 设置subroutine返回值不为0时的severity.INAM
: 初始化时调用SNAM
: process调用INPA-INPU
: input linkA-U
: 输入值, 可以为数组FTA-FTU
: 数据类型, 输入NOA-NOU
: Max. elements, 在subroutine中使用prec->NOT
要注意,not
是C++的关键字, 所以必须大写来规避.NEA-NEU
: Num. elementsOUTA-OUTU
: output linkVALA-VALU
: 由subroutine负责修改, 写入到output linkOVLA-OVLU
: 旧的VALA-VALU
值, 用于比较是否变化.FTVA-FTVU
: 数据类型, 输出NOVA-NOVU
: Max. elements, 输出NEVA-NEVU
: Num. elements, inVALA-VALU
, 输出ONVA-ONVU
: Old Num. elements, inOVLA-OVLU
, 输出