CanOpen通信协议python实现

1. CANOpen——在ISO层级中位置和诞生

CAN(Controller Area Network)现场总线仅仅定义了第1层(物理层,见ISO11898-2标准)、第2层(数据链路层,见ISO11898-1标准),而在实际设计中,这两层完全由硬件实现,设计人员无需再为此开发相关软件(Software)或固件(Firmware),只要了解如何调用相关的接口和寄存器,即可完成对CAN的控制。
在这里插入图片描述
但CAN没有规定应用层。也就是没有规定与实际应用相关的逻辑,比如开关量输入输出,模拟量输入输出。所以本身对于应用来说,是不完整的。这就像铁矿石(物理层)冶炼成铁锭(数据链路层),然后针对具体应用,再加工做成汽车、轮船、钢筋、坦克、钢结构建筑等等。CANOPEN协议是基于CAN总线协议建立的应用层协议。
在这里插入图片描述
CANopen协议是在20世纪90年代末,由总部位于德国纽伦堡的CiA组织——CAN-in-Automation,(http://www.can-cia.org )在CAL(CAN Application Layer)的基础上发展而来。
由于CANopen协议的创始人团队也是CAN-bus的创始人团队,此协议充分发挥了CAN-bus所具备的所有优势,特别是CiA组织的主席蔡豪格(Holger Zeltwanger)先生对于CANopen协议坚持开放、免费、非盈利的原则。一经推出便在欧洲得到了广泛的认可与应用。时至今日已经成为全世界最为流行的CAN应用层协议。让我们记住这位可爱的德国老人。
在这里插入图片描述

2、CANOpen——协议帧

在这里插入图片描述
CANOPEN协议的基本通信单元叫做“通信对象”。常用的通信对象有NMT、SYNC、EMERGENCY、TIME STAMP、SDO、PDO这几个,他们结构相同,包括funciton Code、Node-ID、DLC(数据长度)、DATA(数据)四部分构成,本质上都是通过封装CAN总线协议的数据帧实现的。他们的不同体现在DATA这个部分,有的对象DATA部分可以完全用来传输数据,有的对象针对DATA部分进一步做了划分和要求。报文传输采用CAN标准帧格式。即11bit的ID域,以尽量减小传输时间;
在这里插入图片描述

3、CANOpen——对象

在这里插入图片描述
CANopen总线协议也分为两种基本的数据传输机制:通过进程数据对象(PDO)对小型的数据进行高速数据交换以及通过服务数据对象(SDO)对对象字典进行访问。后者主要用于在设备配置过程中传输参数以及传输大数据块。

4、CANOpen——网络管理NMT(Network management)

一个CANopen网络中为了保证可靠、可控,必须要NMT网络管理,就像一个军队一样,要令行禁止,才能达到稳定、高效的目标。如图 6.1所示。指挥员(NMT主机,CANOPEN主站)通过发号施令,士兵(NMT从机)进行自由俯卧训练,这样整个训练都是有序的。

在这里插入图片描述
所以每个CANopen从节点的CANopen协议栈中,必须具备NMT管理的相应代码,这是节点具备CANopen协议的最基本的要素。

5、CANOpen——NMT节点状态

在这里插入图片描述
在这里插入图片描述

6、CANOpen——NMT节点上线报文

任何一个CANopen从站上线后,为了提示主站它已经加入网络(便于热插拔),或者避免与其他从站Node-ID冲突。这个从站必须发出节点上线报文(boot-up),如图所示,节点上线报文的ID为700h+Node-ID,数据为1个字节0。生产者为CANopen从站。 在这里插入图片描述

7、CANOpen——NMT节点状态与心跳报文

为了监控CANopen节点是否在线与目前的节点状态。CANopen应用中通常都要求在线上电的从站定时发送状态报文(心跳报文),以便于主站确认从站是否故障、是否脱离网络。
如图所示,为心跳报文发送的格式,CANID与节点上线报文相同为700h+Node-ID,数据为1个字节,代表节点目前的状态,04h为停止状态,05h为操作状态,7Fh为预操作状态。
CANopen从站按其对象字典中1017h中填写的心跳生产时间(ms)进行心跳报文的发送,而CANopen主站(NMT主站)则会按其1016h中填写的心跳消费时间进行检查,假设超过诺干次心跳消费时间没有收到从站的心跳报文,则认为从站已经离线或者损坏。 在这里插入图片描述

8、CANOpen——NMT节点状态切换命令

NMT网络管理中,最核心的就是NMT节点状态切换命令,这是NMT主站所进行网络管理的“命令”报文。使用者必须牢记这些命令。
CANID均为000h,具备最高的CAN优先级。数据为2个字节:
 第1个字节代表命令类型:
01h为启动命令(让节点进入操作状态);
02h为停止命令(让节点进入停止状态);
80h为进入预操作状态(让节点进入预操作状态);
81h为复位节点应用层(让节点的应用恢复初始状态,比如列车门都恢复打开状态);
82h为复位节点通讯(让节点的CAN和CANopen通讯重新初始化,一般用于总线收到干扰,导致节点总线错误被动,或者总线关闭时)。
 第二个字节代表被控制的节点Node-ID,
如果要对整个网络所有节点同时进行控制,则这个数值为0即可。 在这里插入图片描述

9、CANOpen——PDO对象(Process data object)

PDO对象称为“过程数据对象”,用于无连接的数据传输,即A站发送数据给B站后,不需要等待B站给出确认收到的应答。当然B站也可以应答一些信息给A站,这个有点像网络通信中的UDP协议,即应答不是强制要求的,B站可以回答,也可以不回答。
PDO对象的DATA部分可以完全用来传输数据,没有进步做要求。一个PDO最大可传输8字节数据。
在这里插入图片描述
PDO分为TPDO(发送PDO)和(接收RPDO),发送和接收是以CANopen节点自身为参考(如果CAN主站或者其他从站就相反)。TPDO和RPDO分别有4个数据对象,每种数据对象就是1条CAN报文封装。

10、CANOpen——SDO对象(Service data object)

SDO称“服务数据对象”,主要用于CANopen主站对从节点的参数配置,是一个有应答的通信对象,即主站发送RSDO给从站后,从站如果接收到,必须发送TSDO给主站进行应答。

在这里插入图片描述

SDO的通讯原则非常单一,发送方(客户端)发送CAN-ID为600h+Node-ID的报文,其中Node-ID为接收方(服务器)的节点地址,数据长度均为8字节; 接收方(服务器)成功接收后,回应CAN-ID为580h+Node-ID的报文。这里的Node-ID依然是接收方(服务器)的节点地址,数据长度均为8字节。

在这里插入图片描述
11、CANOpen——快速SDO协议(Expedited SDO protocol)

最常用最常见的SDO协议是快速SDO,所谓快速,就是1次来回就搞定。前提是读取和写入的值不能大于32位。如图所示,为快速SDO协议的示意图。命令中直接包含了要读写的索引、子索引、数据。可谓直接命中。快速SDO的难点在于CS命令符的记忆。
通过快速SDO,可以直接对CANopen节点的对象字典中的值进行读取和修改,所以在做参数配置之外,也经常作为关键性数据传输之用。比如CANopen控制机器人的电机转动角度时,就使用SDO来传输,保证可靠到达。

在这里插入图片描述

12、CANOpen——特殊协议(Special protocols)

一.同步协议(Sync protocol)
同步(SYNC),该报文对象主要实现整个网络的同步传输。
在同步协议中,有2个约束条件:
 同步窗口时间:索引1007h约束了同步帧发送后,从节点发送PDO的时效,即在这个时间内发送的PDO才有效,超过时间的PDO将被丢弃;
 通讯循环周期:索引1006h规定了同步帧的循环周期。

二.时间戳协议(Time-stamp protocol)
时间标记对象(Time Stamp),NMT主机发送自身的时钟,为网络各个节点提供公共的时间参考,即网络对时。
三.紧急报文协议(Emergency protocol)
紧急事件对象(Emergency),是当设备内部发生错误,触发该对象,发送设备内部错误代码,提示NMT主站。紧急报文属于诊断性报文,一般不会影响CANopen通讯.

13、CANOpen——对象字典OD(Object dictionary)

CANopen对象字典(OD: Object Dictionary)是CANopen协议最为核心的概念。所谓的对象字典就是一个有序的对象组,描述了对应CANopen节点的所有参数,包括通讯数据的存放位置也列入其索引,这个表变成可以传递形式就叫做EDS文件(电子数据文档Electronic Data Sheet)

每个对象采用一个16位的索引值来寻址,这个索引值通常被称为索引,其范围在0x0000到0xFFFF之间。为了避免数据大量时无索引可分配,所以在某些索引下也定义了一个8 位的索引值,这个索引值通常被称为子索引,其范围是0x00到0xFF之间。 每个索引内具体的参数,最大用32位的变量来表示,即Unsigned32,四个字节。 

在这里插入图片描述

14、CANOpen——通讯对象子协议区(Communication profile area)

通讯对象子协议区(Communication profile area)定义了所有和通信有关的对象参数,如表 5.2所示,标绿色底纹的索引范围1000h to 1029h为通用通讯对象,所有CANopen节点都必须具备这些索引,否则将无法加入CANopen网络。其他索引根据实际情况进行分配与定义。
在这里插入图片描述

15、CANOpen——通用通讯对象(General communication objects)

由于通用通讯对象十分重要,NMT主站(CANopen主站)在启动时,通常都全部或者部分读取所有从站中通用通讯对象中的索引,所以所有的通用通讯对象都必须在CANopen从站中实现,使用者也必须熟知这些索引地址与其含义。

在这里插入图片描述
16、CANOpen——子协议
一、制造商特定子协议(Manufacturer-specific Profile)
对象字典索引2000h to 5FFFh为制造商特定子协议,通常是存放所应用子协议的应用数据。而上文所描述的通讯对象子协议区(Communication profile area)是存放这些应用数据的通信参数。比如广州致远电子的XGate-COP10从站模块规定了:
 RPDO的通讯参数存放在1400h to 15FFh ,映射参数存放在1600h to 17FFh ,数据存放为2000h 之后厂商自定义区;
 TPDO的通讯参数存放在1800h to 19FFh ,映射参数存放在1A00h to 1BFFh ,数据存放为2000h 之后厂商自定义区。
对于在设备子协议中未定义的特殊功能,制造商也可以在此区域根据需求定义对象字典对象。因此这个区域对于不同的厂商来说,相同的对象字典项其定义不一定相同。
二. 标准化设备子协议(Standardized profile area)
标准化设备子协议,为各种行业不同类型的标准设备定义对象字典中的对象。目前已有十几种为不同类型的设备定义的子协议,例如 DS401、DS402、DS406等,其索引值范围为0x6000~0x9FFF。同样,这个区域对于不同的标准化设备子协议来说,相同的对象字典项其定义不一定相同。

17、一些为python调试代码

from ctypes import *
if 1:
    dll = windll.LoadLibrary('./ControlCAN.dll')
    nDeviceType = 21 #* USBCAN-2E-U *
    nDeviceInd = 0#* 索引号0 *
    nReserved = 0
    nCANInd = 1 #can通道号

    class _VCI_INIT_CONFIG(Structure):
        _fields_ = [("AccCode", c_ulong), ("AccMask", c_ulong), ("Reserved", c_ulong), ("Filter", c_ubyte),
                    ("Timing0", c_ubyte), ("Timing1", c_ubyte), ("Mode", c_ubyte)]
    class _VCI_CAN_OBJ(Structure):
        _fields_ = [("ID", c_uint), ("TimeStamp", c_uint), ("TimeFlag", c_ubyte), ("SendType", c_ubyte),
                    ("RemoteFlag", c_ubyte), ("ExternFlag", c_ubyte), ("DataLen", c_ubyte), ("Data", c_ubyte*8),
                    ("Reserved", c_ubyte*3)]

    vic = _VCI_INIT_CONFIG()
    vic.AccCode = 0x00000000
    vic.AccMask = 0xffffffff
    vic.Filter = 0
    vic.Timing0 = 0x00
    vic.Timing1 = 0x1c
    vic.Mode = 0

    vco = _VCI_CAN_OBJ()
    vco.ID = 0x00000001
    vco.SendType = 0
    vco.RemoteFlag = 0
    vco.ExternFlag = 0
    vco.DataLen = 8
    vco.Data = (0, 0, 0, 0, 0, 0, 0, 0)

    vco2 = _VCI_CAN_OBJ()
    #ubyte_array8 = c_ubyte*8
    #ubyte_array3 = c_ubyte*3
    vco2.ID = 0x00000001
    vco2.SendType = 0
    vco2.RemoteFlag = 0
    vco2.ExternFlag = 0
    vco2.DataLen = 8
    vco2.Data = (0, 0, 0, 0, 0, 0, 0, 0)

    ret = dll.VCI_OpenDevice(nDeviceType, nDeviceInd, nReserved)
    print("opendevice:",ret)
    ret = dll.VCI_SetReference(nDeviceType, nDeviceInd, nCANInd, 0, pointer(c_int(0x060007)))
    print("setrefernce1:",ret)
    ret = dll.VCI_SetReference(nDeviceType, nDeviceInd, 0, 0, pointer(c_int(0x060007)))
    print("setrefernce0:",ret)  #注意,SetRefernce必须在InitCan之前
    ret = dll.VCI_InitCAN(nDeviceType, nDeviceInd, nCANInd, pointer(vic))
    print("initcan1:",ret)
    ret = dll.VCI_InitCAN(nDeviceType, nDeviceInd, 0, pointer(vic))
    print("initcan0:",ret)
    ret = dll.VCI_StartCAN(nDeviceType, nDeviceInd, nCANInd)
    print("startcan1:",ret)
    ret = dll.VCI_StartCAN(nDeviceType, nDeviceInd, 0)
    print("startcan0:",ret)
rx_n = 0
while 1:
    ret = dll.VCI_Receive(nDeviceType, nDeviceInd, 0, pointer(vco2), 1, -1)# 发送1帧
    if ret > 0:
        rx_n +=1
        show = 'RXD n: {}\t ID:  {}\t len:  {}\t data:{}'.format(rx_n,hex(vco2.ID), vco2.DataLen, [hex(i) for i in list(vco2.Data[0:vco2.DataLen])])
        print(show)
        # ret = dll.VCI_Transmit(nDeviceType, nDeviceInd, 0, pointer(vco2), 1)  # 发送两帧
        # print("transmit:", ret)
        # ret = 0
        if vco2.ID&0xf80 == 0x600 and vco2.DataLen==8 and (vco2.Data[0]==0x23 or vco2.Data[0] == 0x2f or vco2.Data[0] == 0x2b or vco2.Data[0] == 0x27):
            vco.ID = 0x580+vco2.ID&0x07f
            Data = [0,0,0,0,0,0,0,0]
            Data[0] = 0x60
            Data[1] = vco2.Data[1]
            Data[2] = vco2.Data[2]
            Data[3] = vco2.Data[3]
            vco.Data = tuple(Data)
            show = 'TXD n: {}\t ID:  {}\t len:  {}\t data:{}'.format(rx_n, hex(vco.ID), vco.DataLen,[hex(i) for i in list(vco.Data[0:vco.DataLen])])
            print(show)
            ret = dll.VCI_Transmit(nDeviceType, nDeviceInd, 0, pointer(vco), 1)  # 发送两帧
            print("transmit:", ret)
from ctypes import *

dll = windll.LoadLibrary('./ControlCAN.dll')
nDeviceType = 21 #* USBCAN-2E-U *
nDeviceInd = 0#* 索引号0 *
nReserved = 0
nCANInd = 1 #can通道号

class _VCI_INIT_CONFIG(Structure):
    _fields_ = [("AccCode", c_ulong), ("AccMask", c_ulong), ("Reserved", c_ulong), ("Filter", c_ubyte),
                ("Timing0", c_ubyte), ("Timing1", c_ubyte), ("Mode", c_ubyte)]
class _VCI_CAN_OBJ(Structure):
    _fields_ = [("ID", c_uint), ("TimeStamp", c_uint), ("TimeFlag", c_ubyte), ("SendType", c_ubyte),
                ("RemoteFlag", c_ubyte), ("ExternFlag", c_ubyte), ("DataLen", c_ubyte), ("Data", c_ubyte*8),
                ("Reserved", c_ubyte*3)]
#ubyte_array8 = c_ubyte*8
#ubyte_array3 = c_ubyte*3
vic = _VCI_INIT_CONFIG()
vic.AccCode = 0x00000000
vic.AccMask = 0xffffffff
vic.Filter = 0
vic.Timing0 = 0x00
vic.Timing1 = 0x1c
vic.Mode = 0

vco = _VCI_CAN_OBJ()
vco.ID = 0x00000001
vco.SendType = 0
vco.RemoteFlag = 0
vco.ExternFlag = 0
vco.DataLen = 8
vco.Data = (1, 2, 3, 4, 5, 6, 7, 8)

ret = dll.VCI_OpenDevice(nDeviceType, nDeviceInd, nReserved)
print("opendevice:",ret)
ret = dll.VCI_SetReference(nDeviceType, nDeviceInd, nCANInd, 0, pointer(c_int(0x060007)))
print("setrefernce0:",ret)  #注意,SetRefernce必须在InitCan之前
ret = dll.VCI_InitCAN(nDeviceType, nDeviceInd, nCANInd, pointer(vic))
print("initcan0:",ret)
ret = dll.VCI_StartCAN(nDeviceType, nDeviceInd, nCANInd)
print("startcan0:",ret)
while 1:
    # print("transmit:",ret)
    ret = dll.VCI_Receive(nDeviceType, nDeviceInd, nCANInd, pointer(vco), 1, -1)#
    if ret > 0:
        show = 'ID:{},len:{},data:{}'.format(hex(vco.ID),vco.DataLen,vco.Data)
        print(show)
        ret = dll.VCI_Transmit(nDeviceType, nDeviceInd, nCANInd, pointer(vco), 1)  #























  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿Q学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值