POWERLINK プロトコルの stm32 マイクロコントローラー + w5500 移植における成功体験の共有

何晩も続けて寝て、週末にもう一日を費やした後、ついにパワーリンクプロトコルをマイクロコントローラーに移植することに成功しました。やめようかとも思いましたが、使えるかどうか考えた結果、やめて困っている人にシェアしましょう。諦めるのは難しいことではありませんが、粘り強く続けるのはかっこいいに違いありません。このプロトコルの移植テストには多額の費用がかかりました。stm32 開発ボードを 2 セット購入しましたが、1 セット目は移植中にメモリが足りないことが判明したため、より小さいモデルを購入し、最終的に stm32F407ZGT6 開発ボードを購入しました。

序文

STM32F407ZGT6 チップ リソース 1M falsh、192k メモリで十分です。私が購入した Alientek の miniSTM32 開発ボード チップ モデル stm32F103RCT6 の最初のセットは、チップ リソースが 256k フラッシュで、48k RAM では不十分です (主に RAM が不十分なため)。移植プロセス中に、powerlink プロトコル スタックが多くのメモリを占有するが、ROM はそれほど占有しないことが判明したためです。

リソースの使用状況を要約し、参照が必要な人々と共有します。(リソースには、組み込みシステム RTX カーネルのソース コードとスレーブ ステーションのデモ機能のソース コードが含まれます)

プログラムサイズ: コード=94008 RO-data=15352 RW-data=4204 ZI-data=62212  

RW データ + ZI データのメモリ使用量は 4204+62212 = 66416 で、60K を超えます。これは主に辞書ファイルが多くのメモリを消費するためです。ロム占有数:94k。

このプロジェクトはオープンソースなので、テストと評価を歓迎します。オープンソースアドレス: powerlink-stm32: powerlink-stm32

GitHub - yangyongzhen/powerlink-stm32: stm32 MCU 移植の openPOWERLINK スタック 

私が使用している開発ボードは次のようになります。

 移行プロジェクトの構造:

プロジェクト構造の観点から、変更に関係するすべてのファイルをポート フォルダーに個別に配置しました。かなり多くのファイルが関係していますが、幸いなことにコードの量はそれほど多くありません。Keil に付属する RTX 組み込みカーネル システムの関連機能を使用すれば、移植は難しくありません。

移植プロセス

プロトコルスタック移植

移植手順については、以前に共有した記事「Stm32 マイクロコントローラ上の POWERLINK プロトコル ソース コード (最新) の移行ガイド」POWERLINK プロトコル ソース コード (最新) の Stm32 マイクロコントローラへの移行ガイド を参照し、関連ドキュメントを抽出してください。

システムまたはドライバーに関連するインターフェイスおよびコンパイル エラーをシールドし、プロジェクトのディレクトリ構造を確立します。

netif-stm32.c と target-stm32.c はコード量が少ないため、移植が簡単です。

target-stm32.c には主に target_msleep、target_enableGlobalInterrupt、target_getTickCount などの実装が含まれます。RTX システムの関連 API を使用して実装します。target_setIpAdrs インターフェイスは必須ではありません。空白のままにしておきます。

target-mutex.c ファイルの移行:

/**
\brief  Create Mutex

The function creates a mutex.

\param[in]      mutexName_p         The name of the mutex to create.
\param[out]     pMutex_p            Pointer to store the created mutex.

\return The function returns a tOplkError error code.
\retval kErrorOk                    Mutex was successfully created.
\retval kErrorNoFreeInstance        An error occurred while creating the mutex.

\ingroup module_target
*/
//------------------------------------------------------------------------------
tOplkError target_createMutex(const char* mutexName_p,
                              OPLK_MUTEX_T* pMutex_p)
{

  UNUSED_PARAMETER(mutexName_p);
	pMutex_p =  osMutexNew(NULL);
	return kErrorOk;
}

//------------------------------------------------------------------------------
/**
\brief  Destroy Mutex

The function destroys a mutex.

\param[in]      mutexId_p           The ID of the mutex to destroy.

\ingroup module_target
*/
//------------------------------------------------------------------------------
void target_destroyMutex(OPLK_MUTEX_T mutexId_p)
{
//CloseHandle(mutexId_p);
	if(mutexId_p != NULL){
		osMutexDelete(mutexId_p);
	}
}

