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 + 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 */ }; 条目解析(索引、子索引、长度) 映射条目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 + 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 */ }; 映射条目解析(索引、子索引、长度) 映射条目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:占位(没有数据)