canopen主站功能测试

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

对于刚接触canopen协议不久的我来说,这个简单的主站测试实现起来还是有一定困难,所以摸索当中记录下canopen主站一些功能的测试过程,比如NMT网络管理、SDO读写、PDO接收发送等功能,很多原理性的内容我不会多写,大佬们都写了很多,推荐周立功的《canopen轻松入门》,我主要结合自己的例程讲下简单的功能实现;通过在VS上新建一个空的应用台项目作为测试平台,canfestival源码移植到VS上已经做过说明,测试过没有问题,有需要可以去看看。

canfestival源码移植到VS上

一、对象字典

对象字典描述了应用对象和 CANopen 报文之间的关系,是canopen协议最核心的部分;主站和从站通信时的SDO、PDO通信参数,映射参数、心跳报文等都是在这里配置;同时当和从站通信时,需要知道从站的对象字典来配置主站的相关参数,否则无法通信,
在这里插入图片描述

1.对象字典环境安装:参考下面大佬的文章
对象字典环境安装链接
2.创建一个空的主站节点后,可以看到对象字典索引区域,每个索引又包含子索引区域,当我们使用SDO或者PDO时,本质上就是根据这些索引在访问从站对象字典的每个值;点击文件——保存,命名最好和节点名称一样,然后选择文件——建立词典,这时会生成三个文件TestMaster.h、TestMaster.c、TestMaster.od,将.c文件改为.cpp文件,然后把TestMaster.h、TestMaster.cpp添加到VS的项目中;
在这里插入图片描述
在这里插入图片描述

二、NMT网络通信

要使主站可以发送网络管理报文控制从站状态至少需要三部分:
1.定时器驱动:定时器驱动使用的是源码里面的timer_win32.cpp文件中的TimerInit()函数和 StartTimerLoop()函数;
2.can接口驱动:can驱动使用的是USB-CAN分析仪的接口函数;
3.主站节点初始化:setNodeId()函数设置主站id,setState()函数设置主站状态,此时主站便可以发送网络报文了,masterSendNMTstateChange(&TestMaster_Data, nodeId, NMT_Start_Node)便是通过网络报文控制id为nodeid的从站进入启动状态(操作状态)。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
通过另外一个can分析仪监控网络数据,发现以上代码在can总线如下所示:COB-ID:700和701分别代表主站id为0和从站id为1的NMT节点上线报文,000为NMT网络管理报文,含义可以对照上面的节点状态切换命令理解;
在这里插入图片描述
在这里插入图片描述
如果从站设置了心跳报文,主站也可以监控到这个报文,索引1017是心跳时间,主站会按照索引1016设置的值检测来检查心跳报文
在这里插入图片描述

三、接收数据线程

在VS上没有can中断服务函数,接收数据只能通过设计一个接收线程来实现,我是直接在一个线程回调函数中的while循环中不断查询can-usb接收缓存区是否有数据;GetReceiceNum()是can分析仪的接口函数,就是判断can通道是否有数据,

