目的
asynDriver是一个用于连接设备专用代码和底层驱动程序的通用功能。asynDriver允许非阻塞设备支持与阻塞和非阻塞驱动程序一起运行。asynDriver的首要目标是EPICS IOC设备支持,除了使用libCom外,它大部分独立于EPICS。
asynDriver有以下关键概念:
1) 设备支持与驱动程序通信通过接口
驱动程序管理具体如何与一个设备通信并且实现了设备支持使用的接口。为基于消息和基于寄存器的设备都定义了接口。在过去,当为一个新的设备类型编写支持时,必须为标准EPICS记录编写设备支持以及驱动程序支持。现在,一个驱动程序就实现了一个或多个标准接口。
2) 一个端口(port)提供了对设备实例的访问
一个端口,它有一个portName,标识一个到一个或多个设备实例的通信路径。例如,一个GPIB端口可以可以最多有15格设备连接它。一个RS232端口与单个设备通信。驱动程序注册一个端口。设备支持连接一个端口。
3) asynManager控制对一个端口的访问
asynManager时asynDriver的一个组件,通过对queueRequest, lockPort/unlockPort和queueLockPort/queueUnlockPort的调用提供对一个驱动程序的唯一性访问。一旦设备支持可以访问,它能够对驱动程序进行任意数目的调用,知道其它支持不能调用这个驱动程序。由于asynManager为它们实现了队列或信号量,所以设备和驱动程序支持不需要实现它们。
4) asynTrace提供一个常用的诊断工具
为提供诊断消息提供了规则。只要设备和驱动支持遵守这些规则,一个用户能够获取若干等级的诊断信息,能够在console上显示这些消息,被写入一个文件,或者被发送到EPICS errlog工具。
5) asynRecord -- 对一个设备/端口的一般访问。
asynRecord是一个EPICS记录,并且有相关联的MEDM窗口,这些窗口提供对以下的访问:
- 一个端口或连接了一个端口的设备:可以动态更高端口或端口,地址。因而咋一个IOC中存在一个asynReocrd,同具有asyn兼容驱动程序的任何设备会话是可能的。
- asynTrace:用asynRecord可以控制所有asynTrace选项。
- 连接管理:显示和更改连接,启用和自动连接状态。
- 标准接口:这些可用于同设备进行通信。例如,如果一个新设备有串口,GPIB或以太口,则只要通过连接一个asynRecored到它,与它通信经常是可能的。
6) 扩展的串口支持:asynDriver提供了很多工具用于与RS232, RS485以及以太网通信。
状态
这个版本提供:
1) asynManager:设备支持和驱动程序之间的软件层。
2) asynRecord:提供了一个对asynManager, asynCommon, asynOctet, asynGpib的一般接口以及其它接口。
3) asynPortDriver:一个C++基类,它使得编写asyn驱动变得容易,因为大部分样本asyn代码在这个基类方法中被处理。
4) asynPortClient:使得编写与asyn端口驱动程序直接通信的C++ asyn客户端变得容易,不需要运行一个EPICS IOC。
5) 标准接口:定义了标准的基于消息和寄存器的接口。底层驱动程序实现了标准接口。设备支持通过标准接口与底层驱动进行通信。
6)devEpics:EPICS即可的通用设备支持。
7) devGpib:替代Winans/Franksen gpibCore支持的设备支持层的EPICS设备支持。
8) asynGpib:对Franksen gpibCore支持的drvGpbCommon层的替代。
9) drvAsynSerialPort:对连接了串口的设备的支持。
10) dvrAsynIPPort:对TCP/IP和UDP/IP套接字通信的支持,包括通过以太网/串口转换器被访问的设备。
11) drvAsynIPServerPort:对受到来自远程客户端访问的asyn套接字服务器的支持。支持TCP/IP套接字和UDP。
12) VXI-11:对Franksen gpibCore模块的VXI-11支持的替代。
13)Linux-gpib:对Linux GPIB包库的支持。
14) gsIP488:一个对Greensprins IP488 Instrustry Pack模块的底层驱动程序。
15) ni1014:一个对国际仪器VME 1014D的底层驱动程序。
16) Serial Bus Support:添加了asynLockPortNotify接口,使得支持使用标准串行支持的串行总线驱动变得简单。
以下是一些已经被转换成使用asynDriver的已有EPICS通用设备支持系统:
1) StreamDevice:这是来自Dirk Zimoch的对serial/GPIB/CAN的基于协议文件的支持。
2) gpibCore:这是Winans/Franksen GPIB支持的操作系统无关版本。
3) synApps(APS BCDA同步辐射应用程序)。在这个包中的mca,dxp,motor,Ip330, IpUnidig, DAC128V和quadEM应用程序都已经被转成了asyn。不再需要在这个包中的serial和GPIB模块,因为asyn替代了它们。areaDetector模块被编写为使用asyn,并且是开发asynPortDriver的原始动机。
asynDriver概要
定义
asynDriver是介于设备特定代码和驱动程序之间的软件层,它与设备进行通信。它支持阻塞和非阻塞通信,并且能够与基于寄存器和消息的设备一起使用。asynDriver使用以下术语:
1) 接口
软件成之间的所有通信是铜棍接口完成的。一个接口定义是一个完全由函数指针组成的C语言结构体。一个asynDriver接口类似于一个C++或Java纯虚接口。尽管实现是用C,实质是面向对象的。因而此文档使用术语"方法"而不是"函数指针"。
2) 端口
一个物理或逻辑实体,其提供了对一个设备的访问。一个端口提供对一个或多个设备的访问。
3)portDriver
与一个端口进行通信的代码。
4) portThread
如果一个portThread可以阻塞,为每个端口创建一个线程,对这个portDriver的所有I/O是通过这个线程完成的。
5) 设备
连接了一个端口的设备(仪器)。例如,一个GPIB接口可以最多有15个设备连接它。其它端口,例如RS232串口,只支持单个设备。当这个不带修饰符的词device时,它表示连接了一个端口的某个东西。
6) 设备支持
与一个设备交互的代码。
7) 同步
不自愿放弃CPU控制的支持、
8) 异步
非同步的支持。异步操作的一些示例是epicsThreadSleep,epicsEventWait和stdio操作。对epicsMutextTake的调用被认为是同步操作,即:在同步支持中允许它们。
9) asynDriver
在本手册中描述的对应这个支持的名称。它也是描述这个核心接口的头文件名称。
10) asynManager
一个接口和代码,它实现了接口asynManager和asynTrace的方法。
11) 异步驱动
一个驱动程序,在于一个设备进行通信时阻塞。代表性示例是serial,gpib和基于网络的驱动程序。
12)同步驱动
一个驱动程序,在于一个设备进行通信时不阻塞。代表性示例是VME基于寄存器的设备。
13) 基于消息的接口
对读/写操作使用octet数组的接口。
14) 基于寄存器的接口
对读/写操作使用整数或浮点数的接口。
15) 中断
如由asynManager实现的,中断只表示"我有了一个对应(端口,地址)的新值"。
同步/异步和消息/寄存器是传统概念。例如,一个基于寄存器的驱动程序可以是同步或异步。术语寄存器 vs 消息是改编自VXI。
定义了标准接口,因而设备专用代码可以于多个端口驱动程序进行通信。例如,如果设备支持通过由8位字节(octets)组成的读和写进行其所有通信,则它应该对支持octet消息的所有端口驱动程序有效。如果设备支持需要更复杂的支持,则端口类型将受到更多限制。也为接收32位整数或64位浮点数的驱动程序定义了标准接口。可以定义更多的接口,并且预计将定义更多的标准接口。
一个端口可以连接一个或多个设备。例如,一个RS-232端口只能连接一个设备,但一个GPIB端口可以连接最多15个设备。
在设备专用代码和一个端口驱动程序之间可以存在多层。为了被放置在设备专用代码和驱动程序之间,一个软件层调用interposeInterface。对于更复杂的协议,可以创建更多层。例如,以被用户代码调用的asynGpib接口实现了GPIB支持,以及asynGpib调用的asynGpibPort接口。
一个驱动程序通常实现多个接口。例如,asynGpib笑话你了asynCommon, asynOctet和asynGpib。
asynMananger使用了EPICS base的操作系统无关的特性。但它独立于记录/设备支持。因而,它能够被其它代码使用,例如,一个sequence程序。
标准接口
这些接口由asynManager提供,或者被所有或大部分端口驱动程序实现。
这些接口是:
1) asynManager:为与连接了一个端口的设备进行通信提供服务。
2)asynCommon是一个必须被所有底层驱动程序实现的接口。方法是:
- report:报告端口的状态
- connect:连接端口或设备
- disconnect:从端口或设备断开
3)asynTrace是一个产生诊断消息的接口。
4) asynLockPortNotify是一个被一个驱动程序实现的接口,这个驱动程序是另一个驱动程序的asynUser。一个示例是使用标准串行支持的串行总线驱动程序。当asynManager锁定或解锁这个端口时,它调用asynLockPortNotify。
5) asynDrvUser是一个用于从设备支持到驱动程序传递信息而不需要设备支持知道有关所传递的任何细节的接口。
通用接口
除了asynCommon和可选的asynDrvUser,端口驱动还可以实现一个或多个以下基于消息和/或基于寄存器的接口。
1) asynOctet用于基于消息设备的方法。
2) asynFloat64:用于读/写IEEE浮点值设备的方法。
3) asynFloat32Array:用于读/写IEEE 32位浮点值数组设备的方法。
4) asynFloat64Array:用于读/写IEEE 64位浮点值数组设备的方法。
5) asynInt32:用于读/写整数值设备的方法。很多模拟I/O驱动可以使用这个接口。
6) asynInt64:用于读/写64位整数值设备的方法。
7) asynInt8Array:用于读/写8位整数值设备的方法。
8) asynInt16Array:用于读/写16位整数值设备的方法。
9) asynInt32Array:用于读/写32位整数值设备的方法。
10) asynInt64Array:用于读/写64位整数值设备的方法。
11) asynUInt32Digital:用于读/写数字值数组设备的方法。此接口提供掩码来在寄存器内寻址单独的位。
12) asynGenericPoint:读/写任意结构体设备的方法,通过void *指针传递。客户端和服务器当然需要在被指向的结构体类型上达成一致。
13) asynEnum:用于定义枚举串,值和严重性的设备的方法。
14) asynOption:用于设备配置使用键/值对的方法。
asynManager
asynManager是一个接口和相关联代码。它是asynDriver的核心,由于它管理设备支持和驱动程序之间的交互。它提供了以下服务:
1) 报告
方法:report
2) asynUser创建
方法:createAsynUser, duplicateAsynuser, freeAsynUser
asynUser是一个用于访问asynManager服务和用于调用驱动程序实现的接口的"句柄"。只能通过调用createAsynUser或duplicateAsynUser来创建一个asynUser,由于位每个asynUser维护私有信息。freeAsynUser把这个aysnUser放在一个空闲列表上而不是调用free。客户端可以快速地连续调用和释放asynUser而不会使内存碎片化。
对createAsynUser的调用指定了一个processCallback和一个timeoutCallback。这些是由queueReqeust结果而被调用的回调函数。一个asynUser不应该被能够同时访问一个驱动程序的代码段之间共享。例如,标准EPICS记录的设备支持应该为每个记录实例创建一个asynUser。
3) 基本的asynUser服务
方法:connectDevice, disconnect, findInterface
这些方法只能被创建asynUser的代码调用。在一个asynUser被创建后,用户调研connectDevice。用户被连接到一个可以与一个设备进行通信的端口驱动程序。为用户需要的每种接口,调用findInterface。当用户用完这个设备,调用disconnect。
4) 排队服务
方法:queueReqeust, cancelRequest, lockPort, unlockPort, queueLockPort, queueUnlockPort, blockProcessCallback, unblockProcessCallback
queueRequest是一个调用processCallback的请求,processCallback是在对createAsynUser调用中被指定。大部分接口方法只能通过对queueRequest的调用从processCallback或对lockPort/unlockPort的调用之间被调用。此规则的例外必须被明确的记录(一个常见例外是registerInterruptUser/cancelInterruptUser方法)。
queueRequest语义对于能够阻塞的端口和不能阻塞的端口不同
当registerPort被一个能够阻塞的驱动程序调用时,为这个端口创建一个线程。根据优先级为这个线程创建一个队列集合。queueRequest在这些队列之一上放置这个请求。端口线程从队列获取请求并且调用相关联的回调。一次仅一个回调是活跃的。
当registerPort被一个不能阻塞的驱动程序调用,为这个端口创建一个互斥锁(mutex),queueRequest获取这个mutex,调用这个回调并且释放这个mutex。mutex确保对一个端口的两个回调不同时有效。
lockPort是一个在调用unlockPort前锁定对底层驱动程序所有访问的请求。如果端口锁定了,则lockPort和对这个端口驱动程序的所有调用会阻塞。为愿意阻塞或用于与同步端口通信的代码使用,提供了lockPort/unlockPort。对lockPort的调用锁定了与一个多地址端口相关联的所有地址。在asyn R4-14前,pasynManager->lockPort()在端口mutex可用时立即获取它,而不是排队一个获取这个mutex的请求。从asyn R4-14到R4-20,lockPort排队一个访问这个端口的请求并且在这个队列请求回调运行在portThread中前阻塞。当队列请求运行时,调用pasynManager->lockPort()的线程执行,并且在pasynManager->unlockPort()前这个portThread阻塞。在R4-21中,排队的lockPort和unlockPort被重命名为queueLockPort和queueUnlockPort,并且原先轻量级的lockPort和unlockPort函数被恢复了。直到R4-32,当queueLockPort调用queueRequest时,它没有指定一个超时。如果调用queueRequest后但在回调被调用前,端口断开,这会导致代码被挂起。在断开重连前,代码保持挂起。在R4-32中,用一个超时进行queueRequest。默认超时时间是2.0秒,但可以用shell命令asynSetQueueLockPortTimeout(portName, double timeout)更改这个值。如果传递给queueLockPort的pasynUser->timeout大于当前断开超时值,使用来自pasynUser的更大超时替代。
blockProcessCallback是一个在queueRequests之间防止其它asynUsers对一个设备或断开的访问。可以从processCallback或者当asynUser还没有排队的请求时调用blockProcessCallback。当从processCallback被调用时,阻塞立即开始,否则,阻塞从下次调用processCallback开始。阻塞意味着在调用unblockProcessCallback前,将不调用其它asynUser的processCallback。blockProcessCallback只能与可以阻塞的驱动程序一起运行,如果为非阻塞驱动程序调用它,返回一个错误。
5) 基本的驱动程序服务
方法:registerPort, registerInterface
registerPort是被一个portDriver调用。registerInterface是被一个portDriver或一个interposeInterface调用。
每个端口驱动程序提供一个配置命令,为每个端口实例执行这个命令。这个配置命令执行端口专用的初始化,为它实现的每个接口调用registerPort和registerInterface。
6) 属性获取
方法:isMultiDevice, canBlock, getAddr, getPortName, isConnected, isEnabled, isAutoConnect
这些方法可以被能够访问这个asynUser的任何代码调用。
7) 连接服务
方法:enble, autoConnect, setAutoConnectTimeout
这些方法可以被能够访问这个asynUser的任何代码调用。
可以调用这些方法为一个端口和/或设备设定enable和autoConnect设置。如果autoConnect是真,则asynManager做以下事情:
- 当端口注册其asynCommon接口时,asynManager排队一个连接请求。它接着为这个连接回调去完成等待一小段时间。默认时间是0.5秒,但用对函数pasynManager->setAutoConnectTimeout(double timeout)的调用可以更改这个时间。可以从iocsh shell用asynSetAutoConnectTimeout(double timeout)命令访问这个函数。设计这个短的超时时间去使得设备在它们可用时有时间连接,但通过等待例如TCP连接上系统超时,没有过渡减慢IOC的启动。注意:这意味着注册asynCommon接口后立即发生pasynCommon->connect()是非常可能的,这表示在驱动程序注册asynCommon接口前,它必须已经完成了asynCommon->connect()回调所需的所有初始化。如果这个端口初始未连接,或者如果它后来断开了,则asynManager将每20秒钟排队一个连接请求。如果autoConnect是真并且port/device被启用,但设备未连接,则queueManager就在调用processCallback前,调用asynCommon:connect。
8) 异常服务
方法:exceptionCallbackAdd, exceptionCallbackRemove, exceptionConnect, exceptionDisconnect
设备支持代码调用exceptionCallbackAdd和exceptionCallbackRemove。在asynDriver.h中以"enum asynException"定义了完整的异常列表。
当一个端口驱动连接或者断开时,通常由于对asynCommon:connect或asynCommon:disconnect的调用造成,它也必须调用exceptionConnect和exceptionDisconnect。
9) 中断服务
方法:registerInterruptSource, getInterruptPvt, createInterruptNode, freeInterruptNode, addInterruptUser, removeInterruptUser, interruptStart, interruptEnd
中断只表示:“我有一个新值”。很多asyn接口,例如:asynInt32,提供了中断支持。这些接口提供了addInterruptUser和removeInterruptUser的方法。如果设备支持想要在一个中断发生时被调用,它调用addInterruptUser。实现了这个接口的驱动程序或其它代码在其有新值时调用这些注册的用户。asynManager提供帮助驱动程序实现对中断的线程安全支持的服务。
支持中断的驱动程序为有相关联中断的每个接口调用registerInterruptSource。它调用interruptStart来获取一个列表的所有注册用户并且在它调用这些注册用户后调用interruptEnd。驱动程序也负责调用addInterruptUser和removeInterruptUser。
如果在对interruptStart和interruptEnd调用之间对addInterruptUser或removeInterruptUser进行了任何调用,aysnManager将这个请求放在一个列表上并且在interruptEnd被调用后处理这个请求。
很多标准接口,例如asynInt32,提供了registerInterruptUser和cancalInterruptUser方法。这些接口也提供了一个辅助接口,例如asynInt32Base,和代码,它实现了registerInterruptUser和cancelInteruptUser。
在像VxWorks或RTEMS的操作系统上,一定不能从中断级别调用interruptStart, interruptEnd。
10) 时间戳服务
方法:updateTimeStamp, getTimeStamp, registerTimeStampSource, unregisterTimeStampSource
这些方法提供了对为一个端口设置时间戳的支持。这个时间戳一般用于设置在读取或回调操作时被传递给设备支持的pasynUser->timestamp字段。设备支持使用pasynUser->timestamp字段设置记录TIME字段。如果记录TSE字段为-2,这将是这个记录的时间戳。asynManager提供了一个默认时间戳源函数,这个函数就调用epicsTimeGetCurrent()。但registerTimeStampSource可以用于提供一个不同的用户提供的时间戳源函数,例如调用epicsTimeGetEvent(),或者某个其它的站点专用的时间戳源。unregisterTimeStampSource恢复为在pasynManager中默认的时间戳源。
11) 通用空闲列表服务
方法:memMalloc, memFree
这些方法不需要一个aysnUser。为必须连续分配和释放内存的代码提供了它们。由于memFree放置内存到一个空闲列表而不是调用free,它们比calloc/free更高效并且也防止了内存碎片化。
12) 插入服务
方法:interposeInterface
调用interposeInterface的代码实现了一个接口,它不被端口驱动程序支持,或者其是被插入在调用者和端口驱动程序之间。例如:asynInterposeEos插入asynOctet。它为不支持字符串末尾处理的端口驱动程序执行EOS处理。
interposeInterfface是迭代的,即:在一个端口,地址上可以存在任意数目的插入层。
13) 多设备 vs 单设备端口驱动程序
当底层驱动程序调用registerPort时,它声明它是否处理多个设备。这确定了如何处理传递给connectDevice的addr参数以及getAddr返回什么。
- multiDevice false:忽略传递给connectDevice的addr参数并且getAddr总是返回-1。
- multiDevice true:如果用add<0调用connectDevice,连接到端口并且getAddr总是返回-1。如果addr>=0,则调用者连接到指定地址的设备。getAddr将返回这个地址。连接了端口的asynUser可以发出影响在此端口上所有地址的请求。例如,禁止对端口的访问阻止了对此端口上所有地址的访问。
14) 连接管理
asynManager跟踪以下状态:
- connect:端口或设备是否连接?这个状态被初始化为不连接。
- enabled:端口或设备是否启动? 这个窗体被初始为启用。
- autoConnect:如果asynManager发现端口或设备断开了,它是否自动尝试连接?这被初始化为在registerPort调用中指定的状态。
如果端口不支持多设备,则端口和设备状态是相同的。如果端口支持多设备,则asynManager跟踪端口的状态以及连接了端口的每个设备的状态。
如果端口或设备的任何状态变化了,则调用先前为那个端口或设备调用exceptionCallbackAdd的所有用户。
当底层驱动城区连接一个端口或端口,地址时,他们必须调用pasynManager:exceptionConnect,而当它们断开时,调用exceptionDisconnect。
15) 防止一个线程阻塞
方法asynManager:report和asynCommon:report能够被任何线程调用,但在report结束前,调用者被阻塞。lockPort,unlockPort,queueLockPort, queueUnlockPort和大部分端口方法可以阻塞。其它的asynManager方法可以被包括portThread的任何线程调用。所有这些方法都不阻塞。
除非说明,否则对应其它接口的方法只能被processCallback调用或者在lockPort/unlockPort或者queueLockPort/queueUnlockPort之间调用。
接口方法registerInterruptUser和cancelInterruptUser一定不能阻塞。因为registerInterruptUser回调可被一个非阻塞驱动程序调用,所以它一定不阻塞。
16) portThread
如果一个驱动程序用ASYN_CANBLOCK属性置位调用asynManager:registerPort,则asynManager为这个端口创建一个线程。每个portThread有它自己用于对queueRequest调用的队列集合。维持了四个队列。一个队列只用于asynCommon:connect和asynCommon:disconnec请求。其它队列提供不同优先级:低,中和高。如果端口未连接,则对除了连接队列外的任何队列的queueRequests将被拒绝。portThread一直运行,实现以下算法:
1) 通过调用epicsEventMustWait等待运行。诸如queueRequest的其它代码调用epicsEventSignal。
2) 如果端口被禁用,回到1。
3) 对于在队列asynQueuePriorityConnect中每个元素:
- 从队列移除元素
- 调用用户的回调
4) 对于队列asynQueuePriorityHigh, ... , asynQueueLow。
- 如果禁用,跳过这个元素
- 如果对于这个设备未连接并且autoConnect是真,则尝试连接设备。
- 如果未连接,跳过这个元素。
- 如果被其它线程阻塞,跳过这个元素。
- 如果未阻塞并且用户请求阻塞,则阻塞。
- 从队列移除并且:a) 锁定端口;b) 调用用户回调 c) 解锁端口
实际代码更加复杂,因为在其调用asynManager之外的代码前,它解锁。这意味着队列能够被修改并且异常会发生。
17) 排队的概要
当讨论排队时,思考asyn的3个组件:
1)asynManager:这是aysn的核心组件。它对EPICS记录一无所知。实际上,除了其为了像mutexes,消息队列,事件等OS无关的东西使用libCom外,它完全独立于EPICS。
2) 标准asyn设备支持(devEpics目录):这是asyn了解EPICS记录的唯一组件并且取决于EPICS组件而不是libCom。它提供的队列是通过pasynManager->queueRequest()用于回调请求与异步驱动程序进行通信(ASYN_CANBLOCK)。它在三种情况下,支持来自驱动程序的回调:
- 用SCAN=I/O Intr的输入记录
- 用周期扫描的输入记录(仅限asynInt32Average和asynFloat64Average)
- 用asyn:READBACK=1的输出记录
回调值能够被放在一个环形缓存中,因而如果回调发生快于记录能够处理,这些值不会丢失。能够用asyn:FIFO info标签控制环形缓存的大小。默认值对于标量记录是10。对于waveform记录以及对于stringout和stringin记录,这个默认值是0。如果环形缓存在用,则每个驱动回调导致压一个新值到这个缓存以及一个在一个单独回调线程中运行运行这个记录的请求。如果这个环形缓存满了,则在这个队列中的最旧值被丢弃,并且添加这个新值。这确保了记录最终将有最近回调的值,但它可能在此之前跳过了一些值。如果设置了ASYN_TRACE_WARNING,则打印一条警告消息。驱动回调不阻塞等待这个记录去运行。
3) asynPortDriver:asynPortDriver不支持队列。它有一个参数库,其存储标量参数的最近值。它不未数组参数存储值。
运行的理论
1) 初始化
在初始化过程中,端口驱动程序注册每个通信端口以及所有支持的接口。
用户代码创建一个asynUser,它是访问asynDriver工具的句柄,通过调用:
pasynManager->createAsynUser(processCallback, timeoutCallback);
一个asynUser有以下特性:
1) asynUser是asynManager通过其管理用于访问一个端口的多个请求的方式。
2) processCallback,其被以下描述的queueRequest使用,是一个用户提供的回调例程的地址。
3) timeoutCallback是用户提供的回调的地址,如果一个queRequest在队列上保留太长时间,将调用这个回调。
4) 设备支持代码应该为对底层驱动程序每个"原子"访问创建一个asynUser,即:一个调用集合一定不能与对底层驱动程序的其它调用交错。例如,对应EPICS记录支持的设备支持应该为每个记录实例创建一个asynUser。
5) 设备支持代码不应该尝试在用于访问一个端口的多个请求源之间共享一个asynUser。如果这么做了,则设备支持必须自己处理已经由asynManager处理的连接问题。
用户代码通过调用以下连接到底层驱动程序:
status = pasynManager->connectDevice(pasynUser, portName, addr);
这个调用必须指定端口的名称和设备的地址。它接着调用findInterface来定位它用其调用这个驱动程序的接口。例如:
pasynInterface = pasynManager->findInterface(pasynUser, asynOctetType, 1);
2) 请求访问一个端口
用户代码通过两种方式请求访问一个端口:
1) queueReqeust:传递给createAsynUser的processCallback对这个端口接口进行调用。
2) lockPort/unlockPort, queueLockPort/queueUnlockPort:当锁被持有时,调用者可以对这个端口接口进行调用。这些调用以及对端口的调用可以阻塞,并且因而不应该被不应该阻塞的代码使用,即:对EPICS记录的同步设备支持。
3) queueRequest -- 控制流
用户代码通过调用以下请求访问一个端口:
status = pasynManager->queueRequest(pasynUser, priority, timeout);
这导致processCallback或timeoutCallback被调用。对一个端口的大部分请求必须从processCallback进行。queueRequest不阻塞。如果为一个能够阻塞的端口调用queueRequest,这个请求排队到一个专用于这个端口的线程。如果为一个不阻塞的端口调用queueRequest,它只是调用processCallback。只在通过调用queueRequest, lockPort/unlockPort和/或queueLockPort/queueUnlockPort才访问底层驱动程序时,保证才是有效的。
以下示例基于EPICS IOC 记录/设备支持。
第一个示例展示了对能够阻塞的端口的访问。
从一个应用程序线程开始发生的记录设备支持事件的顺序如图1所示,并且按以下步骤解释:
1) 记录运行用PACT 0调用设备支持(运行不是活动的)
2) 设备支持调用queueRequest。
3) queueRequest在驱动程序工作队列上放置这个请求。应用程序线程现在能够继续运行并且执行其它操作。用于这个I/O请求的后续操作在这个端口驱动程序线程中被处理。
4) portThread从工作队列移除这个I/O请求。
5) portThread调用位于记录设备支持中的processCallback。
6) processCallback调用底层驱动程序。底层驱动程序在I/O结束前或者在超时发生前读或写例程阻塞。底层驱动程序例程返回I/O操作的结果给processCallback。
7) processCallback请求记录被运行。注意:运行的请求将由标准的回调请求之一进行而不是这个端口线程。
8) 记录支持再次调用设备支持,这次用PACT 1(运行是活动的)。设备支持更新记录中的字段并且返回到记录支持,这结束了记录运行。
第二个示例显示对一个不能阻塞的端口的访问:
从一个应用程序线程开始发生的记录设备支持事件的顺序如图2所示,并且用以下步骤解释:
1) 记录运行调用设备支持
2)设备支持调用queueRequest
3) 由于这个端口是同步的,即,不能阻塞,queueRequest锁定这个端口并且调用processCallback。
4)processCallback调用底层驱动程序读或写例程。底层驱动程序返回I/O操作的结果给processCallback。
5)processCallback返回到queueRequest,它解锁了这个端口并且返回到设备支持,其返回到记录支持,这结束了记录运行。
asynDriver结构体和接口
asynDriver.h描述以下:
- asynStatus:一个描述很多方法返回的状态的枚举。
- asynException:一个描述异常的枚举。
- asynQueuePriority:一个描述队列优先级的枚举。
- asynUser:一个包含一般信息的结构体并且是用于调用大多数方法的句柄。
- asynInterface:一个描述一个接口的结构体。
- userCallback:对应以上描述的用户process回调函数的typedef。
- exceptionCallback:一个当异常发生时要被调用的用户回调的typedef。
- timeStampCallback:一个将被updateTimeStamp调用的用户回调函数的typedef
- asynManager:一个用于与asynDriver通信的接口。
- asynCommon:一个提供了必须被所有底层驱动程序实现的方法的接口。
- asynTrace:一个接口和相关联函数和定义,它们实现了跟踪功能。
asynStatus
定义了由大多数方法返回的状态。如果一个方法返回了一个除asynSuccess外的状态,以及传给这个方法的其中一个参数是pasynUser,则预计这个方法写一条消息到pasynUser->errorMessage。
typedef enum {
asynSuccess, asynTimeout, asynOverflow, asynError, asynDisconnected, asynDisabled
} asynStatus;
asynStatus
asynSuccess | 请求成功了 |
asynTimeout | 请求由于超时失败了 |
asynOverflow | 驱动程序丢失输入数据。如果一个内部缓存或者用户提供的缓存太小了,这会发生。在可能时,应该写底层驱动程序,使得用户可以用小段读取输入。 |
asynError | 某个其它错误发生了。 |
asynDisconnected | 因为端口未连接,请求失败 |
asynDisabled | 因为端口或设备被禁用,请求失败 |
asynException
为方法exceptionOccurred定义了异常。
typedef enum {
asynExceptionConnect, asynExceptionEnable, asynExceptionAutoConnect,
asynExceptionTraceMask, asynExceptionTraceIOMask, asynExceptionTraceInfoMask,
asynExceptionTraceFile, asynExceptionIOTruncateSize
} asynException;
asynException
asynExceptionConnect | 端口或设备的连接状态发生变化了 |
asynExceptionEnable | 端口或设备的enable状态发生变化了 |
asynExceptionAutoConnect | 端口或设备的autoConnect状态发生变化了 |
asynExceptionTraceMask | 对应端口或设备的traceMask发生变化了 |
asynExceptionTraceIOMask | 对应端口或设备的traceIOMask发生变化了 |
asynExceptionTraceInfoMask | 对应端口或设备的traceInfoMask发生变化了 |
asynExceptionTraceFile | 对应端口或设备的trace文件发生变化了 |
asynExceptionTraceIOTruncateSize | 对应端口或设备的traceIOTruncateSize发生变化了 |
asynQueuePriority
这定义了传递给queueRequest的优先级:
typedef enum {
asynQueuePriorityLow,asynQueuePriorityMedium,asynQueuePriorityHigh,
asynQueuePriorityConnect
}asynQueuePriority;
asynQueuePriority
asynQueuePriorityLow | 最低优先级 |
asynQueuePriorityMedium | 中等优先级 |
asynQueuePriorityHigh | 高优先级 |
asynQueuePriorityConnect | 排队一个连接或断开请求。这个优先级必须用于且只能用于连接/断开请求 |
asynUser
描述一个用户代码传递给大部分asynManager以及驱动程序方法的结构体。代码必须通过调用asynManager:createAsynUser(或者asynManager:duplicateAsynUser)和asynManager:freeAsynUser分配和释放一个asynUser。
typedef struct asynUser {
char *errorMessage;
int errorMessageSize;
/* timeout must be set by the user */
double timeout; /* Timeout for I/O operations*/
void *userPvt;
void *userData;
/* The following is for use by driver */
void *drvUser;
/* The following is normally set by driver via asynDrvUser->create() */
int reason;
epicsTimeStamp timestamp;
/* The following are for additional information from method calls */
int auxStatus; /* For auxillary status*/
int alarmStatus; /* Typically for EPICS record alarm status */
int alarmSeverity; /* Typically for EPICS record alarm severity */
}asynUser;
asynUser
errorMessage | 当一个方法返回asynError时,它应该通过对以下调用把一条错误消息放入errorMessage中: epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize, "<format>", ...) 错误消息不应该以一个新行字符序列结尾(例如:\n),也不包含。用户代码决定是否以及如何显示错误消息。错误消息中不含新行,使得用户代码把此错误消息嵌入到另外的消息或者输出格式变得简单。 |
errorMessageSize | errorMesssage的大小。用户不能更改这个值。 |
timeout | 在用于I/O请求的超时前的秒数。这是由用户设置的,并且能在对一个驱动程序调用之间被更改。如果对底层驱动程序的调用导致了驱动程序进行了很多I/O请求,这是对应每个I/O请求的时间。 含义如下: 1) >0.0:等待I/O结束最多timesout秒。 2) =0.0:执行能够不用阻塞进行的任何I/O。如果没有I/O能够不阻塞地进行,返回超时错误。 3) <0.0:无限超时。一直等待I/O到结束。 |
userPvt | 给用户使用。在调用pasynManager->createAsynUser之后,用户应该立即设置这个。如果在asynUser排队时,这被修改了,结果是未定义的,例如,它会导致程序崩溃。 |
userData | 也对用户使用。 |
drvUser | 一个驱动程序可以使用这个保存asynUser专用的数据。asynDrvUser接口被用于在asynUser和驱动程序之间通信。 |
reason | 驱动程序和asynUsers可以使用这个作为一个通用字段。按惯例,它用于决定在一个特定接口上正在发送什么"命令"。例如,一个实现了asynInt32接口的A/D驱动程序可能定义reason=0表示"返回A/D转换",而reason=1可能表示"返回放大器增益"。一般驱动程序实现了asynDrvUser接口,并且使用这个来把对应命令的描述字符串(例如:在这个示例中的"DATA"或“GAIN”)转换成枚举"reason"。一个正在调用中断用户的驱动程序经常使用reason来决定是否应该调用用户回调。小于0的resaon值被保留位标准含义。例如,ASYN_REASON_SIGNAL用于表示"带外"请求。devGpib支持使用这个来报告SRQs。 |
timestamp | 提供它们自己时间戳的设备使用这个字段来为其TSE字段设为-2的记录提供时间值。 |
auxStatus | 任何方法可以在auxStatus中提供更多的返回信息。含义由方法决定。回调可以使用auxStatus在设备支持回调函数中设置记录警报状态。 |
alarmStatus | 任何方法可以在alarmStatus中提供更多的返回信息。含义由方法决定。回调可以使用alarmStatus在设备支持回调函数中设置记录警报状态。 |
alarmSeverity | 任何方法可以在alarmSeverity中提供更多的返回信息。含义由方法决定。回调可以使用alarmSeverity在设备支持回调函数中设置记录警报严重性。 |
asynInterface
这定义了一个向asynPortAManager:registerPort或asynManager:interposeInterface注册的接口。
typedef struct asynInterface{
const char *interfaceType; /*For example, asynCommonType */
void *pinterface; /*For example, pasynCommon */
void *drvPvt;
}asynInterface;
asynInterface
interfaceType | 一个描述这个接口的字符串 |
pinterface | 一个指向这个接口的指针。用户必须转换这个为正确的类型 |
drvPvt | 用于调用registerPort或interposeInterface的代码专用 |
asynManager
这是用于与asynDriver进行通信的主要接口。
/*registerPort attributes*/
#define ASYN_MULTIDEVICE 0x0001
#define ASYN_CANBLOCK 0x0002
/*standard values for asynUser.reason*/
#define ASYN_REASON_SIGNAL -1
#define ASYN_REASON_RESERVED_LOW 0x70000000
#define ASYN_REASON_RESERVED_HIGH 0x7FFFFFFF
#define ASYN_REASON_QUEUE_EVEN_IF_NOT_CONNECTED ASYN_REASON_RESERVED_LOW
typedef void (*userCallback)(asynUser *pasynUser);
typedef void (*exceptionCallback)(asynUser *pasynUser,asynException exception);
typedef void (*timeStampCallback)(void *userPvt, epicsTimeStamp *pTimeStamp);
typedef struct interruptNode{
ELLNODE node;
void *drvPvt;
}interruptNode;
typedef struct asynManager {
void (*report)(FILE *fp,int details,const char*portName);
asynUser *(*createAsynUser)(userCallback process,userCallback timeout);
asynUser *(*duplicateAsynUser)(asynUser *pasynUser,
userCallback queue,userCallback timeout);
asynStatus (*freeAsynUser)(asynUser *pasynUser);
void *(*memMalloc)(size_t size);
void (*memFree)(void *pmem,size_t size);
asynStatus (*isMultiDevice)(asynUser *pasynUser,
const char *portName,int *yesNo);
/* addr = (-1,>=0) => connect to (port,device) */
asynStatus (*connectDevice)(asynUser *pasynUser,
const char *portName,int addr);
asynStatus (*disconnect)(asynUser *pasynUser);
asynStatus (*exceptionCallbackAdd)(asynUser *pasynUser,
exceptionCallback callback);
asynStatus (*exceptionCallbackRemove)(asynUser *pasynUser);
asynInterface *(*findInterface)(asynUser *pasynUser,
const char *interfaceType,int interposeInterfaceOK);
asynStatus (*queueRequest)(asynUser *pasynUser,
asynQueuePriority priority,double timeout);
asynStatus (*cancelRequest)(asynUser *pasynUser,int *wasQueued);
asynStatus (*blockProcessCallback)(asynUser *pasynUser, int allDevices);
asynStatus (*unblockProcessCallback)(asynUser *pasynUser, int allDevices);
asynStatus (*lockPort)(asynUser *pasynUser);
asynStatus (*unlockPort)(asynUser *pasynUser);
asynStatus (*queueLockPort)(asynUser *pasynUser);
asynStatus (*queueUnlockPort)(asynUser *pasynUser);
asynStatus (*setQueueLockPortTimeout)(asynUser *pasynUser, double timeout);
asynStatus (*canBlock)(asynUser *pasynUser,int *yesNo);
asynStatus (*getAddr)(asynUser *pasynUser,int *addr);
asynStatus (*getPortName)(asynUser *pasynUser,const char **pportName);
/* drivers call the following*/
asynStatus (*registerPort)(const char *portName,
int attributes,int autoConnect,
unsigned int priority,unsigned int stackSize);
asynStatus (*registerInterface)(const char *portName,
asynInterface *pasynInterface);
asynStatus (*exceptionConnect)(asynUser *pasynUser);
asynStatus (*exceptionDisconnect)(asynUser *pasynUser);
/*any code can call the following*/
asynStatus (*interposeInterface)(const char *portName, int addr,
asynInterface *pasynInterface,
asynInterface **ppPrev);
asynStatus (*enable)(asynUser *pasynUser,int yesNo);
asynStatus (*autoConnect)(asynUser *pasynUser,int yesNo);
asynStatus (*isConnected)(asynUser *pasynUser,int *yesNo);
asynStatus (*isEnabled)(asynUser *pasynUser,int *yesNo);
asynStatus (*isAutoConnect)(asynUser *pasynUser,int *yesNo);
asynStatus (*setAutoConnectTimeout)(double timeout);
asynStatus (*waitConnect)(asynUser *pasynUser, double timeout);
/*The following are methods for interrupts*/
asynStatus (*registerInterruptSource)(const char *portName,
asynInterface *pasynInterface, void **pasynPvt);
asynStatus (*getInterruptPvt)(asynUser *pasynUser,
const char *interfaceType, void **pasynPvt);
interruptNode *(*createInterruptNode)(void *pasynPvt);
asynStatus (*freeInterruptNode)(asynUser *pasynUser,interruptNode *pnode);
asynStatus (*addInterruptUser)(asynUser *pasynUser,
interruptNode*pinterruptNode);
asynStatus (*removeInterruptUser)(asynUser *pasynUser,
interruptNode*pinterruptNode);
asynStatus (*interruptStart)(void *pasynPvt,ELLLIST **plist);
asynStatus (*interruptEnd)(void *pasynPvt);
/* Time stamp functions */
asynStatus (*registerTimeStampSource)(asynUser *pasynUser, void *userPvt, timeStampCallback callback);
asynStatus (*unregisterTimeStampSource)(asynUser *pasynUser);
asynStatus (*updateTimeStamp)(asynUser *pasynUser);
asynStatus (*getTimeStamp)(asynUser *pasynUser, epicsTimeStamp *pTimeStamp);
asynStatus (*setTimeStamp)(asynUser *pasynUser, const epicsTimeStamp *pTimeStamp);
const char *(*strStatus)(asynStatus status);
}asynManager;
epicsShareExtern asynManager *pasynManager;
asynManager
report | 报告有关asynPortManager的状态。如果portName非空,它报告一个特定端口。如果portName是空,则它报告每个注册的端口。它也为每个被报告端口调用asynCommon:report。 |
createAsynUser | 创建一个asynUser。调用者指定两个回调:process和timeout。这些调用仅由queueRequest的调用结果被调用。超时回调是可选的。为errorMessage分配errorMessageSize个字符。存储区量不能被更改。如果此方法不能分配这个存储区,它不返回。 |
duplicateAsynUser | 通过调用createAsynUser创建一个asynUser。它接着按如下初始这个新的asynUser:用从pasynUser获取的值初始化字段timeout, userPvt, userData和drvUser。如果connectDevice状态与对应pasynUser的状态相同。 |
freeAsynUser | 释放一个asynUser。用户必须通过这个调用释放一个asynUser。如果asynUser连接了一个端口,asynManager:disconnect被调用。如果断开失败,这个调用也将失败。对应asynUser的存储区被保存在了一个空闲列表,并且将在之后调用createAsynUser或duplicateAsynUser时再被使用。因而连续调用createAsynUser(或duplicateAsynUser)和freeAsynUser是高效的。 |
memMalloc memFree |
分配/释放内存。memMalloc/memFree维护一个不同大小的空闲列表的集合。因而短时间内需要存储区的任何应用程序可以使用memMalloc/memFree分配和释放存储区而不引起内存碎片化。传给memFree的大小必须与再memMalloc调用中指定的值相同。 |
isMultiDevice | 回答这个问题"这个端口支持多设备码?" 再调用connectDevice前,可以调用这个方法。 |
connectDevice | 连接asynUser结构体到由portName, addr指定的设备。端口名称与在registerPort调用中指定的名称相同。如果asynUser已经连接设备了,这个调用将失败。如果端口不支持多设备,则忽略addr。connectDevice只连接asynUser到对应portName,addr的端口驱动程序。端口驱动程序可能连接或者可能没有连接到实际设备。因而,connectDevice和asynCommon:connect是完全不同的。 |
disconnect | 从asynUser连接的port,addr断开它,先前调用connectDevice连接了port,addr。如果asynUser排队或者被锁定或者有一个通过exceptionCallbackAdd注册的回调,这个调用将失败。注意:asynManager:disconnect和asynCommon:disconnect完全不同。 |
exceptionCallback Add |
当由asynException定义的其中一个异常发生时,回调将被调用。这个回调可以调用isConnect, isEnabled或isAutoConnect来查找连接状态。asynTrace提供了找出当前trace设置的方法。 |
exceptionCallback Remove |
回调被移除。在断开前,必须调用这个函数。 |
findInterface | 查找一个驱动接口。如果interposeInterfaceOK是真,则findInterface返回上个注册的或插入的接口。否则,返回由registerPort注册的接口。如果不支持interfaceType,它返回0。 用户需要驱动程序接口和pdrvPvt的地址,使得能够对这个驱动程序进行调用。例如: asynInterface * pasynInterface; asynOctet * pasynOctet; void * pasynOctetPvt; ... pasynInterface = pasynManager->findInterface(pasynUser, asynOctetType,1); if (!pasynInterface){ /*错误,做些事情*/} pasynOctet = (asynOctet * ) pasynInterface->pinterface; pasynOctetPvt = pasynInterface->pdrvPvt ... /*必须从一个回调进行 以下调用 */ pasynOctet->read(pasynOctetPvt, pasynUser, ...) |
queueRequest | 当registerPort被调用时,调用者必须指定它是否能够阻塞,即:置位或清除属性位ASYN_CANBLOCK。如果用ASYN_CANBLOCK为真,注册了这个端口,则这个请求被放置在与此队列相关联线程的一个队列上。如果用ASYN_CANBLOCK false注册这个端口,则queueRequest锁定这个端口并且调用processCallback。在这两种情况中,在createAsynUser调用中指定的processCallback被调用。 如果asynUser已经在一个队列上了,返回一个asynError。当请求排队时,超时开始。在回调被调用前,从队列移除这个请求。允许回调对asynManager进行诸如queueRequest,blockProcessCallback等的请求。从一个回调调用freeAsynUser也是允许的,但将延时这个请求到这个回调结束。 优先级asynQueuePriorityConnect必须用于asynCommon:connect和asynCommon:disconnect调用,并且一定不能用于任何其它调用。 如果一个超时回调未被传递给createAsynUser并且请求了一个非0超时的queueRequest,请求失败。 除非原因是ASYN_REASON_QUEUE_EVEN_IF_NOT_CONNECTED,否则排队一个除连接请求外的请求到一个断开端口的尝试将失败。 |
cancelRequest | 如果一个asynUser排队了,从这个队列移除它。当cancelRequest被调用时,如果process或timeout回调是活动的,则在回调结束前,cancelReqeust将不返回。 |
blockProcessCallback unblockProcessCallback |
blockProcessCallback是一个防止在queueReqeusts之间其它asynUsers访问一个设备或端口的请求。可以从processCallback或者在asynUser没有排队的请求时调用blockProcessCallback。当从processCallback被调用时,阻塞立即开始,否则阻塞从下次processCallback被调用时开始。阻塞表示在调用unblockProcessCallback前,其它asynUser的processCallback将不能被调用。注意:以下对blockProcessCallback的限制: 1)blockProcessCallback只与能够阻塞的驱动程序一起工作,如果为非阻塞驱动程序调用它,返回一个错误。 2) 指定asynQueuePriorityConnect的queueRequests不被阻塞。 允许同时阻塞所有设备和asynUser连接的所有设备。 |
lockPort/unlockPort | 锁定对一个端口驱动程序的访问。这被在对一个端口驱动程序进行调用时愿意阻塞的代码使用。代码可以调用lockPort,对端口驱动程序进行任意数目的调用,并且接着调用unlockPort。调用queueRequest和/或lockPort的其它代码将在对lockPort和unlockPort调用之间被延时。 |
queueLockPort/ queueUnlockPort |
锁定对一个端口驱动程序的访问。这被在对一个端口驱动程序进行调用时愿意阻塞的代码使用。代码可以调用queueLockPort, 对端口驱动程序进行任意数目的调用,并且接着调用queueUnlockPort。调用queueRequest和/或lockPort的其它代码将在对queueLockPort和queueUnlockPort调用之间被延时。lockPort和queueLockPort的差别是queueLockPort使用与queueRequest相同的队列排队一个锁定端口的请求。这意味着一个重复调用queueLockPort的线程,在调用之间没有睡眠,将仍然允许其它线程访问这个端口。对lockPort不是这样,它将在端口空闲时尽快获取一个mutex,并且会完全阻止其它线程访问这个端口。 |
setQueueLockPortTimeout | 设置传递给queueRequest()在queueLockPort()中的超时。当创建这个端口时,设置了2.0秒的默认值。这个函数可以用于更改这个值。注意:如果传递给queueLockPort的pasynUser->timeout大于当前值,则使用这个更大的超时值。 |
canBlock | 根据对底层驱动程序的调用是否可以阻塞,yesNo设置为(0,1),即是:(false, true)。这个值由传递给registerPort的属性决定。 |
getAddr |
*addr被设置成用户在connectDeviced调用中指定的地址或者如果端口不支持多设备,为-1。 |
getPortName | *pportName被设置为用户连接的端口的名称。 |
registerPort | 这个方法被驱动程序调用。为每个端口实例进行一次调用。属性是一个位集合。当前定义了两个位:ASYN_MULTIDEVICE和ASYN_CANBLOCK。驱动程序必须合适地指定这些。autoConnect,其是(0,1),对应(否,是)为这个端口和连接这个端口地所有设备提供初始值。priority和stacksize仅在ASYN_CANBLOCK=1时才有用,在这种情况下,asynManager用epicsThreadCreate()创建这个端口线程时,它使用这些值。如果priority是0,则将分配默认值epicsThreadPriorityMedium。如果stacksize是0,将分配默认值epicsThreadGetStackSize(epicsThreadStackMedium)。portName参数指定asyn上层代码将通过其指向这个通信接口实例的名称。registerPort方法内部复制了这个名称参数执行的字符串。 |
registerInterface | 由端口驱动程序为每个支持的接口调用这个方法。这个方法没有复制paynInterface参数指向的asynInterface。调用者必须在为这个端口存在期间被保留的位置中存储这个asynInterface。通常通过在'driver private'结构体中放置这个asynInterface结构体做这件事。 |
exceptionConnect | 这个方法必须被驱动程序当且仅当其连接到一个端口或设备时被调用。 |
exceptionDisconnect | 这个方法必须被驱动程序当且仅当其从一个端口或设备断开时被调用。 |
interposeInterface | 这被一个介于客户端代码和端口驱动程序之间的软件层调用。例如,一个设备回应写,则可以创建一个在每次写后发布读的软件模块并且它为接口asynOctet调用interposeInterface。 能够发出一个port/add/interface的多个interposeInterface。**ppPrev被设置成先前的asynInterface的地址。因而上次调用interposeInterface的软件模块被用户代码调用。它接着可以调用到数第二次调用interposeInterface的软件模块。在实际的端口驱动程序被调用前,这继续。 也能够用先前还未被注册或放置的asynInterface调用interposeInterface。在这种情况下,*ppPrev将是null。因而,能够实现底层驱动程序不知道的新接口。 |
enable | 如果enable设为yes,则除非queueRequests的队列超时发生,否则它们不出队。 |
autoConnect | 如果autoConnect是真,并且当调度一个用户回调要被调用时,端口或设备没有连接,asynManager调用pasynCommon->connect。详细见以下控制流。 |
isConnected | 根据端口或设备(没有,有)连接,*yesNo被设置成(0,1)。 |
isEnabled | 根据端口或设备(没有,有)使能,*yesNo被设置成(0,1)。 |
isAutoConnect | 根据portThread对端口或设备(不是,是)自动连接,*yesNo被设置成(0,1)。 |
setAutoConnectTimeout | 更改等待来自端口驱动程序的初始连接回调的超时时间。这个回调发生来响应asynManager排队一个连接请求,其在端口驱动程序注册它的asynCommon接口时发生。默认超时是0.5秒。 |
watiConnect | 等待port/device连接最多timeout秒。 |
registerIntteruptSource | 如果底层驱动程序支持中断,它必须为每个支持中断的接口调用这个方法。pasynPvt必须是一个void *的地址,它将被registerInterruptSource赋予一个值。这个参数在interruptStart和interruptEnd中被传递。 |
getInterruptPvt | 想要调用createInterruptNode但不知道pasynPvt地址的任何代码可以通过这个方法找到它。调用者必须连接了一个设备,即,必须已经调用connectDevice。如果调用者没有连接,getInterrputPvt返回asynError。 |
createInterruptNode freeInterruptNode |
这些方法是用户可以分配和释放一个interruptNode的唯一方式。pasynPvt是从getInterruptPvt获取的值。createInterruptNode/freeInterruptNode是独立方法,而不是通过addInterruptUser/removeInterruptUser自动进行的,因而addInterruptUser/removeInterruptUser会是高效的。 |
addInterruptUser removeInterruptUser |
实现了registerInterruptUser/cancelInterruptUser的代码必须调用addInterruptUser/removeInterruptUser来添加和从列表移除用户否则对interruptStart/interruptEnd的调用将不起作用。这是一个高效操作,使得一个用户可以重复调用registerInterruptUser/cancelInterruptUser。如果当一个中断正在被处理时,即在对interruptStart/interruptEnd的调用之间,两个方法之一被调用,这个调用将阻塞到直到interruptEnd被调用。在addInterruptUser调用中指定的asynUser的processCallback一定不能调用removeInterruptUser,否则其将永远阻塞。 |
interruptStart interruptEnd |
实现中断的代码时接口依赖的。asynManager提供的唯一服务是用户列表的一个线程安全实现。当代码想要调用在对registerInterruptUser调用中指定的回调时,它调用interruptStart来获取这个回调列表。当它结束时,它调用interruptEnd。如果在对interruptStart和interruptEnd的调用之间对addInterruptUser/removeInterruptUser进行了任何请求,asynManager延时这些请求到interruptEnd被调用之后。 |
registerTimeStampSource | 注册一个用户定义的时间戳回调函数。 |
unregisterTimeStampSource | 注销任何用户定义的时间戳回调函数并且恢复为在asynManager中的默认时间戳源,它只是调用epicsTimeGetCurrent()。 |
updateTimeStamp | 通过调用默认时间戳源或者用registerTimeStampSource注册的用户定义的时间戳源为这个端口设置当前时间戳。 |
getTimeStamp | 获取这个这个端口的当前时间戳,其由最近一次调用updateTimeStamp返回。 |
setTimeStamp | 直接从传递给这个函数的时间戳值为这个端口设置时间戳。 |
strStatus | 返回一个对应asynStatus值的描述字符串。 |
asynCommon
asynCommon描述了必须被驱动程序实现的方法:
/* Device Interface supported by ALL asyn drivers*/
#define asynCommonType "asynCommon"
typedef struct asynCommon {
void (*report)(void *drvPvt,FILE *fp,int details);
/*following are to connect/disconnect to/from hardware*/
asynStatus (*connect)(void *drvPvt,asynUser *pasynUser);
asynStatus (*disconnect)(void *drvPvt,asynUser *pasynUser);
}asynCommon;
asynCommon
report | 创建一个有关硬件设备的报告。这是其不用必须被queueRequest回调或lockPort/unlockPort调用之间被调用的唯一方法。 |
connect | 连接到硬件设备或通信路径。queueRequest必须指定优先级asynQueuePriorityConnect。 |
disconnect | 从硬件设备或者通信路径断开连接。queueRequest必须指定优先级asynQueuePriorityConnect。 |
asynCommonSyncIO
asynCommonSyncIO为需要对一个aysn设备执行"同步"操作(即:在等待端口可用以及操作结束时阻塞)的软件提供了一个方便的接口。代码不需要处理回调或者了解asynManager和asynCommon接口的详细情况。
typedef struct asynCommonSyncIO {
asynStatus (*connect)(const char *port, int addr,
asynUser **ppasynUser, const char *drvInfo);
asynStatus (*disconnect)(asynUser *pasynUser);
asynStatus (*connectDevice)(asynUser *pasynUser);
asynStatus (*disconnectDevice)(asynUser *pasynUser);
asynStatus (*report)(asynUser *pasynUser, FILE *fd, int details);
} asynCommonSyncIO;
epicsShareExtern asynCommonSyncIO *pasynCommonSyncIO;
注意:这个接口的connect*和disconnect*函数名容易混淆。为了保持与其它SyncIO接口的一致性,connect调用pasyn->connectDevice, disconect调用pasynManager->disconnect, connectDevice调用asynCommon->connect, 而disconnectDevice调用asynCommon->disconnect。
asynDrvUser
asynDrvUser提供方法,允许一个aysnUser和一个端口驱动程序之间传递用户特定的信息。
#define asynDrvUserType "asynDrvUser"
typedef struct asynDrvUser {
/*The following do not have to be called via queueRequest callback*/
asynStatus (*create)(void *drvPvt,asynUser *pasynUser,
const char *drvInfo, const char **pptypeName,size_t *psize);
asynStatus (*getType)(void *drvPvt,asynUser *pasynUser,
const char **pptypeName,size_t *psize);
asynStatus (*destroy)(void *drvPvt,asynUser *pasynUser);
}asynDrvUser;
create | 用户,即,设备支持调用create。驱动程序可以创建它需要的任何资源。它可以使用pasynUser->drvUser提供对这些资源的访问。如果asynUser和驱动程序都知道如何访问这些资源,它们必须在资源名称和大小上达成一致。如果pptypeName不是空,驱动程序可以传一个值给*pptypeName。如果psize不是空,驱动程序可以传一个值给*psize。除非asynUser接收到了它识别的typeName和size,否则它一定不能访问asynUser.drvUser。 |
getType | 如果其它代码,例如interoseInterface想要访问asynUser.drvUser,它必须调用这个方法并且验证typeName和size是它想要的。 |
destroy | 销毁由create创建的资源并且设置asynUser.drvUser为空。 |
asynLockPortNotify
为其是另一个端口驱动程序的一个asynUser的端口驱动程序提供。例如,通过连接到一个标准串行口来执行实际I/O,可以实现一个串行总线驱动。当通过请求者调用lockPort或者一个queueRequest出队时,串行总线端口被锁定时,则串行总线驱动需要锁定相应的串行口。
串行总线驱动注册接口asynLockPortNotify。当串行总线被锁定时,asynManager调用pasynLockPortNotify.lock。串行总线驱动为其连接到的串行口调用asynManager.lockPort。对unlockPort类似。因而当串行总线端口被锁定时,串行总线也被锁定。
asynLockPortNotify仅被asynManager自身使用。它不被放入端口的接口列表中。
asynLockPortNotify是:
#define asynLockPortNotifyType "asynLockPortNotify"
typedef struct asynLockPortNotify {
asynStatus (*lock)(void *drvPvt,asynUser *pasynUser);
asynStatus (*unlock)(void *drvPvt,asynUser *pasynUser);
}asynLockPortNotify;
asynLockPortNotify
lock | 当asynManger.lockPort被调用时被调用。驱动程序通常对其连接的端口调用asynManager.lockPort。 |
unlock | 当调用asynManger.unlockPort被调用时被调用。驱动程序通常对其连接的端口调用asynManager.unlockPort。 |
asynOption
asynOption提供了一种设置驱动程序特定选项的通用方法。例如,串口驱动使用这个来指定波特率等。
#define asynOptionType "asynOption"
/*The following are generic methods to set/get device options*/
typedef struct asynOption {
asynStatus (*setOption)(void *drvPvt, asynUser *pasynUser,
const char *key, const char *val);
asynStatus (*getOption)(void *drvPvt, asynUser *pasynUser,
const char *key, char *val, int sizeval);
}asynOption;
asynOption
setOption | 设置与键相关联的值 |
getOption | 获取与键相关联的值 |
Trace接口
/*asynTrace is implemented by asynManager*/
/*All asynTrace methods can be called from any thread*/
/* traceMask definitions*/
#define ASYN_TRACE_ERROR 0x0001
#define ASYN_TRACEIO_DEVICE 0x0002
#define ASYN_TRACEIO_FILTER 0x0004
#define ASYN_TRACEIO_DRIVER 0x0008
#define ASYN_TRACE_FLOW 0x0010
#define ASYN_TRACE_WARNING 0x0020
/* traceIO mask definitions*/
#define ASYN_TRACEIO_NODATA 0x0000
#define ASYN_TRACEIO_ASCII 0x0001
#define ASYN_TRACEIO_ESCAPE 0x0002
#define ASYN_TRACEIO_HEX 0x0004
/* traceInfo mask definitions*/
#define ASYN_TRACEINFO_TIME 0x0001
#define ASYN_TRACEINFO_PORT 0x0002
#define ASYN_TRACEINFO_SOURCE 0x0004
#define ASYN_TRACEINFO_THREAD 0x0008
/* asynPrint and asynPrintIO are macros that act like
int asynPrintSource(asynUser *pasynUser,int reason, __FILE__, __LINE__, const char *format, ... );
int asynPrintIOSource(asynUser *pasynUser,int reason,
const char *buffer, size_t len, __FILE__, __LINE__, const char *format, ... );
*/
typedef struct asynTrace {
/* lock/unlock are only necessary if caller performs I/O other than */
/* by calling asynTrace methods */
asynStatus (*lock)(asynUser *pasynUser);
asynStatus (*unlock)(asynUser *pasynUser);
asynStatus (*setTraceMask)(asynUser *pasynUser,int mask);
int (*getTraceMask)(asynUser *pasynUser);
asynStatus (*setTraceIOMask)(asynUser *pasynUser,int mask);
int (*getTraceIOMask)(asynUser *pasynUser);
asynStatus (*setTraceInfoMask)(asynUser *pasynUser,int mask);
int (*getTraceInfoMask)(asynUser *pasynUser);
asynStatus (*setTraceFile)(asynUser *pasynUser,FILE *fp);
FILE *(*getTraceFile)(asynUser *pasynUser);
asynStatus (*setTraceIOTruncateSize)(asynUser *pasynUser,size_t size);
size_t (*getTraceIOTruncateSize)(asynUser *pasynUser);
#if defined(__GNUC__) && (__GNUC__ < 3)
/* GCC 2.95 does not allow EPICS_PRINTF_STYLE on function pointers */
int (*print)(asynUser *pasynUser,int reason, const char *pformat, ...);
int (*printSource)(asynUser *pasynUser,int reason, const char *fileName, int line, const char *pformat, ...);
int (*vprint)(asynUser *pasynUser,int reason, const char *pformat, va_list pvar);
int (*vprintSource)(asynUser *pasynUser,int reason, const char *file, int line, const char *pformat, va_list pvar);
int (*printIO)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *pformat, ...);
int (*printIOSource)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *file, int line, const char *pformat, ...);
int (*vprintIO)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *pformat, va_list pvar);
int (*vprintIOSource)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *file, int line, const char *pformat, va_list pvar);
#else
int (*print)(asynUser *pasynUser,int reason, const char *pformat, ...) EPICS_PRINTF_STYLE(3,4);
int (*printSource)(asynUser *pasynUser,int reason, const char *fileName, int line, const char *pformat, ...) EPICS_PRINTF_STYLE(5,6);
int (*vprint)(asynUser *pasynUser,int reason, const char *pformat, va_list pvar) EPICS_PRINTF_STYLE(3,0);
int (*vprintSource)(asynUser *pasynUser,int reason, const char *file, int line, const char *pformat, va_list pvar) EPICS_PRINTF_STYLE(5,0);
int (*printIO)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *pformat, ...) EPICS_PRINTF_STYLE(5,6);
int (*printIOSource)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *file, int line, const char *pformat, ...) EPICS_PRINTF_STYLE(7,8);
int (*vprintIO)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *pformat, va_list pvar) EPICS_PRINTF_STYLE(5,0);
int (*vprintIOSource)(asynUser *pasynUser,int reason,
const char *buffer, size_t len,const char *file, int line, const char *pformat, va_list pvar) EPICS_PRINTF_STYLE(7,0);
#endif
}asynTrace;
epicsShareExtern asynTrace *pasynTrace;
asynTrace
asynDriver提供了一个具有以下特性的trace工具:
1) 对单独设备打开胡关闭跟踪,即:portName, addr
2)对于未连接port或port,addr的asynUsers,trace有一个全局跟踪掩码。
3)输出被发送到一个文件或stdout或errlog
4) 掩码确定能够被显式的信息类型。各种选项可以ORed到一起。在一个端口被创建时,这个掩码的默认值是ASYN_TRACE_ERROR。
- ASYN_TRACE_ERROR:报告运行时操作,例如:超时。
- ASYN_TRACEIO_DEVICE:设备支持报告I/O活动。
- ASYN_TRACEIO_FILTER:在设备支持和底层驱动程序之间的任何层报告其对I/O进行的任何过滤。
- ASYN_TRACEIO_DRIVER:底层驱动程序报告I/O活动。
- ASYN_TRACE_FLOW:报告逻辑流。设备支持应该报告所有队列请求,进入的回调,以及对驱动程序的所有回调。在设备支持和底层驱动之间的层应该报告它们对底层驱动做的所有调用。底层驱动报告它们对其它支持做的调用。
- ASYN_TRACE_WARNING:报告警告,即:在ASYN_TRACE_ERROR和ASYN_TRACE_FLOW之间的情况。
5) 另一个掩码决定如何打印消息缓存。各种选项可以被ORed到一起。在创建一个端口时这个掩码的默认值是ASYN_TRACEIO_NODATA。
- ASYN_TRACEIO_NODATA:不要打印来自消息缓存的任何数据。
- ASYN_TRACEIO_ASCII:用"%"风格格式打印一个"%s"。
- ASYN_TRACEIO_ESCAPE:调用epicsStrPrintEscaped。
- ASYN_TRACEIO_HEX:用"%2.2x"打印每个字节。
6) 另一个掩码决定在每条消息开头打印什么信息。各种选项可以被ORed到一起。在创建一个端口时这个掩码的默认值是ASYN_TRACEINFO_TIME。
- ASYN_TRACEINFO_TIME:打印消息的日期和时间。
- ASYN_TRACEINFO_PORT:打印[port, addr, reason],此处port是端口名,addr是asyn地址,以及reasson是asynUser->reason。这些是在asyn中3段"寻址"信息。
- ASYN_TRACEIO_SOURCE:打印文件名和行号,即:[__FILE__, __LINE__],此处asynPrint或asynPrintIO语句发生的地方。
- ASYN_TRACEIO_THREAD:打印线程名,线程IO和线程优先级,即:[epicsThreadGetNameSelf(), epicsThreadGetIdSelf(), epicsThreadGetPrioritySelf()]。
为了使跟踪功能正常执行;设备支持和所有驱动必须使用这个跟踪功能。设备和驱动支持能够直接调用asynTrace方法。提供了asynPrint和asynPrintIO宏,使得对设备/驱动支持更加简单。支持可以项这样调用:
asynPrint(pasynUser,ASYN_TRACE_FLOW,"%s Calling queueRequest\n", someName);
为发出读或写请求的设备支持或驱动程序设计了asynPrintIO调用。它们像这样进行调研:
asynPrintIO(pasynUser,ASYN_TRACEIO_DRIVER,data,nchars,"%s nchars %d", someName,nchars);
asynManager实现了asynTrace方法。这些方法可以被已经创建了一个asynUser并且连接了一个设备的任何代码使用。所有方法都能被任何线程调用。那是,一个应用程序线程和/或一个portThread。如果一个线程通过对print或printIO的调用执行所有I/O,则它不是必须调用lock或unlock。如果它确实想要进行它自己的I/O,它必须在任何I/O前锁定并且在之后解锁。例如:
pasynTrace->lock(pasynUser);
fd = pasynTrace->getTraceFile(pasynUser);
/*perform I/O to fd */
pasynTrace->unlock(pasynUser);
如果asynUser没有连接大一个端口,即pasynManager->connectDevice还未被调用,则认为是一个"全局"设备。当在connnectDevice前调用asynPrint是有用的。
asynTrace
lock/unlock | 调用asynTrace.print或asynTrace.printIO而不是asynPrint和asynPrintIO的代码才需要这些。当print和printIOz执行它们的操作时,它们都锁定。get方法不阻塞(除了getTraceFile)并且它们是安全的。除了setTraceFile外,set方法不阻塞,由于会发生的最坏情况是用户多一点或少一点的输出。 |
setTraceMask | 设置跟踪掩码。通常由通过一条shell命令或devTrace设备支持请求它的用户设置。为一个端口设置跟踪掩码,也为连接到那个端口的所有设备设置了跟踪掩码 |
getTraceMask | 获取跟踪掩码。想要发出跟踪消息的设备支持调用这个方法查看已经请求了什么跟踪选项。 |
setTraceIOMask | 设置traceIO掩码。通常由通过一条shell命令或者devTrace设备支持请求它的用户设置。为一个端口设置traceIO掩码,也为连接那个端口的所有设备设置了traceIO掩码。 |
getTraceIOMask | 获取traceIO掩码。想要发出它自己IO消息而不是调用asynPrintIO的支持应该调用这个方法并且遵守掩码设置。大部分代码不需要它。 |
setTraceInfoMask | 设置traceInfo掩码。通过由通过一条shelll命令或devTrace设备支持请求它的用户设置。为一个端口设置traceInfo掩码,也为连接那个端口的所有设备设置了traceInfo。 |
getTraceInfoMask | 获取traceInfo掩码。想要发出它自己IO消息而不是调用asynPrint的支持应该调用这个方法并且遵守这个掩码设置。大部分代码不需要它。 |
setTraceFile | 设置输出使用的流。一个NULL参数表示errlog。通常由通过一个shell命令或者通过devTrace设备支持请求它的用户设置。如果当前输出流不是(NULL, stdout, stderr),则在使用新流前关闭当前输出流。 |
getTraceFile | 获取用于输出的文件描述符。想要发出它自己IO消息而不是调用asynPrintIO的设备支持调用这个方法并且遵守这个掩码设定。在此情况中,lock必须首先已经被调用。大部分代码将需要它。如果返回值是0,则输出应该被定向到了errlog。 |
setTraceIO TruncateSize |
确定printIO打印多少数据。在所有情况中,它确定了显式缓存多少个字节。打印的实际字符数目取决于traceIO掩码。例如,ASYN_TRACEIO_HEX导致为每个字节输出3个字符。通常,由通过一条shell命令或devTrace设备支持请求它的用户设置。 |
getTraceIO TruncateSize |
获取当前截短的大小。被asynPrintIO调用。进行它自己I/O的代码也应该支持traceIO掩码。 |
如果reason与当前traceMask ORed一起不为0,则消息被打印。为了后向兼容性提供了这个方法。asynPrint宏现在调用printSource()。 | |
printSource | 如果reason与当前traceMask ORed一起不为0,则消息被打印。大部分代码应该调用asynPrint而不是调用这个方法。这个方法与print()相同,但还有了file和line参数。 |
vprint | 这是与print相同,但使用了一个va_list作为其末尾参数。 |
vprintSource | 这与printSource相同,但使用了一个va_list作为其末尾参数。 |
printIO | 如果reason与当前traceMask ORed一起不为0,则打印消息。如果len>0,则使用traceIO掩码和getTraceIOTruncateSize打印这个缓存。为了后向兼容性提供了这个方法。asynPrintIO宏调用printIOSouce。 |
printIOSouce | 如果reason与当前traceMask ORed一起不为0,则打印消息。如果len>0,则使用traceIO掩码和getTraceIOTruncateSize打印缓存。大部分代码应该调用asynPrintIO而不是调用这个方法。这个方法与printIO相同,但还带有file和line参数。 |
vprintIO | 这与printIO相同,但使用一个va_list作为其末尾参数。 |
vprintIOSource | 这与printIOSource相同,但使用一个va_list作为其末尾参数。 |
标准的基于消息的接口
这些是用于与基于消息设备进行通信的接口,此处基于消息表示设备通过octet字符串,即8位字节的数组,进行通信。提供了三个接口:asynOctet, asynOctetBase和asynOctetSyncIO。asynOctet是一个通用的基于消息的接口。asynOctetBase是一个由实现了asynOctet的端口驱动程序使用的接口。它主要作用是帮助中断支持。asynOctetSyncIO提供了对asynOctet的同步接口,并且可以被愿意阻塞的代码。
asynOctet
asynOctet描述了由使用octet字符串用于发送命令和接收来自一个设备的响应的驱动程序实现的方法。
注意:使用名称octet替代ASCII,因为它隐含了通过8位字节进行通信。
#define ASYN_EOM_CNT 0x0001 /*Request count reached*/
#define ASYN_EOM_EOS 0x0002 /*End of String detected*/
#define ASYN_EOM_END 0x0004 /*End indicator detected*/
typedef void (*interruptCallbackOctet)(void *userPvt, asynUser *pasynUser,
char *data,size_t numchars, int eomReason);
typedef struct asynOctetInterrupt {
asynUser *pasynUser;
int addr;
interruptCallbackOctet callback;
void *userPvt;
}asynOctetInterrupt;
#define asynOctetType "asynOctet"
typedef struct asynOctet{
asynStatus (*write)(void *drvPvt,asynUser *pasynUser,
const char *data,size_t numchars,size_t *nbytesTransfered);
asynStatus (*read)(void *drvPvt,asynUser *pasynUser,
char *data,size_t maxchars,size_t *nbytesTransfered,
int *eomReason);
asynStatus (*flush)(void *drvPvt,asynUser *pasynUser);
asynStatus (*registerInterruptUser)(void *drvPvt,asynUser *pasynUser,
interruptCallbackOctet callback, void *userPvt,
void **registrarPvt);
asynStatus (*cancelInterruptUser)(void *drvPvt, asynUser *pasynUser,
void *registrarPvt);
asynStatus (*setInputEos)(void *drvPvt,asynUser *pasynUser,
const char *eos,int eoslen);
asynStatus (*getInputEos)(void *drvPvt,asynUser *pasynUser,
char *eos, int eossize, int *eoslen);
asynStatus (*setOutputEos)(void *drvPvt,asynUser *pasynUser,
const char *eos,int eoslen);
asynStatus (*getOutputEos)(void *drvPvt,asynUser *pasynUser,
char *eos, int eossize, int *eoslen);
}asynOctet;
/* asynOctetBase does the following:
calls registerInterface for asynOctet.
Implements registerInterruptUser and cancelInterruptUser
Provides default implementations of all methods.
registerInterruptUser and cancelInterruptUser can be called
directly rather than via queueRequest.
*/
#define asynOctetBaseType "asynOctetBase"
typedef struct asynOctetBase {
asynStatus (*initialize)(const char *portName,
asynDriverasynInterface *pasynOctetInterface,
int processEosIn,int processEosOut,int interruptProcess);
void (*callInterruptUsers)(asynUser *pasynUser,void *pasynPvt,
char *data,size_t *nbytesTransfered,int *eomReason);
} asynOctetBase;
epicsShareExtern asynOctetBase *pasynOctetBase;
asynOctet
write | 发送一条消息给设备。*nbytesTransfered是发送到设备的8位字节的数目。插入或驱动代码可以添加字符串末尾终止符到消息,但额外的字符不包含在*nbytesTransfered。 |
read | 从设备读取一条消息。*nbytesTransfered是从设备读取的8位字节数目。如果返回asynSuccess,则eomReason(ASYN_EOM_CNT, ASYN_EOM_EOS和ASYN_EOM_END的某个组合)告诉这个读取为什么结束。插入或驱动代码可以从消息删除字符串末尾终止符。如果它进行了,第一个eos字符将被null替代,并且在nbytesTransfered中将不包含eos字符将。 |
flush | 冲刷输入缓存。 |
registerInterruptUser | 注册一个用户,当接收到一条新消息时,将调用它。注意:回调一定不能阻塞并且一定不能调用registerInterruptUser或cancelInerrruptUser。 |
cancelInterruptUser | 取消一个已注册用户。 |
setInputEos | 为输入设置字符串末尾。例如"\n"。注意:gpib驱动程序通常最多接受一个字符终止符。 |
getInputEos | 获取当前的字符串末尾。 |
getOutputEos | 为输出设置字符串末尾。 |
setOutputEos | 获取当前的字符串末尾。 |
asynOctetBase是一个接口和对实现了接口asynOctet的驱动程序的实现。asynOctetBase实现了registerInterruptUser和cancelInterruptUser。
对于单设备支持,它可以可选地实现中断支持。一个实现了中断的驱动必须调用registerInterruptSource。如果请求asynOctetBase处理中断,当它有新数据时,它调用asynOctetBase:callInterruptUsers。
对于单个设备支持,asynOctetBase可以可选地调用asynInterposeEosConfig为输入和/或输出处理字符串末尾处理。
在这个接口中被传递给initialize地任何null方法被由asynOctetBase提供的方法替代。
一个如何使用asynOctetBase的示例,见asyn/testApp/src/echoDriver.c。
asynOctetBase
intialize | 在asynInterface中的任何null方法被默认实现替代。如果端口不是多设备,并且指定了processEosIn或processEosOut,asynInterposeEosConfig被调用。如果这个端口不是多设备,并且interruptProcess被指定,则当读取被调用时,asynBase调用所有注册的中断用户。如果端口不是一个多设备端口,asynOctetBase不能实现processEosIn, processEosOut和InterruptBase。由于这个方法只在初始化过程中被调用,它可以被直接调用而不是通过queueRequest。 |
callInterruptUsers | 调用通过registerInterruptUser注册的回调。 |
asynOctetSyncIO
asynOctetSyncIO提供一个便捷接口,用于需要对一个asyn设备执行"同步"I/O的软件,即:启动一个I/O操作并且在等待响应时阻塞的设备。代码不需要处理回调或者理解asynManager和asynOctet接口的细节。示例包括运行在它们自己线程中的电机驱动程序,SNL程序以及在此文档中描述的shell命令。
typedef struct asynOctetSyncIO {
asynStatus (*connect)(const char *port, int addr,
asynUser **ppasynUser, const char *drvInfo);
asynStatus (*disconnect)(asynUser *pasynUser);
asynStatus (*write)(asynUser *pasynUser,
char const *buffer, size_t buffer_len,
double timeout,size_t *nbytesTransfered);
asynStatus (*read)(asynUser *pasynUser, char *buffer, size_t buffer_len,
double timeout, size_t *nbytesTransfered,int *eomReason);
asynStatus (*writeRead)(asynUser *pasynUser,
const char *write_buffer, size_t write_buffer_len,
char *read_buffer, size_t read_buffer_len,
double timeout,
size_t *nbytesOut, size_t *nbytesIn, int *eomReason);
asynStatus (*flush)(asynUser *pasynUser);
asynStatus (*setInputEos)(asynUser *pasynUser,
const char *eos,int eoslen);
asynStatus (*getInputEos)(asynUser *pasynUser,
char *eos, int eossize, int *eoslen);
asynStatus (*setOutputEos)(asynUser *pasynUser,
const char *eos,int eoslen);
asynStatus (*getOutputEos)(asynUser *pasynUser,
char *eos, int eossize, int *eoslen);
asynStatus (*writeOnce)(const char *port, int addr,
char const *buffer, size_t buffer_len, double timeout,
size_t *nbytesTransfered, const char *drvInfo);
asynStatus (*readOnce)(const char *port, int addr,
char *buffer, size_t buffer_len, double timeout,
size_t *nbytesTransfered,int *eomReason, const char *drvInfo);
asynStatus (*writeReadOnce)(const char *port, int addr,
const char *write_buffer, size_t write_buffer_len,
char *read_buffer, size_t read_buffer_len,
double timeout,
size_t *nbytesOut, size_t *nbytesIn, int *eomReason,
const char *drvInfo);
asynStatus (*flushOnce)(const char *port, int addr,const char *drvInfo);
asynStatus (*setInputEosOnce)(const char *port, int addr,
const char *eos,int eoslen,const char *drvInfo);
asynStatus (*getInputEosOnce)(const char *port, int addr,
char *eos, int eossize, int *eoslen,const char *drvInfo);
asynStatus (*setOutputEosOnce)(const char *port, int addr,
const char *eos,int eoslen,const char *drvInfo);
asynStatus (*getOutputEosOnce)(const char *port, int addr,
char *eos, int eossize, int *eoslen,const char *drvInfo);
} asynOctetSyncIO;
epicsShareExtern asynOctetSyncIO *pasynOctetSyncIO;
asynOctetSyncIO
connect | 连接到一个asyn端口和地址,返回一个指向asynUser结构体的指针。 |
disconnect | 断开。这释放由connect分配的所有资源。 |
write | 调用asynOctet->write并且等待这个操作结束或超时 |
read | 调用asynOctet->read。等待这个操作结束或超时 |
writeRead | 调用pasynOctet->flush,pasyOctet->write和asynOctet->read。等待操作结束或超时 |
flush | 调用pasynOctet->flush |
setInputEos | 调用pasynOctet->setInputEos |
getInputEos | 调用pasynOctet->getInputEos |
setOutputEos | 调用pasynOctet->setOutputEos |
getOutputEos | 调用pasynOctet->getOutputEos |
writeOnce | 这进行一次连接,写并且断开连接 |
readOnce | 这进行一次连接,读取并且断开连接 |
writeReadOnce | 这进行一次连接,writeRead并且断开连接 |
字符串结尾支持
asynOctet提供用于处理字符串结尾(消息)运行的方法。它不指定策略。设备支持代码,插入层或者底层驱动程序都可以处理EOS运行。一个应用程序开发者必须对各自设备遵守什么策略。策略将由设备,设备支持和驱动程序确定。