asynPortDriver是一个C++基类,它被设计成大大简化编写一个asyn端口驱动的任务。它处理注册这个驱动驱动程序,注册可支持接口以及注册所需中断源的所有详情。
驱动程序一般需要支持大量控制它们运行以及提供状态信息的参数。这些参数中大部分可以被当成32位整数,64位floats或strings。当一个参数的新值被发送给一个驱动程序,来自一个asyn客户端(例如:一个EPICS基类)的(例如:新D/A输出值),接着驱动程序将需要采集某个操作。它可能更高某个其它参数来响应这个新值。在这个驱动程序中运行顺序能够被概括成:
- 1) 新参数值到达,或者新数据从一个设备到来
- 2) 更改一个或多个参数的值
- 3) 对于每个其值变化的参数,设置一个表明其变化的标记
- 4) 当操作结束时,为每个变化的参数调用注册的回调。
asynPortDriver提供了简化以上顺序的方法,必须为这个驱动程序经常支持的很多参数中每一个实现它。每个参数被分配一个编号,当读取或者写入那个参数时,其是asyn客户端在pasynUser->reason字段中传递给这个驱动程序的值。asynPortDriver维护一个参数值的表格,关联每个参数编号和一种数据类型(integer, UInt32Digital, double或string),缓存当前值,并且维护一个表示一个值是否发生变化的标志。驱动程序使用asynPortDriver方法来从表格读取当前值,并且在表格中设置新值。有一个方法,它为从上次进行回调以来已经发生变化的值调用所有注册的回调。
对应asynPortDriver的详细文档是在这些文件中:asynPortDriver class
在asyn(testAsynPortDriver)中这个示例驱动程序是一个如何使用这个类的简单示例
示例驱动程序 - testAsynPortDriver
在asyn中的testAsynPortDriverApp应用程序中提供了一个使用asynPortDriver类的示例驱动程序。这个示例是一个简单的数字示波器仿真器。在这个示例中,所有输出控制和输入数据是在一个计算仿真中进行的。但看如何使用这个驱动作为一个真实设备控制基础是简单的。进行仿真的代码简单地被修改成与一个真实设备会话。使用asyn串行或IP驱动程序通过asynOctetSyncIO接口(因为在这个驱动程序层级,阻塞是被允许的),或者通过VME寄存器访问或者任何其它I/O机制做这件事。
这是控制这个示例应用程序的medm窗口。在testAsynPortDriverApp/adl目录中用以下命令启动它:
medm -x -macro "P=testAPD:, R=scope1:" testAsynPortDriver.adl &
这个仿真示波器输入是一个有噪声的幅度+/-1V的正弦波。噪声的幅度是一个可调整的参数。能够被调整的示波器参数是垂直volts/division, 垂直vlot偏移,水平time/division,触发延时(相对于对应sin正弦波的time=0)。计算这个waveform的最小、最大和平均值。run/stop控制启动/停止waveform的仿真。以秒为单位的update time控制以哪个速率计算这个waveform和统计数据。默认,所有EPICS输入基类都是被I/O Intr扫描的。在这个medm窗口上有一个更改waveform记录扫描速率的控件。例如,可以更改这个控件为1秒,并且接着仿真能够运行更快(例如50Hz,Update time=0.02),因此统计数据将快速更新,但waveform只以1Hz来节省CPU时间和网络带宽。
这个驱动程序支持20个EPICS记录,包括ao,ai,bo,bi和waveform。当任何输入记录变化时,它对设备支持进行回调,因此这些记录可以使用I/O Intr扫描而不是轮询。它只使用了作为asyn组成部分提供的标志asyn EPICS记录设备支持。但这个驱动程序只有大约注释清晰的340行C++代码,因为很多基本功能是由asynPortDriver基类处理了。
以下是来自启动这个IOC的启动脚本的重要行:
testAsynPortDriverConfigure("testAPD", 1000)
dbLoadRecords("../../db/testAsynPortDriver.db","P=testAPD:,R=scope1:,PORT=testAPD,ADDR=0,TIMEOUT=1,NPOINTS=1000")
第一行用一个带有1000个点waveform的asyn端口驱动。第二行装载这个数据库。PORT参数是在首行中创建的asyn端口的名称。ADDR参数是0,这个驱动程序不是ASYN_MULTIDEVICE,它只支持单个地址。TIMEOUT参数不重要,因为这是一个同步asyn端口驱动程序,即:ASYN_CANBLOCK=0。NPOINTS是数据库中这个waveform记录的NELM值。它正常匹配在以上配置命令中提供的值。
在数据库文件testAsynPortDrvier.db中中记录的定义:
###################################################################
# These records control run/stop #
###################################################################
record(bo, "$(P)$(R)Run")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_RUN")
field(ZNAM, "Stop")
field(ONAM, "Run")
}
record(bi, "$(P)$(R)Run_RBV")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_RUN")
field(ZNAM, "Done")
field(ZSV, "NO_ALARM")
field(ONAM, "Running")
field(OSV, "MINOR")
field(SCAN, "I/O Intr")
}
###################################################################
# This records is the number of points #
###################################################################
record(longin, "$(P)$(R)MaxPoints_RBV")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_MAX_POINTS")
field(SCAN, "I/O Intr")
}
###################################################################
# These records are the time per division #
###################################################################
record(mbbo, "$(P)$(R)TimePerDivSelect")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_TIME_PER_DIV_SELECT")
field(ZRST, "0.01 msec")
field(ZRVL, "10")
field(ONST, "0.02 msec")
field(ONVL, "20")
field(TWST, "0.05 msec")
field(TWVL, "50")
field(THST, "0.1 msec")
field(THVL, "100")
field(FRST, "0.2 msec")
field(FRVL, "200")
field(FVST, "0.5 msec")
field(FVVL, "500")
field(SXST, "1 msec")
field(SXVL, "1000")
field(SVST, "2 msec")
field(SVVL, "2000")
field(EIST, "5 msec")
field(EIVL, "5000")
field(NIST, "10 msec")
field(NIVL, "10000")
}
record(ai, "$(P)$(R)TimePerDiv_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_TIME_PER_DIV")
field(PREC, "5")
field(SCAN, "I/O Intr")
}
###################################################################
# These records are the vertical gain #
###################################################################
record(mbbo, "$(P)$(R)VertGainSelect")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_VERT_GAIN_SELECT")
field(ZRST, "1")
field(ZRVL, "1")
field(ONST, "10")
field(ONVL, "10")
field(TWST, "100")
field(TWVL, "100")
}
record(ai, "$(P)$(R)VertGain_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_VERT_GAIN")
field(PREC, "0")
field(SCAN, "I/O Intr")
}
###################################################################
# These records are the volts per division. #
# The driver sets these dynamically based on the vertical gain #
###################################################################
record(mbbo, "$(P)$(R)VoltsPerDivSelect")
{
field(PINI, "1")
field(DTYP, "asynInt32")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_VOLTS_PER_DIV_SELECT")
field(ZRST, "Garbage")
field(ZRVL, "0")
}
record(mbbi, "$(P)$(R)VoltsPerDivSelect_RBV")
{
field(DTYP, "asynInt32")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_VOLTS_PER_DIV_SELECT")
field(ZRST, "Garbage")
field(ZRVL, "0")
field(SCAN, "I/O Intr")
}
record(ai, "$(P)$(R)VoltsPerDiv_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_VOLTS_PER_DIV")
field(PREC, "2")
field(SCAN, "I/O Intr")
}
###################################################################
# These records are the volt offset #
###################################################################
record(ao, "$(P)$(R)VoltOffset")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_VOLT_OFFSET")
field(PREC, "3")
}
record(ai, "$(P)$(R)VoltOffset_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_VOLT_OFFSET")
field(PREC, "3")
field(SCAN, "I/O Intr")
}
###################################################################
# These records are the trigger delay #
###################################################################
record(ao, "$(P)$(R)TriggerDelay")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_TRIGGER_DELAY")
field(PREC, "5")
}
record(ai, "$(P)$(R)TriggerDelay_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_TRIGGER_DELAY")
field(PREC, "5")
field(SCAN, "I/O Intr")
}
###################################################################
# These records are the noise amplitude #
###################################################################
record(ao, "$(P)$(R)NoiseAmplitude")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_NOISE_AMPLITUDE")
field(PREC, "3")
}
record(ai, "$(P)$(R)NoiseAmplitude_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_NOISE_AMPLITUDE")
field(PREC, "3")
field(SCAN, "I/O Intr")
}
###################################################################
# These records are the update time #
###################################################################
record(ao, "$(P)$(R)UpdateTime")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(OUT, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_UPDATE_TIME")
field(PREC, "3")
}
record(ai, "$(P)$(R)UpdateTime_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_UPDATE_TIME")
field(PREC, "3")
field(SCAN, "I/O Intr")
}
###################################################################
# This record is the waveform #
###################################################################
record(waveform, "$(P)$(R)Waveform_RBV")
{
field(DTYP, "asynFloat64ArrayIn")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_WAVEFORM")
field(FTVL, "DOUBLE")
field(NELM, "$(NPOINTS)")
field(LOPR, "0")
field(HOPR, "10")
field(SCAN, "I/O Intr")
}
###################################################################
# This record is the time base #
###################################################################
record(waveform, "$(P)$(R)TimeBase_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64ArrayIn")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_TIME_BASE")
field(FTVL, "DOUBLE")
field(NELM, "$(NPOINTS)")
field(LOPR, "0")
field(HOPR, "10")
}
###################################################################
# This record is the minimum value #
###################################################################
record(ai, "$(P)$(R)MinValue_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_MIN_VALUE")
field(PREC, "4")
field(SCAN, "I/O Intr")
}
###################################################################
# This record is the maximum value #
###################################################################
record(ai, "$(P)$(R)MaxValue_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_MAX_VALUE")
field(PREC, "4")
field(SCAN, "I/O Intr")
}
###################################################################
# This record is the mean value #
###################################################################
record(ai, "$(P)$(R)MeanValue_RBV")
{
field(PINI, "1")
field(DTYP, "asynFloat64")
field(INP, "@asyn($(PORT),$(ADDR),$(TIMEOUT))SCOPE_MEAN_VALUE")
field(PREC, "4")
field(SCAN, "I/O Intr")
}
注意:对应一个参数SCOPE_RUN有一个输出记录和一个输入记录。这么做使得如果这个驱动程序被强制修改一个参数(例如,因为设备不能支持输出它接收到的值),有一个向用户反馈正在被使用的实际值。在这种情况中,ai记录将总是与ao记录相同。但在这个驱动程序执行一个0.02秒的最小时间的update time参数时,因而如果一个小于这个的值被请求,ao和ai记录将不一致。在那种特定情况,DRVL可用于在ao记录层级执行那个限制,但这情况并不是总是如此,因为设备限制可以随着其它参数值而变化。
SCOPE_UPDATE_TIME参数对应$(P)$(R)UpdateTime记录和$(P)$(R)UpdateTime_RBV记录的DTYP字段是asynFloat64,它使用了用于在asyn/devEpics/devAsynFloat64.c中提供ao和ai记录的标准asyn设备支持。
OUT和INP字段使用以上描述的PORT,ADDR和TIMEOUT字段。在这些字段中末尾参SCOPE_UPDATE_TIME由这个驱动程序使用来识别这些记录被连接到了哪个驱动程序参数。这是asyn drvInfo参数,在下面讨论。
最终,注意ai记录有SCAN=I/O Intr。这表示这个记录非必须被周期地扫描(其是低效的),而是在其值被驱动程序更改时将运行它。对于在这个示例中ai统计记录(min, max, mean),如果噪声不为0,记录回调在每次仿真运行时发生。
testAsynPortDriver类的定义:
/*
* testAsynPortDriver.h
*
* 继承自asynPortDriver类的Asyn驱动程序演示其用法。
* 它模拟一个观察一个1kHz 1000点有噪声的正弦波数字示波器。
* 提供了对time/division, volts/division, volt offset, trigger delay, noise amplitude, update time,
* 和run/stop的控制。
* 为waveform数据,min,max和mean值提供了反馈。
*/
#include "asynPortDriver.h"
#define NUM_VERT_SELECTIONS 4
/*
* 这些是被用于识别参数的drvInfo字符串。由asyn客户端使用它们,包括标准的asyn设备支持
*/
#define P_RunString "SCOPE_RUN" /* asynInt32, r/w */
#define P_MaxPointsString "SCOPE_MAX_POINTS" /* asynInt32, r/o */
#define P_TimePerDivString "SCOPE_TIME_PER_DIV" /* asynFloat64, r/w */
#define P_TimePerDivSelectString "SCOPE_TIME_PER_DIV_SELECT" /* asynInt32, r/w */
#define P_VertGainString "SCOPE_VERT_GAIN" /* asynFloat64, r/w */
#define P_VertGainSelectString "SCOPE_VERT_GAIN_SELECT" /* asynInt32, r/w */
#define P_VoltsPerDivString "SCOPE_VOLTS_PER_DIV" /* asynFloat64, r/w */
#define P_VoltsPerDivSelectString "SCOPE_VOLTS_PER_DIV_SELECT" /* asynInt32, r/w */
#define P_VoltOffsetString "SCOPE_VOLT_OFFSET" /* asynFloat64, r/w */
#define P_TriggerDelayString "SCOPE_TRIGGER_DELAY" /* asynFloat64, r/w */
#define P_NoiseAmplitudeString "SCOPE_NOISE_AMPLITUDE" /* asynFloat64, r/w */
#define P_UpdateTimeString "SCOPE_UPDATE_TIME" /* asynFloat64, r/w */
#define P_WaveformString "SCOPE_WAVEFORM" /* asynFloat64Array, r/o */
#define P_TimeBaseString "SCOPE_TIME_BASE" /* asynFloat64Array, r/o */
#define P_MinValueString "SCOPE_MIN_VALUE" /* asynFloat64, r/o */
#define P_MaxValueString "SCOPE_MAX_VALUE" /* asynFloat64, r/o */
#define P_MeanValueString "SCOPE_MEAN_VALUE" /* asynFloat64, r/o */
/**
* 演示使用asynPortDriver基类来大大简化编写一个asyn端口驱动程序的任务的类。
* 这个类进行一个数字示波器的简单仿真。它计算一个waveform, 对这个waveform计算统计数据,并且用统计数据和waveform数据自身
* 进行回调。
class testAsynPortDriver : public asynPortDriver {
public:
testAsynPortDriver(const char *portName, int maxArraySize);
/* 这些是重写来自asynPortDriver的方法 */
virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value);
virtual asynStatus writeFloat64(asynUser *pasynUser, epicsFloat64 value);
virtual asynStatus readFloat64Array(asynUser *pasynUser, epicsFloat64 *value,
size_t nElements, size_t *nIn);
virtual asynStatus readEnum(asynUser *pasynUser, char *strings[], int values[], int severities[],
size_t nElements, size_t *nIn);
/* 这些是对这个类新的方法 */
void simTask(void);
protected:
/**
* 用于pasynUser->reason的值,以及对参数库的索引
*/
int P_Run;
int P_MaxPoints;
int P_TimePerDiv;
int P_TimePerDivSelect;
int P_VertGain;
int P_VertGainSelect;
int P_VoltsPerDiv;
int P_VoltsPerDivSelect;
int P_VoltOffset;
int P_TriggerDelay;
int P_NoiseAmplitude;
int P_UpdateTime;
int P_Waveform;
int P_TimeBase;
int P_MinValue;
int P_MaxValue;
int P_MeanValue;
private:
/* 我们的数据 */
epicsEventId eventId_;
epicsFloat64 *pData_;
epicsFloat64 *pTimeBase_;
// 每格实际volts是这些值除以垂直增益
char *voltsPerDivStrings_[NUM_VERT_SELECTIONS];
int voltsPerDivValues_[NUM_VERT_SELECTIONS];
int voltsPerDivSeverities_[NUM_VERT_SELECTIONS];
void setVertGain();
void setVoltsPerDiv();
void setTimePerDiv();
};
testAsynPortDriver是派生自asynPortDriver。它重写了以下方法writeInt32, writeFloat64, readFloat64Array和drvUserCreate。它添加了一个新方法simTask,其运行一个以指定的更新时间计算waveform的单独线程。
这是如何在testAsynPortDriver.cpp驱动程序中定义参数:
#define P_RunString "SCOPE_RUN" /* asynInt32, r/w */
#define P_MaxPointsString "SCOPE_MAX_POINTS" /* asynInt32, r/o */
#define P_TimePerDivString "SCOPE_TIME_PER_DIV" /* asynFloat64, r/w */
#define P_TimePerDivSelectString "SCOPE_TIME_PER_DIV_SELECT" /* asynInt32, r/w */
#define P_VertGainString "SCOPE_VERT_GAIN" /* asynFloat64, r/w */
#define P_VertGainSelectString "SCOPE_VERT_GAIN_SELECT" /* asynInt32, r/w */
#define P_VoltsPerDivString "SCOPE_VOLTS_PER_DIV" /* asynFloat64, r/w */
#define P_VoltsPerDivSelectString "SCOPE_VOLTS_PER_DIV_SELECT" /* asynInt32, r/w */
#define P_VoltOffsetString "SCOPE_VOLT_OFFSET" /* asynFloat64, r/w */
#define P_TriggerDelayString "SCOPE_TRIGGER_DELAY" /* asynFloat64, r/w */
#define P_NoiseAmplitudeString "SCOPE_NOISE_AMPLITUDE" /* asynFloat64, r/w */
#define P_UpdateTimeString "SCOPE_UPDATE_TIME" /* asynFloat64, r/w */
#define P_WaveformString "SCOPE_WAVEFORM" /* asynFloat64Array, r/o */
#define P_TimeBaseString "SCOPE_TIME_BASE" /* asynFloat64Array, r/o */
#define P_MinValueString "SCOPE_MIN_VALUE" /* asynFloat64, r/o */
#define P_MaxValueString "SCOPE_MAX_VALUE" /* asynFloat64, r/o */
#define P_MeanValueString "SCOPE_MEAN_VALUE" /* asynFloat64, r/o */
注意:每个参数由一个识别它的整数值。它也与一个字符串相关联,这个字符串被用于记录INP或OUT字段的drvInfo字段来关联一个记录和一个参数。
这是testAsynPortDriver C++类构建函数的开始。
#define NUM_VERT_SELECTIONS 4
#define FREQUENCY 1000 /* 以Hz为单位的频率 */
#define AMPLITUDE 1.0 /* 正弦波的正负峰值 */
#define NUM_DIVISIONS 10 /* 在X和Y方向范围划分数目*/
#define MIN_UPDATE_TIME 0.02 /* 最小更新时间,防止CPU饱和 */
#define MAX_ENUM_STRING_SIZE 20
static int allVoltsPerDivSelections[NUM_VERT_SELECTIONS]={1,2,5,10};
/** testAsynPortDriver类的构造函数。调用asynPortDriver基类的构造函数。
* [in] portName: 要被创建的asyn端口驱动程序的名称。
* [in] maxPoints:在volt和时间数组中点的最大数目
*/
testAsynPortDriver::testAsynPortDriver(const char *portName, int maxPoints)
: asynPortDriver(portName,
1, /* maxAddr */
asynInt32Mask | asynFloat64Mask | asynFloat64ArrayMask | asynEnumMask | asynDrvUserMask, /* Interface mask */
asynInt32Mask | asynFloat64Mask | asynFloat64ArrayMask | asynEnumMask, /* Interrupt mask */
0, /* asynFlags. This driver does not block and it is not multi-device, so flag is 0 */
1, /* Autoconnect */
0, /* Default priority */
0) /* Default stack size*/
{
asynStatus status;
int i;
const char *functionName = "testAsynPortDriver";
/* 确保maxPoints是正的,如果传入的小于1,则设置其为100 */
if (maxPoints < 1) maxPoints = 100;
/* 分配这个waveform数组,元素类型是epicsFloat64, 数目为传入的maxPoint*/
pData_ = (epicsFloat64 *)calloc(maxPoints, sizeof(epicsFloat64));
/* 分配一个时基数组,数组长度与waveform数组一致*/
pTimeBase_ = (epicsFloat64 *)calloc(maxPoints, sizeof(epicsFloat64));
/* 设置时基数组 */
for (i=0; i<maxPoints; i++) pTimeBase_[i] = (double)i / (maxPoints-1) * NUM_DIVISIONS;
/* 创建一个空事件 */
eventId_ = epicsEventCreate(epicsEventEmpty);
/* 根据指定的参数类型为指定参数名,在参数库中创建这个参数,并且返回这个参数在参数库中的编号 */
createParam(P_RunString, asynParamInt32, &P_Run);
createParam(P_MaxPointsString, asynParamInt32, &P_MaxPoints);
createParam(P_TimePerDivString, asynParamFloat64, &P_TimePerDiv);
createParam(P_TimePerDivSelectString, asynParamInt32, &P_TimePerDivSelect);
createParam(P_VertGainString, asynParamFloat64, &P_VertGain);
createParam(P_VertGainSelectString, asynParamInt32, &P_VertGainSelect);
createParam(P_VoltsPerDivString, asynParamFloat64, &P_VoltsPerDiv);
createParam(P_VoltsPerDivSelectString, asynParamInt32, &P_VoltsPerDivSelect);
createParam(P_VoltOffsetString, asynParamFloat64, &P_VoltOffset);
createParam(P_TriggerDelayString, asynParamFloat64, &P_TriggerDelay);
createParam(P_NoiseAmplitudeString, asynParamFloat64, &P_NoiseAmplitude);
createParam(P_UpdateTimeString, asynParamFloat64, &P_UpdateTime);
createParam(P_WaveformString, asynParamFloat64Array, &P_Waveform);
createParam(P_TimeBaseString, asynParamFloat64Array, &P_TimeBase);
createParam(P_MinValueString, asynParamFloat64, &P_MinValue);
createParam(P_MaxValueString, asynParamFloat64, &P_MaxValue);
createParam(P_MeanValueString, asynParamFloat64, &P_MeanValue);
for (i=0; i<NUM_VERT_SELECTIONS; i++) {
// 以mV为单位计算每格垂直volts
voltsPerDivValues_[i] = 0;
voltsPerDivStrings_[i] = (char *)calloc(MAX_ENUM_STRING_SIZE, sizeof(char));
voltsPerDivSeverities_[i] = 0;
}
/* 设置某些参数的初始值 */
setIntegerParam(P_MaxPoints, maxPoints);
setIntegerParam(P_Run, 0);
setIntegerParam(P_VertGainSelect, 10);
setVertGain();
setDoubleParam (P_VoltsPerDiv, 1.0);
setDoubleParam (P_VoltOffset, 0.0);
setDoubleParam (P_TriggerDelay, 0.0);
setDoubleParam (P_TimePerDiv, 0.001);
setDoubleParam (P_UpdateTime, 0.5);
setDoubleParam (P_NoiseAmplitude, 0.1);
setDoubleParam (P_MinValue, 0.0);
setDoubleParam (P_MaxValue, 0.0);
setDoubleParam (P_MeanValue, 0.0);
/* 在后台创建计算这个waveform的线程 */
status = (asynStatus)(epicsThreadCreate("testAsynPortDriverTask",
epicsThreadPriorityMedium,
epicsThreadGetStackSize(epicsThreadStackMedium),
(EPICSTHREADFUNC)::simTask,
this) == NULL);
if (status) {
printf("%s:%s: epicsThreadCreate failure\n", driverName, functionName);
return;
}
}
它调用asynPortDriver基类的构造函数。它传递:
1) portName,是要被创建的asyn端口的名称。在以上st.cmd文件中,这是"testAPD"。
2) 这个驱动支持的asyn地址的最大数目,这是1。
3)一个掩码,它定义这个支持支持的asyn接口,在这里的情况:asynInt32, asynFloat64, asynFloat64Array和asynDrvUser。所有驱动必须支持asynCommon,因此那个bit位在这个基类中被添加。
4) 一个掩码,它定义哪些asyn接口能够产生中断(回调)。在这里的情况,那是asynInt32, asynFloat64和asynFloat64。
5) 一个掩码,它为这个驱动定义asyn属性。asyn当前定义了两个属性bits,ASYN_CANBLOCK和ASYN_MULTIDEVICE。必须为在它们接口上执行"慢"操作的驱动程序设置ASYN_CANBLOCK,需要asynManager为它们创建一个单独的端口线程并且使用异步设备支持。必须为支持多个asyn地址的驱动程序设置ASYN_MULTIDEVICE,例如,一个用于一个16通道A/D转换器的驱动程序。
6) 一个告诉asynManager它应该在对其接口进行调用时自动尝试连接这个设备的标记。这些结果在一个对asynCommon->connect()的调用中。
7) 如果ASYN_CANBLOCK是1,一个对应asynManger将创建的端口驱动的优先级标记。如果这是0,则asyn将使用一个默认的中等线程优先级。
8)如果ASYN_CANBLOCK是1,一个对应asynManager将创建的端口驱动的栈大小。如果这是0,则asyn将适应一个默认的中等线程栈大小。
/** 当客户端调用pasynFloat64->write()时被调用。
* 如果P_UpdateTime的值发生变化了,这个函数发送一个信号给simTask线程。
* 对于所有参数,它在参数库中设置值并且调用任意注册的回调。
* [in] pasynUser:编码reason和address的pasynUser结构体。
* [in] value : 要写的值。
*/
asynStatus testAsynPortDriver::writeFloat64(asynUser *pasynUser, epicsFloat64 value)
{
int function = pasynUser->reason;
asynStatus status = asynSuccess;
epicsInt32 run;
const char *paramName;
const char* functionName = "writeFloat64";
/* 根据传入的reason,在参数库中设置以reason为编号的参数的值 */
status = (asynStatus) setDoubleParam(function, value);
/* 获取参数编号对应的参数名称,在调试中可能使用 */
getParamName(function, ¶mName);
if (function == P_UpdateTime) {
/* 确认更新时间是否有效。如果不是,更改它并且放回参数库 */
if (value < MIN_UPDATE_TIME) {
asynPrint(pasynUser, ASYN_TRACE_WARNING,
"%s:%s: warning, update time too small, changed from %f to %f\n",
driverName, functionName, value, MIN_UPDATE_TIME);
value = MIN_UPDATE_TIME;
setDoubleParam(P_UpdateTime, value);
}
/* 如果更新时间发生变化并且我们正在运行,则唤醒仿真任务 */
getIntegerParam(P_Run, &run);
if (run) epicsEventSignal(eventId_);
} else {
/* 所有其它参数,只要在参数库中获取和设置, 不需要在这里响应它们 */
}
/* 进行回调,因而更高层看到任何变化 */
status = (asynStatus) callParamCallbacks();
if (status)
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
"%s:%s: status=%d, function=%d, name=%s, value=%f",
driverName, functionName, status, function, paramName, value);
else
asynPrint(pasynUser, ASYN_TRACEIO_DRIVER,
"%s:%s: function=%d, name=%s, value=%f\n",
driverName, functionName, function, paramName, value);
return status;
}
这是在这个函数中正在执行什么:
1) pasynUser->reason字段用于获取function的值。这是其中一个参数索引(例如:P_TimePerDivision)。由在iocInit时被调用的这个驱动程序的drvUserCreate方法放置它到pasynUser->reason字段,并且从这个记录链接,例如"SCOPE_TIME_PER_DIV"传递drvUser字段。
2) 用setDoubleParam在参数列表中设置传递的值。
3) 一系列不同地处理每个参数的语句。在这里地情况,仅一个float64参数P_UpdateTime需要在这个函数中实际地采取任何操作。所有其它参数仅在参数列表中是遏制它们的值为之后使用。
4) 对于P_UpdateTime,检查这个值的有效性来确保它大于MIN_UPDATE_TIME。如果不是,这个值被更改,并且新值被写到这个参数列表。这个新值将在回调中被传递给任何回调客户端,例如一个正在监视这个参数的ai记录。函数接着从参数列表获取P_Run的值,并且如果它是1,它发送一个EPICS事件信号去唤醒simTask。这么做使得如果更新时间被从一个非常长的值改为一个更短的值,则它不等待这个长定时器超时。
5) 调用callParamCallbacks(),这导致为由此函数调用造成其改变的任意参数对所有注册客户端进行回调。在这里的情况,将发生变化的唯一参数是在pasynUser->reason中所传递的参数,但一般更改这个参数的副作用为其它参数会发生变化。
6) 可选地为错误和成功状态打印诊断信息。
writeInt32函数非常类似。
最终这是simTask函数,它实际进行仿真。它运行在构造函数中创建的一个单独线程中。
/**
* 作为一个单独线程运行的仿真任务。当P_Run参数被置为1来开始这个仿真:
* 它用1V幅度和用户可控制的噪声计算一个1kHz正弦波,并且在一个模拟示波器上显示它。
* 它计算waveforms的X(时间)和Y(电压),并且计算有关这个waveform的统计数据。
*/
void testAsynPortDriver::simTask(void)
{
/* 这个线程计算这个waveform并且用它进行回调 */
double timePerDiv, voltsPerDiv, voltOffset, triggerDelay, noiseAmplitude;
double updateTime, minValue, maxValue, meanValue;
double time, timeStep;
double noise, yScale;
epicsInt32 run, i, maxPoints;
double pi=4.0*atan(1.0);
lock(); //锁定,保护参数库
/* 死循环 */
while (1) {
getDoubleParam(P_UpdateTime, &updateTime); //从参数库获取更新时间
getIntegerParam(P_Run, &run); // 从参数库获取运行状态
// 当我们等待一个命令开始或者等待updateTime时,释放锁
unlock();
if (run) epicsEventWaitWithTimeout(eventId_, updateTime);//如果仿真在运行,则等待指定的事件发生,超时时间为updateTime
else (void) epicsEventWait(eventId_);//如果仿真没有运行,则一直等待指定的事件出现
// 再次获取锁
lock();
/* run could have changed while we were waiting 在我们等待期间,run可能发生了变化 */
getIntegerParam(P_Run, &run);
if (!run) continue; //如果仿真未在运行,则从头运行死循环
getIntegerParam(P_MaxPoints, &maxPoints); //获取最大点数
getDoubleParam (P_TimePerDiv, &timePerDiv);//获取每格的时间
getDoubleParam (P_VoltsPerDiv, &voltsPerDiv);//获取每格的电压
getDoubleParam (P_VoltOffset, &voltOffset);//获取电压偏移
getDoubleParam (P_TriggerDelay, &triggerDelay);//获取触发延时
getDoubleParam (P_NoiseAmplitude, &noiseAmplitude);//获取噪声幅度
time = triggerDelay;
timeStep = timePerDiv * NUM_DIVISIONS / maxPoints; //时间步长
minValue = 1e6;
maxValue = -1e6;
meanValue = 0.;
yScale = 1.0 / voltsPerDiv; // Y方向的缩放
for (i=0; i<maxPoints; i++) {
noise = noiseAmplitude * (rand()/(double)RAND_MAX - 0.5);//计算噪声
pData_[i] = AMPLITUDE * (sin(time*FREQUENCY*2*pi)) + noise;//计算Y值
/* 进行Y缩放和Y偏移前计算统计数据 */
if (pData_[i] < minValue) minValue = pData_[i]; //获取最小值
if (pData_[i] > maxValue) maxValue = pData_[i]; //获取最大值
meanValue += pData_[i]; //用于获取平均值
pData_[i] = NUM_DIVISIONS/2 + yScale * (voltOffset + pData_[i]);
time += timeStep;
}
updateTimeStamp();
meanValue = meanValue/maxPoints;
setDoubleParam(P_MinValue, minValue);
setDoubleParam(P_MaxValue, maxValue);
setDoubleParam(P_MeanValue, meanValue);
callParamCallbacks();
doCallbacksFloat64Array(pData_, maxPoints, P_Waveform, 0);
}
}
这是这个函数重要部分:
1) P_Run的值决定了这个仿真是正在运行或者被停止。如果停止,它仅等待一个信号(来自WriteInt32()函数)来启动运行。如果正在运行,它等待这个更新时间,或者直到它接收到一个信号,如果在writeFloat64()函数中更改这个更新时间,这将发生。
2) 它从参数列表 读取仿真参数(P_TimePerDivsion等)的值。
3) 它在for循环中计算这个waveform中每个点,使用这个仿真的当前值。它也在这个循环中更新统计参数(min,max,mean)。
4) 在循环结束后,用setDoubleParam把统计参数的新值写到了参数列表中。
5) 用对callParamCallbacks()的调用把在参数列表中所有标量参数(int32, float64, string)的新值发送到注册客户端(例如:对输入记录的asyn设备支持)。
6) 用对doCallbacksFloat64Array()的调用把waveform的新值发送给注册客户端(例如:对waveform输入记录的设备支持)。
真实的驱动程序可能需要或者可能不需要一个单独线程。需要周期轮询状态信息的驱动程序将可能需要一个单独线程。除了drvUserCreate()外,大部分驱动程序还可能实现了一个或多个writeInt32(), writeFloat64()或者writeOctet()函数。