Bootstrap

LWIP学习 (1) LWIP简介

LWIP 简介

LwIP 全名:Light weight IP,意思是轻量化的 TCP/IP 协议。LwIP 的设计初衷是:用少量的资源消耗实现一个较为完整的 TCP/IP 协议栈,其中“完整”主要指的是 TCP 协议的完整性,实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。此外 LwIP 既可以移植到操作系统上运行,也可以在无操作系统的情况下独立运行。

LWIP 文件说明

最关心的是src文件中的内容
api :文件装的是NETCONN API 和SOCKET API 源文件,只有在操作系统中才会编译
apps:文件夹里面装的是应用程序的源文件,包括常见的应用程序,如 httpd、mqtt、tftp、sntp、snmp等
core:文件夹里面是 LwIP 的内核源文件
include:文件夹里面是 LwIP 所有模块对应的头文件。
netif:文件夹里面是与网卡移植有关的文件,这些文件为我们移植网卡提供了模板,我们可以直接使用。

core内核文件简介

altcp.c、altcp_alloc.c、altcp_tcp.c 等文件是应用程序分层 TCP 连接 API,从 TCPIP 线程使用,是一个抽象层,可以模拟应用程序的 tcp 回调 API,同时防止直接链接,没有使用
def.c:文件定义了一些基础类函数,比如主机序和网络序的转换、字符串的查找和比较、整数转换成字符串等,这些函数会被 LwIP 内核的很多模块所调用。在 include 目录里面的 def.h 文件对外声明了 def.c 所实现的函数,同时定义了许多宏,能实现一些基础操作,比如取最大值、取最小值、计算数组长度等,这些宏同样也被内核的许多模块所调用。
我们经常可以看到某个内核的源文件在开始的地方 #include “def.h”
dns:文件实现了域名解析的功能
inet_chksum.c:文件提供了 LwIP 所需的校验和功能
init.c:文件对 LwIP 的用户宏配置进行了检查,会将配置错误和不合理的地方,通过编译器的 #error和 #warning 功能表示出来。另外,init.c 定义了lwip_init 初始化函数,这个函数会依次对 LwIP 的各个模块进行初始化。
ip.c:文件实现了 IP 协议相关的函数
mem.c:文件实现了内存堆(heap)管理机制
memp.c:文件实现了动态内存池(Dynamic)管理机制
netif.c文件实现了网卡的操作,比如注册/删除网卡、使能/禁能网卡、设置网卡 IP 地址等等
pbuf.c:文件实现了 LwIP 对网络数据包的各种操作
raw.c:文件实现了一个传输层协议的框架
stat.c:文件实现了 LwIP 内核的统计功能,使用户可以实时地查看 LwIP 内核对网络数据包的处理情况
sys.c:它提供了与临界区相关的操作
tcp.c、tcp_in.c 和 tcp_out.c:文件实现了 TCP 协议,包括对 TCP 连接的操作、对 TCP 数据包的输入输出操作和 TCP 定时器
timeouts.c:定义了 LwIP 内核的超时处理机制
udp.c:文件实现了 UDP 协议,包括对 UDP 连接的操作和 UDP 数据包的操作。

LWIP 三种编程接口

RAW

RAW/Callback API 是指内核回调型的 API,这在许多通信协议的 C 语言实现中都有所应用

NETCONN

可以使用 NETCONN API 或者 Socket API 进行网络应用程序的开发。NETCONN API 是基于操作系统的 IPC 机制(即信号量和邮箱机制)实现。在操作系统环境中,LwIP 内核会被实现为一个独立的线程,名为tcpip_thread
函数的调用 lwip_system_init->tcpip_init(tcpip_init_done_callback, (void *)&done_sem);

void tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
  lwip_init();
  tcpip_init_done = initfunc;
  tcpip_init_done_arg = arg;
  if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
    LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
  }
#if LWIP_TCPIP_CORE_LOCKING
  if (sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
    LWIP_ASSERT("failed to create lock_tcpip_core", 0);
  }
#endif /* LWIP_TCPIP_CORE_LOCKING */

  sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}

SOCKET API

Socket,即套接字,它对网络连接进行了高级的抽象,使得用户可以像操作文件一样操作网络连接