//------------------------------------------------------------------------------
/**
\brief  Lock Mutex

The function locks a mutex.

\param[in]      mutexId_p           The ID of the mutex to lock.

\return The function returns a tOplkError error code.
\retval kErrorOk                    Mutex was successfully locked.
\retval kErrorNoFreeInstance        An error occurred while locking the mutex.

\ingroup module_target
*/
//------------------------------------------------------------------------------
tOplkError target_lockMutex(OPLK_MUTEX_T mutexId_p)
{
  
    tOplkError  ret;
	  osStatus_t status;
 
    ret = kErrorOk;
		if (mutexId_p != NULL) {
			status = osMutexAcquire(mutexId_p, osWaitForever);
			if (status != osOK)  {
				// handle failure code
			}
		}    
    return ret;
}

//------------------------------------------------------------------------------
/**
\brief  Unlock Mutex

The function unlocks a mutex.

\param[in]      mutexId_p           The ID of the mutex to unlock.

\ingroup module_target
*/
//------------------------------------------------------------------------------
void target_unlockMutex(OPLK_MUTEX_T mutexId_p)
{
    //ReleaseMutex(mutexId_p);
	osStatus_t status;
 
  if (mutexId_p != NULL)  {
    status = osMutexRelease(mutexId_p);
    if (status != osOK)  {
      // handle failure code
    }
  }
}

int target_lock(void)
{
    target_enableGlobalInterrupt(FALSE);

    return 0;
}
int target_unlock(void)
{
    target_enableGlobalInterrupt(TRUE);

    return 0;
}

RTX の Mutex API インターフェイスを使用すると、この部分は簡単に移植できます。

circbuf-stm32.c ファイルでは、ロックとロック解除が主であり、移植は簡単です。

//------------------------------------------------------------------------------
/**
\brief  Lock circular buffer

The function enters a locked section of the circular buffer.

\param[in]      pInstance_p         Pointer to circular buffer instance.

\ingroup module_lib_circbuf
*/
//------------------------------------------------------------------------------
void circbuf_lock(tCircBufInstance* pInstance_p)
{
    osStatus_t              waitResult;
    tCircBufArchInstance*   pArchInstance;

    // Check parameter validity
    ASSERT(pInstance_p != NULL);

    pArchInstance = (tCircBufArchInstance*)pInstance_p->pCircBufArchInstance;
	  waitResult = osMutexAcquire(pArchInstance->lockMutex, osWaitForever);
		switch (waitResult) {
				case osOK:
					break;
				default:
					DEBUG_LVL_ERROR_TRACE("%s() Mutex wait unknown error! Error:%ld\n",
                                  __func__);
					break;
			}
}

//------------------------------------------------------------------------------
/**
\brief  Unlock circular buffer

The function leaves a locked section of the circular buffer.

\param[in]      pInstance_p         Pointer to circular buffer instance.

\ingroup module_lib_circbuf
*/
//------------------------------------------------------------------------------
void circbuf_unlock(tCircBufInstance* pInstance_p)
{
    tCircBufArchInstance* pArchInstance;

    // Check parameter validity
    ASSERT(pInstance_p != NULL);

    pArchInstance = (tCircBufArchInstance*)pInstance_p->pCircBufArchInstance;
    osMutexRelease(pArchInstance->lockMutex);
}

 Eventkcal-stm32.c ファイルの移植:

 これは、eventkcal-linux.c よりも単純な、eventkcal-win32.c の実装を指します。RTX のセマフォ機構を利用すれば、置き換えを実現することは難しくありません。

