学习CANopen --- [7] 使用块(Block)下载

2023-05-16

对于一次传输超过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


# 创建一个网络用来表示CAN总线
network = canopen.Network()

# 添加slave节点,其id是6,对象字典为CANopenSocket.eds
node = canopen.RemoteNode(6, 'test.eds')
network.add_node(node)

# 连接到CAN总线
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 # default 64 seg per one block
        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: # init block download
            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
                # print("total seg num: {}".format(self._blk_dnld_seg_num))

                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)

            # download done
            if self._blk_dnld_received_seg_num == self._blk_dnld_seg_num:
                
                self.blk_dnld_state  = False # reset
                self._blk_dnld_received_seg_num = 0 # reset

                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
            
            # dealing download segments
            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

    # 解析client command specifier
    command, = struct.unpack_from('B', data, 0)
    ccs = command & 0xE0

    if ccs == REQUEST_BLOCK_DOWNLOAD: # 使用自定义的函数处理块下载的ccs
        sdoBlockDldDealer.block_download(data)
    else: # 使用canopen库自带的函数处理其它ccs
        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)

    # 创建一个网络用来表示CAN总线
    network = canopen.Network()

    # 连接到CAN总线
    network.connect(bustype='socketcan', channel='vcan0')

    # 创建slave节点,其id是6,对象字典为CANopenSocket.eds
    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)


    # 向CAN总线上发送启动消息
    node.nmt.send_command(0)

    # 把状态从INITIALISING设置为PRE-OPERATIONAL状态
    node.nmt.state = 'PRE-OPERATIONAL'

    # 发送心跳报文,每隔1s发送一次
    node.nmt.start_heartbeat(5000) # 5000ms


    # 循环
    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(使用前将#替换为@)

学习CANopen --- [7] 使用块(Block)下载 的相关文章

  • CANopen资料收集

    CANopen资料收集 文章目录 CANopen资料收集开源CANopen协议栈1 CanFestival2 CANopenNode 3 canopen stack CIA官网canopen说明 https www can cia org
  • block(块),page(页),buffer cache(块缓冲)区别与联系

    在自己的理解里 块就是用来管理磁盘空间的 就像我们在给一个磁盘建立文件系统时候 我们可以指定block size 而页是针对内存管理 例如从磁盘读出的数据就缓存在内存页中 但突然对关buffer cache block buffer 这些东
  • 面试经典-不被忽略的@property

    我们都知道 property是用来声明属性的 可以保存类的状态或信息 而与其相关的内容 诸如copy weak 深拷贝等 经常会在面试的过程中出现 接下来深入下这些模糊 熟悉的内容 理理顺 内容概要 1 property的本质 2 自动合成
  • Java基础学习之并发篇:手写阻塞队列ArrayBlockingQueue

    学习目标 我们都知道在并发编程中 阻塞队列在多线程中的场景特别有用 比如在生产和消费者模型中 生产者生产数据到队列 队列满时需要阻塞线程 停止生产 消费者消费队列 对队列为空时阻塞线程停止消费 在Java中有提供不同场景的阻塞队列 那么接下
  • Ruby 中的块和过程

    我已经开始学习 Ruby 读了一些教程 甚至买了一本书 Programming Ruby 1 9 The Pragmatic Programmers Guide 我遇到了一些我以前在任何课程中都没有见过的新东西 我知道的其他语言 我是一名
  • Ruby 块采用数组或多个参数

    今天 我惊讶地发现 ruby 会自动查找作为块参数给出的数组的值 例如 foo foo bar bar p foo chars zip bar chars map pair pair first gt f b p foo chars zip
  • 为什么带有 splat 参数的 Ruby 过程/块的行为与方法和 lambda 不同?

    为什么带有 splat 参数的 Ruby 2 0 过程 块的行为与方法和 lambda 不同 def foo ids args p ids end foo 1 2 3 gt 1 2 3 bar lambda do ids args p id
  • 如何在闭包中调用函数

    在模特的课堂上Location 我得到当前城市的名称 var currentLatitude Double var currentLongitude Double var currentLocation String var current
  • 在 Magento 后端添加自定义仪表板“框”而不编辑默认模板的正确方法是什么?

    我正在努力创建一个我希望有一天能成为公开可用的 Magento 扩展的东西 我提到这一部分是因为我在这里做 正确的事 对我来说很重要 我想做的一件事是在默认的 Magento 仪表板中添加一个框 基本上是一个新的 框 与 前 5 个搜索词
  • 在 Ruby 中是否可以显式创建局部变量

    e g x 123 p Proc new x I do not want change the value of the outer x I want to create a local x 在 Ruby 中是否有与 Perl 中的 my
  • 如何在 javascript 中更改 CSS 属性“display”

    如何更改 CSS 属性display 在 JavaScript 中 来自display none to display normal对于这些div hide 0 display none hide 1 display none hide 2
  • 将块元素排列在一条水平线上

    我不知道 要么我不太擅长 搜索 艺术 要么这个话题太简单了 通常没有人问这个问题 但自从我开始我的网站以来 我一直在搜索这个问题 我的网页上只有四个块元素 第一个 Block 元素单独显示在顶部 第二个 第三个和第四个块元素 我想从下一行排
  • 使用RestKit,使用块加载对象,何时以及如何取消请求?

    RKObjectManager sharedManager loadObjectsAtResourcePath app site pattern usingBlock RKObjectLoader loader loader setObje
  • ruby 中不带参数的 DSL 块

    我正在用 ruby 编写一个简单的 dsl 几周前 我偶然发现了一些博客文章 其中展示了如何转换代码 例如 some method argument do book book some method on book book some ot
  • Magento 静态块。去除包装

    当我创建静态块时 magento 将内容包装为 p 标签 这对于 DOM 来说非常糟糕 有可能以某种方式将其删除 我想这是一些 JavaScript 但我不知道是哪一个 其实我之前的回答是错误的 您需要默认关闭静态块所见即所得编辑器 Go
  • 等到动画块完成后再继续

    我正在为我的卡片应用程序实现一些简单的动画 到目前为止 一切都很顺利 但在我可以说它已经完成之前 我还需要修复一个细节 场景非常简单 在 segue 模态地调出新屏幕之前 三张卡必须以动画退出屏幕 到目前为止 他的动画已执行并加载了新视图
  • 完成块?嵌入同步工作流中的异步流程

    潜伏已久 第一次发海报 我对 Objective C 比较陌生 所以如果我问的问题相当简单 我很抱歉 我的谷歌和堆栈溢出让我失望了 所以我想有人可以提供帮助 我有一个同步进程 比如说 连续执行三个函数 称之为 A gt B gt C 其中执
  • 将数据从块传递到视图

    我发现您可以通过将数据设置到 xml 文件来将数据设置到块视图 有没有办法在不访问 xml 文件的情况下设置它 还有一个问题 如何将数据从控制器传递到块 用于将数据从块传递到视图 在块中使用 this gt setVariableName
  • 将clear:both替换为伪类

    以前 当我有浮动块时 我将停止浮动 我使用 div div 但现在 我用伪类解决这个问题 last floating div after content display table clear both 我一直工作得很完美 但今天 不行了
  • 非静态初始化块的替代方案是什么?

    我的项目有一些开发人员喜欢非静态初始化块 是什么选择对此 什么是downside这个替代方案 我猜想 初始化中的值构造函数 为什么我们应该使用非初始化块 首先 在那里将 test 初始化为 new String 是没有意义的 因为初始化块立

随机推荐

  • 缓存行填充与@sun.misc.Contended注解

    1 缓存模型 CPU和主内存之间有好几层缓存 xff0c 因为与cpu的速度相比 xff0c 访问主内存的速度是非常慢的 如果频繁对同一个数据做运算 xff0c 每次都从内存中加载 xff0c 运算完之后再写回到主内存中 xff0c 将会严
  • ThreadLocal那点事

    目录 1 ThreadLocal原理 2 ThreadLocal内存泄漏 3 ThreadLocal最佳实践 4 FastThreadLocal原理 5 FastThreadLocal最佳实践 6 ThreadLocal与FastThrea
  • 关于雪花算法的设计与思考

    2017年的时候项目组在开发一款大区游戏 xff0c 由于之前demo阶段的玩家id都是单服生成的 xff0c 只能保证单进程中的唯一 xff0c 而无法保证在分布式服务器端的唯一性 随着项目的开发进展 xff0c 需要设计能保证在分布式的
  • java反射之Method的invoke方法实现

    在框架中经常会会用到method invoke 方法 xff0c 用来执行某个的对象的目标方法 以前写代码用到反射时 xff0c 总是获取先获取Method xff0c 然后传入对应的Class实例对象执行方法 然而前段时间研究invoke
  • A*寻路算法之解决路径多拐点问题

    1 问题描述 最近公司正在开发的游戏涉及到了寻路算法 xff0c 然后我从网上找了一份A 算法代码 xff0c 整理了一下写了一个A 算法基础实现 然而 xff0c 在真正实用时A 寻路时 xff0c 却发现了几个问题 xff1a 基础实现
  • 代理模式与委托模式的异同点

    在 设计模式之禅 xff08 第二版 xff09 中 xff0c 作者说 代理模式也叫做委托模式 xff0c 显然是认为代理模式和委托模式是毫无差别的 然而在实际开发中 xff0c 我们通常可以很明确的知道一个模式究竟是代理模式还是委托模式
  • TCP/IP编程之select函数详解

    前述 xff1a linux下的I O复用模型目前很多都已经不用select函数了 xff0c 而是用epoll xff0c 但是为什么还需要了解select编程呢 xff0c 其实是从两个方面考虑的 xff1a 一是为了通过select去
  • 堆栈的详细解释

    一 在c中分为这几个存储区 1 栈 由编译器自动分配释放 2 堆 一般由程序员分配释放 xff0c 若程序员不释放 xff0c 程序结束时可能由OS回收 3 全局区 xff08 静态区 xff09 xff0c 全局变量和静态变量的存储是放在
  • Gmapping、hector、Cartographer三种激光SLAM算法简单对比

    文章目录 一 Gmapping是基于粒子滤波的算法 二 Hector SLAM三 Cartographer 一 Gmapping是基于粒子滤波的算法 缺点 xff1a 严重依赖里程计 xff0c 无法适应无人机及地面不平坦的区域 xff0c
  • TCP连接建立过程

    TCP连接建立过程 浏览器访问网站 xff0c 通过域名解析找到ip地址后会与服务器端建立连接 其中TCP xff08 Transmission Control Protocol xff0c 传输控制协议 xff09 是一种面向连接的 可靠
  • 海康威视错误代码文档大全【完整版】

    简介 本文收录了海康各大设备错误码 xff0c 按ctrl 43 f查询 xff1b 包含网络通讯库错误码 阵列错误码 安全激活相关错误码 智能设备错误码 RTSP通讯库错误码 软解码库错误码 转封装库错误码 语音对讲库错误码 Qos流控库
  • lighttpd http响应报文(Response)增加安全头Referrer-Policy和X-Permitted-Cross-Domain-Policies方法

    X Permitted Cross Domain Policies和Referrer Policy说明 X Permitted Cross Domain Policies X Permitted Cross Domain Policies
  • ROS使用ARUCO识别二维码获取位置信息做定位使用

    使用ARUCO识别二维码获取位置信息 1 安装软件 cd catkin ws src git clone b kinetic devel https github com pal robotics aruco ros cd catkin m
  • Keil 编译时无法找到头文件解决

    Keil 编译时无法找到头文件解决方法 1 背景 Keil 编译的时候无法找到头文件 xff0c 搜了下相关问题及解决方法 xff0c 有介绍说是因为文件夹中有数字 xff0c 无法搜到头文件 xff0c 进行了更改 xff0c 还是找不到
  • 学习open62541 --- [71] Alarm and Condition

    本文讲述Alarm and Condition的用法 xff0c 主要以源码里提供的例子为基础进行讲解和演示 xff0c 即tutorial server alarms conditions c xff0c 该例子写的有点乱 xff0c 本
  • 学习open62541 ---[68] 使用Wireshark观察通信消息

    Wireshark是强大的网络协议分析工具 xff0c 而open62541也是基于socket的 xff0c 所以也可以用其来观察OPCUA通信消息 一 安装Wireshark 去https www wireshark org 去下载并安
  • UART、IIC、SPI、CAN通信的区别与应用

    文章目录 1 通信的基本知识1 1 数据通信的种类1 1 1 串行通信1 1 2 并行通信1 1 3 总结 1 2 数据通信的传输方向1 2 1 单工1 2 2 半双工1 2 3 全双工1 2 4 总结 1 3 数据通信的方式1 3 1 同
  • 学习open62541 --- [69] Client监测多个变量值

    有读者问Client如何监测多个变量值 xff0c 这篇文章给了提示 xff0c 但是没给例子 xff0c 本文给出详细例子 xff0c 使用的open62541版本是1 3 3 xff0c 运行环境debian10 5 一 准备Serve
  • 学习CANopen --- [6] 自定义对象字典

    在前面几篇文章中 xff0c 我们运行例子时都需要一个eds文件 xff0c 比较麻烦 xff0c 本文讲述如何在代码中自定义对象字典 xff0c 只添加自己需要的OD项 如果是作为master来使用 xff0c 还是比较方便的 自定义对象
  • 学习CANopen --- [7] 使用块(Block)下载

    对于一次传输超过4字节的情形 xff0c SDO可以使用Segment传输或者Block传输 xff0c Segment传输在第6篇文章中已经介绍 xff0c 本文讲解Block传输中的下载情况 一 与Segment传输的比较 相比于Seg