Powerlink总线协议在QNX系统上的移植

一、硬件平台
通讯主站选用安装QNX系统的工控机,交叉编译工具选用QNX Momentics IDE,QNX Momentics IDE软件是基于WINDOWS系统下的QNX交叉开发环境,使用户可以使用熟悉的windows系统进行软件的开发工作。通讯从站选用以FPGA为核心的Powerlink控制板卡,由于FPGA的高通讯速率,使得Powerlink从站的反应时间大大缩减。
在此次实验中,主要的工作为将powerlink协议移植到QNX实时通信系统上,以及从站与主站的联合通信调试,以测试移植的实时性以及正确性。 二、Powerlink在QNX下实现的整体设计思路
Powerlink协议找在QNX系统上的移植可以分为内核空间的移植和用户空间的移植。所谓内核移植就是将Powerlink作为一个内核模块被执行,Powerlink协议运行在内核态,而用户空间的移植就是将协议找运行在用户空间,可直接与QNX应用进行绑定。两种移植方式的特点:
Powerlink协议找在用户空间实现的特点:
(1)实时性能可以满足一般的工业控制要求。
(2)借助开源网络libpacp库对以太网底层驱动进行管理,用户无需管理内核底层代码。
(3)系统维护较容易,调试简单。
(4)移植工作相对来说比较容易。
Powerlink协议找在内核空间实现的特点:
(1)可以提供高性能、高精度。
(2)需要对太网底层驱动进行重新编写,使底层驱动程序成为Powerlink协议的一部分。
(3)具有一定的开发难度,不容易调试。
本文采用用户空间的方式,开发难度小,周期短,采用此种方式,则要将powerlink协议栈移植到qnx系统上,需要调用libpcap库中提供的api函数来调用网卡驱动函数来进行数据包的收发。Libpcap是数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。libpcap主要由两部份组成:网络分接头(Network Tap)和数据过滤器(Packet Filter)。网络分接头从网络设备驱动程序中收集数据拷贝,过滤器决定是否接收该数据包。Libpcap利用BSD Packet Filter(BPF)算法对网卡接收到的链路层数据包进行过滤。BPF算法的基本思想是在有BPF监听的网络中,网卡驱动将接收到的数据包复制一份交给BPF过滤器,过滤器根据用户定义的规则决定是否接收此数据包以及需要拷贝该数据包的那些内容,然后将过滤后的数据给与过滤器相关联的上层应用程序。libpcap的包捕获机制就是在数据链路层加一个旁路处理。当一个数据包到达网络接口时,libpcap首先利用已经创建的Socket从链路层驱动程序中获得该数据包的拷贝,再通过Tap函数将数据包发给BPF过滤器。BPF过滤器根据用户已经定义好的过滤规则对数据包进行逐一匹配,匹配成功则放入内核缓冲区,并传递给用户缓冲区,匹配失败则直接丢弃。如果没有设置过滤规则,所有数据包都将放入内核缓冲区,并传递给用户层缓冲区。
我们通过改写Powerlin协议栈里数据包发送和接收函数来将Powerlink协议栈融入到QNX系统里,主要用到的API为:
1. pcap_sendpacket()函数,此函数通过调用libpacp库来实现网络数据包的发送。
2. pcap_recvpacket()函数, 此函数通过调用libpacp库来实现网络数据包的接收。
3. pcap_open_live()函数,获取网卡操作对象。
通过改写Powerlink的网络传输层函数,来实现对数据发送的调度。
部分代码实现:

///协议栈初始化函数,通过调用pcap_open_live 函数来与QNX系统的网卡相连
//---------------------------------------------------------
tEplKernel EdrvInit(tEdrvInitParam *pEdrvInitParam_p)
{
    tEplKernel                  Ret;
    char                        sErr_Msg[PCAP_ERRBUF_SIZE];
    struct sched_param          schedParam;

    Ret = kEplSuccessful;

    // clear instance structure
    EPL_MEMSET(&EdrvInstance_l, 0, sizeof (EdrvInstance_l));

    if (pEdrvInitParam_p->m_HwParam.m_pszDevName == NULL)
    {
        Ret = kEplEdrvInitError;
        goto Exit;
    }

    /* if no MAC address was specified read MAC address of used
     * ethernet interface
     */
    if ((pEdrvInitParam_p->m_abMyMacAddr[0] == 0) &&
        (pEdrvInitParam_p->m_abMyMacAddr[1] == 0) &&
        (pEdrvInitParam_p->m_abMyMacAddr[2] == 0) &&
        (pEdrvInitParam_p->m_abMyMacAddr[3] == 0) &&
        (pEdrvInitParam_p->m_abMyMacAddr[4] == 0) &&
        (pEdrvInitParam_p->m_abMyMacAddr[5] == 0)  )
    {   // read MAC address from controller
        getMacAdrs(pEdrvInitParam_p->m_HwParam.m_pszDevName,
                   pEdrvInitParam_p->m_abMyMacAddr);
    }

    // save the init data (with updated MAC address)
    EdrvInstance_l.m_initParam = *pEdrvInitParam_p;
///打开
    EdrvInstance_l.m_pPcap = pcap_open_live (
                        EdrvInstance_l.m_initParam.m_HwParam.m_pszDevName,
                        65535,  // snaplen
                        1,      // promiscuous mode
                        1,      // milli seconds read timeout
                        sErr_Msg
                    );

    if ( EdrvInstance_l.m_pPcap == NULL )
    {
        EPL_DBGLVL_ERROR_TRACE("%s() Error!! Can't open pcap: %s\n", __func__,
                                sErr_Msg);
        Ret = kEplEdrvInitError;
        goto Exit;
    }
#if (TARGET_SYSTEM != _QNX_)

    if (pcap_setdirection(EdrvInstance_l.m_pPcap, PCAP_D_OUT) < 0)
    {
        EPL_DBGLVL_ERROR_TRACE("%s() couldn't set PCAP direction\n", __func__);
        Ret = kEplEdrvInitError;
        goto Exit;
    }
#endif

    if (pthread_mutex_init(&EdrvInstance_l.m_mutex, NULL) != 0)
    {
        EPL_DBGLVL_ERROR_TRACE("%s() couldn't init mutex\n", __func__);
        Ret = kEplEdrvInitError;
        goto Exit;
    }

    if (sem_init(&EdrvInstance_l.m_syncSem, 0, 0) != 0)
    {
        EPL_DBGLVL_ERROR_TRACE("%s() couldn't init semaphore\n", __func__);
        Ret = kEplEdrvInitError;
        goto Exit;
    }

    if (pthread_create(&EdrvInstance_l.m_hThread, NULL,
                       EdrvWorkerThread,  &EdrvInstance_l) != 0)
    {
        EPL_DBGLVL_ERROR_TRACE("%s() Couldn't create worker thread!\n", __func__);
        Ret = kEplEdrvInitError;
        goto Exit;
    }

    schedParam.sched_priority = EPL_THREAD_PRIORITY_MEDIUM;

    if (pthread_setschedparam(EdrvInstance_l.m_hThread, SCHED_FIFO, &schedParam) != 0)
    {
        EPL_DBGLVL_ERROR_TRACE("%s() couldn't set thread scheduling parameters!\n",
                                __func__);
    }

    /* wait until thread is started */
    sem_wait(&EdrvInstance_l.m_syncSem);

#if (TARGET_SYSTEM == _QNX_)
    sleep(1);
#endif

Exit:
    return Ret;
}
//---------------------------------------------------------------------------
////网络数据包发送函数,通过调用pcap_sendpacket函数来实现网络数据包的发送
tEplKernel EdrvSendTxMsg(tEdrvTxBuffer *pBuffer_p)
{
    tEplKernel  Ret = kEplSuccessful;
    INT         iRet;

    FTRACE_MARKER("%s", __func__);

    if (pBuffer_p->m_BufferNumber.m_pVal != NULL)
    {
        Ret = kEplInvalidOperation;
        goto Exit;
    }

    if (getLinkStatus(EdrvInstance_l.m_initParam.m_HwParam.m_pszDevName) == FALSE)
    {
        if (pBuffer_p->m_pfnTxHandler != NULL)
        {
            pBuffer_p->m_pfnTxHandler(pBuffer_p);
        }
    }
    else
    {
        pthread_mutex_lock(&EdrvInstance_l.m_mutex);
        if (EdrvInstance_l.m_pTransmittedTxBufferLastEntry == NULL)
        {
            EdrvInstance_l.m_pTransmittedTxBufferLastEntry =
                EdrvInstance_l.m_pTransmittedTxBufferFirstEntry = pBuffer_p;
        }
        else
        {
            EdrvInstance_l.m_pTransmittedTxBufferLastEntry->m_BufferNumber.m_pVal = pBuffer_p;
            EdrvInstance_l.m_pTransmittedTxBufferLastEntry = pBuffer_p;
        }
        pthread_mutex_unlock(&EdrvInstance_l.m_mutex);

        iRet = pcap_sendpacket(EdrvInstance_l.m_pPcap, pBuffer_p->m_pbBuffer,
                               (int) pBuffer_p->m_uiTxMsgLen);
        if  (iRet != 0)
        {
            EPL_DBGLVL_EDRV_TRACE("%s() pcap_sendpacket returned %d (%s)\n",
                    __func__, iRet, pcap_geterr(EdrvInstance_l.m_pPcap));
            Ret = kEplInvalidOperation;
        }
    }

Exit:
    return Ret;
}

三、移植的不足与改进
将POWERLINK移植到QNX系统之后,需要验证POWERLINK协议能够在QNX系统下能够正常运行,且可实现移植前的功能。本章将对在QNX系统上实现的POWERLINK进行些功能性的验证。其中验证平台为:
主站:移植Powerlink协议成功的QNX系统。
从站:以FPGA为主控器的de2-115开发板,在此开发板中运行的是用nios软核实现的Powerlink协议。
诊断工具:我们使用的网络分析工具是Wireshark,Wireshark主要有两个功能:数据分析功能和数据过滤功能。
主站和从站之间使用RJ45网线连接。
本次测试的主要目的是由主站发送数据包给从站板卡,然后从站板卡接收数据后实现板卡上led灯的暗亮,同时为了验证从站给主站的数据发送的正确性,我们操作从站上的按钮来作为传输的数据给主站,并且在主站上显示由从站收到的数据。
由于此次移植是在半年前所做的,部分验证资料没有及时保留,这里只是简述其部分数据。移植的QNX主站和从站能够实现正常的通信,在单个节点的情况下,通信周期可以达到5ms,虽然实验数据不太令人满意,但是也是为Powerlink在QNX下的应用奠定了基础。
本次移植过程时间较短,移植的也比较粗糙,其中有很多不足以及有待改进的地方。
1.用libpcap方案去移植powerlink协议不针对特定的网卡。具有通用性。但是实时性能较低。
2.本次工作仅仅针对于io的实现。并未实现canopen 标准里的cia402标准,因此并不能作为powerlink协议的电机驱动器的主站。如果需要控制电机。则需要进一步的工作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章