STM32H7+FreeRTOS+LwIP移植EtherCAT开源主站SOEM

代码下载什么的就不多说了,直接看需要移植修改的代码。

1、osal.c修改

c 复制代码
/******************************************************************************
 *                *          ***                    ***
 *              ***          ***                    ***
 * ***  ****  **********     ***        *****       ***  ****          *****
 * *********  **********     ***      *********     ************     *********
 * ****         ***          ***              ***   ***       ****   ***
 * ***          ***  ******  ***      ***********   ***        ****   *****
 * ***          ***  ******  ***    *************   ***        ****      *****
 * ***          ****         ****   ***       ***   ***       ****          ***
 * ***           *******      ***** **************  *************    *********
 * ***             *****        ***   *******   **  **  ******         *****
 *                           t h e  r e a l t i m e  t a r g e t  e x p e r t s
 *
 * http://www.rt-labs.com
 * Copyright (C) 2009. rt-labs AB, Sweden. All rights reserved.
 *------------------------------------------------------------------------------
 * $Id: osal.c 452 2013-02-26 21:02:58Z smf.arthur $
 *------------------------------------------------------------------------------
 */

#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOSConfig.h"
#include "osal.h"
#include <stdint.h>
#include <stdbool.h>

/* --- 常量定义 --- */
#define USECS_PER_SEC   1000000UL
#define USECS_PER_MSEC  1000UL
#define SECS_PER_DAY    86400UL

/* --- 静态函数声明 --- */
static void udelay(uint32_t us);
static inline uint32_t get_usec_per_tick(void);

/* 
 * 获取每个tick的微秒数(运行时计算避免除法)
 */
static inline uint32_t get_usec_per_tick(void) {
    static const uint32_t usec_per_tick = USECS_PER_SEC / configTICK_RATE_HZ;
    return usec_per_tick;
}

/**
 * @brief 获取高精度时间戳(微秒)
 * @return 从系统启动开始的微秒数
 */
uint64_t get_highres_time_us(void) {
    static uint32_t last_cnt = 0;
    static uint32_t overflow_count = 0;
    
    uint32_t current_cnt = TIM17->CNT;
    
    // 检测计数器溢出
    if (current_cnt < last_cnt) {
        overflow_count++;
    }
    last_cnt = current_cnt;
    
    // 计算总微秒数
    return (uint64_t)overflow_count * 65536 + current_cnt;
}

/**
 * @brief 高精度微秒级延时(基于 TIM17)
 * @param us 延时的微秒数
 */
void udelay(uint32_t us) {
    if (us == 0) return;
    
    // 获取 TIM17 当前计数值
    uint32_t start = TIM17->CNT;
    
    // 计算目标计数值(处理计数器溢出)
    uint32_t target = start + us;
    
    // 处理 16 位计数器溢出
    if (target > 0xFFFF) {
        // 等待计数器溢出
        while (TIM17->CNT >= start) {
            __NOP();
        }
        
        // 重新计算目标值
        target = us - (0xFFFF - start + 1);
        start = 0;
    }
    
    // 等待计数器达到目标值
    while (TIM17->CNT < target) {
        __NOP();
    }
}

/*
 * 获取当前时间(高精度实现)
 */
int gettimeofday(struct timeval *tp, void *tzp) {
    if (!tp) return -1;
    
    const TickType_t total_ticks = xTaskGetTickCount();
    const uint32_t ticks_per_sec = configTICK_RATE_HZ;
    
    tp->tv_sec = total_ticks / ticks_per_sec;
    tp->tv_usec = (uint32_t)((total_ticks % ticks_per_sec) * 
                             (uint64_t)USECS_PER_SEC / ticks_per_sec);
    
    (void)tzp;
    return 0;
}

/*
 * 微秒级睡眠
 */
int osal_usleep(uint32_t usec) {
    udelay(usec);
    return 0;
}

/*
 * 获取当前时间(标准函数封装)
 */
int osal_gettimeofday(struct timeval *tv, struct timezone *tz) {
    (void)tz; // 时区通常不使用
    return gettimeofday(tv, NULL);
}

/*
 * 获取当前时间(ECAT格式)
 */
ec_timet osal_current_time(void) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (ec_timet){tv.tv_sec, tv.tv_usec};
}

/*
 * 启动定时器
 */
void osal_timer_start(osal_timert *self, uint32_t timeout_us) {
    if (!self) return;
    
    self->start_tick = xTaskGetTickCount();
    const uint32_t usec_per_tick = get_usec_per_tick();
    
    // 四舍五入计算tick数
    self->timeout_ticks = (timeout_us + usec_per_tick / 2) / usec_per_tick;
    
    // 确保至少1个tick的延时
    if (self->timeout_ticks == 0 && timeout_us > 0) {
        self->timeout_ticks = 1;
    }
}

/*
 * 检查定时器是否过期
 */