DWORD WINAPI receiveCanData(LPVOID lpParam) {//接收线程回调函数

    while (1) {
        if (GetReceiveNum(nDeviceType, nDeviceInd, nCANInd) > 0) {
            receiveCan();
        }     
    }
    return 0;
}
void receiveCan(void) {

    UNS8 i = 0;
    UNS8 rxbuf[8] = { 0 };
    Message RxMSG;
    int rec = Receive(nDeviceType, nDeviceInd, nCANInd, &can_rx_header, 8, 100);
    if (rec > 0) {
        for (i = 0; i < can_rx_header.DataLen; i++)
            rxbuf[i] = can_rx_header.Data[i];

        if (can_rx_header.RemoteFlag == 1) {
            RxMSG.rtr = 1;
        }
        else {
            RxMSG.rtr = 0;
        }

        RxMSG.cob_id = can_rx_header.ID;
        RxMSG.len = can_rx_header.DataLen;

        for (i = 0; i < 8; i++) {
            RxMSG.data[i] = rxbuf[i];

        }
        EnterMutex();//上锁
        canDispatch(&TestMaster_Data, &(RxMSG));//源码里面解包函数,需要阻塞接收,根据不同的COB-ID进行分类处理
        LeaveMutex();//解锁

        if (can_rx_header.ID == 0x180 + nodeId)
        {
            printf("cMotorStatus = %x, cActualSpeed = %x, cActualPosition = %x, cActualCurrent = %x\r\n", cMotorStatus, cActualSpeed, cActualPosition, cActualCurrent);
        }
        if (can_rx_header.ID == 0x280 + nodeId)
        {
            printf("cActualMotorTemp = %x, cActualBusVoltage = %x, cActualMosTemp = %x\r\n", cActualMotorTemp, cActualBusVoltage, cActualMosTemp);
        }
        if (can_rx_header.ID == 0x580 + nodeId)
        {
            printf("SDO返回数据成功\r\n");
        }
        
    }
    else {
        printf("读取数据失败\r\n");
    } 
}
void CreateReceiveTask(CAN_HANDLE fd0, TASK_HANDLE* Thread, void* ReceiveLoopPtr)
{
	unsigned long thread_id = 0;
	//线程结构体地址  用来继承
	//线程堆栈大小
	//线程起始地址  线程 函数名
	//线程函数的参数
	//创建方式
	//线程id
	*Thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReceiveLoopPtr, fd0, 0, &thread_id);
}

主函数中调用:

CreateReceiveTask(NULL, &hThread1, receiveCanData);//创建接收任务线程

四、主站SDO通信

1.主站SDO读(上传服务)

在主站对象字典添加客户端SDO参数,在从站添加服务端SDO参数
在这里插入图片描述

通过SDO读从站对象字典主要用了源码的两个接口函数:
(1)发送读从站节点对象字典的指令
在这里插入图片描述
(2)成功发送并收到响应后,接收函数中会把接收到的报文输送给canDispatch()进行解析,然后调用getReadResultNetworkDict()函数,可以把值读取出来

UNS8 getReadResultNetworkDict (CO_Data* d, UNS8 nodeId, void* data, UNS32 *size, UNS32 * abortCode);

代码如下:

//sdo读canopen从站数据
UNS8 ReadSDO(UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS8 dataType, void* data, UNS32* size)
{
	UNS32 abortCode = 0;
	UNS8 res = SDO_UPLOAD_IN_PROGRESS;
	// Read SDO
	UNS8 err = readNetworkDict(&TestMaster_Data, nodeId, index, subIndex, dataType, 0);
	if (err)
		return 0xFF;
	for (;;)
	{
		res = getReadResultNetworkDict(&TestMaster_Data, nodeId, data, size, &abortCode);
        printf("sendsdo res = %x\n", res);
		if (res != SDO_UPLOAD_IN_PROGRESS)
			break;
		sleep_proc(1);
		continue;
	}
	closeSDOtransfer(&TestMaster_Data, nodeId, SDO_CLIENT);
	if (res == SDO_FINISHED)
		return 0;
	return 0xFF;
}

下面为主函数调用代码,这个是参考win32例程里面的,可以修改主站SDO对象字典的值,针对不同的从站比较方便,避免每次在对象字典里面修改;当然不加这些代码也是可以的,直接调用上面的readSDO(),只要在配置客户端SDO参数时,直接写上对应的COB-ID,比如我从站id是1,子索引1,2,3就写0x601和0x581和0x01即可。

