对于一次传输超过4字节的情形,SDO可以使用Segment传输或者Block传输,Segment传输在第6篇文章中已经介绍,本文讲解Block传输中的下载情况。
一 与Segment传输的比较
相比于Segment传输,Block传输可以大幅缩短下载时间,因为其规定传输多个Segment之后才需要一次应答,而Segment传输需要对每个Segment都进行应答。
一个Block由一次传输的多个Segment组成,Block大小就是指一个Block里包含的Segment数量,最大值是127,最小值是1。注意:Block大小不是传输的字节数量,但是可以换算成字节数,即Block大小乘以7。
二 使canopen库支持Block下载
python的这个canopen库在SDO Server这边暂时不支持Block下载,在SDO Client端支持,本人仔细阅读源码后,发现可以通过对SDO Server的SDO回调函数进行修改,这样就能支持Block下载了。
源码
SDO Client源码如下,使用Block下载对0x100A进行修改,
import time, os
import canopen
network = canopen.Network()
node = canopen.RemoteNode(6, 'test.eds')
network.add_node(node)
network.connect(bustype='socketcan', channel='vcan0')
FileSize = os.path.getsize('random.bin')
infile = open('random.bin', 'rb')
outfile = node.sdo.open(0x100A, mode='wb', block_transfer=True, size=FileSize)
data = infile.read()
outfile.write(data)
time.sleep(1)
infile.close()
outfile.close()
time.sleep(1)
network.disconnect()
代码里的random.bin是通过dd命令生成的,大小是128字节,生成命令如下,
time dd if=/dev/urandom of=./random.bin bs=1 count=128
重点是SDO Server源码,如下,这里把Block大小设置为4,即一次传输4*7=28个字节,
import signal
import canopen
import struct
from canopen.sdo.constants import *
running = True
def sigint_handler(signum, frame):
global running
print('')
running = False
exit(0)
class SDOBlockDownloadDealer(object):
def __init__(self, network, tx_cobid, blockSize=64):
self.network = network
self.tx_cobid = tx_cobid
self._index = None
self._subindex = None
self.blk_dnld_state = False
self._blk_size = blockSize
self._blk_dnld_seg_num = 0
self._blk_dnld_received_seg_num = 0
def send_response(self, response):
self.network.send_message(self.tx_cobid, response)
def abort(self, abort_code=0x08000000):
"""Abort current transfer."""
data = struct.pack("<BHBL", RESPONSE_ABORTED,
self._index, self._subindex, abort_code)
self.send_response(data)
def block_download(self, data):
if self.blk_dnld_state == False:
cmd, index, subindex = SDO_STRUCT.unpack_from(data)
if cmd & (REQUEST_BLOCK_DOWNLOAD | INITIATE_BLOCK_TRANSFER) > 0:
self._index = index
self._subindex = subindex
self.blk_dnld_state = True
_, totalSize = struct.unpack_from("<LL", data)
self._blk_dnld_seg_num = totalSize // 7
if totalSize % 7:
self._blk_dnld_seg_num += 1
res_command = 0xA0
response = bytearray(8)
SDO_STRUCT.pack_into(response, 0, res_command, index, subindex)
response[4] = self._blk_size
self.send_response(response)
else:
command, = struct.unpack_from("B", data, 0)
if self._blk_dnld_received_seg_num == self._blk_dnld_seg_num:
self.blk_dnld_state = False
self._blk_dnld_received_seg_num = 0
if command & (REQUEST_BLOCK_DOWNLOAD | END_BLOCK_TRANSFER):
response = bytearray(8)
response[0] = RESPONSE_BLOCK_DOWNLOAD | END_BLOCK_TRANSFER
self.send_response(response)
else:
self.abort(0x05040001)
return
moreSeg = command & NO_MORE_BLOCKS
segNum = command & 0x7F
if moreSeg:
self._blk_dnld_received_seg_num += segNum
response = bytearray(8)
response[0] = RESPONSE_BLOCK_DOWNLOAD | BLOCK_TRANSFER_RESPONSE
response[1] = segNum
response[2] = self._blk_size
self.send_response(response)
elif segNum == self._blk_size:
self._blk_dnld_received_seg_num += segNum
response = bytearray(8)
response[0] = RESPONSE_BLOCK_DOWNLOAD | BLOCK_TRANSFER_RESPONSE
response[1] = self._blk_size
response[2] = self._blk_size
self.send_response(response)
def on_request_supportBlockDownload(can_id, data, timestamp):
global node
global sdoBlockDldDealer
if sdoBlockDldDealer.blk_dnld_state:
sdoBlockDldDealer.block_download(data)
return
command, = struct.unpack_from('B', data, 0)
ccs = command & 0xE0
if ccs == REQUEST_BLOCK_DOWNLOAD:
sdoBlockDldDealer.block_download(data)
else:
node.sdo.on_request(can_id, data, timestamp)
if __name__ == "__main__":
signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGHUP, sigint_handler)
signal.signal(signal.SIGTERM, sigint_handler)
network = canopen.Network()
network.connect(bustype='socketcan', channel='vcan0')
node = network.create_node(6, 'test.eds')
rx_cobid = node.sdo.rx_cobid
network.unsubscribe(rx_cobid, node.sdo.on_request)
network.subscribe(rx_cobid, on_request_supportBlockDownload)
sdoBlockDldDealer = SDOBlockDownloadDealer(network, node.sdo.tx_cobid)
node.nmt.send_command(0)
node.nmt.state = 'PRE-OPERATIONAL'
node.nmt.start_heartbeat(5000)
while running:
pass
代码是在运行时修改SDO的回调函数,不用修改库的源码。
三 测试及分析
先运行SDO Server,然后运行SDO Client,最后CAN报文如下,
这里先简要总结下传输情况:需要传输128个字节,SDO Server支持的Block大小是6,即6*7=42个字节
下面进行具体分析
1. 发起Block下载
绿框中的CAN报文是发起Block下载,首先分析其command specifier,即CS值,其定义如下图,
SDO Client这边发送的是0xC6,即cc和s都为1,cc用于表示Client这边是否支持传输数据的crc检查,为1表示支持,为0表示不支持;s表示是否指示要传输的总的字节数,为1就会指示,然后在这条报文中的最后4个字节里放入总的字节数,本例子要传输128个字节,就是0x80,与报文一致
SDO Server回应的是A0,即sc为0,sc用于表示Slave是否支持传输数据的crc检查,为1表示支持,为0表示不支持,这里不支持。
2. 下载Block数据
橙色框里正式下载数据,即Download Block Segment,其CS定义如下,每个橙色框是一次Block传输,
Block中每一次Segment的第一个字节都是CS,bit0~6组成的值表示该Segment的Sequence number,bit7表示本次Segment传输后是否还有更多Segment需要传输,0表示还有,1表示没有了。
CS之后的7个字节就是实际传输的数据
一次Block传输结束后会有一次Server的的应答,其值为0xA2,其后面跟着7个字节,其中第一个字节表示:该次Block传输最后一次Segment传输的Sequence number,如果为0则表示没有收到sequence 1;第二个字节表示下一次Block传输使用的Block size,即6
由于一个Block传输6*7=42字节,对于128个字节就要传输4个Block,前3个是完整的,最后一个Block只传输2个字节,其报文如下
Client的CS是0x81,即c为1,表示后续没有segment需要传输了,其sequence number是1
Server回应的字节里,CS依然为A2,和之前分析一致,后面数据的第一个字节是0x01,即该次Block传输最后一次的Segment的sequence number是1
3. 结束Block下载
最后的蓝色框是结束块下载,即End Block Download,其CS定义如下,
n表示上次Block中最后一次Segment中不包含数据的字节数,由于最后一次Segment只传输了2个字节,那么7个字节中有5个字节不包含数据,组合后就是1101 0101b,即0xD5,和实际报文一致
Server回应的值就是0xA1
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)