//------------------------------------------------------------------------------
/**
\brief  Ethernet driver initialization

This function initializes the Ethernet driver.

\param[in]      pEdrvInitParam_p    Edrv initialization parameters

\return The function returns a tOplkError error code.

\ingroup module_edrv
*/
//------------------------------------------------------------------------------
tOplkError edrv_init(const tEdrvInitParam* pEdrvInitParam_p)
{

    // Check parameter validity
    ASSERT(pEdrvInitParam_p != NULL);

    // Clear instance structure
    OPLK_MEMSET(&edrvInstance_l, 0, sizeof(edrvInstance_l));

    if (pEdrvInitParam_p->pDevName == NULL)
        return kErrorEdrvInit;

    // Save the init data
    edrvInstance_l.initParam = *pEdrvInitParam_p;

    edrvInstance_l.fStartCommunication = TRUE;
    edrvInstance_l.fThreadIsExited = FALSE;

    // If no MAC address was specified read MAC address of used
    // Ethernet interface
    if ((edrvInstance_l.initParam.aMacAddr[0] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[1] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[2] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[3] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[4] == 0) &&
        (edrvInstance_l.initParam.aMacAddr[5] == 0))
    {   // read MAC address from controller
        getMacAdrs(edrvInstance_l.initParam.pDevName,
                   edrvInstance_l.initParam.aMacAddr);
    }

	edrvInstance_l.sock = socket(0, Sn_MR_MACRAW, 0,0);
    if (edrvInstance_l.sock < 0)
    {
        DEBUG_LVL_ERROR_TRACE("%s() cannot open socket\n", __func__);
        return kErrorEdrvInit;
    }

    edrvInstance_l.hThread = osThreadNew(workerThread,&edrvInstance_l,NULL);
//    // wait until thread is started
//    sem_wait(&edrvInstance_l.syncSem);

    return kErrorOk;
}
//------------------------------------------------------------------------------
/**
\brief  Event handler thread function

This function contains the main function for the event handler thread.

\param[in]      arg                 Thread parameter. Used to get the instance structure.

\return The function returns the thread exit code.
*/
//------------------------------------------------------------------------------
static void eventThread(void* arg)
{
    const tEventkCalInstance*   pInstance = (const tEventkCalInstance*)arg;
    osStatus_t waitResult;

    DEBUG_LVL_EVENTK_TRACE("Kernel event thread %d waiting for events...\n", GetCurrentThreadId());
    while (!pInstance->fStopThread)
    {
      waitResult = osSemaphoreAcquire(pInstance->semKernelData, 100UL);       // wait for max. 10 ticks for semaphore token to get available
			switch (waitResult) {
				case osOK:
					if (eventkcal_getEventCountCircbuf(kEventQueueKInt) > 0)
					{
							eventkcal_processEventCircbuf(kEventQueueKInt);
					}
					else
					{
							if (eventkcal_getEventCountCircbuf(kEventQueueU2K) > 0)
							{
									eventkcal_processEventCircbuf(kEventQueueU2K);
							}
					}
					break;
				case osErrorResource:
					DEBUG_LVL_ERROR_TRACE("kernel event osErrorResource!\n");
					break;
				case osErrorParameter:
					DEBUG_LVL_ERROR_TRACE("kernel event osErrorParameter!\n");
					break;
				case osErrorTimeout:
					DEBUG_LVL_ERROR_TRACE("kernel event timeout!\n");
					break;
				default:
					DEBUG_LVL_ERROR_TRACE("%s() Semaphore wait unknown error! \n",
                                      __func__);
					break;
			}
    }

    DEBUG_LVL_EVENTK_TRACE("Kernel event thread is exiting!\n");

}

edrv-rawsock_stm32.c ファイルの移植:

これは非常に重要で、グリッドの基礎となる通信に関連するすべてがこのファイルに含まれています。w5500 モジュールが提供する API を使用して、元の MAC メッセージ フレームを操作します。pthread_mutex_lock や sem_post などの Linux システムのミューテックスとセマフォは、RTX が提供する関連インターフェイスに置き換えられます。

//------------------------------------------------------------------------------
/**
\brief  Send Tx buffer

This function sends the Tx buffer.

\param[in,out]  pBuffer_p           Tx buffer descriptor

\return The function returns a tOplkError error code.

\ingroup module_edrv
*/
//------------------------------------------------------------------------------
tOplkError edrv_sendTxBuffer(tEdrvTxBuffer* pBuffer_p)
{
    int    sockRet;

    // Check parameter validity
    ASSERT(pBuffer_p != NULL);

    FTRACE_MARKER("%s", __func__);

    if (pBuffer_p->txBufferNumber.pArg != NULL)
        return kErrorInvalidOperation;

    if (getLinkStatus(edrvInstance_l.initParam.pDevName) == FALSE)
    {
        /* If there is no link, we pretend that the packet is sent and immediately call
         * tx handler. Otherwise the stack would hang! */
        if (pBuffer_p->pfnTxHandler != NULL)
        {
            pBuffer_p->pfnTxHandler(pBuffer_p);
        }
    }
    else
    {
        //pthread_mutex_lock(&edrvInstance_l.mutex);
			  osMutexAcquire(edrvInstance_l.mutex,osWaitForever);
        if (edrvInstance_l.pTransmittedTxBufferLastEntry == NULL)
        {
            edrvInstance_l.pTransmittedTxBufferLastEntry = pBuffer_p;
            edrvInstance_l.pTransmittedTxBufferFirstEntry = pBuffer_p;
        }
        else
        {
            edrvInstance_l.pTransmittedTxBufferLastEntry->txBufferNumber.pArg = pBuffer_p;
            edrvInstance_l.pTransmittedTxBufferLastEntry = pBuffer_p;
        }
        //pthread_mutex_unlock(&edrvInstance_l.mutex);
				osMutexRelease(edrvInstance_l.mutex);

        sockRet = send(edrvInstance_l.sock, (u_char*)pBuffer_p->pBuffer, (int)pBuffer_p->txFrameSize);
        if (sockRet < 0)
        {
            DEBUG_LVL_EDRV_TRACE("%s() send() returned %d\n", __func__, sockRet);
            return kErrorInvalidOperation;
        }
        else
        {
            packetHandler((u_char*)&edrvInstance_l, sockRet, pBuffer_p->pBuffer);
        }
    }

    return kErrorOk;
}
//------------------------------------------------------------------------------
/**
\brief  Edrv worker thread

This function implements the edrv worker thread. It is responsible to receive frames

\param[in,out]  pArgument_p         User specific pointer pointing to the instance structure

\return The function returns a thread error code.
*/
//------------------------------------------------------------------------------
static void workerThread(void* pArgument_p)
{
    tEdrvInstance*  pInstance = (tEdrvInstance*)pArgument_p;
    int             rawSockRet;
    u_char          aBuffer[EDRV_MAX_FRAME_SIZE];

    DEBUG_LVL_EDRV_TRACE("%s(): ThreadId:%ld\n", __func__, syscall(SYS_gettid));

    // signal that thread is successfully started
    //sem_post(&pInstance->syncSem);
	 osSemaphoreRelease(pInstance->syncSem);

    while (edrvInstance_l.fStartCommunication)
    {
        rawSockRet = recvfrom(edrvInstance_l.sock, aBuffer, EDRV_MAX_FRAME_SIZE, 0, 0);
        if (rawSockRet > 0)
        {
            packetHandler(pInstance, rawSockRet, aBuffer);
        }
    }
    edrvInstance_l.fThreadIsExited = TRUE;

}