ETH(以太网)

介绍

  TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准,PHY 层芯片 LAN8720 相当于物理层,STM32F407 自带的MAC 层相当于数据链路层,而 LWIP 提供的就是网络层、传输层的功能,应用层是需要用户自己根据自己想要的功能去实现的。
  STM32F407自带有10/100Mbit/s的以太网MAC(这个是内核,介子控制器)内核,这个以太网MAC内核有 如下特性:

  1. 支持外部PHY(物理层这个是纯模拟电路)接口实现10/100Mbit/s数据传输速率。
  2. 通过符合IEEE802.3的(MII接口)介子独立接口和(RMII接口)简化介子独立接口与外接快速以太网PHY进行通信
  3. 支持全双工和半双工操作
  4. 报头和帧起始数据(SFD)在发送路径中插入、在接收路径中删除
  5. 可逐帧控制CRC和pad自动生成
  6. 可编程帧长度,支持高大16KB的巨型帧
  7. 可编程帧间隔
  8. SMI接口是站管理接口,通过这个接口来访问PHY寄存器,数据线MDIO和时钟线MDC,可以访问32个PHY设备
网络数据交换
设备配置
MAC
MII
SMII
PHY

在这里插入图片描述

SMI接口

  站管理接口(SMI) 允许应用程序通过 2 条线:时钟(MDC)和数据线(MDIO)访问任意 PHY 寄存器。该接口支持访问多达 32 个PHY。应用程序可以从 32 个PHY 中选择一个PHY,然后从任意PHY 包含的 32 个寄存器中选择一个寄存器,发送控制数据或接收状态信息。

MII接口

  MII接口:介质独立接口 (MII) 定义了 10 Mbit/s 和 100 Mbit/s 的数据传输速率下 MAC 子层与 PHY 之间的互连。
在这里插入图片描述
  TX_CLK和RX_CLK为发送和接收连续时钟,当速率为10Mbit/s时为2.5MHZ,速率为100Mbit/s时为25MHZ,外部晶振提供25MHZ的时钟。

RMII接口

  介质独立接口 (RMII) 规范降低了 10/100 Mbit/s 下微控制器以太网外设与外部 PHY 间的 引脚数。根据 IEEE 802.3u 标准, MII 包括 16 个数据和控制信号的引脚。 RMII 规范将引脚 数减少为 7个(引脚数减少 62.5%)。不过RMII接口的参考时钟必须是50MHZ!
在这里插入图片描述
REF_CLK:参考时钟
TX_EN: 发送使能
TXD[1:0]:发送数据
RXD[1:0]:接收数据

以太网DMA描述符

  F407有一个以太网专用的DAM,DMA可以在CPU完全不干预的情况下,通过描述符有效地将数据从源传送到目标,接收缓冲区和发送缓冲区的数据都可以通过以太网DMA来传送。429可以直接从HAL库中把32f4xx_hal_eth.c添加进来。

typedef struct  
{
  __IO uint32_t   Status;           /*!< Status */
  
  uint32_t   ControlBufferSize;     /*!< Control and Buffer1, Buffer2 lengths */
  
  uint32_t   Buffer1Addr;           /*!< Buffer1 address pointer 这个是缓冲区的地址 */
  
  uint32_t   Buffer2NextDescAddr;   /*!< Buffer2 or next descriptor address pointer
  										下一个描述符的地址 */
  
  /*!< Enhanced ETHERNET DMA PTP Descriptors */
  uint32_t   ExtendedStatus;        /*!< 增强型描述符状态 */
  
  uint32_t   Reserved1;             /*!< Reserved */
  
  uint32_t   TimeStampLow;          /*!< Time Stamp 时间戳 Low value for transmit and receive */
  
  uint32_t   TimeStampHigh;         /*!< Time Stamp 时间错High value for transmit and receive */

} ETH_DMADescTypeDef;
//DMA描述符数组
ETH_DMADescTypeDef  DMARxDscrTab[ETH_RXBUFNB] //DMA接收描述符
ETH_DMADescTypeDef  DMATxDscrTab[ETH_TXBUFNB] //DMA发送描述符
//数据缓冲区
uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE] 
uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]  