UNS32 COB_ID_Client_to_Server_Transmit_SDO = 0x600 + nodeId;//客户端(主站)发送的COB-ID
    UNS32 COB_ID_Server_to_Client_Receive_SDO = 0x580 + nodeId;//服务器端(从站)返回的COB-ID
    UNS8 Node_ID_of_the_SDO_Server = nodeId;//从站ID
    UNS32 ExpectedSize = sizeof(UNS32);
    UNS32 ExpectedSizeNodeId = sizeof(UNS8);
        
    写SDO 索引1280 子索引1,2,3的参数
    if (OD_SUCCESSFUL == writeLocalDict(&TestMaster_Data, 0x1280, 1, &COB_ID_Client_to_Server_Transmit_SDO, &ExpectedSize, RW)
        && OD_SUCCESSFUL == writeLocalDict(&TestMaster_Data, 0x1280, 2, &COB_ID_Server_to_Client_Receive_SDO, &ExpectedSize, RW)
        && OD_SUCCESSFUL == writeLocalDict(&TestMaster_Data, 0x1280, 3, &Node_ID_of_the_SDO_Server, &ExpectedSizeNodeId, RW))
    {

        UNS32 size;
        UNS8 res;

        printf("\nnode_id: %d (%xh) info\n", (int)nodeId, (int)nodeId);
        printf("********************************************\n");
           
        size = sizeof(cNode_ID);
        res = ReadSDO(nodeId, 0x2003, 0x00, uint16, &cNode_ID, &size);
        printf("res = %x,cNode_ID: %x\n", res, cNode_ID);
           

        size = sizeof(cActualMosTemp);
        res = ReadSDO(nodeId, 0x2028, 0x00, uint16, &cActualMosTemp, &size);
        printf("res = %x,cActualMosTemp: %x\n", res, cActualMosTemp);
   /*     UNS32 abortCode = 0;    
        UNS8 sendsdodata[4] = { 0 };
        sendsdodata[0] = 0x33;
        sendsdodata[1] = 0x22;
        sendsdodata[2] = 0x00;
        sendsdodata[3] = 0x00;
        sleep_proc(10);

        WriteSDO(nodeId, 0x2001, 0x00, 2, uint16, &sendsdodata, 0);*/
        printf("********************************************\n");
    }
    else
    {
        printf("ERROR: Object dictionary access failed\n");
    }

结果如下:可以看到我们读从站id为1,索引为2003和2028的值的指令为601,而服务端(从站)也会返回一个581的数据帧,每个字节含义如图,快速SDO最多只能读写四个数据。注意:读写从站的对象字典的自定义的变量一定要根据从站在主站对象字典也要添加对应参数,如图。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

2.主站SDO写(下载服务)

对从站对象字典进行写数据和读类似,调用接口函数writeNetworkDict()向从站对象字典对应索引出写数据,通过getWriteResultNetworkDict()读取是否写成功。

UNS8 WriteSDO(UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS32 count, UNS8 dataType, void* data, UNS8 useBlockMode)
{
    UNS32 abortCode = 0;
    UNS8 res = SDO_DOWNLOAD_IN_PROGRESS;
    // Write SDO
    UNS8 err = writeNetworkDict(&TestMaster_Data, nodeId, index, subIndex, count, dataType, data, useBlockMode);
    if (err)
        return 0xFF;
    for (;;)
    {
        res = getWriteResultNetworkDict(&TestMaster_Data, nodeId, &abortCode);
        printf("write res = %x\n", res);
        if (res != SDO_DOWNLOAD_IN_PROGRESS)
            break;
        sleep_proc(1);
        continue;
    }
    closeSDOtransfer(&TestMaster_Data, nodeId, SDO_CLIENT);
    
    if (res == SDO_FINISHED)
        return 0;
    return 0xFF;
}

主函数中调用:

        UNS32 abortCode = 0;    
        UNS8 sendsdodata[4] = { 0 };
        sendsdodata[0] = 0x33;
        sendsdodata[1] = 0x22;
        sendsdodata[2] = 0x00;
        sendsdodata[3] = 0x00;
        sleep_proc(10);

        WriteSDO(nodeId, 0x2001, 0x00, 2, uint16, &sendsdodata, 0);

结果如下:向从站对象字典索引为2001处写两个字节的数据0x2233,从站返回写成功应答0x60。
在这里插入图片描述

五、主站PDO通信

1.PDO接收从站数据