bool osal_timer_is_expired(const osal_timert *self) {
    if (!self) return true;
    
    const TickType_t current = xTaskGetTickCount();
    const TickType_t elapsed = current - self->start_tick;
    
    // 处理tick计数器溢出情况
    if (elapsed > current) {
        // 发生溢出时,elapsed值将大于当前值
        return true;
    }
    
    return (elapsed >= self->timeout_ticks);
}

2、oshw.c 增加大小端转换宏定义

c 复制代码
#ifndef htons
#define htons(x) ((((x)&0xff)<<8)|(((x)&0xff00)>>8))
#endif

#ifndef ntohs
#define ntohs(x) htons(x)
#endif

#ifndef htonl
#define htonl(x) ((((x)&0xff)<<24)| \
                 (((x)&0xff00)<<8) | \
                 (((x)&0xff0000)>>8) | \
                 (((x)&0xff000000)>>24))
#endif

#ifndef ntohl
#define ntohl(x) htonl(x)
#endif

3、nicdrv.c

在nicdrv.h增加FreeRTOS的互斥量操作宏定义

c 复制代码
#include "FreeRTOS.h"
#include "semphr.h"

// 定义信号量类型
typedef SemaphoreHandle_t mtx_t;

// 创建互斥锁
#define mtx_create() xSemaphoreCreateMutex()

// 锁定互斥锁(带超时)
#define mtx_lock(mutex) (xSemaphoreTake((mutex), portMAX_DELAY) == pdPASS)

// 解锁互斥锁
#define mtx_unlock(mutex) (xSemaphoreGive(mutex) == pdPASS)

// 销毁互斥锁
#define mtx_destroy(mutex) vSemaphoreDelete(mutex)
c 复制代码
/*
 * Simple Open EtherCAT Master Library 
 *
 * File    : nicdrv.c
 * Version : 1.3.0
 * Date    : 24-02-2013
 * Copyright (C) 2005-2013 Speciaal Machinefabriek Ketels v.o.f.
 * Copyright (C) 2005-2013 Arthur Ketels
 * Copyright (C) 2008-2009 TU/e Technische Universiteit Eindhoven 
 *
 * SOEM is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2 as published by the Free
 * Software Foundation.
 *
 * SOEM is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * As a special exception, if other files instantiate templates or use macros
 * or inline functions from this file, or you compile this file and link it
 * with other works to produce a work based on this file, this file does not
 * by itself cause the resulting work to be covered by the GNU General Public
 * License. However the source code for this file must still be made available
 * in accordance with section (3) of the GNU General Public License.
 *
 * This exception does not invalidate any other reasons why a work based on
 * this file might be covered by the GNU General Public License.
 *
 * The EtherCAT Technology, the trade name and logo "EtherCAT" are the intellectual
 * property of, and protected by Beckhoff Automation GmbH. You can use SOEM for
 * the sole purpose of creating, using and/or selling or otherwise distributing
 * an EtherCAT network master provided that an EtherCAT Master License is obtained
 * from Beckhoff Automation GmbH.
 *
 * In case you did not receive a copy of the EtherCAT Master License along with
 * SOEM write to Beckhoff Automation GmbH, Eiserstraße 5, D-33415 Verl, Germany
 * (www.beckhoff.com).
 */

/** \file
 * \brief
 * EtherCAT RAW socket driver.
 *
 * Low level interface functions to send and receive EtherCAT packets.
 * EtherCAT has the property that packets are only send by the master,
 * and the send packets allways return in the receive buffer.
 * There can be multiple packets "on the wire" before they return.
 * To combine the received packets with the original send packets a buffer
 * system is installed. The identifier is put in the index item of the
 * EtherCAT header. The index is stored and compared when a frame is recieved.
 * If there is a match the packet can be combined with the transmit packet
 * and returned to the higher level function.
 *
 * The socket layer can exhibit a reversal in the packet order (rare).
 * If the Tx order is A-B-C the return order could be A-C-B. The indexed buffer
 * will reorder the packets automatically.
 *
 * The "redundant" option will configure two sockets and two NIC interfaces.
 * Slaves are connected to both interfaces, one on the IN port and one on the
 * OUT port. Packets are send via both interfaces. Any one of the connections
 * (also an interconnect) can be removed and the slaves are still serviced with
 * packets. The software layer will detect the possible failure modes and
 * compensate. If needed the packets from interface A are resend through interface B.
 * This layer is fully transparent for the higher layers.
 */
//主要是网络数据收发
#include <stdio.h>
#include <string.h>
#include "osal.h"
#include "oshw.h"
#include "soem_hook.h"

#ifndef MAX
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#endif

/** Redundancy modes */
enum
{
   /** No redundancy, single NIC mode */
   ECT_RED_NONE,
   /** Double redundant NIC connecetion */
   ECT_RED_DOUBLE
};

/** Primary source MAC address used for EtherCAT.
 * This address is not the MAC address used from the NIC.
 * EtherCAT does not care about MAC addressing, but it is used here to
 * differentiate the route the packet traverses through the EtherCAT
 * segment. This is needed to find out the packet flow in redundant
 * configurations. */
