Wiznet makers

gavinchang

Published June 29, 2023 ©

109 UCC

25 WCC

68 VAR

0 Contests

4 Followers

0 Following

Original Link

POWERLINK protocol successful experience sharing in stm32 microcontroller + w5500 transplantation

POWERLINK protocol successful experience sharing in stm32 microcontroller + w5500 transplantation

COMPONENTS Hardware components

WIZnet - W5500

x 1


PROJECT DESCRIPTION

foreword


STM32F407ZGT6 chip resource 1M falsh, 192k memory is enough. The first set of Alientek's miniSTM32 development board chip model stm32F103RCT6 I bought, the chip resource is 256k flash, and the 48k ram is not enough (mainly because the ram is not enough). Because during the transplantation process, it was found that the powerlink protocol stack occupies a lot of memory, but the rom does not take up much.

 

Summarize the resource usage and share it with those in need for reference. (Resources include embedded system RTX kernel source code and slave station demo function source code)

 

Program Size: Code=94008 RO-data=15352 RW-data=4204 ZI-data=62212  

 

The memory usage of RW-data+ZI-data is 4204+62212 = 66416, more than 60K, mainly because the dictionary file takes up a lot of memory. Rom occupation: 94k.

 

The project is open source, welcome to test and evaluate. Open source address: powerlink-stm32: powerlink-stm32

 

GitHub - yangyongzhen/powerlink-stm32: openPOWERLINK stack on stm32 mcu transplant

 

The development board I use looks like this:

 

Migration project structure:

 

From the perspective of project structure, I put all the files involved in the change into the port folder separately. There are quite a lot of files involved, but fortunately, the amount of code is not too large. Using the relevant features of the RTX embedded kernel system that comes with Keil, porting is not difficult.

 

transplant process


protocol stack porting

 

For the porting process, refer to an article shared before "Guide to Migration of POWERLINK Protocol Source Code (Latest) on Stm32 Microcontroller" Migration Guide of POWERLINK Protocol Source Code (Latest) to Stm32 Microcontroller , and extract the relevant documents involved.

Shield the interface and compilation errors related to the system or driver, and establish the project directory structure.

 

netif-stm32.c and target-stm32.c have a small amount of code and are easy to port.

 

target-stm32.c mainly involves the implementation of target_msleep, target_enableGlobalInterrupt, target_getTickCount, etc. Use the relevant api of the RTX system to implement. The target_setIpAdrs interface is not required, just leave it blank.

 

target-mutex.c file migration:

 

/**
\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;
}

 

Using RTX's Mutex api interface, this part is easy to port.

 

In the circbuf-stm32.c file, it mainly involves locking and unlocking, and it is easy to transplant.

 

//------------------------------------------------------------------------------
/**
\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 file porting:

 

This refers to the implementation of eventkcal-win32.c, which is simpler than eventkcal-linux.c. Using the semaphore mechanism of RTX, it is not difficult to realize the replacement.

 

//------------------------------------------------------------------------------
/**
\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 file porting:

 

This is very important. Everything related to the underlying communication of the grid is in this file. Use the api provided by the w5500 module to operate the original MAC message frame. The mutexes and semaphores of the Linux system, such as pthread_mutex_lock and sem_post, are replaced by the relevant interfaces provided by 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;
 
}

 

Transplant from demo

 

Transplant the demo of the slave station, the demo of the slave station in the demo_cn_console folder, on the basis of the successful transplantation of the above protocol stack, the transplantation of this part of the demo of the slave station is very simple.

 

/*
** 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;
}

 

how to use


After completing the above porting process, it needs to be downloaded to the board to run. It is necessary to configure the serial port pins to facilitate the serial port output log debugging. The pins of spi also need to be configured according to the actual resources on the board. Then connect the network cable, first run the master station, then run the slave station, and print the log with the serial port for debugging.

Documents
Comments Write