这个主要是根据从站配置,我的从站设备会定时发送两个PDO数据包,所以主站配置两个接收PDO,并根据从站的映射数据来配置主站的映射数据;
如图:RPDO1的索引为0x1400,子索引0x01是发送PDO数据的从站COB-ID:0x180+id,不同的从站的id不同;总线上配置了接收PDO的节点,将根据COB-ID来接收对应节点数据,其他子索引可以默认,下图我是直接按照从站来配置的;
在这里插入图片描述
接收PDO映射参数必须和从设备的映射参数一一对应,即8个字节的PDO数据中从站是映射了几个变量,占几个字节,主站必须相同;配置完成后只要从站发送数据到总线,接收函数解析了对应的COB-ID后会把数据传到对应的PDO映射变量上;
在这里插入图片描述
结果如下:
在这里插入图片描述
在这里插入图片描述

2.PDO发送控制指令

我需要主站实时发送几个控制数据到从站用来控制从设备,使用SDO一次只能发送四个字节,再多就需要分段发送,不稳定,所以想要使用PDO发送;查看PDO发送类型,发现传输类型FF或FE,可以通过事件触发PDO传输(我从设备发送PDO采用的定时发送也是一种事件触发),即通过改变PDO映射参数的状态从而触发PDO传输,例如,我想控制电机使能,只需要将电机使能的变量映射到PDO发送上,然后在调用sendPDOEvent(),这时我一旦在上位机上改变了电机使能这个变量的值,就是自动发送一个PDO到总线上,只要对应的从设备配置一个接收PDO,并和主站一样映射电机使能参数即可。
在这里插入图片描述

我映射了四个变量一共8个字节,只要这四个变量有任何一个发送值改变,就会触发PDO发送;
在这里插入图片描述
增加了一个线程一直调用sendPDOevent()函数来检测值是否发生变化(也可以在值改变前调用),

DWORD WINAPI PDOEvent_Change_Of_State(LPVOID lpParam) {//监控PDO映射参数状态是否发生改变,若改变,发送一个PDO
    while (1) {

        sendPDOevent(&TestMaster_Data);
    }
    return 0;
}

在主函数中创建PDO触发线程,并将发送线程的优先级设为最高

    CreatePDOEventTask(&hThread2, PDOEvent_Change_Of_State);//创建PDO传输事件触发线程
    SetThreadPriority(&hThread2, THREAD_PRIORITY_HIGHEST);

主函数while循环中改变变量cMotorOperation和cReferenceSpeed的值;

            cMotorOperation = 0x02;
            printf("cMotorOperation: %x\n", cMotorOperation);
            sleep_proc(1000);
            cReferenceSpeed = 0xc489;
            printf("cReferenceSpeed: %x\n", cReferenceSpeed);
            cMotorOperation = 0x01;
            printf("cMotorOperation: %x\n", cMotorOperation);
            sleep_proc(1000);
            cReferenceSpeed = 0xc089;
            printf("cReferenceSpeed: %x\n", cReferenceSpeed);

结果如下:两个变量的值改变时,主站循环发送PDO
在这里插入图片描述在这里插入图片描述
从站也接收到这个值
在这里插入图片描述
在这里插入图片描述

以上功能是我主站需要的功能,所以只写了这么多,我也是刚接触canopen,导师需要,写的不是很严谨,搞这个也遇到不少坑,主要是基于PC端的主站这方面的资料太少了,大多数都是基于MCU开发的,记录一下我自己的成果,希望能帮到后面的朋友。

  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