const uint16 priMAC[3] = { 0x0101, 0x0101, 0x0101 };
/** Secondary source MAC address used for EtherCAT. */
const uint16 secMAC[3] = { 0x0404, 0x0404, 0x0404 };

/** second MAC word is used for identification */
#define RX_PRIM priMAC[1]
/** second MAC word is used for identification */
#define RX_SEC secMAC[1]


/** Basic setup to connect NIC to socket.
 * @param[in] port        = port context struct
 * @param[in] ifname      = Name of NIC device, f.e. "eth0"
 * @param[in] secondary   = if >0 then use secondary stack instead of primary
 * @return >0 if succeeded
 */
int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) 
{
   int i;
   int rVal;
   int *psock;

   port->getindex_mutex = mtx_create();
   port->tx_mutex = mtx_create();
   port->rx_mutex = mtx_create();

//   rVal = bfin_EMAC_init((uint8_t *)priMAC);
//   if (rVal != 0)
//      return 0;
      
   if (secondary)
   {
      /* secondary port stuct available? */
      if (port->redport)
      {
         /* when using secondary socket it is automatically a redundant setup */
         psock = &(port->redport->sockhandle);
         *psock = -1;
         port->redstate                   = ECT_RED_DOUBLE;
         port->redport->stack.sock        = &(port->redport->sockhandle);
         port->redport->stack.txbuf       = &(port->txbuf);
         port->redport->stack.txbuflength = &(port->txbuflength);
         port->redport->stack.tempbuf     = &(port->redport->tempinbuf);
         port->redport->stack.rxbuf       = &(port->redport->rxbuf);
         port->redport->stack.rxbufstat   = &(port->redport->rxbufstat);
         port->redport->stack.rxsa        = &(port->redport->rxsa);
      }
      else
      {
         /* fail */
         return 0;
      }
   }
   else
   {
      port->getindex_mutex = mtx_create();
      port->tx_mutex = mtx_create();
      port->rx_mutex = mtx_create();
      port->sockhandle        = -1;
      port->lastidx           = 0;
      port->redstate          = ECT_RED_NONE;
      port->stack.sock        = &(port->sockhandle);
      port->stack.txbuf       = &(port->txbuf);
      port->stack.txbuflength = &(port->txbuflength);
      port->stack.tempbuf     = &(port->tempinbuf);
      port->stack.rxbuf       = &(port->rxbuf);
      port->stack.rxbufstat   = &(port->rxbufstat);
      port->stack.rxsa        = &(port->rxsa);
      psock = &(port->sockhandle);
   }  

	 if(install_hook(port, ifname)==0){
			return 0; //fail
		}
	 
   /* setup ethernet headers in tx buffers so we don't have to repeat it */
   for (i = 0; i < EC_MAXBUF; i++)
   {
      ec_setupheader(&(port->txbuf[i]));
      port->rxbufstat[i] = EC_BUF_EMPTY;
   }
   ec_setupheader(&(port->txbuf2));

   return 1;
}

/** Close sockets used
 * @param[in] port        = port context struct
 * @return 0
 */
int ecx_closenic(ecx_portt *port) 
{
	mtx_destroy(port->getindex_mutex);
	mtx_destroy(port->tx_mutex);
	mtx_destroy(port->rx_mutex);
	uninstall_hook(port);
	return 0;
}

/** Fill buffer with ethernet header structure.
 * Destination MAC is allways broadcast.
 * Ethertype is allways ETH_P_ECAT.
 * @param[out] p = buffer
 */
void ec_setupheader(void *p) 
{
   ec_etherheadert *bp;
   bp = p;
   bp->da0 = oshw_htons(0xffff);
   bp->da1 = oshw_htons(0xffff);
   bp->da2 = oshw_htons(0xffff);
   bp->sa0 = oshw_htons(priMAC[0]);
   bp->sa1 = oshw_htons(priMAC[1]);
   bp->sa2 = oshw_htons(priMAC[2]);
   bp->etype = oshw_htons(ETH_P_ECAT);
}

/** Get new frame identifier index and allocate corresponding rx buffer.
 * @param[in] port        = port context struct
 * @return new index.
 */
int ecx_getindex(ecx_portt *port)
{
   int idx;
   int cnt;

   mtx_lock (port->getindex_mutex);

   idx = port->lastidx + 1;
   /* index can't be larger than buffer array */
   if (idx >= EC_MAXBUF) 
   {
      idx = 0;
   }
   cnt = 0;
   /* try to find unused index */
   while ((port->rxbufstat[idx] != EC_BUF_EMPTY) && (cnt < EC_MAXBUF))
   {
      idx++;
      cnt++;
      if (idx >= EC_MAXBUF) 
      {
         idx = 0;
      }
   }
	 
   port->rxbufstat[idx] = EC_BUF_ALLOC;
   if (port->redstate != ECT_RED_NONE)
   {
      port->redport->rxbufstat[idx] = EC_BUF_ALLOC;
   }
   port->lastidx = idx;

   mtx_unlock (port->getindex_mutex);
   
   return idx;
}