以太网驱动库 stm32f4xx_hal_eth.c 中我们采用的是 DMA 链接结构,但是DMARxDscrTabDMATxDscrTab 是两个指针(或数组),那么我们必然要将这两个指针(或数组)改为链接结构。在 stm32f4xx_hal_eth.c 文件中有两个函数 HAL_ETH_DMATxDescListInit()HAL_ETH_DMARxDescListInit (),通过这两个函数我们就可以将上面两个指针(或数组)变为链接结构。

//在应用层定义指针,用于发送和接收数据的缓冲区
uint8_t *Rx_Buff; //以太网底层驱动接收 buffers 指针
uint8_t *Tx_Buff; //以太网底层驱动发送 buffers 指针
//这两个值,在stm32f4xx_hal_conf.h 定义
uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]

LWIP数据包和网络接口管理

netif 结构体

如何使硬件和软件无缝的连接起来。LWIP使用一个数据结构体netif来描述网卡。
单个网卡中,netif 结构体只有一个,多个网卡的时候,LwIP 会将每个用 netif 描述的网卡连接成一个链表(单向链表),该链表就记录每个网卡的 netif。

struct netif {
#if !LWIP_SINGLE_NETIF
  /** pointer to next in linked list */
  /** 指向netif 链表的下一个 */
  struct netif *next;
#endif

#if LWIP_IPV4
  /** IP address configuration in network byte order */
  /** IP 地址 子网掩码 默认网关 */
  ip_addr_t ip_addr;
  ip_addr_t netmask;
  ip_addr_t gw;
#endif /* LWIP_IPV4 */
  /** This function is called by the network device driver
   *  to pass a packet up the TCP/IP stack. 
   * 此函数由网络设备驱动程序调用,将数据包传递到 TCP/IP 协议栈。
   * 对于以太网物理层,这通常是 ethernet_input() */
  netif_input_fn input;
#if LWIP_IPV4
  /** This function is called by the IP module when it wants
   *  to send a packet on the interface. This function typically
   *  first resolves the hardware address, then sends the packet.
   *  For ethernet physical layer, this is usually etharp_output() 
   * 此函数由 IP 层调用,在接口上发送数据包。通常这个功能,
   * 首先解析硬件地址,然后发送数据包。
   * 对于以太网物理层,这通常是 etharp_output()/* 
   * */
  netif_output_fn output;
#endif /* LWIP_IPV4 */
  /** This function is called by ethernet_output() when it wants
   *  to send a packet on the interface. This function outputs
   *  the pbuf as-is on the link medium.
   *  这个函数被ethernet_output()调用,当需要在网卡上发送一个数据包的时候,
   *  底层硬件输出数据,
    */
  netif_linkoutput_fn linkoutput;

#if LWIP_NETIF_STATUS_CALLBACK
  /** This function is called when the netif state is set to up or down
  		状态 
   */
  netif_status_callback_fn status_callback;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
  /** This function is called when the netif link is set to up or down
      网络连接
   */
  netif_status_callback_fn link_callback;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_NETIF_REMOVE_CALLBACK
  /** This function is called when the netif has been removed 
	网卡将要删除的时候
	*/
  netif_status_callback_fn remove_callback;
#endif /* LWIP_NETIF_REMOVE_CALLBACK */
  /** This field can be set by the device driver and could point
   *  to state information for the device. */
  void *state;
#ifdef netif_get_client_data
  void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];
#endif
#if LWIP_NETIF_HOSTNAME
  /* the hostname for this netif, NULL is a valid value 
	netif 的主机名 NULL也是一个有效值
*/
  const char*  hostname;
#endif /* LWIP_NETIF_HOSTNAME */
#if LWIP_CHECKSUM_CTRL_PER_NETIF
  u16_t chksum_flags;
#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/
  /** maximum transfer unit (in bytes) */
  u16_t mtu;
#if LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES
  /** maximum transfer unit (in bytes), updated by RA */
  u16_t mtu6;
#endif /* LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES */
  /** link level hardware address of this interface */
  u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
  /** number of bytes used in hwaddr */
  u8_t hwaddr_len;
  /** flags (@see @ref netif_flags)
  网卡状态信息标志位,是很重要的控制字段,
* 它包括网卡功能使能、广播使能、 ARP 使能等等重要控制位。 */ 
* */
  u8_t flags;
  /** descriptive abbreviation */
  char name[2];
  /** number of this interface. Used for @ref if_api and @ref netifapi_netif, 
   * as well as for IPv6 zones */
  u8_t num;
