0、提醒
CANOpen使用时,需要清楚什么是大端和小端,这对于CANOpen数据发送及解析时,有很大的帮助。且学习开发CANOpen时,需要具备一定的CAN基础。
1、CANOpen协议介绍
①、什么是CANOpen协议
CANOpen协议是一种架构在控制局域网络(Controller Area Network, CAN)上的高层通信协议,它广泛应用于工业自动化、机械工程和汽车电子等领域。CANOpen协议通过对象字典、服务数据对象(SDO)、过程数据对象(PDO)等机制,为机器人、运动控制、过程控制、楼宇自动化、交通运输等行业提供了一种标准化的通信解决方案。
CANOpen协议的标准由非营利组织CiA(CAN in Automation)进行起草、审核及维护工作。
CANOpen协议本身是一个单一的标准,它定义了设备间的通信协议和接口规范。
CANOpen协议支持从10kbps到1Mbps的波特率,具体包括10k、20k、50k、125k、250k、500k、800k和1Mbps。
简单来说,CANOpen协议是一个基于CAN协议的的一个应用层协议。
②、CANOpen协议的特性
开放性:CANOpen是一个公开的标准,任何公司都可以使用它来开发产品,无需支付许可费用。
网络管理:CANOpen协议定义了网络管理功能,如节点自诊断、网络监控等,使得网络维护更加方便。
对象字典:CANOpen使用对象字典来描述设备的功能和状态,使得设备之间的通信更加标准化。
服务:CANOpen定义了一系列的服务,如NMT(Network Management)、TPDO(Time-Triggered Protocol Data Object)、RPDO(Real-Time Protocol Data Object)等,以满足不同的应用需求。
PDO(Process Data Object):PDO是CANOpen中用于快速传输过程数据的数据对象,可以实时地传输控制和状态信息。
SDO(Service Data Object):SDO用于访问设备的对象字典,可以读取或写入设备的状态和配置信息。
紧急消息:CANOpen还定义了紧急消息,用于在发生严重故障时立即通知网络中的其他设备。
心跳消息:心跳消息用于监测网络中设备的运行状态,如果一段时间内没有收到某个设备的心跳消息,网络管理系统会认为该设备已经离线。
CANOpen常用缩写
③、CANOpen协议应用场景
在CANOpen协议中,对于不同类型的CANopen 设备和应用场景分别有不同的子协议,如CiA301、CiA401、CiA402、CiA421、CiA423、CiA424、CiA426、CiA430、CiA433等众多子协议构成了CANopen协议。
2、CANOpen字典
CANOpen字典(Object Dictionary, OD)是CANopen协议的核心组成部分,它是一个有序的对象组,用于描述CANopen节点的所有参数,包括通讯数据的存放位置。
CANOpen字典的结构由索引和子索引组成:
索引:每个对象采用一个16位的索引值来寻址,范围在0x0000到0xFFFF之间。
子索引:在某些索引下,为了访问数据结构中的单个元素,定义了一个8位的子索引,范围是0x00到0xFF之间。
CANOpen的SDO模式下的数据传输时,也是按如下格式进行发送和接收。其中字典中的部分索引可能没有子索引。
3、CANOpen通信
CANOpen通信有两种模式,分别是SDO通信、和PDO通信,如下图所示为CANOpen通信的设备模型图。
①、SDO通信
SDO 全称为服务数据对象,其通过对象索引和子索引与CANOpen字典建立联系,通过SDO可以读取对象数据,在允许的情况下,也可以修改写入对象数据。
SDO 传输方式遵循客户端—服务器模式(CS),即一应一答方式。由 CAN 总线网络中的 SDO 客户端发起,SDO 服务器作出应答。
SDO 传输报文由COB-ID和数据段组成,数据段采用小端模式,即低位在前,高位在后排列。所有的 SDO 报文数据段都必须是 8 个字节。
SDO发送数据的COB-ID都是0x600+节点ID
SDO接收数据的COB-ID都是0x580+节点ID
SDO读数据报文格式
SDO写数据报文格式
②、PDO通信
PDO 全称为 过程数据对象, 其用来传输实时的数据,是 CANopen 中最主要的数据传输方式。通过SDO配置PDO后,可以实现设备端的数据周期性自动上传到服务器,也可以实现控制发送的数据小于8字节,由于PDO发送的数据无返回,因此可以极大的提升数据传输速度。
按照接收与发送的不同,PDO又分为TPDO和RPDO,其中RPDO用于服务器给设备端发送控制数据,TPDO为设备端周期性上传数据到服务器。
PDO 的传输遵循的是生产者消费者模型,即 CAN 总线网络中生产者产生的 TPDO 可根据COB-ID 由网络上一个或者多个消费者 RPDO 接收。每个PDO由通信参数和映射参数共同决定最终传输的方式及内容。
如下是所使用的电机控制器中实现 PDO 的传输的4 个 RPDO 和4 个 TPDO。注意,不同控制器的PDO数量可能不一致,需要根据实际产品进行手册参考。
PDO使用时,需要进行映射配置操作,如下所示为PDO映射操作的流程图。
因PDO映射内容较多,因此将在下一篇博客中详细介绍PDO映射操作。
3、CANOpen应用
①、设置CiA402模式
发送:601 2B 02 20 01 00 00 00 00 (设置为 CiA402 模式)
//2B 02 20 01 00 00 00 00 (CiA402 模式)
#define WRITE_2_BYTE 0x2B
#define WRITE_60_CTRL_PARAM 0x2002
#define CTRL_MODE_SELECT 0x01
#define CIA402_MODE 0x00
//将数据按规定填充到CAN的8字节数据区
char data[8] = { 0 };
data[0] = WRITE_2_BYTE;
data[1] = WRITE_60_CTRL_PARAM & 0xFF;
data[2] = WRITE_60_CTRL_PARAM >> 8 & 0xFF;
data[3] = CTRL_MODE_SELECT;
data[4] = CIA402_MODE;
serve_motor_send_data(0x601, data);
//成功会返回数据:60 02 20 01 00 00 00 00
②、循环同步位置模式
发送:601 2F 60 60 00 08 00 00 00 (循环同步位置模式)
//2F 60 60 00 08 00 00 00 (循环同步位置模式)
#define WRITE_1_BYTE 0x2F
#define WRITE_60_RUN_MODE 0x6060
#define CYCLIC_SYNC_POS_MODE 0x08
//将数据按规定填充到CAN的8字节数据区
char data[8] = {0};
data[0] = WRITE_1_BYTE;
data[1] = WRITE_60_RUN_MODE & 0xFF;
data[2] = WRITE_60_RUN_MODE >> 8 & 0xFF;
data[3] = 0x00;
data[4] = CYCLIC_SYNC_POS_MODE;
serve_motor_send_data(0x601, data);
//成功会返回数据: 60 60 60 00 00 00 00 00
③、电机准备
发送: 601 2B 40 60 00 06 00 00 00 (设置 6040h为 0x6, 使电机准备)
//2B 40 60 00 06 00 00 00
#define WRITE_2_BYTE 0x2B
#define WRITE_60_CTRL_STATE 0x6040
#define MOTOR_READY 0x06
//将数据按规定填充到CAN的8字节数据区
char data[8] = { 0 };
data[0] = WRITE_2_BYTE;
data[1] = WRITE_60_CTRL_STATE & 0xFF;
data[2] = WRITE_60_CTRL_STATE >> 8 & 0xFF;
data[3] = 0x00;
data[4] = MOTOR_READY;
servo_motor_send_data(0x601, data);
//正常会返回数据:60 40 60 00 00 00 00 00
③、电机锁轴使能
发送: 601 2B 40 60 00 0F 00 00 00 (设置 6040h为 0xF, 使电机使能)
//2B 40 60 00 0F 00 00 00
#define WRITE_2_BYTE 0x2B
#define WRITE_60_CTRL_STATE 0x6040
#define MOTOR_ENABLE 0x7F
//将数据按规定填充到CAN的8字节数据区
char data[8] = { 0 };
data[0] = WRITE_2_BYTE;
data[1] = WRITE_60_CTRL_STATE & 0xFF;
data[2] = WRITE_60_CTRL_STATE >> 8 & 0xFF;
data[3] = 0x00;
data[4] = MOTOR_ENABLE;
servo_motor_send_data(0x601, data);
//正常会返回数据:60 40 60 00 00 00 00 00
④、电机松轴失能
发送: 601 2B 40 60 00 07 00 00 00 (设置 6040h为 0x7, 使电机失能)
//2B 40 60 00 07 00 00 00
#define WRITE_2_BYTE 0x2B
#define WRITE_60_CTRL_STATE 0x6040
#define MOTOR_DISABLE 0x07
//将数据按规定填充到CAN的8字节数据区
char data[8] = { 0 };
data[0] = WRITE_2_BYTE;
data[1] = WRITE_60_CTRL_STATE & 0xFF;
data[2] = WRITE_60_CTRL_STATE >> 8 & 0xFF;
data[3] = 0x00;
data[4] = MOTOR_DISABLE;
servo_motor_send_data(0x601, data);
//正常会返回数据:60 40 60 00 00 00 00 00
⑤、设置电机目标位置
发送: 601 23 7A 60 00 10 27 00 00 (设置目标位置 607Ah为 10000)
//目标位置 : 23 7A 60 00 10 27 00 00 (设置 607Ah为 10000)
#define WRITE_4_BYTE 0x23
#define WRITE_60_POSITION 0x607A
int pos = 90;//90度
int pos_pulse = (int)(pos * 10000.0 / 360);//数值单位转换(角度值数据转换为脉冲)
char data[8] = { 0 };
//将数据按规定填充到CAN的8字节数据区
data[0] = WRITE_4_BYTE;
data[1] = WRITE_60_POSITION & 0xFF;
data[2] = WRITE_60_POSITION >> 8 & 0xFF;
data[3] = 0x00;
data[4] = pos_pulse & 0xFF;
data[5] = pos_pulse >> 8 & 0xFF;
data[6] = pos_pulse >> 16 & 0xFF;
data[7] = pos_pulse >> 24 & 0xFF;
servo_motor_send_data(0x601, data);
//正常会返回到数据
//例如:60 7A 60 00 00 00 00 00
⑥、读取电机实际位置
发送:601 40 63 60 00 00 00 00 00
#define READ_60_CMD 0x40
#define READ_60_POSITION 0x6063
//将数据按规定填充到CAN的8字节数据区
char data[8] = { 0 };
data[0] = READ_60_CMD;
data[1] = READ_60_POSITION & 0xFF;
data[2] = READ_60_POSITION >> 8 & 0xFF;
servo_motor_send_data(0x601, data);
//正常会返回到读取到的位置数据
//例如:43 63 60 00 F1 2D C0 01
⑦、读取电机实际速度
发送:601 40 6C 60 00 00 00 00 00
//40 6C 60 00 00 00 00 00
#define READ_60_CMD 0x40
#define READ_60_SPEED 0x606C
//将数据按规定填充到CAN的8字节数据区
char data[8] = { 0 };
data[0] = READ_60_CMD;
data[1] = READ_60_SPEED & 0xFF;
data[2] = READ_60_SPEED >> 8 & 0xFF;
servo_motor_send_data(0x601, data);
//正常会返回到读取到的速度数据
//例如:43 6C 60 00 7C F6 FF FF
⑧、读取电机实际电流
发送:601 40 78 60 00 00 00 00 00
//40 78 60 00 00 00 00 00
#define READ_60_CMD 0x40
#define READ_60_CURRENT 0x6078
//将数据按规定填充到CAN的8字节数据区
char data[8] = { 0 };
data[0] = READ_60_CMD;
data[1] = READ_60_CURRENT & 0xFF;
data[2] = READ_60_CURRENT >> 8 & 0xFF;
servo_motor_send_data(0x601, data);
//正常会返回到读取到的电流数据
//例如:4B 78 60 00 FC FF 00 00