CANOPEN(DS402)学习记录
2026/8/20 17:29:36 网站建设 项目流程

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 } };

上面的对象字典代码配置了RPDO1,0x1400是RPDO1的参数配置,0x1600是RPDO1的映射配置,将0x6040(控制字)映射到RPDO1的前两个字节。

参数配置

1.子索引

UNS8 Ds402_Slave_highestSubIndex_obj1400 = 5; // 子索引数量为6个(0-5) // 索引0x1400有6个子项,这是RPDO1的完整参数集

2.COB-ID

UNS32 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-0:CAN-ID = 0x200 + NodeID

3.传输类型

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一般设为0

5.兼容性字段

UNS8 Ds402_Slave_obj1400_Compatibility_Entry = 0x0; //永远写0

6.事件定时器

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 */ }; 条目解析(索引、子索引、长度) 映射条目1:0x60400010 索引: 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 = 0x200+node-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(0x200+node-id)

DLC:8(实际只消费前两个字节,消费多少字节由索引数据类型决定,0x6040数据类型UNS16,即两字节)

DATA:0F 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字节),0x6060(1字节),已经原本的0x6040(2字节)

2+2+1=5<8

/* 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-ID:0x201

DLC:8

DATA:0F 00 01 00 03 00 00 00

0F 00发给第一条目(2字节),转换小端序后将0x000F写入0x6040(2字节)

01 00发给第二条目(2字节),转换小端序后将0x0001写入0x605D(2字节)

03发给第三条目(1字节),转换小端序后将0x03写入0x6060(1字节)

修改配置后效果

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-0:CAN-ID = 0x180 + NodeID

3.传输类型

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 // 用于向后兼容,通常为0

6.事件定时器

UNS16 Ds402_Slave_obj1800_Event_Timer = 1000; // PDO自动发送的时间间隔,单位ms

7.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 */ }; 映射条目解析(索引、子索引、长度) 映射条目1:0x60400010 索引: 0x6040 (控制字 Controlword) 子索引: 0x00 长度: 0x10 (16位, 2字节) 映射条目2:0x60640020 索引: 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;

修改一下0x6040,0x6064的值

UNS16 Controlword = 0x12; /* Mapped at index 0x6040, subindex 0x00 */ ...... INTEGER32 Position_actual_value = 0x34; /* Mapped at index 0x6064, subindex 0x00 */

解释上图中的帧结构
SYNC帧

通常是用于时间同步和动作对齐的广播报文

COB-ID:0x080

DLC:0

DATA:无数据

TPDO1

发送到主机的帧

COD-ID:0x181

DLC:8

12 00是TPDO1条目1的映射对象(2字节)经过小端序转换后的值,实际为0x0012

34 00是TPDO1条目2的映射对象(2字节)经过小端序转换后的值,实际为0x0034

其他要求与RPDO相同

DATA:12 00 34 00 00 00 00 00

3、NMT

解释帧结构

网络管理帧通常由主机发出
COB-ID: 0x000(固定,NMT主站广播)
RTR: 0(数据帧)
DLC: 2(2字节数据)
字节0:CS(命令字 Command Specifier)
字节1:Node-ID(目标节点ID,0表示所有节点)

指令代码(CS)定义:

  • 0x01: 启动节点(Start Remote Node)

  • 0x02: 停止节点(Stop Remote Node)

  • 0x80: 进入预操作状态(Enter Pre-operational State)

  • 0x81: 复位节点(Reset Node)

  • 0x82: 复位通信(Reset Communication)

状态描述
  • Initialization(初始化):节点启动,进行自检和初始化

  • Pre-operational(预操作):允许SDO通信,禁止PDO通信

  • Operational(操作):允许所有通信(SDO、PDO)

  • Stopped(停止):仅允许NMT通信

BOOT-UP消息

当节点上电进入初始化时。会发送BOOT-UP消息

COB-ID:0x701

DATA:00

LEN:1

心跳报文

COB-ID:0x701

DATA:[状态码]

状态码如下所示

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-ID:0x601

DLC:8

DATA:40 40 60 00 00 00 00 00

40:命令字节

40:索引低字节

60:索引高字节

00:子索引

00 00 00 00:占位(没有数据)

从站响应帧

COB-ID:0x581

DLC:8

DATA:4B 40 60 00 00 00 00 00

4B:命令字节(响应2字节)

40:索引低字节

60:索引高字节

00:子索引

00 00:0x6040的当前值

00 00:占位(没有数据)

写入对象字典
主站发送帧

向0x6040写6

COB-ID:0x601

DLC:8

DATA:2B 40 60 00 06 00 00 00

2B:命令字节(写两个字节)

40:索引低字节

60:索引高字节

00:子索引

06 00:小端序转换后的要写入的值,实际值为0x0006,因为0x6040(2字节),所以要写入的值所占字节为两字节

00 00:占位(没有数据)

从站响应帧

COB-ID:0x581

DLC:8

DATA:2B 40 60 00 06 00 00 00

60:命令字节(写成功应答)

40:索引低字节

60:索引高字节

00:子索引

00 00 00 00:占位(没有数据)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询