#if LWIP_IPV6_AUTOCONFIG
  /** is this netif enabled for IPv6 autoconfiguration */
  u8_t ip6_autoconfig_enabled;
#endif /* LWIP_IPV6_AUTOCONFIG */
#if LWIP_IPV6_SEND_ROUTER_SOLICIT
  /** Number of Router Solicitation messages that remain to be sent. */
  u8_t rs_count;
#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */
#if MIB2_STATS
  /** link type (from "snmp_ifType" enum from snmp_mib2.h) */
  u8_t link_type;
  /** (estimate) link speed */
  u32_t link_speed;
  /** timestamp at last change made (up/down) */
  u32_t ts;
  /** counters */
  struct stats_mib2_netif_ctrs mib2_counters;
#endif /* MIB2_STATS */
#if LWIP_IPV4 && LWIP_IGMP
  /** This function could be called to add or delete an entry in the multicast
      filter table of the ethernet MAC.*/
  netif_igmp_mac_filter_fn igmp_mac_filter;
#endif /* LWIP_IPV4 && LWIP_IGMP */
#if LWIP_IPV6 && LWIP_IPV6_MLD
  /** This function could be called to add or delete an entry in the IPv6 multicast
      filter table of the ethernet MAC. */
  netif_mld_mac_filter_fn mld_mac_filter;
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
#if LWIP_NETIF_USE_HINTS
  struct netif_hint *hints;
#endif /* LWIP_NETIF_USE_HINTS */
#if ENABLE_LOOPBACK
  /* List of packets to be queued for ourselves. */
  struct pbuf *loop_first;
  struct pbuf *loop_last;
#if LWIP_LOOPBACK_MAX_PBUFS
  u16_t loop_cnt_current;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#endif /* ENABLE_LOOPBACK */
};

netif 使用

将网卡定义一个 netif结构体变量 struct netif gnetif,然后要把网卡挂载到 netif_list链表上才能够使用。
netif_add()函数,把网卡进行挂载。

struct netif *netif_add(struct netif *netif,
#if LWIP_IPV4
          const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
#endif /* LWIP_IPV4 */
          void *state, netif_init_fn init, netif_input_fn input)
{
  LWIP_ASSERT_CORE_LOCKED();
  LWIP_ERROR("netif_add: invalid netif", netif != NULL, return NULL);
  LWIP_ERROR("netif_add: No init function given", init != NULL, return NULL);

#if LWIP_IPV4
  if (ipaddr == NULL) {
    ipaddr = ip_2_ip4(IP4_ADDR_ANY);
  }
  if (netmask == NULL) {
    netmask = ip_2_ip4(IP4_ADDR_ANY);
  }
  if (gw == NULL) {
    gw = ip_2_ip4(IP4_ADDR_ANY);
  }

  /* reset new interface configuration state */   第一步 地址清零
  ip_addr_set_zero_ip4(&netif->ip_addr);
  ip_addr_set_zero_ip4(&netif->netmask);
  ip_addr_set_zero_ip4(&netif->gw);
  netif->output = netif_null_output_ip4;
#endif /* LWIP_IPV4 */
  NETIF_SET_CHECKSUM_CTRL(netif, NETIF_CHECKSUM_ENABLE_ALL);
  netif->mtu = 0;
  netif->flags = 0;
#ifdef netif_get_client_data
  memset(netif->client_data, 0, sizeof(netif->client_data));
#endif /* LWIP_NUM_NETIF_CLIENT_DATA */

#if LWIP_NETIF_STATUS_CALLBACK
  netif->status_callback = NULL;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
  netif->link_callback = NULL;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_IGMP
  netif->igmp_mac_filter = NULL;
#endif /* LWIP_IGMP */

#if ENABLE_LOOPBACK
  netif->loop_first = NULL;
  netif->loop_last = NULL;
#endif /* ENABLE_LOOPBACK */

  /* remember netif specific state information data */
  netif->state = state;           第二步根据传递进来的参数填写网卡 state、input 等字段的相关信息
  netif->num = netif_num;
  netif->input = input;

  NETIF_RESET_HINTS(netif);
#if ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS
  netif->loop_cnt_current = 0;
#endif /* ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS */

#if LWIP_IPV4
  netif_set_addr(netif, ipaddr, netmask, gw); 第三步调用网卡设置函数 netif_set_addr() 设置网卡 IP 地址、子网掩码、网关等信息
#endif /* LWIP_IPV4 */

  /* call user specified initialization function for netif */
  if (init(netif) != ERR_OK) {  第四步通过传递进来的回调函数 init() 进行网卡真正的初始化操作,所以该函数是由用户实现的
    return NULL;
  }
  {
    struct netif *netif2;
    int num_netifs;
    do {
      if (netif->num == 255) {
        netif->num = 0;
      }
      num_netifs = 0;
      for (netif2 = netif_list; netif2 != NULL; netif2 = netif2->next) {
        LWIP_ASSERT("netif already added", netif2 != netif);
        num_netifs++;
        LWIP_ASSERT("too many netifs, max. supported number is 255", num_netifs <= 255);
        if (netif2->num == netif->num) {
          netif->num++;
          break;
        }
      }
    } while (netif2 != NULL);
  }
  if (netif->num == 254) {
    netif_num = 0;
  } else {
    netif_num = (u8_t)(netif->num + 1);5步初始化网卡成功,则遍历当前设备拥有多少个网卡,并为当前网卡分配维一标识 num
  }

  /* add this netif to the list */
  netif->next = netif_list;6步将当前网卡插入 netif_list 链表中。
  netif_list = netif;
#endif /* "LWIP_SINGLE_NETIF */
  mib2_netif_added(netif);

#if LWIP_IGMP
  /* start IGMP processing */
  if (netif->flags & NETIF_FLAG_IGMP) {
    igmp_start(netif);
  }
#endif /* LWIP_IGMP */

  LWIP_DEBUGF(NETIF_DEBUG, ("netif: added interface %c%c IP",
                            netif->name[0], netif->name[1]));
#if LWIP_IPV4
  LWIP_DEBUGF(NETIF_DEBUG, (" addr "));
  ip4_addr_debug_print(NETIF_DEBUG, ipaddr);
  LWIP_DEBUGF(NETIF_DEBUG, (" netmask "));
  ip4_addr_debug_print(NETIF_DEBUG, netmask);
  LWIP_DEBUGF(NETIF_DEBUG, (" gw "));
  ip4_addr_debug_print(NETIF_DEBUG, gw);
#endif /* LWIP_IPV4 */
  LWIP_DEBUGF(NETIF_DEBUG, ("\n"));

  netif_invoke_ext_callback(netif, LWIP_NSC_NETIF_ADDED, NULL);

  return netif;
}

与netif相关的底层函数

在ethernetifi.c中常用的几个底层函数

err_t ethernetif_init(struct netif *netif);

ethernetif_init() 函数是在上层管理网卡 netif 的到时候会被调用的函数,如使用 netif_add() 添加网卡的时候,就会调用 ethernetif_init() 函数对网卡进行初始化,其实该函数的最终调用的初始化函数就是low_level_init() 函数

static void low_level_init(struct netif *netif);

网卡初始化话函数,它主要完成网卡的复位及参数初始化,根据实际的网卡属性
进行配置 netif 中与网卡相关的字段,例如网卡的 MAC 地址、长度,最大发送单元等。调用我们自己实现的以太网驱动初始化函数 Bsp_Eth_Init(),这是根据网卡的驱动所编写的函数,不同的网卡是不一样的,由用户实现,
初始化完成就需要启动网卡,才能进行数据的收发操作。

static err_t low_level_output(struct netif *netif, struct pbuf *p);

函数为网卡的发送函数,它主要将内核的数据包发送出去,数据包采用 pbuf 数据结构进行描述

static struct pbuf * low_level_input(struct netif *netif);

函数为网卡的数据接收函数,该函数会接收一个数据包,为了内核易于对数据包的管理,该函数必须将接收的数据封装成 pbuf 的形式。

