不知道对不对的前提说明:
SDO的读写操作都是客户端()对服务器()的读写。
这里配置的是SDO服务器
接下来看几张图
注意:
nodeID是接收和发送两方都必须要一致
COBID client to server是接收 SDO ,cobid是0x600+nodeID。这里的cobid必须和客户端(从机)的发送SDO的cobid一致
意思就是 客户端 发送给 服务器,客户端的发送SDO必须是0x600+nodeID,服务器的接收SDO才会接收SDO
COBID server to client 是发送 SDO , cobid是0x580+nodeID,这里的cobid必须和客户端的接收SDO的cobid一样。
意思就是 服务器 返回给 客户端,服务器的发送必须是0x580+nodeID,客户端才会接收SDO
注意:
nodeID是接收和发送两方都必须要一致
下图是clientSDO的配置,意思是我这个字节有多少个客户端可以通信
可以看到一个节点充当server和client的0x600+NODEID是不一样的。
一个是接收。一个是发送。
client的发送就是server的接收cobid
接下来看程序上是怎么写的:
注意》》》》》》这里的服务器和客户端。并不是主机从机的意思。。
实在不懂,就看下面的图:
可以看到,从机1的客户端配置,发送0x602需要和从机2的服务器的接收0x602一致
程序例子是从节点1向从节点2,读写SDO。
从节点1,从节点的NODEID就是0x1
服务器的SDO的接收cobid = 0x600 +NODEID =0x601
UNS32 TestSlave_obj1200_COB_ID_Client_to_Server_Receive_SDO = 0x600; /* 1536 */
服务器的SDO的发送cobid = 0x580 +NODEID = 0x581
UNS32 TestSlave_obj1200_COB_ID_Server_to_Client_Transmit_SDO = 0x580; /* 1408 */
客户端的SDO的发送cobid = 0x602 ,说明别人的接收cobid的0x602
UNS32 TestSlave_obj1280_COB_ID_Client_to_Server_Transmit_SDO = 0x602; /* 1538 */
客户端的SDO的接收cobid = 0x582,说明别人的发送cobid的0x582
UNS32 TestSlave_obj1280_COB_ID_Server_to_Client_Receive_SDO = 0x582; /* 1410 */
所以客户端的NODEID = 0x2
从节点2:从节点的NODEID就是0x2
解析和节点1一样。
这里的服务器的SDO的接收cobid = 0x600 +NODEID =x602
服务器的SDO的发送cobid = 0x580 +NODEID = 0x582
可以看到从节点2的服务器和上面从节点1写的客户端配置的是一样的id。
因为这样才能通信。(发送对应接收)
下来是初始化id的函数。
使用的读写函数:
写操作 主要用到
UNS8 writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS32 count, UNS8 dataType, void *data, UNS8 useBlockMode);
和获取结果的函数
UNS8 getWriteResultNetworkDict (CO_Data* d, UNS8 nodeId, UNS32 * abortCode)
读操作 主要用到
UNS8 readNetworkDictCallback (CO_Data* d, UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS8 dataType, SDOCallback_t Callback, UNS8 useBlockMode)
和获取结果的函数
UNS8 getReadResultNetworkDict (CO_Data* d, UNS8 nodeId, void* data, UNS32 *size,
UNS32 * abortCode)
下面这个是我有点上位机,需要和网络发送命令,然后下位机通过can发送指令给另一个下位机。
UNS8 CanFestival_write_And_Read_NetworkDict(UNS8 commond,UNS8 * databuf,UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS32 count, UNS8 dataType)
{
static UNS32 Canopen_Wait_Response_time=0;
UNS32 abortCode=0;
UNS32 data=0;
UNS32 size=0;
UNS16 objresponse;
UNS8 write_result=0;
UNS8 Wait_SDO_Result=0;
if(commond==11)
{
Canopen_Wait_Response_time = lwTickCount_M4;
//count是多少个字节
write_result=writeNetworkDict(&TestSlave_Data,nodeId,index,subIndex,count,dataType,databuf,0);
//只等待5个ms
while (getWriteResultNetworkDict (&TestSlave_Data, nodeId, &abortCode) != SDO_FINISHED )
{
if(lwTickCount_M4>(Canopen_Wait_Response_time + 10))
Wait_SDO_Result=1;
IWDG_Feed_M4();
}
if(Wait_SDO_Result==0)
{
//printf("write\r\n");
return Wait_SDO_Result;
}
else
{
CanBus_Commun_OverTime_Alarm=1;
}
}
else if(commond==10)
{
Canopen_Wait_Response_time = lwTickCount_M4;
readNetworkDict(&TestSlave_Data, nodeId, index, subIndex, dataType,0); //
//只等待5个ms
while (getReadResultNetworkDict(&TestSlave_Data, nodeId, &data, &size,&abortCode) != SDO_FINISHED )
{
if(lwTickCount_M4>(Canopen_Wait_Response_time + 5))
Wait_SDO_Result=1;
IWDG_Feed_M4();
}
if(Wait_SDO_Result==0)
{
return Wait_SDO_Result;
}
else
{
CanBus_Commun_OverTime_Alarm=1;
}
}
return 1;
}
遇到的坑:
1.cansend这个底层需要调用的函数如果发送成功,必须返回0。
unsigned char canSend(CAN_PORT notused, Message *m)
{
uint32_t i;
uint8_t txbuf[8]={0,0,0,0,0,0,0,0};
FDCAN1_TxHeader.Identifier=m->cob_id;
//32位ID
FDCAN1_TxHeader.IdType=FDCAN_STANDARD_ID; //标准ID
FDCAN1_TxHeader.DataLength=FDCAN_DLC_BYTES_8; //数据长度
FDCAN1_TxHeader.ErrorStateIndicator=FDCAN_ESI_ACTIVE;
FDCAN1_TxHeader.BitRateSwitch=FDCAN_BRS_OFF; //关闭速率切换
FDCAN1_TxHeader.FDFormat=FDCAN_CLASSIC_CAN; //传统的CAN模式
FDCAN1_TxHeader.TxEventFifoControl=FDCAN_NO_TX_EVENTS; //无发送事件
FDCAN1_TxHeader.MessageMarker=0;
if(m->rtr==1)
{
FDCAN1_TxHeader.TxFrameType=FDCAN_REMOTE_FRAME; //远程帧
}
else
{
FDCAN1_TxHeader.TxFrameType=FDCAN_DATA_FRAME; //数据帧
}
for(i = 0; i < m->len; i++)
{
//printf("%x ",m->data[i]);
txbuf[i] = m->data[i];
}
//printf("\r\n");
if(HAL_FDCAN_AddMessageToTxFifoQ(&FDCAN1_Handler,&FDCAN1_TxHeader,txbuf)!=HAL_OK)
{
return 1;//发送
}
else
return 0;
}
writeNetworkDict会调用_writeNetworkDict ,_writeNetworkDict 调用sendSDO
sendSDO 会调用cansend,
然后看_writeNetworkDict 的最后,如果不是0,就是错误。
UNS8 sendSDO (CO_Data* d, UNS8 whoami, UNS8 CliServNbr, UNS8 *pData)
{
UNS16 offset;
UNS8 i;
Message m;
MSG_WAR(0x3A38, "sendSDO",0);
if( !((d->nodeState == Operational) || (d->nodeState == Pre_operational ))) {
MSG_WAR(0x2A39, "unable to send the SDO (not in op or pre-op mode", d->nodeState);
return 0xFF;
}
/*get the server->client cobid*/
if ( whoami == SDO_SERVER ) {
offset = d->firstIndex->SDO_SVR;
if ((offset == 0) || ((offset+CliServNbr) > d->lastIndex->SDO_SVR)) {
MSG_ERR(0x1A42, "SendSDO : SDO server not found", 0);
return 0xFF;
}
m.cob_id = UNS16_LE( (UNS16) *((UNS32*) d->objdict[offset+CliServNbr].pSubindex[2].pObject) );
MSG_WAR(0x3A41, "I am server Tx cobId : ", m.cob_id);
}
else { /*case client*/
/* Get the client->server cobid.*/
offset = d->firstIndex->SDO_CLT;
if ((offset == 0) || ((offset+CliServNbr) > d->lastIndex->SDO_CLT)) {
MSG_ERR(0x1A42, "SendSDO : SDO client not found", 0);
return 0xFF;
}
m.cob_id = UNS16_LE( (UNS16) *((UNS32*) d->objdict[offset+CliServNbr].pSubindex[1].pObject) );
MSG_WAR(0x3A41, "I am client Tx cobId : ", m.cob_id);
}
/* message copy for sending */
m.rtr = NOT_A_REQUEST;
/* the length of SDO must be 8 */
m.len = 8;
for (i = 0 ; i < 8 ; i++) {
m.data[i] = pData[i];
}
return canSend(d->canHandle,&m);
}
INLINE UNS8 _writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS32 count, UNS8 dataType, void *data, SDOCallback_t Callback, UNS8 endianize, UNS8 useBlockMode)
{
(void)endianize;
UNS8 err;
UNS8 line;
UNS8 CliNbr;
UNS32 j;
UNS8 i;
UNS8 buf[8];
MSG_WAR(0x3AC0, "Send SDO to write in the dictionary of node : ", nodeId);
MSG_WAR(0x3AC1, " At index : ", index);
MSG_WAR(0x3AC2, " subIndex : ", subIndex);
MSG_WAR(0x3AC3, " nb bytes : ", count);
/* Check that the data can fit in the transfer buffer */
#ifndef SDO_DYNAMIC_BUFFER_ALLOCATION
if(count > SDO_MAX_LENGTH_TRANSFER){
MSG_ERR(0x1AC3, "SDO error : request for more than SDO_MAX_LENGTH_TRANSFER bytes to transfer to node : ", nodeId);
return 0xFF;
}
#endif
/* First let's find the corresponding SDO client in our OD */
CliNbr = GetSDOClientFromNodeId( d, nodeId);
if(CliNbr >= 0xFE)
return CliNbr;
/* Verify that there is no SDO communication yet. */
err = getSDOlineOnUse(d, CliNbr, SDO_CLIENT, &line);
if (!err) {
MSG_ERR(0x1AC4, "SDO error : Communication yet established. with node : ", nodeId);
return 0xFF;
}
/* Taking the line ... */
err = getSDOfreeLine( d, SDO_CLIENT, &line );
if (err) {
MSG_ERR(0x1AC5, "SDO error : No line free, too many SDO in progress. Aborted for node : ", nodeId);
return (0xFF);
}
else {
MSG_WAR(0x3AE1, "Transmission on line : ", line);
}
if(useBlockMode) {
initSDOline(d, line, CliNbr, index, subIndex, SDO_BLOCK_DOWNLOAD_IN_PROGRESS);
d->transfers[line].objsize = count;
}
else
initSDOline(d, line, CliNbr, index, subIndex, SDO_DOWNLOAD_IN_PROGRESS);
d->transfers[line].count = count;
d->transfers[line].dataType = dataType;
#ifdef SDO_DYNAMIC_BUFFER_ALLOCATION
{
UNS8* lineData = d->transfers[line].data;
if (count > SDO_MAX_LENGTH_TRANSFER)
{
d->transfers[line].dynamicData = (UNS8*) malloc(count);
d->transfers[line].dynamicDataSize = count;
if (d->transfers[line].dynamicData == NULL)
{
MSG_ERR(0x1AC9, "SDO. Error. Could not allocate enough bytes : ", count);
return 0xFE;
}
lineData = d->transfers[line].dynamicData;
}
#endif //SDO_DYNAMIC_BUFFER_ALLOCATION
/* Copy data to transfers structure. */
for (j = 0 ; j < count ; j++) {
#ifdef SDO_DYNAMIC_BUFFER_ALLOCATION
# ifdef CANOPEN_BIG_ENDIAN
if (dataType == 0 && endianize)
lineData[count - 1 - j] = ((char *)data)[j];
else /* String of bytes. */
lineData[j] = ((char *)data)[j];
# else
lineData[j] = ((char *)data)[j];
# endif
}
#else //SDO_DYNAMIC_BUFFER_ALLOCATION
# ifdef CANOPEN_BIG_ENDIAN
if (dataType == 0 && endianize)
d->transfers[line].data[count - 1 - j] = ((char *)data)[j];
else /* String of bytes. */
d->transfers[line].data[j] = ((char *)data)[j];
# else
d->transfers[line].data[j] = ((char *)data)[j];
# endif
#endif //SDO_DYNAMIC_BUFFER_ALLOCATION
}
if(useBlockMode) {
buf[0] = (6 << 5) | (1 << 1 ); /* CCS = 6 , CC = 0 , S = 1 , CS = 0 */
for (i = 0 ; i < 4 ; i++)
buf[i+4] = (UNS8)((count >> (i<<3))); /* i*8 */
}
else {
/* Send the SDO to the server. Initiate download, cs=1. */
if (count <= 4) { /* Expedited transfer */
buf[0] = (UNS8)((1 << 5) | ((4 - count) << 2) | 3);
for (i = 4 ; i < 8 ; i++)
buf[i] = d->transfers[line].data[i - 4];
d->transfers[line].offset = count;
}
else { /** Normal transfer */
buf[0] = (1 << 5) | 1;
for (i = 0 ; i < 4 ; i++)
buf[i+4] = (UNS8)((count >> (i<<3))); /* i*8 */
}
}
buf[1] = (UNS8)(index & 0xFF); /* LSB */
buf[2] = (UNS8)((index >> 8) & 0xFF); /* MSB */
buf[3] = subIndex;
d->transfers[line].Callback = Callback;
err = sendSDO(d, SDO_CLIENT, CliNbr, buf);
if (err) {
MSG_ERR(0x1AD1, "SDO. Error while sending SDO to node : ", nodeId);
/* release the line */
resetSDOline(d, line);
return 0xFF;
}
return 0;
}
2.getWriteResultNetworkDict这个函数在进行中会返回SDO_DOWNLOAD_IN_PROGRESS
如果成功会返回SDO_FINISHED了,
如果成功你还调用这个函数,并不会返回SDO_FINISHED,
而是会返回SDO_ABORTED_INTERNAL,
我之前的逻辑是下面这样的。这会导致我status 的值一直不是SDO_FINISHED,
然后报错超时。
while (getWriteResultNetworkDict (&TestSlave_Data, nodeId, &abortCode) == SDO_DOWNLOAD_IN_PROGRESS)
{
if(lwTickCount_M4>(Canopen_Wait_Response_time + 10))
Wait_SDO_Result=1;
IWDG_Feed_M4();
}
status =getWriteResultNetworkDict (&TestSlave_Data, nodeId, &abortCode)
if(status ==SDO_FINISHED)
{
//printf("write\r\n");
return 0;
}
else
{
CanBus_Commun_OverTime_Alarm=1;
}
3.
readNetworkDict这个函数没有回调函数,需要使用readNetworkDictCallback这个函数,然后这个两个函数都会调用_readNetworkDict ,这里有个dataType的参数,好像填什么都不会有啥影响,一样能读回。
UNS8 readNetworkDictCallback (CO_Data* d, UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS8 dataType, SDOCallback_t Callback, UNS8 useBlockMode)
{
return _readNetworkDict (d, nodeId, index, subIndex, dataType, Callback, useBlockMode);
}
然后getReadResultNetworkDict这个函数的原型是
UNS8 getReadResultNetworkDict (CO_Data* d, UNS8 nodeId, void* data, UNS32 *size,
UNS32 * abortCode)
这里有个参数是size,写的是你需要读回多少个字节的数据,一般都是写4个。
我之前初始化写了0,导致读取失败。。
最后给上SDO的abort code error
有时候写SDO会出现08 00 00 21,不知道为啥
更新:
最后发现是我的cansend返回不为0导致的、
例子:
SDO就是通过索引来修改值,可以直接修改,简单粗暴。
没有pDO那么麻烦,又是映射又是什么的。
快速SDO分上传和下载,其实就是读取和写入
给上周立功文档的图
举个例子:
可以把心跳的周期修改了,下面是把2S改成了1S
2B:写入2个字节
17:索引低位
10:索引高位
00:子索引
E8:数据低位
03:数据高位
00:XX个字节时的低位,取决于写入几个字节
00:XX个字节时的高位,取决于写入几个字节
也可以直接修改映射参数的值。
就是自定义区的2000h,
这里我就不写实验了。