源码测试
18岁码农__2024年6月28日
前言
- 顺序按实验时间编写的,无严格的逻辑关系。
- 内容基于自己的理解,难免有错误,仅供参考。
- 随着对CANOPEN协议理解的深入,内容在不断的更新、更正。
- CAN盒用的ZLG的USBCAN-II,主从站都是F103平台,源代码见资料。
1、单独主站上电
现象如下:
主站上线,发送两个字节报文:81 00.
表示复位所有节点应用层。
NMT节点状态切换命,CANID为000,具有最高的CAN优先级,数据两个字节。
然后主站发送同步报文,具体见下图解释。
2、从站上线
(1)主站工作中,从站上电。
现象如下:
可以看出从站701,发出1个字节0,代表上线报文。
主站000发出2个字节01 01,代表让从站01进入操作状态。
(2)单独从站上电时现象如下:
从站701先发出1个字节00,表示从站上线。
然后1s发送一次心跳报文,内容7f,代表从站进入预操作状态了。
具体心跳周期设置解释见下面的心跳测试。
3、从站心跳测试
源码中:
/* 从站上报心跳包间隔时间,从站才需要配置 */
UNS16 ObjDict_obj1017 = 1000;
如果改成2000,心跳则为2ms。
将硬件的拨码开关设置:1高、2低、3低,则从站地址为4. 如果3个都高,则地址为7。
黑框:心跳时间,可以看出,从站1ms发送一次心跳。
绿框:从站的地址。拨码开关确定了从站的地址。
粉框:从站发送来的数据7f,表示Pre-Operational状态(节点初始化完成后,进入Pre-Operational状态)。
4、主站通过SDO修改数据
使用一次SDO报文,修改从站索引0x****子索引0x**地址中的数据。
(1)主站while循环中:
if(key_ctrol()==1) //按下了S1
{
if(one_flag_1)
{
writeNetworkDictCallBack(&ObjDict_Data,1,0x2000,0x01,1,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
one_flag_1=0;
}
}
主站中的配置中:
/SDO配置(客户端)/
/* 子索引个数 */
UNS8 ObjDict_highestSubIndex_obj1280 = 3;
/* 客户端->服务器用的COB-ID */
UNS32 ObjDict_obj1280_COB_ID_Client_to_Server_Transmit_SDO = 0x600 + 1; /* 1号节点 */
/* 服务器->客户端用的COB-ID */
UNS32 ObjDict_obj1280_COB_ID_Server_to_Client_Receive_SDO = 0x580 + 1; /* 1号节点 */
/* 对应服务器的节点号 */
UNS8 ObjDict_obj1280_Node_ID_of_the_SDO_Server = 0x1; /* 1号节点 */
从站由拨码开关将其地址设置为1,运行时,按下S1后,现象为:
红框:主站601发出2f 00 20 01 0a 00 00 00
由canopen手册知道,按下S1意思是向主索引2000,子索引01的从站写入1个字节的数据,数据内容为0a。
绿框:从站回复:60 00 20 01 00 00 00 00
表明发送SDO应答成功了。
从站中对象字典配置了索引2000子索引01,如下:
//发送PDO映射参数配置(客户端)/
/* 子索引数 */
UNS8 ObjDict_highestSubIndex_obj1A00 = 2;
/* bit0-7表示位数,bit8-15表示子索引,bit16-32表示索引 */
UNS32 ObjDict_obj1A00[] =
{
0x20000108, /* 索引为2000,子索引为1,位数为8位 */
0x20000210, /* 索引为2000,子索引为2,位数为16位 */
};
subindex ObjDict_Index1A00[] =
{
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1A00},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1A00[0]},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1A00[1]},
};
(2)如果将主站中按下S1的函数改为:writeNetworkDictCallBack(&ObjDict_Data,1,0x2000,0x02,2,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
则结果为:
由canopen手册知道,按下S1意思是向主索引2000,子索引02的从站写入2个字节的数据,数据内容为0a 01。
从站回复:60 00 20 02 00 00 00 00
表明发送SDO应答成功了。
(3)如果将主站中按下S1的函数改为:writeNetworkDictCallBack(&ObjDict_Data,1,0x2000,0x02,1,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
则结果为:
由canopen手册知道,按下S1意思是向主索引2000,子索引02的从站写入1个字节的数据,数据内容为0a。
从站回复:80 00 20 02 10 00 07 06
表明发送SDO应答失败。
10 00 07 06,根据ADO abort code error可查,代表数据长度不匹配。因为对象字典中索引2000,子索引02为两个字节的数据,但SDO发送时修改的是1个字节的。
(4)如果将主站中按下S1的函数改为:writeNetworkDictCallBack(&ObjDict_Data,1,0x1800,0x01,1,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
或者:
writeNetworkDictCallBack(&ObjDict_Data,1,0x1800,0x01,2,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
或者:
writeNetworkDictCallBack(&ObjDict_Data,1,0x1800,0x01,3,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
则结果为(只看回复的,发送的字节不一样,只贴1个字节的):
由canopen手册知道,按下S1意思是向主索引1800,子索引01的从站写入1、2、3个字节的数据。
从站回复:80 00 18 01 10 00 07 06
表明发送SDO应答失败,失败原因同上。
但是发送:
writeNetworkDictCallBack(&ObjDict_Data,1,0x1800,0x01,4,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
结果为:
由canopen手册知道,按下S1意思是向主索引1800,子索引01的从站写入4个字节的数据,数据内容为01 01 01 0a。
从站回复:60 00 18 01 00 00 00 00
表明发送SDO应答成功了。
(5)如果将主站中按下S1的函数改为:writeNetworkDictCallBack(&ObjDict_Data,1,0x1200,0x00,1,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
则结果为:
由canopen手册知道,按下S1意思是向主索引1200,子索引00的从站写入1个字节的数据,数据内容为0a。
从站回复:80 00 12 00 02 00 01 06
表明发送SDO应答失败了。由主站的配置代码:
subindex ObjDict_Index1200[] =
{
{RO, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1200},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1200_COB_ID_Client_to_Server_Transmit_SDO},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1200_COB_ID_Server_to_Client_Receive_SDO},
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_obj1200_Node_ID_of_the_SDO_Server}
};
可知:主索引1200,子索引00是只读的,因此不能写入。
(6)如果将主站中按下S1的函数改为:writeNetworkDictCallBack(&ObjDict_Data,1,0x1200,0x03,1,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
因为uint8_t tt[1]={10};
因此发送SDO后,改变了从站节点号码,因此无法回复,导致回复了如图内容:
超时!
如果改为uint8_t tt[1]={01};
也就是说改节点号码仍为01,则SDO应答成功。
小结:客户端(主站)通过SDO要修改服务器(从站)的数据,首先主索引、子索引要匹配,修改的字节要一致,而且要是能写入的,非只读的。
5、主站通过SDO查询数据
(1)如果将主站中按下S2的函数改为:
readNetworkDictCallback(&ObjDict_Data, 1, 0x1200, 0x00, int8, SDOCallback_t_Index1800_Subindex0, 0);
则结果为:
由canopen手册知道,按下S2意思是读取主索引1200,子索引00的数据
从站回复:4f 00 12 00 03 00 00 00
因为从站源代码中:
UNS8 ObjDict_highestSubIndex_obj1200 = 3;
/* 客户端->服务器用的COB-ID */
UNS32 ObjDict_obj1200_COB_ID_Client_to_Server_Transmit_SDO = 0x600 + 1; /* 1号节点 */
/* 服务器->客户端用的COB-ID */
UNS32 ObjDict_obj1200_COB_ID_Server_to_Client_Receive_SDO = 0x580 + 1; /* 1号节点 */
/* 对应服务器的节点号 */
UNS8 ObjDict_obj1200_Node_ID_of_the_SDO_Server = 0x1; /* 1号节点 */
subindex ObjDict_Index1200[] =
{
{RO, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1200},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1200_COB_ID_Client_to_Server_Transmit_SDO},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1200_COB_ID_Server_to_Client_Receive_SDO},
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_obj1200_Node_ID_of_the_SDO_Server}
子索引00代表的是子索引个数,为3,因此查询的数据结果为03 00 00 00。
(2)如果将主站中按下S2的函数改为:
readNetworkDictCallback(&ObjDict_Data, 1, 0x1200, 0x01, int8, SDOCallback_t_Index1800_Subindex0, 0);
则结果为:
子索引01代表的是客户端->服务器用的COB-ID,为601,因此查询结果为01 06 00 00。
(3)如果将主站中按下S2的函数改为:
readNetworkDictCallback(&ObjDict_Data, 1, 0x1200, 0x02, int8, SDOCallback_t_Index1800_Subindex0, 0);
则结果为:
子索引01代表的是服务器->客户端用的COB-ID,为581,因此查询结果为81 05 00 00。
(4)如果将主站中按下S2的函数改为:
readNetworkDictCallback(&ObjDict_Data, 1, 0x1A00, 0x01, int8, SDOCallback_t_Index1800_Subindex0, 0);
则结果为:
如果改为:
readNetworkDictCallback(&ObjDict_Data, 1, 0x1A00, 0x02, int8, SDOCallback_t_Index1800_Subindex0, 0);
则结果为:
因为从站源码中:
/* 子索引数 */
UNS8 ObjDict_highestSubIndex_obj1A00 = 2;
/* bit0-7表示位数,bit8-15表示子索引,bit16-32表示索引 */
UNS32 ObjDict_obj1A00[] =
{
0x20000108, /* 索引为2000,子索引为1,位数为8位 */
0x20000210, /* 索引为2000,子索引为2,位数为16位 */
};
subindex ObjDict_Index1A00[] =
{
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1A00},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1A00[0]},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1A00[1]},
};
所以:
子索引01中数据为0x20000108,因此查询结果为08 01 00 20。
子索引02中数据为0x20000210, 因此查询结果为10 02 00 20。
6、SDO应用小结
有了上面的知识,我们举例说明SDO应用。
(1)读取参数
发送:601 40 01 65 00 00 00 00 00 (读取6501h/00h地址的数据,节点地址01)
接收:581 43 01 65 00 A0 8C 00 00 (6501h/00h地址的参数为0x00008CA0,4个字节)
发送:601 40 00 20 00 00 00 00 00 (读取2000h/00h地址的数据,节点地址01)
接收:581 4F 00 20 00 00 00 00 00 (2000h/00h地址的参数为0x00,1个字节数据)
(2)写入参数
发送:601 23 01 65 00 A0 8C 00 00 (将6501h/00h地址参数设置为0x00008CA0,4个字节)
接收:581 60 01 65 00 00 00 00 00 (6501h/00h地址参数修改成功)
发送:601 2F 00 20 00 00 00 00 00 (将2000h/00h地址参数设置为0x00,1个字节)
接收:581 60 00 20 00 00 00 00 00 (2000h/00h地址参数修改成功)
注意:参数修改后并没有直接保存,必须在Object 1010h:save parameters(保存参数)内进行“save”写入才能保存所修改的参数。在Sub-Index 1写入命令“save”(0x65766173h),参数将会存入存储器。
比如:
发送:601 23 10 10 01 73 61 76 65(将0x65766173h写入1010h/01h地址)
接收:581 60 10 10 01 00 00 00 00(1010h/01h地址的参数修改成功)
恢复默认参数时,也必须对Object 1011h:Restore parameters(恢复默认参数)内进行“load”修改参数并保存。在Sub-Index 1写入命令“load”(0x64616F6Ch),参数将会恢复成默认值并写入存储器。
比如:
发送:601 23 11 10 01 6C 6F 61 64(将0x64616F6Ch写入1011h/01h地址)
接收:581 60 11 10 01 00 00 00 00(1011h/01h地址的参数修改成功)。
实验:
首先修改uint8_t tt[4]={01 ,02,03 ,04};
修改按下S1函数: writeNetworkDictCallBack(&ObjDict_Data,1,0x1a00,0x01,4,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
意思是将0x1a00,0x01 的4个字节修改为01 02 03 04.
修改按下S2函数:
readNetworkDictCallback(&ObjDict_Data, 1, 0x1a00, 0x01, int8, SDOCallback_t_Index1800_Subindex0, 0);
读取0x1a00,0x01 的4个字节数据。
现象为:
23:写入4个字节;
00 1a 01:主索引1a00,从索引:01
60:写入成功!
40:读取数据
43:读取数据响应成功(4个字节)。
重新上下电后,直接读取数据,则读取的是主索引1a00,从索引01的原始数据,而不是01 02 03 04.
7、上位机发送SDO测试
(1)单独从站工作时,通过CanTest上位机向601节点发送23 00 1a 01 01 02 03 04。
结果如下:
应答成功。
(2)主、从站都正常工作时,通过CanTest上位机向601节点发送23 00 1a 01 01 02 03 04。
结果如下:
应答成功后,从站又发送了80 00 00 00 21 00 00 08.表示因为是本地操作,导致数据无法被发送或存储到应用层。
具体原因不清楚……
8、修改从站心跳
将主站中的代码修改如下:
uint8_t tt[2]={0xD0,07};
/* 修改从站心跳 */ writeNetworkDictCallBack(&ObjDict_Data,1,0x1017,0x00,2,int8,tt,SDOCallback_t_Index1800_Subindex0,0);
现象如下:
按下S1之前,从站1s一次心跳,报文05代表从站进入操作状态了。
按下S1之后,从站仍然1s一次心跳。
但按下S1,应该是将索引1017的数据修改为7D0(2000)的,不知为何………………
这时,将主站复位下,现象如下:
然后,主站的心跳就变成2s了。
最后这样:正常上电,从站心跳默认1s一次,按下S1,修改心跳为2s,监测心跳仍然是1s,通过上位机发送81 00(复位节点),监测到的心跳变为2s。
9、NMT命令
NMT是管理报文,用于实现一些管理操作,比如:节点重启、进入操作状态等等。
NMT报文格式很简单,ID为000,数据为1个字节的命令+1个字节的节点号(0表示广播)。
节点状态切换命令见下图所示。
下图中,通过NMT发送01 01.
序号第61个报文,就是让1号节点进入操作状态(01h)。
然后,1号节点(701),心跳报文变成了05,代表进入操作状态。
又比如:通过NMT发送02 01.
让1号节点进入停止状态(02h)。
然后,1号节点(701),心跳报文变成了04,代表进入停止状态(心跳存在,但是节点不工作了)。
又比如:通过NMT发送81 01.
让1号节点复位应用层(81h)。
复位后,1号节点(701)会首先进入Initializing状态(0x00),初始化完成后,进入PreOperational状态(0x7f)。
又比如:通过NMT发送81 01.
让1号节点复位应用层(81h)。
复位后,1号节点(701)会首先进入Initializing状态(0x00),初始化完成后,进入PreOperational状态(0x7f)。
10、读取从站心跳时间
这里的操作都是通过上位机发送命令的,仅仅从站上电。
下面通过SDO查询从站的心跳时间。
发送命令为:40 17 10 00 00 00 00 00,这个就是一个SDO_Read报文(40h命令字为读取命令字),告诉从站,要读取OD索引1017,子索引00的数据(这个位置存放的是从站的心跳频率)。
从站回复:4b 17 10 00 e8 03 00 00,命令字4b表示读取到了2个字节数据,读取的结果0x3E8(1000ms)就是心跳的时间。
11、PDO应用实例
目的:
通过PDO,将从站的参数发送给主站。
分析:
PDO 通信是基于生产者/消费者(Producer/Consumer)模型,主要用于传输实时数据。产生数据的节点将带有自己节点号的数据放到总线上,需要该数据的节点,可以配置为接收该 PDO。
过程:
对象字典的编写。
对象字典的结构和条目对于所有设备是共同的,本例中采用索引定位,子索引确定对象的思想构建对象字典,方法是使用结构体定义子索引,子索引结构体的成员变量包含对象的属性(读写权限,数据类型,数据长度等)和指向对象的指针,定义索引时包含指向子索引的指针和子索引数目,这样可以方便地通过索引和子索引找到对应的项,对象定义为指针的形式可以通过主站的 SDO 报文进行读写,实现对对象字典的灵活配置,同时这种方式实现通讯层与应用层共享数据变量的特点。
对从站对象字典的配置:
TPDO1 通讯参数 配置(客户端)
/* 子索引数 */
UNS8 ObjDict_highestSubIndex_obj1800 = 5;
UNS32 ObjDict_obj1800_COB_ID_used_by_PDO = 0x181; /* PDO1 1号节点 */
UNS8 ObjDict_obj1800_Transmission_Type = 0x01;
UNS16 ObjDict_obj1800_Inhibit_Time = 1000; /* 0 0.1ms */
UNS8 ObjDict_obj1800_Compatibility_Entry = 0x0;
UNS16 ObjDict_obj1800_Event_Timer = 0;
subindex ObjDict_Index1800[] =
{
{RO, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1800},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1800_COB_ID_used_by_PDO},
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_obj1800_Transmission_Type},
{RW, uint16, sizeof(UNS16), (void *)&ObjDict_obj1800_Inhibit_Time},
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_obj1800_Compatibility_Entry},
{RW, uint16, sizeof(UNS16), (void *)&ObjDict_obj1800_Event_Timer}
};
ODCallback_t ObjDict_Index1800_callbacks[] =
{
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
/// TPDO1 映射参数 配置(客户端) /
UNS8 ObjDict_highestSubIndex_obj1A00 = 2;
UNS32 ObjDict_obj1A00[] =
{
0x20000108, /* 索引为2000,子索引为1,位数为8位 */
0x20000210, /* 索引为2000,子索引为2,位数为16位 */
};
subindex ObjDict_Index1A00[] =
{
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1A00},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1A00[0]},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1A00[1]},
};
/ TPDO1 应用数据 的索引配置(客户端) /
UNS8 ObjDict_highestSubIndex_obj2000 = 2;
UNS8 ControlWordAxis1 = 100;
UNS16 ControlWordAxis2 = 1000;
subindex ObjDict_Index2000[] =
{
{RW, int8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj2000},
{RW, uint8, sizeof(UNS8), (void *)&ControlWordAxis1},
{RW, uint16, sizeof(UNS16), (void *)&ControlWordAxis2},
};
主站对象字典的配置:
/// RPDO1 通讯参数 配置(客户端)
UNS8 ObjDict_highestSubIndex_obj1400 = 5;
UNS32 ObjDict_obj1400_COB_ID_used_by_PDO = 0x181; /* PDO1 1号节点 */
UNS8 ObjDict_obj1400_Transmission_Type = 0x1;
UNS16 ObjDict_obj1400_Inhibit_Time = 0x0; /* 0 单位10us */
UNS8 ObjDict_obj1400_Compatibility_Entry = 0x0;
UNS16 ObjDict_obj1400_Event_Timer = 0x0;
subindex ObjDict_Index1400[] =
{
{RO, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1400},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1400_COB_ID_used_by_PDO},
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_obj1400_Transmission_Type},
{RW, uint16, sizeof(UNS16), (void *)&ObjDict_obj1400_Inhibit_Time},
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_obj1400_Compatibility_Entry},
{RW, uint16, sizeof(UNS16), (void *)&ObjDict_obj1400_Event_Timer}
};
/ RPDO1 映射参数 配置(客户端)// // /
UNS8 ObjDict_highestSubIndex_obj1600 = 2;
UNS32 ObjDict_obj1600[] =
{
0x20000108, /* 索引为2000,子索引为1,位数为8位 */
0x20000210, /* 索引为2000,子索引为2,位数为16位 */
};
subindex ObjDict_Index1600[] =
{
{RW, uint8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj1600},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1600[0]},
{RW, uint32, sizeof(UNS32), (void *)&ObjDict_obj1600[1]},
};
/// RPDO1 应用数据 配置(客户端) /// ///
UNS8 ObjDict_highestSubIndex_obj2000 = 2;
UNS8 ControlWordAxis1 = 0;
UNS16 ControlWordAxis2 = 0;
subindex ObjDict_Index2000[] =
{
{RW, int8, sizeof(UNS8), (void *)&ObjDict_highestSubIndex_obj2000},
{RW, uint8, sizeof(UNS8), (void *)&ControlWordAxis1},
{RW, uint16, sizeof(UNS16), (void *)&ControlWordAxis2},
};
根据上面的配置,可知UNS8 ObjDict_obj1800_Transmission_Type = 0x01;
即每收到一次同步报文,从站就发送PDO。
现象如下:
帧ID:080为主站发出的同步报文,从站收到该同步报文后,发送PDO。
帧ID:181,数据00 e8 03 就为从站发出的PDO报文。
因为UNS16 ControlWordAxis2 = 1000;(3e8)。更改此处的值,PDO的报文的第2、3字节也会相应改变,但是第一字节一直是00.
为什么没有发UNS8 ControlWordAxis1 = 100的内容?
解答:因为程序中变量ControlWordAxis1虽然赋初值100,但它的值由用户按键值确定,实验中用户按键一个都没按下,因此发送的内容为00.
验证情况:
按下用户按键,通过SDO读取这个变量的值,结果如下:
黑色为按下S3的结果;
红色为按下S2的结果;
黄色为按下S1的结果。
如果将UNS8 ObjDict_obj1800_Transmission_Type = 0x03;
即每收到3次同步报文,从站就发送PDO。
则现象如下:
12、利用SDO配置TPDO
在上述每收到3次同步报文,从站就发送PDO的基础上,通过使用SDO报文,修改TPDO。
要求:配置从节点1的TPDO 1800 在每5个SYNC传输。
应该发送的SDO(s)为:
601 23 00 18 01 81 01 00 00 //将主索引1800、子索引01处的值修改为181(COB_ID)
601 2F 00 18 02 05 00 00 00 //将TPDO的发送类型改为05
601 2F 00 1A 00 02 00 00 00 //定义2个数据的映射
601 23 00 1A 01 08 01 00 20 //定义在主索引2000、子索引01处寻找1个字节的数据
601 23 00 1A 02 10 02 00 20//定义在主索引2000、子索引02处寻找2个字节的数据
结果如图:
通过上位机一次性发送上述5个SDO,回复了4个成功,漏回复了最后一个,一次性发送4个,则回复4个成功,一个一个的发送SDO,每个都能回复成功。因此,怀疑是发送的太快,来不及回复导致漏了。
以上是主、从站都工作的情况,可以看出操作成功后,会出现80 00 00 00 21 00 00 08的报文。如果关掉主站电源,单独从站工作,则不会出现80 00 00 00 21 00 00 08的报文。
原因?
13、利用SDO配置RPDO
情况和上述的类似,因为程序中将主站配置为了RPDO,而且它还是SDO客户端,而不是服务器,因此无法实验。
这里贴一个例子,共参考。
/***************************************************************************************************/
创作不易,关注才有动力,源代码见主页其他文章