文章目录
背景
- 目前很多CANOpen介绍的文章比较繁琐,讲很多历史由来,虽然更方便读者了解原委,但对于快速上手是不合适的。
- 本文简单直接,默认大家都熟悉CAN协议,在此基础上快速对CANOpen协议进行学习。
- 本文介绍了为什么CAN通信需要终端负载电阻。
协议介绍
CAN总线协议
接线方式
-
CAN是差分总线通信,包括H和L两根线,当二者电压相同时称为隐性表示数据1,电压不同时称为显性表示数据0。空闲状态时,总线上为隐性,只要有一个节点表现为显性,总线电平就会被拉到显性。
-
其接线方式如下图所示:可以看到,H线和L线不是完全无连接的,而是通过终端负载电阻连接在了一起。
-
我们直观的感觉,差分通信,只需要发送端和接收端的H线和L线分别对应连接,然后检测二者压差即可。 为什么需要一个终端负载电阻将H线和L线连接起来呢?
-
假设没终端负载电阻,则连接方式如下:H接H,L接L。
-
现在我们需要检测压差,我们只需要将H和L线上电压送入比较器,比较高低即可,这是没有问题的。
-
我们看CAN芯片原理图发现,当输出显性电平时,Q1和Q2打开,H线电压为VCC,L线电压为GND;当输出隐形电平时,Q1和Q2截止,此时该芯片对总线上输出为开漏状态,这也保证了输出隐形电平的节点不会影响别的节点输出显性电平。
-
因此当所有节点都处于空闲状态时,每个节点都输出为开漏状态,而不是输出一个固定的驱动电压。此时H线和L线上电压是不确定的,二者也不一定相等,因此无法满足空闲状态时H线和L线电压相等的需求。
-
并且每个节点在对总线上电压进行检测时,都是尽量地小电流进行取样,这也导致H线上高电平时的电量不容易被消耗掉,即H线上电压下降缓慢,限制了通信速率。
-
因此我们在H和L间加上 120 Ω 120 \Omega 120Ω 电阻,使得H和L线组成一个回路,不仅保证了隐性状态时H和L线上电压相等,还使得H线上的电压可以快速从高电平切换到低电平,并且当存在外界电磁干扰时能量也可以被电阻消耗掉,一举多得。
通信协议
- CAN协议在通信时,采用数据帧的格式进行通信。帧格式有数据帧、遥控帧等类型,最常见的是数据帧。其格式如下:
- 在节点A向B发数据时,首先A发送0帧起始位SOF,表示自己要发送数据了。当数据发送完毕时,发送连续的7位1代表帧结束标志EOF。因此,节点的ID不能设置为高7位全是1,否则会被当做帧结束标志直接结束传输。
- 在CAN网络中,每个消息都有自己的地址,称为ID(注意:ID并不是说节点的地址,而是消息的ID,具体可以看附录内容)。ID不仅用于标识不同的消息内容,还牵扯到优先级,当多个节点同时发起SOF请求时,ID值越小的优先级越高,具体竞争原理参考本文后部分。
- 早期的CAN数据帧的ID为11位,后来不够用扩展到了29位,因此前者称为标准数据帧,后者称为扩展数据帧,其差别只有仲裁段的ID长度不同。
- 发送完ID之后,发送RTR位,用于分辨该帧是数据帧(RTR为0)还是遥控帧(RTR为1)。对于后来才提出来的扩展帧,由于想兼容老的标准帧格式,因此也发送数据帧和遥控帧标志,该位记为SRR,用于实现和RTR一样的功能。
- 扩展帧发送完SRR位之后,会多发一位IDE位,用于表示该帧是标准帧还是扩展帧。接收端不知道要接收的帧是标准帧还是扩展帧,在接收完11位ID和1位的RTR之后,会认为进入标准帧的控制段了,标准帧控制段第一位是IDE,如果该位为0,则认为是标准帧没错,后面跟着的就是r0保留位和数据长度标识DLC(4位)。
- 而如果接收到IDE位是1,接收端就知道接收的帧不是标准帧,而是扩展帧,接着就会再接收18位的扩展ID,接着再接收RTR,然后是2个保留位和4个DLC位。
- 在控制段接收完之后,开始接收数据段。数据段最长是8个字节。
- 接着就接收CRC校验段。CRC校验段会对从帧起始位到数据位的数据进行校验,结果为15位的CRC值,为了界定出CRC值所在地方,会在15位的CRC后追加1位CRC界定符逻辑1,最终一起组成16位的CRC校验位。
- 接着就是ACK段。ACK段时,发送方发送隐形电平1,如果接收方前面的信息接收正确,则需要在此期间发送线性电平0。这期间称为ACK槽,然后是1个隐形电平的ACK分界符。
- 最后,是帧结束标志,由7个隐形电平1组成,帧通信完成。
CANOpen协议介绍
CANOpen诞生背景
- 上述的CAN协议,定义了物理层的高低电平,也定义了数据链路层的帧结构,而直接通过总线连在了一起也不需要传输层和网络层,因此对于应用CAN协议时,只需要制定应用层协议就可以了。
- 由于不同家有不同的CAN应用层协议,而近年来的需求则是不同家的CAN设备可以有一个统一的协议,向USB那样接入就可以使用,因此诞生了CANOpen。
- CANopen是一种基于CAN(Controller Area Network,控制器局域网络)总线的高层协议,主要用于嵌入式系统的网络化通信。它最初由CiA(CAN in Automation)组织开发,广泛应用于工业自动化、医疗设备、建筑自动化和其他嵌入式系统领域。
- CANOpen主要通过对象字典和配置文件实现协议的通用性。
CANOpen的COB_ID
- CANOpen给把原本CAN协议的11位ID分为两部分使用,选其中7位作为节点的NODE_ID(因此网络最多有127个节点),剩下4位用作表示帧不同的功能码,用来表示是SDO、PDO还是其他的。如下:
- 由于CAN协议可以设置过滤器,只过滤出ID为某些特定的位,因此在网络中可以实现一个节点发送的某个信息,只送达给指定的节点,而其他节点则不会收到。
- 由于只有4位作为功能码,因此CANOpen网络中最多只能存在4个PDO。
CANOpen的NMT
-
每个节点都有自己的状态,上电后,节点处于初始化状态;初始化完成之后,进入可操作态;如下:
/** * Internal network state of the CANopen node */ typedef enum { CO_NMT_UNKNOWN = -1, /**< -1, Device state is unknown (for heartbeat consumer) */ CO_NMT_INITIALIZING = 0, /**< 0, Device is initializing */ CO_NMT_PRE_OPERATIONAL = 127, /**< 127, Device is in pre-operational state */ CO_NMT_OPERATIONAL = 5, /**< 5, Device is in operational state */ CO_NMT_STOPPED = 4 /**< 4, Device is stopped */ } CO_NMT_internalState_t;
代码对应的逻辑:
-
CANOpen网络中,存在一个可以发送NMT命令的节点为主机。主机可以控制节点进入复位状态。
-
NMT对应的功能ID为0000,当给所有节点发送NMT命令时,对应的COB_ID为000_0000,因此NMT指令对应的CAN_ID为11位的0。
-
NMT的指令码在CAN帧的数据段,如下。
-
例如:主机要发送NMT控制NODE_ID为10的节点复位,则发送出去的CAN帧如下:CAN_ID为000表示NMT信息,NMT的指令码为0X82,表示复位连接;目标NODE_ID为0X0A,表示对象是10的节点复位。
CANOpen的对象字典
- 对象字典(Object Dictionary)是CANopen协议中一个关键的概念。它是一个数据结构,用于描述和存储设备的所有通信和配置参数。每个CANopen设备都有一个独立的对象字典,在支持CANOpen的设备上,通过修改对象字典的参数,就可以对该设备进行配置,类似于常见的传感器中寄存器。
- 对象字典中的条目通过16位索引和8位子索引进行标识。如下是一个对象字典的实例:
Index | Sub-Index | Name | Type | Value
-------------------------------------------------------------
1000h | 0 | Device Type | Unsigned32 | 0x00000001
1001h | 0 | Error Register | Unsigned8 | 0x00
1018h | 0 | Identity Object | |
| 1 | Vendor ID | Unsigned32 | 0x00000123
| 2 | Product Code | Unsigned32 | 0x00004567
| 3 | Revision Number | Unsigned32 | 0x00000001
| 4 | Serial Number | Unsigned32 | 0x00000000
- Index是索引,由2个字节组成。Sub-Index是子类型,由1个字节组成。
- 在上述实例中,索引1000h是设备类型。
- 索引1018h是身份对象,包含的子类型有:厂商ID、产品代码、版本号和序列号。
- 对象字典可以根据自己的设备需要进行自定义。CANopen协议定义了一组所有设备上标准化的对象字典条目,用于常见的设备功能。这些标准条目确保了不同设备之间的互操作性。以下是一些常见的标准对象字典条目:
0x1000-0x1FFF:通信配置参数
0x1000:设备类型
0x1001:错误寄存器
0x1018:身份对象(包含厂商ID、产品代码等)
0x2000-0x5FFF:应用对象
0x2000-0x27FF:设备配置参数
0x2800-0x28FF:输入PDO映射
0x2900-0x29FF:输出PDO映射
- 除了标准条目,用户可以根据具体应用需求定义自定义对象字典条目。这些条目通常放在标准范围之外,以避免冲突。例如:
0x6000-0x9FFF:用户自定义参数
0x6000:自定义传感器数据
0x7000:自定义控制参数
- 有一些通用的对象字典,可以见附录。
- 在CANOpen进行通信传输时,由专门的通信对象来访问和传输对象字典中的数据。通信对象包括服务数据对象(SDO)和过程数据对象(PDO)。
CANOpen的服务数据对象(SDO)
- SDO用于节点间非实时信息的发送和接收。比如主机去读节点0X0A的对象字典中0X1000寄存器的数据,过程如下:
- SDO的帧格式如下图,按照帧格式分析上面的读取过程:主机发送帧ID为60A,其中高4位为功能码1100,表示R_SDO,即需要节点接收的SDO帧。低7位0A表示NODE_ID,即需要0A节点接收。接着是8个字节的数据段。第一个字节是command,查下表,40表示该SDO是用于请求数据返回的。接着2个字节Index L和Index H,即0X1000,表示想返回的对象字典索引是0X1000。接着是1个字节的子索引Sub-index,00表示子索引为0。接着4个字节是数据,由于没有数据上发,因此4个字节全为0X00。
- 来自0A节点的响应SDO,帧ID为58A,其中高4位表示功能码1011,表示该帧为T_SDO,即来自节点发送的SDO。低7位0A表示NODE_ID,即节点0A发送的SDO。接着是8个字节的数据段。第一个字节表示command,43表示返回4字节数据。接着2个字节Index L和Index H,即0X1000,表示返回的对象字典索引是0X1000。接着是1个字节的子索引Sub-index,00表示子索引为0。接着4个字节是数据。
- 当写入参数时,过程和上面类似,只是数据段内第一个字节的command由读取的40变成了23,如下写入0X1001的过程:
- 可以看到,返回的帧中的command为80,表示SDO错误。具体的错误索引为1001,子索引为00,错误码为后面的4个字节:06 01 00 02,可以按照下述链接中看错误码意义:https://microcontrol-umic.github.io/CANopen-Master-Library/group__SDO__ERR.html
- 可以发现我们的错误是尝试写入一个只读寄存器。
参考
附录问题
CAN总线竞争原理
- 在节点发送自己的ID时,会一边输出自己的ID到总线上,一边测量总线上的电平是不是自己输出的电平。如果不是,则认为此时有其他的节点也在竞争,且ID优先级比自己高,自己就立即停止发送。
- 过程如下:
- 当ID相同时,会继续比较RTR位。由于数据帧的RTR位为0,遥控帧的RTR位为1,因此ID相同的数据帧优先级高于遥控帧。
在CAN协议中,帧中的ID是发送者的ID还是接收者的ID?
- 在CAN(Controller Area Network)协议中,帧中的ID(标识符)既不是发送者的ID,也不是接收者的ID。
- 相反,ID表示的是消息的内容和优先级。CAN协议采用基于消息的通信方式,而不是基于节点的通信方式。
- ID用于标识消息的内容。每个消息类型都有一个唯一的ID,不同ID的消息表示不同的内容。例如,一个ID可以表示发动机转速,另一个ID可以表示温度传感器数据。
通用的对象字典地址:
- 如下: