CANOPEN(DS402)学习记录

📅 2026/8/20 17:29:48
CANOPEN(DS402)学习记录
2026/8/19芯片为STM32F103RC移植了CANfestival使用HAL库。都是照葫芦画瓢没有理解是什么原理所以在测试时想要记录一下输出才能学会。有任何不对的地方敬请指出以便及时修正非常感谢。1、RPDO上面将0x6040映射到TPDO1是不对的0x6040是控制字是主站发送给从站的命令应该映射到RPDO不过现在测试也无所谓同时我现在是直接修改对象字典来配置的在实际使用中应该每一次开机时配置或是对象字典设计初配置好/* index 0x1400 : Receive PDO 1 Parameter. */ UNS8 Ds402_Slave_highestSubIndex_obj1400 5; /* number of subindex - 1*/ UNS32 Ds402_Slave_obj1400_COB_ID_used_by_PDO 0x00000200; /* 2147484160 */ UNS8 Ds402_Slave_obj1400_Transmission_Type 0xFF; /* 254 */ UNS16 Ds402_Slave_obj1400_Inhibit_Time 0x0; /* 0 */ UNS8 Ds402_Slave_obj1400_Compatibility_Entry 0x0; /* 0 */ UNS16 Ds402_Slave_obj1400_Event_Timer 0x0; /* 0 */ subindex Ds402_Slave_Index1400[] { { RO, uint8, sizeof (UNS8), (void*)Ds402_Slave_highestSubIndex_obj1400, NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1400_COB_ID_used_by_PDO, NULL }, { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_obj1400_Transmission_Type, NULL }, { RW, uint16, sizeof (UNS16), (void*)Ds402_Slave_obj1400_Inhibit_Time, NULL }, { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_obj1400_Compatibility_Entry, NULL }, { RW, uint16, sizeof (UNS16), (void*)Ds402_Slave_obj1400_Event_Timer, NULL } }; ...... /* index 0x1600 : Receive PDO 1 Mapping. */ UNS8 Ds402_Slave_highestSubIndex_obj1600 1; /* number of subindex - 1*/ UNS32 Ds402_Slave_obj1600[] { 0x60400010, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0 /* 0 */ }; subindex Ds402_Slave_Index1600[] { { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_highestSubIndex_obj1600, NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[0], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[1], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[2], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[3], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[4], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[5], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[6], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[7], NULL } };上面的对象字典代码配置了RPDO10x1400是RPDO1的参数配置0x1600是RPDO1的映射配置将0x6040控制字映射到RPDO1的前两个字节。参数配置1.子索引UNS8 Ds402_Slave_highestSubIndex_obj1400 5; // 子索引数量为6个(0-5) // 索引0x1400有6个子项这是RPDO1的完整参数集2.COB-IDUNS32 Ds402_Slave_obj1400_COB_ID_used_by_PDO 0x00000200; // 这是一个32位值 // 位31有效位(0有效, 1无效) // 位30数据帧(0数据帧, 1远程帧) // 位29标准帧(0标准帧1扩展帧) // 位28保留必须写0 // 位27-12扩展帧的CAN-ID 高位标准帧写0 // 位11-0CAN-ID 0x200 NodeID3.传输类型UNS8 Ds402_Slave_obj1400_Transmission_Type 0xFF; // 0x00 保留 // 1-240 同步收到N次SYNC后处理 // 0xFF / 0xFE -- 异步 PDO // 对于RPDO表示一收到这条CAN帧立刻处理4.禁止时间UNS16 Ds402_Slave_obj1400_Inhibit_Time 0x0; // 只对TPDO与意义 // RPDO一般设为05.兼容性字段UNS8 Ds402_Slave_obj1400_Compatibility_Entry 0x0; //永远写06.事件定时器UNS16 Ds402_Slave_obj1400_Event_Timer 0x0; // 只对TPDO与意义 // RPDO一般设为0映射配置1.映射数量UNS8 Ds402_Slave_highestSubIndex_obj1600 1; // 有1个映射条目2.映射索引项结构UNS32 Ds402_Slave_obj1600[] { 0x60400010, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0 /* 0 */ }; 条目解析(索引、子索引、长度) 映射条目10x60400010 索引: 0x6040 (控制字 Controlword) 子索引: 0x00 长度: 0x10 (16位, 2字节)3.映射索引接口subindex Ds402_Slave_Index1600[] { { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_highestSubIndex_obj1600, NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[0], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[1], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[2], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[3], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[4], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[5], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[6], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1600[7], NULL } }; // 子索引0映射条目数量 // 子索引1-8具体的映射条目配置完成效果上面配置的效果是主站只要向 从机CAN ID 0x200node-id发送 2 字节数据对应从站的0x6040(Controlword)就会被自动更新。下面为回调函数示例代码//回调函数接口 switch(idx_tbl-index) //这个就是数据字典的索引 { case 0x6040: printf(set 0x6040 %x\r\n,cmd_data); break; case 0x605D: printf(set 0x605D %x\r\n,cmd_data); break; case 0x6060: printf(set 0x6060 %x\r\n,cmd_data); break; default: break; }解释上图中的帧结构主机发送的帧COB-ID:0x201(0x200node-id)DLC8(实际只消费前两个字节消费多少字节由索引数据类型决定0x6040数据类型UNS16即两字节)DATA0F 00 00 00 00 00 00 00注意1.由于CAN帧的数据段最大只有8字节因此在确定映射时需要保证映射条目的字节和8字节。因此每个RPDO实际只能映射最多8字节的索引2.此时只映射了0x6040(2字节)且排在第一条目前两个字节就是发送给条目1的数据同时因为DATA为小端序转换后将0x000F写入0x6040(2字节)。3.需要注意映射条目必须连续定义不能存在空条目。主机发送的帧DLC必须大于映射总长度。修改配置测试下面修改RPDO1新增0x605D(2字节)0x60601字节已经原本的0x60402字节22158/* index 0x1600 : Receive PDO 1 Mapping. */ UNS8 Ds402_Slave_highestSubIndex_obj1600 3; /* number of subindex - 1*/ UNS32 Ds402_Slave_obj1600[] { 0x60400010, /* 0 */ 0x605D0010, /* 0 */ 0x60600008, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0 /* 0 */ };解释上图中的帧结构主机发送的帧COD-ID0x201DLC8DATA0F 00 01 00 03 00 00 000F 00发给第一条目2字节转换小端序后将0x000F写入0x60402字节01 00发给第二条目2字节转换小端序后将0x0001写入0x605D2字节03发给第三条目1字节转换小端序后将0x03写入0x60601字节修改配置后效果2、TPDO观察对象字典可以看到PDO通讯存在4个RPDO通道4个TPDO通道似乎可以在对象字典设计时确定我没有研究默认的8个通道每个通道都分为参数配置和映射配置两个索引。每个通道可以映射8个索引如0x1800就是TPDO1的参数配置0x1A00就是TPDO1的映射配置当前只映射了两个索引0x6040、0x6064如对象字典代码所示UNS16 Controlword 0x0; /* Mapped at index 0x6040, subindex 0x00 */ ...... INTEGER32 Position_actual_value 0x0; /* Mapped at index 0x6064, subindex 0x00 */ ...... /* index 0x1800 : Transmit PDO 1 Parameter. */ UNS8 Ds402_Slave_highestSubIndex_obj1800 6; /* number of subindex - 1*/ UNS32 Ds402_Slave_obj1800_COB_ID_used_by_PDO 0x00000180; /* 3221225856 */ UNS8 Ds402_Slave_obj1800_Transmission_Type 0xFE; /* 254 */ UNS16 Ds402_Slave_obj1800_Inhibit_Time 0x000A; /* 0 */ UNS8 Ds402_Slave_obj1800_Compatibility_Entry 0x0; /* 0 */ UNS16 Ds402_Slave_obj1800_Event_Timer 1000; /* 0 */ UNS8 Ds402_Slave_obj1800_SYNC_start_value 0x0; /* 0 */ subindex Ds402_Slave_Index1800[] { { RO, uint8, sizeof (UNS8), (void*)Ds402_Slave_highestSubIndex_obj1800, NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1800_COB_ID_used_by_PDO, NULL }, { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_obj1800_Transmission_Type, NULL }, { RW, uint16, sizeof (UNS16), (void*)Ds402_Slave_obj1800_Inhibit_Time, NULL }, { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_obj1800_Compatibility_Entry, NULL }, { RW, uint16, sizeof (UNS16), (void*)Ds402_Slave_obj1800_Event_Timer, NULL }, { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_obj1800_SYNC_start_value, NULL } }; ...... /* index 0x1A00 : Transmit PDO 1 Mapping. */ UNS8 Ds402_Slave_highestSubIndex_obj1A00 2; /* number of subindex - 1*/ UNS32 Ds402_Slave_obj1A00[] { 0x60400010, /* 0 */ 0x60640020, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0 /* 0 */ }; subindex Ds402_Slave_Index1A00[] { { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_highestSubIndex_obj1A00, NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[0], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[1], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[2], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[3], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[4], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[5], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[6], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[7], NULL } }; ...... ...... /* index 0x6040 : Mapped variable Controlword */ subindex Ds402_Slave_Index6040[] { { RW, uint16, sizeof (UNS16), (void*)Controlword, NULL } }; ...... /* index 0x6064 : Mapped variable Position actual value */ subindex Ds402_Slave_Index6064[] { { RO, int32, sizeof (INTEGER32), (void*)Position_actual_value, NULL } };上面的代码是生成的对象字典中关于TPDO1的参数配置和映射配置下面具体说明参数配置1.子索引UNS8 Ds402_Slave_highestSubIndex_obj1800 6; // 子索引数量为7个(0-6) // 索引0x1800有7个子项这是TPDO1的完整参数集2.COB-ID配置UNS32 Ds402_Slave_obj1800_COB_ID_used_by_PDO 0x00000180; // 这是一个32位值 // 位31有效位(0有效, 1无效) // 位30数据帧(0数据帧, 1远程帧) // 位29标准帧(0标准帧1扩展帧) // 位28保留必须写0 // 位27-12扩展帧的CAN-ID 高位标准帧写0 // 位11-0CAN-ID 0x180 NodeID3.传输类型UNS8 Ds402_Slave_obj1800_Transmission_Type 0xFE; // 0同步传输无确认 // 1-240同步传输每N个SYNC发送一次 // 254设备特定事件制造商特定 // 255异步传输事件驱动 // 254和255似乎没区别 // DS402通常设置为0xFF 异步传输事件驱动4.禁止时间UNS16 Ds402_Slave_obj1800_Inhibit_Time 0x000A; // 禁止时间 0单位100us // 防止PDO发送过于频繁两个相同PDO之间的最小时间间隔 // 0表示无限制5.兼容性条目UNS8 Ds402_Slave_obj1800_Compatibility_Entry 0x0; // 兼容性条目 0 // 用于向后兼容通常为06.事件定时器UNS16 Ds402_Slave_obj1800_Event_Timer 1000; // PDO自动发送的时间间隔单位ms7.SYNC起始值UNS8 Ds402_Slave_obj1800_SYNC_start_value 0x0; // SYNC起始值 0 // 定义从哪个SYNC计数器值开始发送 // 主要用于同步PDO映射配置1.映射数量UNS8 Ds402_Slave_highestSubIndex_obj1A00 2; // 有2个映射条目2.映射索引项结构UNS32 Ds402_Slave_obj1A00[] { 0x60400010, /* 0 */ 0x60640020, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0, /* 0 */ 0x0 /* 0 */ }; 映射条目解析(索引、子索引、长度) 映射条目10x60400010 索引: 0x6040 (控制字 Controlword) 子索引: 0x00 长度: 0x10 (16位, 2字节) 映射条目20x60640020 索引: 0x6064 (实际位置 Actual Position) 子索引: 0x00 长度: 0x20 (32位, 4字节)3.映射索引接口subindex Ds402_Slave_Index1A00[] { { RW, uint8, sizeof (UNS8), (void*)Ds402_Slave_highestSubIndex_obj1A00, NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[0], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[1], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[2], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[3], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[4], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[5], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[6], NULL }, { RW, uint32, sizeof (UNS32), (void*)Ds402_Slave_obj1A00[7], NULL } }; // 子索引0映射条目数量 // 子索引1-8具体的映射条目配置完成效果上面设置的效果是MCU每秒发送一次TPDO1映射的索引修改传输类型为同步传输(0x1)每一个SYNC发送一次TPDO映射的索引UNS8 Ds402_Slave_highestSubIndex_obj1800 6; UNS32 Ds402_Slave_obj1800_COB_ID_used_by_PDO 0x00000180; UNS8 Ds402_Slave_obj1800_Transmission_Type 0x01; // 同步 PDO表示每收到1个SYNC帧发送一次 UNS16 Ds402_Slave_obj1800_Inhibit_Time 0; UNS8 Ds402_Slave_obj1800_Compatibility_Entry 0; UNS16 Ds402_Slave_obj1800_Event_Timer 0; // 必须为 0 UNS8 Ds402_Slave_obj1800_SYNC_start_value 0;修改一下0x60400x6064的值UNS16 Controlword 0x12; /* Mapped at index 0x6040, subindex 0x00 */ ...... INTEGER32 Position_actual_value 0x34; /* Mapped at index 0x6064, subindex 0x00 */解释上图中的帧结构SYNC帧通常是用于时间同步和动作对齐的广播报文COB-ID0x080DLC0DATA无数据TPDO1发送到主机的帧COD-ID0x181DLC812 00是TPDO1条目1的映射对象2字节经过小端序转换后的值实际为0x001234 00是TPDO1条目2的映射对象2字节经过小端序转换后的值实际为0x0034其他要求与RPDO相同DATA12 00 34 00 00 00 00 003、NMT解释帧结构网络管理帧通常由主机发出COB-ID: 0x000固定NMT主站广播RTR: 0数据帧DLC: 22字节数据字节0CS命令字 Command Specifier字节1Node-ID目标节点ID0表示所有节点指令代码CS定义0x01: 启动节点Start Remote Node0x02: 停止节点Stop Remote Node0x80: 进入预操作状态Enter Pre-operational State0x81: 复位节点Reset Node0x82: 复位通信Reset Communication状态描述Initialization初始化节点启动进行自检和初始化Pre-operational预操作允许SDO通信禁止PDO通信Operational操作允许所有通信SDO、PDOStopped停止仅允许NMT通信BOOT-UP消息当节点上电进入初始化时。会发送BOOT-UP消息COB-ID0x701DATA00LEN1心跳报文COB-ID0x701DATA[状态码]状态码如下所示enum enum_nodeState { Initialisation 0x00, //初始化 Disconnected 0x01, //未连接 Connecting 0x02, //已连接 Preparing 0x02, //准备中 Stopped 0x04, //已停止 Operational 0x05, //运行中 Pre_operational 0x7F, //预运行 Unknown_state 0x0F //未知 };修改索引0x1017为1000可设定1000ms心跳间隔/* index 0x1017 : Producer Heartbeat Time. */ UNS16 Ds402_Slave_obj1017 1000; /* 0 */ subindex Ds402_Slave_Index1017[] { { RW, uint16, sizeof (UNS16), (void*)Ds402_Slave_obj1017, NULL } };可以看到控制器每秒发送一条心跳报文当前为预运行状态发送NMT信息启动节点4、SDO通讯读取对象字典快速上传小于等于4字节主站发送帧主站读取0x6040的值发送请求帧COB-ID0x601DLC8DATA40 40 60 00 00 00 00 0040命令字节40索引低字节60索引高字节00子索引00 00 00 00占位没有数据从站响应帧COB-ID0x581DLC8DATA4B 40 60 00 00 00 00 004B命令字节(响应2字节)40索引低字节60索引高字节00子索引00 000x6040的当前值00 00占位没有数据写入对象字典主站发送帧向0x6040写6COB-ID0x601DLC8DATA2B 40 60 00 06 00 00 002B命令字节写两个字节40索引低字节60索引高字节00子索引06 00小端序转换后的要写入的值实际值为0x0006因为0x60402字节所以要写入的值所占字节为两字节00 00占位没有数据从站响应帧COB-ID0x581DLC8DATA2B 40 60 00 06 00 00 0060命令字节写成功应答40索引低字节60索引高字节00子索引00 00 00 00占位没有数据