/** Set rx buffer status.
 * @param[in] port     = port context struct
 * @param[in] idx      = index in buffer array
 * @param[in] bufstat  = status to set
 */
void ecx_setbufstat(ecx_portt *port, int idx, int bufstat)
{
   port->rxbufstat[idx] = bufstat;
   if (port->redstate != ECT_RED_NONE)
   {
      port->redport->rxbufstat[idx] = bufstat;
   }
}

/** Transmit buffer over socket (non blocking).
 * @param[in] port        = port context struct
 * @param[in] idx         = index in tx buffer array
 * @param[in] stacknumber  = 0=Primary 1=Secondary stack
 * @return socket send result
 */
int ecx_outframe(ecx_portt *port, int idx, int stacknumber)
{
   int lp, rval;
   ec_stackT *stack;

   if (!stacknumber)
   {
      stack = &(port->stack);
   }
   else
   {
      stack = &(port->redport->stack);
   }
   lp = (*stack->txbuflength)[idx];
   rval = net_send((*stack->txbuf)[idx], lp);
   (*stack->rxbufstat)[idx] = EC_BUF_TX;

   return rval;
}

/** Transmit buffer over socket (non blocking).
 * @param[in] port        = port context struct
 * @param[in] idx = index in tx buffer array
 * @return socket send result
 */
int ecx_outframe_red(ecx_portt *port, int idx)
{
   ec_comt *datagramP;
   ec_etherheadert *ehp;
   int rval;

   ehp = (ec_etherheadert *)&(port->txbuf[idx]);
   /* rewrite MAC source address 1 to primary */
   ehp->sa1 = oshw_htons(priMAC[1]);
   /* transmit over primary socket*/
   rval = ecx_outframe(port, idx, 0);
   if (port->redstate != ECT_RED_NONE)
   {   
	   mtx_lock (port->tx_mutex);
      ehp = (ec_etherheadert *)&(port->txbuf2);
      /* use dummy frame for secondary socket transmit (BRD) */
      datagramP = (ec_comt*)&(port->txbuf2[ETH_HEADERSIZE]);
      /* write index to frame */
      datagramP->index = idx;
      /* rewrite MAC source address 1 to secondary */
      ehp->sa1 = oshw_htons(secMAC[1]);
      /* transmit over secondary socket */
      //send(sockhandle2, &ec_txbuf2, ec_txbuflength2 , 0);
      // OBS! redundant not ACTIVE for BFIN, just added to compile
//      ASSERT (0);
      net_send(port->txbuf2, port->txbuflength2);
      mtx_unlock (port->tx_mutex);
      port->redport->rxbufstat[idx] = EC_BUF_TX;
   }   
   
   return rval;
}

/** Non blocking read of socket. Put frame in temporary buffer.
 * @param[in] port        = port context struct
 * @param[in] stacknumber = 0=primary 1=secondary stack
 * @return >0 if frame is available and read
 */
static int ecx_recvpkt(ecx_portt *port, int stacknumber)
{
   int lp, bytesrx;
   ec_stackT *stack;

   if (!stacknumber)
   {
      stack = &(port->stack);
   }
   else
   {
      stack = &(port->redport->stack);
   }
   lp = sizeof(port->tempinbuf);
   bytesrx = net_recv((*stack->tempbuf), lp);
   port->tempinbufs = bytesrx;

   return (bytesrx > 0);
}

/** Non blocking receive frame function. Uses RX buffer and index to combine
 * read frame with transmitted frame. To compensate for received frames that
 * are out-of-order all frames are stored in their respective indexed buffer.
 * If a frame was placed in the buffer previously, the function retreives it
 * from that buffer index without calling ec_recvpkt. If the requested index
 * is not already in the buffer it calls ec_recvpkt to fetch it. There are
 * three options now, 1 no frame read, so exit. 2 frame read but other
 * than requested index, store in buffer and exit. 3 frame read with matching
 * index, store in buffer, set completed flag in buffer status and exit.
 * 
 * @param[in] port        = port context struct
 * @param[in] idx         = requested index of frame
 * @param[in] stacknumber = 0=primary 1=secondary stack
 * @return Workcounter if a frame is found with corresponding index, otherwise
 * EC_NOFRAME or EC_OTHERFRAME.
 */
