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協議的電機驅動器的主站。如果需要控制電機。則需要進一步的工作。

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