作为DSO Client想要对SDO Server进行写操作(调用_writeNetworkDict):
1、根据nodeID找出OD中对应的SDO Client Parameter的序号CliNbr(即index-0x1280)。
2、再在SDO transfers[]中判断作为SDO_CLIENT的CliNbr是否已经存在(GetSDOClientFromNodeId),即是否存在对同一个Server的通信,没有则继续。
3、在SDO transfers[]中选择一个没有被使用的传输线(getSDOfreeLine得到line序号)并保存whoami(SDO_CLIENT or SDO_SERVER),再初始化此传输线(initSDOline),将状态(SDO_DOWNLOAD_IN_PROGRESS或SDO_BLOCK_DOWNLOAD_IN_PROGRESS)和CliNbr以及索引等保存其中。传输量较大的分配内存到传输线(dynamicData)中,再复制全部数据到此传输线中。
4、构建SDO帧信息(8字节部分),并将Callback函数指针赋值给此传输线的回调函数指针,用于传输结束时调用。
5、发送SDO帧(根据SDO_CLIENT和CliNbr确定COB_ID部分),启动SDO传输。
注:函数writeNetworkDictCallBackAI是用于没有找到对应nodeID的SDO Client Parameter的传输,利用SDO_CLIENT中ServerID为0的SDO Client Parameter来赋值替代。
对于SDO写操作的结果可以通过getWriteResultNetworkDict来查看,在执行时先判断是否存在对应的Client以及其SDOline是否在使用,若是直接判断传输线的状态(transfers[line].state)是否为SDO_FINISHED,若不是,返回当前的状态;若是,复位传输线(resetSDOline)并返回SDO_FINISHED。
当需要结束SDO传输时(SDO传输完成或者超时),调用closeSDOtransfer,根据nodeID找到对应的在使用的传输线,并复位传输线。
利用SDO传输文件
Server端准备工作:传输一个文件至少需要传输三个部分,即文件名、文件大小以及文件内容,由于CanOpen协议本身是没有传输文件的规定的,所以需要在CanOpen协议基础上实现文件的传输。
这里在OD对象字典上定义三个变量,分别保存传输文件的三个部分(如上所述)。文件名利用visible_string类型存储,为避免动态分配内存,这里定义32字节的数组,也就是说文件名的最大长度为31字节(最后一个为结束符),所以文件名固定传输32个字节。文件大小用uint32类型存储。文件内容用domain类型存储,其大小和存储区域在传输完文件大小后动态分配。
这里至少FileSize和FileBuffer需要回调函数,在其值发生改变时调用。远程Client传输文件前需要先传输文件的大小,在FileSize的回调函数中,分配需要尺寸的内存,并初始化FileBuffer变量的相关参数(如类型、大小、指针)。注:其实FileBuffer定义的数组并没有利用,使用动态分配的存储空间。
这里FileName的回调函数并没有利用。
当FileBuffer 的回调函数调用时说明文件传输已经完成,可以对文件进行存储。注意由于这个函数是在_setODentry中调用的,也就是在canDispatch()中调用,而canDispatch()调用时已经关中断(定时器中断)了,同时在这个回调函数中对文件系统经行打开读写操作可能会导致故障(我这里发现写的时候系统自动复位),所以FileBuffer的回调函数应该尽量快速完成,可以设置一个标志位,这里置位,在某个线程(Task)中,轮询此标志并进行处理。处理完成后需要释放(free)分配的内存空间,并初始化FileBuffer索引相关的变量。
Client端准备工作:对于Server端的存储文件的三个变量索引必须知道(最好固定下来),在获得文件大小,文件名,文件内容后,调用WriteSDOFile(自己实现)启动文件发送。
这里首先发送的是文件大小,同时确定文件大小传输完成后的回调函数,在其回调函数启动发送文件名,在发送文件名的回调函数中启动发送文件内容,在发送文件内容的回调函数中释放分配的内存。
这里在开始就已经将文件名和文件大小以及文件内容的指针用全局变量保存下来。
发送文件后记:在SDO发送数据时会先将要发送的数据保存在SDO传输线中(小尺寸保存在transfers[line].data中,若尺寸过大则先动态分配内存(transfers[line].dynamicData),将所有要传输的数据复制到其中),原数据的内存可以释放掉。而在接收方,SDO在initiate download阶段只是在传输线中保存以后传送文件的大小,并没有分配内存,在之后接收数据时先保存在transfers[line].data[SDO_MAX_LENGTH_TRANSFER]中,当接收到的数据量大于SDO_MAX_LENGTH_TRANSFER(32)时再一次动态分配SDO_DYNAMIC_BUFFER_ALLOCATION_SIZE(128*1024)量的内存,并将之前收到的数据复制到其中,之后的接收数据直接保存到新分配的内存中。当接收的数据量再次大于SDO_DYNAMIC_BUFFER_ALLOCATION_SIZE时,会重新分配再加SDO_DYNAMIC_BUFFER_ALLOCATION_SIZE的内存量,也就是说每次超过分配的数据量时会再加SDO_DYNAMIC_BUFFER_ALLOCATION_SIZE大小的内存量。当数据接收完成之后,再将数据复制到对象字典中。
后记二:当SDO CLIENT发送或者接收超时,会在SDOTimeoutAlarm()中发送SDO ABORT给与该SDO通信的节点。同时置通信状态transfers[line].state为SDO_ABORTED_INTERNAL,并且置transfers[line].abortCode为SDOABT_TIMED_OUT,之后再调用此SDO传输线的CallBack函数transfers[line].Callback(d, nodeId),在这个函数中用户可以进行相应的处理。若传输成功,则置通信状态为transfers[line].state = SDO_FINISHED,并调用CallBack函数。若SDO CLIENT接收到SDO ABORT帧,会置通信状态为transfers[line].state = SDO_ABORTED_RCV,并且transfers[line].abortCode置为接收的错误代码,并调用transfers[line].Callback函数。
注意:在处理SDO帧(proceedSDO)时,由于自身原因导致的错误(如toggle位错误,通信阶段错误,line存取错误等),若自身为SDO_SERVER,则只resetSDOline并发送SDO ABORT帧;若自身为SDO_CLIENT,则先停掉该line的定时器,并置状态和错误代码,再发送SDO ABORT帧。都没有调用CallBack函数。
综上可知,当SDO Client发起读或者写远程OD时,可以初始化传输线(line)时设置CallBack函数,并发起传输;SDO Server接收到此初始SDO传输时选择一个空闲的SDO line并初始化,之后开始传输。传输过程中若SDO Server出现错误,则调用failedSDO复位传输线(line)并发送SDO ABORT帧,Client接收到此帧则停止定时器、置状态和错误代码并调用CallBack函数。若传输过程中SDO Client出现错误,则调用failedSDO停止定时器、置状态并发送SDO ABORT帧,没有复位传输线,也没有调用CallBack函数(需要应用检查传输状态并结束传输线,认为可以加上callback调用),Server接收到此帧则复位传输线。若Client规定时间内没有接收到Server响应,则置状态发送SDO ABORT帧,并调用CallBack函数,若此函数没有复位(或关闭)传输线,则还会复位此传输线。
后记三:对于OD对象字典的参数变量的回调函数,在完成处理后要返回OD_SUCCESSFUL,若返回其他非零值,会当做错误代码发送至与其通信的远端节点。注:没有写返回值的,编译器可能会随即返回某个值导致发送错误(Keil出现过)。