LWIP 数据包

  利用pbuf来描述数据包,这个结构体在pbuf.h中。

struct pbuf {
 struct pbuf *next; //指向下一个 pbuf 结构体,可以构成链表
 void *payload; //指向该 pbuf 真正的数据区
 u16_t tot_len; //当前 pbuf 和链表中后面所有 pbuf 的数据长度,它们属于一个数据包
 u16_t len; //当前 pbuf 的数据长度
 u8_t type; //当前 pbuf 的类型
 u8_t flags; //状态为,保留
 u16_t ref; //该 pbuf 被引用的次数
};

  next:指向下一个 pbuf 结构体,每个 pbuf 能存放的数据有限,如果应用有大量的数据的话,我们就需要多个 pbuf 来存放,我们将同一个数据包的 pbuf 连接在一起形成一个链表,那么 next字段就是实现这个链表的关键。
  payload:指向该 pbuf 的数据存储区的首地址,STM32F407 内部网络模块接收到数据,并将数据提交给 LWIP 的时候,就是将数据存储在 payload 指定的存储区中。同样在发送数据的时候将 payload 所指向的存储区数据转给 STM32F407 的网络模块去发送。
  tot_len:我们在接收或发送数据的时候数据会存放在 pbuf 链表中,tot_len 字段就表示当前pbuf 和链表中以后所有 pbuf 总的数据长度。
  len:当前 pbuf 总数据的长度
  type:当前 pbuf 类型,一共有四种:PBUF_RAMPBUF_ROMPBUF_REFPBUF_POOL
  flag:保留位
  ref:该 pbuf 被引用的次数,当还有其他指针指向这个 pbuf 的时候 ref 字段就加一。

LWIP网络接口管理

  在 LWIP 中对于网络接口的描述是通过一个 netif 结构体完成的,netif 结构体在netif.h 文件中有定义。

struct netif { 
 struct netif *next; //指向下过一个netif 结构体 
 ip_addr_t ip_addr; //网络接口IP 地址 
 ip_addr_t netmask; //子网掩码 
 ip_addr_t gw; //默认网关 
 netif_input_fn input; //IP 层接收数据函数 
 netif_output_fn output; //IP 层发送数据包调用 
 netif_linkoutput_fn linkoutput; //底层数据包发送 
 void *state; //设备的状态信息 
 u16_t mtu; //该网络接口最大允许传输的数据长度 
 u8_t hwaddr_len; //物理地址长度 
 u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; //该网络接口的物理地址 
 u8_t flags; //该网络接口的状态和属性 
 char name[2]; //该网络接口的名字 
 u8_t num; //该网络接口的编号 
} 

添加中间文件

  LWIP 文件夹可以发现有一个 arch 文件夹,在 arch文件中有 5 个文件 cc.h、cpu.h、perf.h、sys_arch.hsys_arch.c
  cc.h :主要完成协议内部使用的数据类型的定义
  cpu.h :用来定义CPU 的大小端模式,因为STM32 是小端模式,因此这里定义BYTE_ORDER=LITTLE_ENDLAN//小端模式
  perf.h :是和系统测量与统计相关的文件,我们不使用任何的测量和统计
  sys_arch.h :是在使用操作系统的时候才使用到的文件,获取时间的函数 sys_now(),为LWIP提供时钟。
  sys_arch.c :用来定义CPU 的大小端模式,因为STM32 是小端模式,因此这里定义BYTE_ORDER=LITTLE_ENDLAN//小端模式

开始移植

硬件 STM32F407VET6 PHY是DP83848(外接50Mhz)用RMII模式
REF_CLK: 参考时钟
TX_EN:发送使能
TXD[1:0]:发送数据
RXD[1:0]:接收数据
CRS_DV:载波监听,接收数据有效
RX_ED:接收错误(可选)
MDC:周期性时钟,提供以最大频率 2.5 MHz 传输数据时的参考时序,在空闲 状态下, SMI 管理接口将 MDC 时钟信号驱动为低电平。
MDIO:数据输入/输出比特流,用于通过 MDC 时钟信号向/从 PHY 设备同步传输状态 信息。
ETH_INT:这个引脚自己写的

在Cubemx中选用ETH

在这里插入图片描述
在这里插入图片描述

;