デモからの移植

スレーブ ステーションのデモを移植します。demo_cn_console フォルダ内のスレーブ ステーションのデモは、上記のプロトコル スタックの移植の成功に基づいて、スレーブ ステーションのデモのこの部分の移植は非常に簡単です。

/*
** main function
**
**  Arguments:
**      none
**   
*/ 
int main (int argc, char* argv[]) 
{
  tOplkError  ret = kErrorOk;
	tOptions    opts;

	// System Initialization
  SystemCoreClockUpdate();
	
  if (getOptions(argc, argv, &opts) < 0)
     return 0;
	
  LED_Initialize();
	uart_init();
	//stdout_init();
	printf("hello test\r\n");
	LED_On(2);
	spi_init();
	
	reset_w5500();
	set_w5500_mac();
	set_w5500_ip();
	
	eventlog_init(opts.logFormat,
                  opts.logLevel,
                  opts.logCategory,
                  (tEventlogOutputCb)console_printlogadd);

	initEvents(&fGsOff_l);

	printf("----------------------------------------------------\n");
	printf("openPOWERLINK console CN DEMO application\n");
	printf("Using openPOWERLINK stack: %s\n", oplk_getVersionString());
	printf("----------------------------------------------------\n");

	eventlog_printMessage(kEventlogLevelInfo,
												kEventlogCategoryGeneric,
												"demo_cn_console: Stack version:%s Stack configuration:0x%08X",
												oplk_getVersionString(),
												oplk_getStackConfiguration());

	ret = initPowerlink(CYCLE_LEN,
											opts.devName,
											aMacAddr_l,
											opts.nodeId);
	if (ret != kErrorOk)
			goto Exit;

	ret = initApp();
	if (ret != kErrorOk)
			goto Exit;
 
  osKernelInitialize();                       // Initialize CMSIS-RTOS
  osThreadNew(Main_Loop_Thread, NULL, NULL);   // Create application main thread
  osThreadNew(LED_Blink_PortE, NULL, NULL);   // Create application test thread
  osKernelStart();                            // Start thread execution
  for (;;) 
  {
    //Dummy infinite for loop.
  }
Exit:	
	 printf("openPOWERLINK console Exit\n");
	 shutdownApp();
   shutdownPowerlink();
	 return 0;
}

使い方

上記の移植プロセスが完了したら、実行するにはボードにダウンロードする必要があります。シリアル ポート出力ログのデバッグを容易にするために、シリアル ポート ピンを設定する必要があります。spi のピンも、ボード上の実際のリソースに応じて構成する必要があります。次に、ネットワーク ケーブルを接続し、最初にマスター ステーションを実行し、次にスレーブ ステーションを実行して、デバッグ用にシリアル ポートを使用してログを出力します。

おすすめ

転載: blog.csdn.net/qq8864/article/details/130918508