### 回答1: Moons的CANopen主站是一款用于CANopen通信协议的主控设备。CANopen是一种针对控制器区域网络(CAN)的通信协议,用于实现工业自动化设备之间的数据交换和通信。 作为主站,Moons的CANopen主控设备具备以下功能。首先,它能够与其他CANopen设备进行数据通信,实现设备之间的数据传输和控制命令的发送与接收。通过CANopen协议,Moons的主站可以与多个从站设备进行连接,互相交换数据和信息。 其次,Moons的CANopen主站支持各种不同的CANopen通信对象和数据类型,包括进程变量、SDO(服务数据对象)、PDO(过程数据对象)等。它能够对这些对象进行配置和管理,达到对从站设备进行监控和控制的目的。同时,Moons的主站还支持CANopen网络管理协议,可以对设备进行配置和节点管理,并实现设备的启动和停止等操作。 此外,Moons的CANopen主站还具备网络诊断和错误处理能力。当从站设备发生错误或通信故障时,主站能够检测并进行相应的错误处理。它可以通过CANopen协议中定义的错误码和诊断信息,快速识别和定位问题,并采取相应的纠正措施。 总的来说,Moons的CANopen主站是一款功能强大的CANopen通信主控设备。它能够实现多个从站设备之间的数据交换和控制命令的发送与接收,并具备丰富的配置和管理能力,方便用户对设备进行监控和控制。通过其网络诊断和错误处理功能,主站能够快速识别和解决通信问题,提高设备的可靠性和稳定性。 ### 回答2: Moons的CANopen主站是一款先进的控制系统,用于管理和控制CANopen规约的设备网络。CANopen是一种广泛应用于工业领域的通信协议,能够实现设备之间的相互通信和数据交换。 Moons的CANopen主站具有以下特点和功能: 1. 灵活性:它支持多种CANopen网络拓扑结构,如主-从结构、多主结构等,能够适应不同的应用场景和网络配置。 2. 可扩展性:它可以连接多个CANopen从站设备,实现对这些设备的集中管理和控制,适用于大规模设备网络的场景。 3. 高性能:它使用先进的通信协议和算法,能够实现高速的数据传输和响应速度,确保设备之间的实时通信和高效运行。 4. 易用性:它提供友好的用户界面和配置工具,帮助用户轻松地进行网络配置、设备管理和参数设置,提高了设备的可操作性和易用性。 5. 可靠性:它具有自动诊断和故障检测功能,能够监测网络中的错误和异常情况,并及时采取相应的措施,提高了系统的可靠性和稳定性。 总之,Moons的CANopen主站是一款功能强大、性能优越的控制系统,能够实现对CANopen网络中的设备进行集中管理和控制,加强设备之间的通信和协同工作,提高了工业生产的效率和可靠性。 ### 回答3: moons是一个生产CanOpen主站设备的公司。CanOpen是一种面向现场总线的通信协议,常用于工业自动化领域。CanOpen主站是指能够通过CanOpen协议与从站(例如传感器、执行器等设备)进行通信的控制器或设备。 moons的CanOpen主站是一款功能强大、性能稳定的设备。它具有以下特点和优势: 首先,moons的CanOpen主站具有高度的兼容性。它支持与各种CanOpen从站设备进行通信,并兼容多种CanOpen标准和版本。这使得它可以与市场上广泛使用的CanOpen设备无缝集成,方便用户进行设备的组网和通信配置。 其次,moons的CanOpen主站具有灵活的通信功能。它支持多通道的连接,能够同时与多个CanOpen从站进行通信。同时,它支持多种通信速率和数据传输模式的配置,可以根据实际情况进行灵活调整,以满足不同应用场景的需求。 此外,moons的CanOpen主站还具有高度可靠的通信性能。它采用高性能的通信芯片和可靠的通信协议,能够实时监控从站设备的状态和数据,并能够进行快速响应和处理。这样可以保证通信的稳定性和可靠性,确保数据的准确传输和设备的正常工作。 最后,moons的CanOpen主站还提供了丰富的配置和监控功能。它具有友好的人机界面和易于操作的配置工具,用户可以通过简单的设置和调整来实现对主站和从站设备的配置和监控。同时,它还支持故障诊断和报警功能,能够及时发现和处理通信故障和异常情况。 总之,moons的CanOpen主站是一款功能强大、性能稳定的设备,能够满足工业自动化领域对CanOpen通信的需求。它的高兼容性、灵活性、可靠性和丰富的配置功能,使得用户可以方便地实现设备的组网和通信控制,提高工作效率和生产质量。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值