int ecx_inframe(ecx_portt *port, int idx, int stacknumber)
{
   uint16  l;
   int     rval;
   uint8   idxf;
   ec_etherheadert *ehp;
   ec_comt *ecp;
   ec_stackT *stack;
   ec_bufT *rxbuf;

   if (!stacknumber)
   {
      stack = &(port->stack);
   }
   else
   {
      stack = &(port->redport->stack);
   }
   rval = EC_NOFRAME;
   rxbuf = &(*stack->rxbuf)[idx];
   /* check if requested index is already in buffer ? */
   if ((idx < EC_MAXBUF) && (   (*stack->rxbufstat)[idx] == EC_BUF_RCVD)) 
   {
      l = (*rxbuf)[0] + ((uint16)((*rxbuf)[1] & 0x0f) << 8);
      /* return WKC */
      rval = ((*rxbuf)[l] + ((uint16)(*rxbuf)[l + 1] << 8));
      /* mark as completed */
      (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE;
   }
   else 
   {
      mtx_lock (port->rx_mutex);
      /* non blocking call to retrieve frame from socket */
      if (ecx_recvpkt(port, stacknumber)) 
      {
         rval = EC_OTHERFRAME;
         ehp =(ec_etherheadert*)(stack->tempbuf);
         /* check if it is an EtherCAT frame */
         if (ehp->etype == oshw_htons(ETH_P_ECAT))
         {
            ecp =(ec_comt*)(&(*stack->tempbuf)[ETH_HEADERSIZE]); 
            l = etohs(ecp->elength) & 0x0fff;
            idxf = ecp->index;
            /* found index equals reqested index ? */
            if (idxf == idx) 
            {
               /* yes, put it in the buffer array (strip ethernet header) */
               memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idx] - ETH_HEADERSIZE);
               /* return WKC */
               rval = ((*rxbuf)[l] + ((uint16)((*rxbuf)[l + 1]) << 8));
               /* mark as completed */
               (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE;
               /* store MAC source word 1 for redundant routing info */
               (*stack->rxsa)[idx] = oshw_ntohs(ehp->sa1);
            }
            else 
            {
               /* check if index exist? */
               if (idxf < EC_MAXBUF) 
               {
                  rxbuf = &(*stack->rxbuf)[idxf];
                  /* put it in the buffer array (strip ethernet header) */
                  memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idxf] - ETH_HEADERSIZE);
                  /* mark as received */
                  (*stack->rxbufstat)[idxf] = EC_BUF_RCVD;
                  (*stack->rxsa)[idxf] = oshw_ntohs(ehp->sa1);
               }
               else 
               {
                  /* strange things happend */
               }
            }
         }
      }
      mtx_unlock (port->rx_mutex);
      
   }
   
   /* WKC if mathing frame found */
   return rval;
}

/** Blocking redundant receive frame function. If redundant mode is not active then
 * it skips the secondary stack and redundancy functions. In redundant mode it waits
 * for both (primary and secondary) frames to come in. The result goes in an decision
 * tree that decides, depending on the route of the packet and its possible missing arrival,
 * how to reroute the original packet to get the data in an other try. 
 *
 * @param[in] port        = port context struct
 * @param[in] idx = requested index of frame
 * @param[in] timer = absolute timeout time
 * @return Workcounter if a frame is found with corresponding index, otherwise
 * EC_NOFRAME.
 */
