canfestival之SDO服务器和client客户端的配置和实验

不知道对不对的前提说明:

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,
这里我就不写实验了。

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
CANopen协议是一种应用于控制系统中的开放式网络通讯协议,CANopen协议栈是一种实现CANopen协议的软件库,而CanFestival是其中的一种开源实现。CanFestival协议栈提供了一套用于实现CANopen从站设备的软件工具,用户可以利用CanFestival来开发符合CANopen协议的从站设备。 CanFestival协议栈的实现主要由以下几个步骤构成:首先,用户需要配置CanFestival协议栈,包括设置从站设备的节点ID、通信速率等参数;然后,用户需要定义从站设备的对象字典,包括输入对象、输出对象、PDO对象等;接着,用户需要编写应用程序,根据需求实现从站设备的控制逻辑;最后,用户需要编译链接代码,并将生成的可执行文件下载到从站设备中运行。 CanFestival协议栈的源码主要包括以下几个模块:CO_Data模块负责管理CANopen通信过程中的数据;对于CAN总线的操作交由CO_CAN模块处理;NMT模块实现CANopen网络管理功能,负责从站设备的初始化和启动过程;SDO模块用于实现从站设备上的服务数据对象传输;PDO模块处理实时数据的传输;EMCY模块处理紧急事件报告;SYNC模块管理同步传输数据;TIME模块提供时间基准。 值得注意的是,CanFestival协议栈的实现需要根据具体的硬件平台和应用场景进行调整和修改,用户在使用时需要充分理解CANopen协议的原理和特点,结合具体需求进行定制和优化。 CanFestival协议栈的开源性质意味着用户可以根据需要对其进行二次开发和定制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值