static int ecx_waitinframe_red(ecx_portt *port, int idx, const osal_timert timer)
{
   int wkc  = EC_NOFRAME;
   int wkc2 = EC_NOFRAME;
   int primrx, secrx;
   
   /* if not in redundant mode then always assume secondary is OK */
   if (port->redstate == ECT_RED_NONE)
   {
      wkc2 = 0;
   }
   do 
   {
      /* only read frame if not already in */
      if (wkc <= EC_NOFRAME)
      {
         wkc  = ecx_inframe(port, idx, 0);
      }
      /* only try secondary if in redundant mode */
      if (port->redstate != ECT_RED_NONE)
      {   
         /* only read frame if not already in */
         if (wkc2 <= EC_NOFRAME)
            wkc2 = ecx_inframe(port, idx, 1);
      }    
   /* wait for both frames to arrive or timeout */   
   } while (((wkc <= EC_NOFRAME) || (wkc2 <= EC_NOFRAME)) && (osal_timer_is_expired(&timer) == FALSE));
   /* only do redundant functions when in redundant mode */
   if (port->redstate != ECT_RED_NONE)
   {
      /* primrx if the reveived MAC source on primary socket */
      primrx = 0;
      if (wkc > EC_NOFRAME) 
      {  
         primrx = port->rxsa[idx];
      }
      /* secrx if the reveived MAC source on psecondary socket */
      secrx = 0;
      if (wkc2 > EC_NOFRAME) 
      {
         secrx = port->redport->rxsa[idx];
      }
      /* primary socket got secondary frame and secondary socket got primary frame */
      /* normal situation in redundant mode */
      if ( ((primrx == RX_SEC) && (secrx == RX_PRIM)) )
      {
         /* copy secondary buffer to primary */
         memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
         wkc = wkc2;
      }    
      /* primary socket got nothing or primary frame, and secondary socket got secondary frame */
      /* we need to resend TX packet */ 
      if ( ((primrx == 0) && (secrx == RX_SEC)) ||
           ((primrx == RX_PRIM) && (secrx == RX_SEC)) )
      {
          osal_timert read_timer;

         /* If both primary and secondary have partial connection retransmit the primary received
          * frame over the secondary socket. The result from the secondary received frame is a combined
          * frame that traversed all slaves in standard order. */
         if ( (primrx == RX_PRIM) && (secrx == RX_SEC) )
         {   
            /* copy primary rx to tx buffer */
            memcpy(&(port->txbuf[idx][ETH_HEADERSIZE]), &(port->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
         }
         osal_timer_start(&read_timer, EC_TIMEOUTRET);
         /* resend secondary tx */
         ecx_outframe(port, idx, 1);
         do 
         {
            /* retrieve frame */
            wkc2 = ecx_inframe(port, idx, 1);
         } while ((wkc2 <= EC_NOFRAME) && (osal_timer_is_expired(&read_timer) == FALSE));
         if (wkc2 > EC_NOFRAME)
         {   
            /* copy secondary result to primary rx buffer */
            memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE);
            wkc = wkc2;
         }   
      }      
   }
   
   /* return WKC or EC_NOFRAME */
   return wkc;
}   

/** Blocking receive frame function. Calls ec_waitinframe_red().
 * @param[in] port        = port context struct
 * @param[in] idx       = requested index of frame
 * @param[in] timeout   = timeout in us
 * @return Workcounter if a frame is found with corresponding index, otherwise
 * EC_NOFRAME.
 */
int ecx_waitinframe(ecx_portt *port, int idx, int timeout)
{
   int wkc;
   osal_timert timer;
   
   osal_timer_start (&timer, timeout);
   wkc = ecx_waitinframe_red(port, idx, timer);
   /* if nothing received, clear buffer index status so it can be used again */
   if (wkc <= EC_NOFRAME) 
   {
      ecx_setbufstat(port, idx, EC_BUF_EMPTY);
   }
   
   return wkc;
}

/** Blocking send and recieve frame function. Used for non processdata frames.
 * A datagram is build into a frame and transmitted via this function. It waits
 * for an answer and returns the workcounter. The function retries if time is
 * left and the result is WKC=0 or no frame received.
 *
 * The function calls ec_outframe_red() and ec_waitinframe_red().
 *
 * @param[in] port        = port context struct
 * @param[in] idx      = index of frame
 * @param[in] timeout  = timeout in us
 * @return Workcounter or EC_NOFRAME
 */
int ecx_srconfirm(ecx_portt *port, int idx, int timeout)
{
   int wkc = EC_NOFRAME;
   osal_timert timer;

   osal_timer_start(&timer, timeout);
   do 
   {
      osal_timert read_timer;

      /* tx frame on primary and if in redundant mode a dummy on secondary */
      ecx_outframe_red(port, idx);
      osal_timer_start(&read_timer, MIN(timeout, EC_TIMEOUTRET));
      /* get frame from primary or if in redundant mode possibly from secondary */
      wkc = ecx_waitinframe_red(port, idx, read_timer);
   /* wait for answer with WKC>0 or otherwise retry until timeout */   
   } while ((wkc <= EC_NOFRAME) && (osal_timer_is_expired(&timer) == FALSE));
   /* if nothing received, clear buffer index status so it can be used again */
   if (wkc <= EC_NOFRAME) 
   {
      ecx_setbufstat(port, idx, EC_BUF_EMPTY);
   }
   
   return wkc;
}


#ifdef EC_VER1
int ec_setupnic(const char *ifname, int secondary)
{
   return ecx_setupnic(&ecx_port, ifname, secondary);
}

int ec_closenic(void)
{
   return ecx_closenic(&ecx_port);
}

int ec_getindex(void)
{
   return ecx_getindex(&ecx_port);
}

void ec_setbufstat(int idx, int bufstat)
{
   ecx_setbufstat(&ecx_port, idx, bufstat);
}

int ec_outframe(int idx, int stacknumber)
{
   return ecx_outframe(&ecx_port, idx, stacknumber);
}

int ec_outframe_red(int idx)
{
   return ecx_outframe_red(&ecx_port, idx);
}

int ec_inframe(int idx, int stacknumber)
{
   return ecx_inframe(&ecx_port, idx, stacknumber);
}

int ec_waitinframe(int idx, int timeout)
{
   return ecx_waitinframe(&ecx_port, idx, timeout);
}

int ec_srconfirm(int idx, int timeout)
{
   return ecx_srconfirm(&ecx_port, idx, timeout);
}
#endif

4、创建一个hook源文件,使用LwIP协议栈底层hook函数来处理PHY芯片的收发。

c 复制代码
#include "lwip/netif.h"
#include "lwip/pbuf.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"


#include "osal.h"

#define HOOK_RX_BUFSIZE 10

static uint8_t netfrmbuf[HOOK_RX_BUFSIZE][1540];
static int netfrmbuf_cnt[HOOK_RX_BUFSIZE];
static int netfrm_head = 0;
static int netfrm_tail = 0;
static bool netfrm_full = false;

static struct netif *target_netif = NULL;
static netif_input_fn orig_input = NULL;
static netif_linkoutput_fn orig_linkoutput = NULL;  // 正确类型声明
static SemaphoreHandle_t buf_mutex = NULL;

/******************************************************************************
* Hook Functions
******************************************************************************/
// 修正为正确的参数和返回类型
static err_t _netif_linkoutput(struct netif *netif, struct pbuf *p) {
    return orig_linkoutput(netif, p);
}

static err_t _netif_input(struct pbuf *p, struct netif *inp) {
    if (p->tot_len >= 14) {
        uint8_t *data = (uint8_t*)p->payload;
        
        if (data[12] == 0x88 && data[13] == 0xa4) { // EtherCAT frame
            xSemaphoreTake(buf_mutex, portMAX_DELAY);
            if (!netfrm_full) {
                pbuf_copy_partial(p, netfrmbuf[netfrm_tail], p->tot_len, 0);
                netfrmbuf_cnt[netfrm_tail] = p->tot_len;
                
                netfrm_tail = (netfrm_tail + 1) % HOOK_RX_BUFSIZE;
                netfrm_full = (netfrm_tail == netfrm_head);
            }
            xSemaphoreGive(buf_mutex);
        }
    }
    return orig_input(p, inp);
}

/******************************************************************************
* Hook Management
******************************************************************************/
int install_hook(const char *ifname) {
    if (buf_mutex == NULL) {
        buf_mutex = xSemaphoreCreateMutex();
        if (!buf_mutex) return 0;
    }

    target_netif = netif_find(ifname);
    if (!target_netif) {
        EC_PRINT("[HOOK] Netif %s not found\n", ifname);
        return 0;
    }

    taskENTER_CRITICAL();
    // 确保使用正确的类型匹配
    orig_linkoutput = target_netif->linkoutput;   // 2个参数的linkoutput
    orig_input = target_netif->input;
    
    target_netif->linkoutput = _netif_linkoutput;  // 2个参数
    target_netif->input = _netif_input;
    taskEXIT_CRITICAL();

    EC_PRINT("[HOOK] Installed on %s (0x%p)\n", ifname, (void*)target_netif);
    return 1;
}

int uninstall_hook(void) {
    if (!target_netif) return 0;

    taskENTER_CRITICAL();
    target_netif->input = orig_input;
    target_netif->linkoutput = orig_linkoutput;
    target_netif = NULL;
    taskEXIT_CRITICAL();

    EC_PRINT("[HOOK] Uninstalled\n");
    return 1;
}

/******************************************************************************
* Network Operations
******************************************************************************/
int net_send(uint8_t *data, int len) {
    if (len <= 0 || !target_netif) return -1;

    struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
    if (!p) {
        EC_PRINT("[SEND] PBuf alloc failed\n");
        return -1;
    }
    
    pbuf_take(p, data, len);
    err_t err = _netif_linkoutput(target_netif, p);
    pbuf_free(p);
    
    return (err == ERR_OK) ? len : -1;
}

int net_recv(uint8_t *data, int max_len) {
    if (!data || max_len <= 0) return -1;
    
    xSemaphoreTake(buf_mutex, portMAX_DELAY);
    
    if (netfrm_head == netfrm_tail && !netfrm_full) {
        xSemaphoreGive(buf_mutex);
        return 0;
    }
    
    int total = netfrmbuf_cnt[netfrm_head];
    if (total > max_len) total = max_len;
    
    memcpy(data, netfrmbuf[netfrm_head], total);
    
    netfrm_head = (netfrm_head + 1) % HOOK_RX_BUFSIZE;
    netfrm_full = false;
    
    xSemaphoreGive(buf_mutex);
    return total;
}

5 、hook测试函数

lwip 默认stm32的网卡名为"st"

c 复制代码
void test_hook(void) {
		#define TEST_FRAME_LEN 64 
    const char *ifname = "st"; 
		uint8_t send_buf[TEST_FRAME_LEN];
    uint8_t recv_buf[TEST_FRAME_LEN];
    int recv_len;
    
    // 初始化一个测试帧,设置协议类型为0x88a4(EtherCAT)
    memset(send_buf, 0, TEST_FRAME_LEN);
    send_buf[12] = 0x88;
    send_buf[13] = 0xa4;

    const char payload[8] = {0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc};
    memcpy(send_buf + 14, payload, sizeof(payload));
    
    // 安装钩子
    if (!install_hook(ifname)) {
        EC_PRINT("Hook installation failed.\n");
        return;
    }
    
    // 发送测试帧
    if (net_send(send_buf, TEST_FRAME_LEN) < 0) {
        EC_PRINT("Send test frame failed.\n");
        goto uninstall;
    }
    
    // 由于我们的钩子函数在发送时也会被调用(实际上是通过linkoutput钩子发送的,但注意我们的接收钩子是在输入路径上)
    // 但我们发送的帧不会直接进入接收钩子,除非我们将其注入到接收路径(比如在回环接口上发送)。
    // 因此,这里需要说明:在真实硬件上,发送的帧不会被自己接收,除非网络设备支持回环或者我们使用回环接口。
    
    // 所以我们需要调整测试方法:在同一个网络接口上,我们不能直接捕获自己发送的帧(除非是回环)。
    // 我们可能需要两个设备或者使用一个支持回环的接口。
    
    // 由于测试环境限制,我们可能无法捕获自己发送的帧。因此,这个测试用例可能需要修改:
    // 方案1:如果我们的网络接口支持回环(例如通过配置),则可以通过回环接收到发送的帧。
    // 方案2:在模拟的TAP设备上,可以自己发送自己接收。
    // 方案3:修改钩子函数,使其同时捕获发送和接收的帧。但原设计是只捕获接收路径上的EtherCAT帧。
    
    // 重新考虑:我们设计的钩子函数是挂接在netif的input函数上,即当有数据包从网络设备接收时会被调用。发送的数据包只会经过linkoutput。
    // 因此,我们之前的接收钩子不会捕获发送的包。
    
    // 所以我们需要改变测试思路:我们测试接收钩子的正确性需要另一个设备发送EtherCAT帧,或者我们通过其他方式注入一个接收帧(比如直接调用netif->input函数)。
    // 对于单元测试,我们可以模拟一个接收帧直接调用netif->input函数,但这样就不经过硬件,而我们的钩子函数就是挂在这个input上的。
    // 但是我们安装钩子后,原来的input函数已经被替换,所以我们可以直接调用钩子函数来模拟接收。
    
    // 由于测试的复杂性,这里我们改变策略,分为两个测试:
    // 测试1:测试发送功能。直接调用net_send,然后在外部验证(如使用抓包工具)是否发送成功。
    // 测试2:测试接收功能。我们可以模拟一个接收事件:通过直接调用安装钩子后的input函数(即_netif_input)来模拟接收。
    
    // 测试1:发送
    // 已经通过net_send发送了一个帧,在外部抓包验证。
    EC_PRINT("Test frame sent. Please use external tool to verify.\n");
    
    // 测试2:模拟接收
    // 构建一个模拟接收的帧
    uint8_t fake_recv_frame[TEST_FRAME_LEN];
    memset(fake_recv_frame, 0, TEST_FRAME_LEN);
    fake_recv_frame[12] = 0x88;
    fake_recv_frame[13] = 0xa4;
    const char *fake_payload = "Fake received EtherCAT frame";
    memcpy(fake_recv_frame+14, fake_payload, strlen(fake_payload)+1);
    
    // 构建一个pbuf来模拟接收
    struct pbuf *p = pbuf_alloc(PBUF_RAW, TEST_FRAME_LEN, PBUF_POOL);
    if (p == NULL) {
        EC_PRINT("pbuf_alloc failed for simulated receive.\n");
        goto uninstall;
    }
    pbuf_take(p, fake_recv_frame, TEST_FRAME_LEN);
    
    // 调用钩子函数(即我们挂接的_netif_input)来处理这个模拟的接收帧
    _netif_input(p, NULL);   // 第二个参数是netif,可以传NULL因为我们内部不用(注意:内部函数使用了target_netif?实际我们函数里没用到inp参数)
    pbuf_free(p);
    
    // 现在我们应该可以通过net_recv接收到这个帧
    recv_len = net_recv(recv_buf, TEST_FRAME_LEN);
    if (recv_len <= 0) {
        EC_PRINT("Didn't receive the simulated frame.\n");
    } else {
        EC_PRINT("Received frame, len=%d\n", recv_len);
        // 打印接收到的内容,对比
        if (memcmp(fake_recv_frame, recv_buf, recv_len) == 0) {
            EC_PRINT("Simulated receive frame matches the sent one.\n");
        } else {
            EC_PRINT("Simulated receive frame does NOT match.\n");
        }
    }
    
uninstall:
    // 卸载钩子
    uninstall_hook();
    EC_PRINT("Test finished.\n");
}

6、测试实例

在任务中循环调用test_hook函数
使用wireshark抓包观察

在这里插入图片描述,数据收发ok,可以识别ethercat frame

相关推荐
JasmineX-110 小时前
STM32内部读写FLASH
c语言·stm32·单片机·嵌入式硬件
源远流长jerry12 小时前
电路基础相关知识
stm32·单片机·嵌入式硬件
1+2单片机电子设计13 小时前
基于STM32的数控机床物联网改造研究
stm32·单片机·嵌入式硬件·51单片机
猫猫的小茶馆13 小时前
【STM32】HAL库中的实现(三):PWM(脉冲宽度调制)
stm32·单片机·嵌入式硬件·mcu·51单片机·智能硬件
嵌入式×边缘AI:打怪升级日志15 小时前
韦东山STM32_HAl库入门教程(SPI)学习笔记[09]内容
stm32·嵌入式硬件·microsoft
yiqiqukanhaiba17 小时前
江协科技STM32学习笔记1
科技·stm32·学习
猫猫的小茶馆20 小时前
【STM32】HAL库中的实现(四):RTC (实时时钟)
stm32·单片机·嵌入式硬件·mcu·51单片机·实时音视频·pcb工艺
_smart_boy__20 小时前
基于铁头山羊STM32的平衡车电机转速开环闭环matlab仿真
stm32·嵌入式硬件·matlab
机器视觉知识推荐、就业指导21 小时前
STM32 外设驱动模块一:LED 模块
stm32·单片机·嵌入式硬件