【LwIP】移植(NON-OS)

LwIP(A Lightweight TCP/IP stack),嵌入式系统常用的一个网络协议栈。移植LwIP比我想象的稍微简单一点(当然我不是从零开始移植的,参考例程并稍作修改),我准备移植RAW API版本的LwIP,也就是在无RTOS的情况下移植LwIP。其实只要抓住一个重点:首先忽略协议栈的实现细节和什么Raw API、Sequential API、BSD socket API的区别,然后自己清楚的知道我需要做的就是将我的网卡收到的MAC帧送入到LwIP协议栈,以及将LwIP协议栈传过来的数据发送出去,这样无论使用自带的以太网MAC外设还是使用ENC28J60这样的外部MAC(+PHY)芯片移植LwIP思路都很明确,中间怎么对数据进行处理是协议栈和应用程序层面需要处理的。

进入官网的下载通道:http://download.savannah.nongnu.org/releases/lwip/,选择使用较多较稳定的版本lwip-1.4.1.zip。解压之后:

进入源码src文件夹:

api/          - The code for the high-level wrapper API. Not needed if you use the lowel-level call-back/raw API.

core/        - The core of the TPC/IP stack; protocol implementations, memory and buffer management, and the low-level raw API.

include/    - lwIP include files.

netif/         - Generic network interface device drivers are kept here, as well as the ARP module.

For more information on the various subdirectories, check the FILES file in each directory.

其中api文件夹是针对Sequential API和BSD socket API的,使用Raw API进行编程的话这个文件夹用不上。core文件夹是TCP/IP协议栈核心。netif文件夹好乱,不知道怎么形容它的具体功能,网卡驱动在这里,ARP模块也在这里。这里需要做一个偷懒的工作,将和CPU架构相关的头文件都直接“抄袭”过来,这些文件可以单独放在一个文件夹中,然后将头文件地址包含到工程中去,这些头文件都是与处理器架构相关的:

进入到 src/netif 文件夹,里面有个ethernetif.c文件,这个文件不是协议栈相关的文件,而是一个以太网接口的驱动程序框架,也就是说所有的以太网接口(Ethernet Interface)想要移植LwIP,可以按照这个文件为例进行修改,得到相对应网卡的驱动程序文件。我的实验使用的主芯片是STM32F407V,PHY芯片是DP83848,我们需要以ethernetif.c为模板得到对应的网卡驱动,修改(或者说填充)其中的一些函数得到网络接口的驱动文件ethernetif_stm32f407.c:

/**
 * @file
 * Ethernet Interface Skeleton
 *
 */

/*
 * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
 * All rights reserved. 
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 * 
 * Author: Adam Dunkels <[email protected]>
 *
 */

/*
 * This file is a skeleton for developing Ethernet network interface
 * drivers for lwIP. Add code to the low_level functions and do a
 * search-and-replace for the word "ethernetif" to replace it with
 * something that better describes your network interface.
 */

#include "lwip/opt.h"

#if 1 /* don't build, this is only a skeleton, see previous comment */

#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include <lwip/stats.h>
#include <lwip/snmp.h>
#include "netif/etharp.h"
#include "netif/ppp_oe.h"

#include "stm32f4x7_eth.h"
#include "main.h"
#include <string.h>

/* Define those to better describe your network interface. */
#define IFNAME0 's'
#define IFNAME1 't'

/* Ethernet Rx & Tx DMA Descriptors */
extern ETH_DMADESCTypeDef  DMARxDscrTab[ETH_RXBUFNB], DMATxDscrTab[ETH_TXBUFNB];

/* Ethernet Driver Receive buffers  */
extern uint8_t Rx_Buff[ETH_RXBUFNB][ETH_RX_BUF_SIZE]; 

/* Ethernet Driver Transmit buffers */
extern uint8_t Tx_Buff[ETH_TXBUFNB][ETH_TX_BUF_SIZE]; 

/* Global pointers to track current transmit and receive descriptors */
extern ETH_DMADESCTypeDef  *DMATxDescToSet;
extern ETH_DMADESCTypeDef  *DMARxDescToGet;

/* Global pointer for last received frame infos */
extern ETH_DMA_Rx_Frame_infos *DMA_RX_FRAME_infos;

/**
 * Helper struct to hold private data used to operate your ethernet interface.
 * Keeping the ethernet address of the MAC in this struct is not necessary
 * as it is already kept in the struct netif.
 * But this is only an example, anyway...
 */
struct ethernetif {
  struct eth_addr *ethaddr;
  /* Add whatever per-interface state that is needed here. */
};

/* Forward declarations. */
err_t  ethernetif_input(struct netif *netif);

/**
 * In this function, the hardware should be initialized.
 * Called from ethernetif_init().
 *
 * @param netif the already initialized lwip network interface structure
 *        for this ethernetif
 */
static void
low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
  int i; 
#endif
  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  netif->hwaddr[0] =  MAC_ADDR0;
  netif->hwaddr[1] =  MAC_ADDR1;
  netif->hwaddr[2] =  MAC_ADDR2;
  netif->hwaddr[3] =  MAC_ADDR3;
  netif->hwaddr[4] =  MAC_ADDR4;
  netif->hwaddr[5] =  MAC_ADDR5;
  
  /* initialize MAC address in ethernet MAC */ 
  ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); 

  /* maximum transfer unit */
  netif->mtu = 1500;

  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

  /* Initialize Tx Descriptors list: Chain Mode */
  ETH_DMATxDescChainInit(DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);
  /* Initialize Rx Descriptors list: Chain Mode  */
  ETH_DMARxDescChainInit(DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);
  
#ifdef CHECKSUM_BY_HARDWARE
  /* Enable the TCP, UDP and ICMP checksum insertion for the Tx frames */
  for(i=0; i<ETH_TXBUFNB; i++)
    {
      ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
    }
#endif

   /* Note: TCP, UDP, ICMP checksum checking for received frame are enabled in DMA config */

  /* Enable MAC and DMA transmission and reception */
  ETH_Start();
}

/**
 * This function should do the actual transmission of the packet. The packet is
 * contained in the pbuf that is passed to the function. This pbuf
 * might be chained.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
 * @return ERR_OK if the packet could be sent
 *         an err_t value if the packet couldn't be sent
 *
 * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
 *       strange results. You might consider waiting for space in the DMA queue
 *       to become availale since the stack doesn't retry to send a packet
 *       dropped because of memory failure (except for the TCP timers).
 */

static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
  struct pbuf *q;
  int framelength = 0;
  u8 *buffer =  (u8 *)(DMATxDescToSet->Buffer1Addr);
  
  /* copy frame from pbufs to driver buffers */
  for(q = p; q != NULL; q = q->next) 
  {
    memcpy((u8_t*)&buffer[framelength], q->payload, q->len);
	framelength = framelength + q->len;
  }
  
  /* Note: padding and CRC for transmitted frame 
     are automatically inserted by DMA */

  /* Prepare transmit descriptors to give to DMA*/ 
  ETH_Prepare_Transmit_Descriptors(framelength);

  return ERR_OK;
}

/**
 * Should allocate a pbuf and transfer the bytes of the incoming
 * packet from the interface into the pbuf.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return a pbuf filled with the received packet (including MAC header)
 *         NULL on memory error
 */
static struct pbuf *
low_level_input(struct netif *netif)
{
  struct pbuf *p, *q;
  u16_t len;
  int l =0;
  FrameTypeDef frame;
  u8 *buffer;
  uint32_t i=0;
  __IO ETH_DMADESCTypeDef *DMARxNextDesc;
  
  
  p = NULL;
  
  /* get received frame */
  frame = ETH_Get_Received_Frame();
  
  /* Obtain the size of the packet and put it into the "len" variable. */
  len = frame.length;
  buffer = (u8 *)frame.buffer;
  
  /* We allocate a pbuf chain of pbufs from the Lwip buffer pool */
  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);
  
  /* copy received frame to pbuf chain */
  if (p != NULL)
  {
    for (q = p; q != NULL; q = q->next)
    {
      memcpy((u8_t*)q->payload, (u8_t*)&buffer[l], q->len);
      l = l + q->len;
    }    
  }
  
  /* Release descriptors to DMA */
  /* Check if frame with multiple DMA buffer segments */
  if (DMA_RX_FRAME_infos->Seg_Count > 1)
  {
    DMARxNextDesc = DMA_RX_FRAME_infos->FS_Rx_Desc;
  }
  else
  {
    DMARxNextDesc = frame.descriptor;
  }
  
  /* Set Own bit in Rx descriptors: gives the buffers back to DMA */
  for (i=0; i<DMA_RX_FRAME_infos->Seg_Count; i++)
  {  
    DMARxNextDesc->Status = ETH_DMARxDesc_OWN;
    DMARxNextDesc = (ETH_DMADESCTypeDef *)(DMARxNextDesc->Buffer2NextDescAddr);
  }
  
  /* Clear Segment_Count */
  DMA_RX_FRAME_infos->Seg_Count =0;
  
  /* When Rx Buffer unavailable flag is set: clear it and resume reception */
  if ((ETH->DMASR & ETH_DMASR_RBUS) != (u32)RESET)  
  {
    /* Clear RBUS ETHERNET DMA flag */
    ETH->DMASR = ETH_DMASR_RBUS;
    /* Resume DMA reception */
    ETH->DMARPDR = 0;
  }
  return p;
}

/**
 * This function should be called when a packet is ready to be read
 * from the interface. It uses the function low_level_input() that
 * should handle the actual reception of bytes from the network
 * interface. Then the type of the received packet is determined and
 * the appropriate input function is called.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
err_t ethernetif_input(struct netif *netif)
{
  err_t err;
  struct pbuf *p;

  /* move received packet into a new pbuf */
  p = low_level_input(netif);

  /* no packet could be read, silently ignore this */
  if (p == NULL) return ERR_MEM;

  /* entry point to the LwIP stack */
  err = netif->input(p, netif);
  
  if (err != ERR_OK)
  {
    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
    pbuf_free(p);
    p = NULL;
  }
  return err;
}

/**
 * Should be called at the beginning of the program to set up the
 * network interface. It calls the function low_level_init() to do the
 * actual setup of the hardware.
 *
 * This function should be passed as a parameter to netif_add().
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return ERR_OK if the loopif is initialized
 *         ERR_MEM if private data couldn't be allocated
 *         any other err_t on error
 */
err_t
ethernetif_init(struct netif *netif)
{
  struct ethernetif *ethernetif;

  LWIP_ASSERT("netif != NULL", (netif != NULL));
    
  ethernetif = mem_malloc(sizeof(struct ethernetif));
  if (ethernetif == NULL) {
    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
    return ERR_MEM;
  }

#if LWIP_NETIF_HOSTNAME
  /* Initialize interface hostname */
  netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */

  /*
   * Initialize the snmp variables and counters inside the struct netif.
   * The last argument should be replaced with your link speed, in units
   * of bits per second.
   */
  NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);

  netif->state = ethernetif;
  netif->name[0] = IFNAME0;
  netif->name[1] = IFNAME1;
  /* We directly use etharp_output() here to save a function call.
   * You can instead declare your own function an call etharp_output()
   * from it if you have to do some checks before sending (e.g. if link
   * is available...) */
  netif->output = etharp_output;
  netif->linkoutput = low_level_output;
  
  ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);
  
  /* initialize the hardware */
  low_level_init(netif);

  return ERR_OK;
}

#endif /* 0 */

注意ethernetif.c源文件一开始有一段条件编译:

#if 0 /* don't build, this is only a skeleton, see previous comment */

提醒我们这只是一个模板文件,需要进行修改,我们修改完之后需要把条件编译使能。文件里面的底层操作函数(STM32F407的MAC初始化以及DMA初始化和收发)都是把现成的程序移植过来的(重复造车轮是有必要的)。这时候lwip协议栈的文件树结构为:

├─doc
├─port
│  └─STM32F4x7
│      ├─arch - 与CPU架构相关的头文件定义
│      └─driver - 网卡驱动,现已移植到了netif文件夹中
└─src
    ├─api - Hight level API
    ├─core - 协议栈核心
    │  ├─ipv4
    │  ├─ipv6
    │  └─snmp
    ├─include - 核心头文件
    │  ├─ipv4
    │  │  └─lwip
    │  ├─ipv6
    │  │  └─lwip
    │  ├─lwip
    │  ├─netif
    │  └─posix
    │      └─sys
    └─netif - 网络接口驱动以及ARP模块、PPP模块
        └─ppp

接下来进行LwIP的剪裁工作,在src/inclue/lwip下有一个opt.h文件,这是LwIP的缺省配置文件,这个文件我们一般不进行修改,而是创建一个lwipopts.h文件,因为在opt.h文件中包含了lwipopts.h这个头文件,称为user defined options,参考opt.h文件中的解释:

#ifndef __LWIP_OPT_H__
#define __LWIP_OPT_H__

/*
 * Include user defined options first. Anything not defined in these files
 * will be set to standard values. Override anything you dont like!
 */
#include "lwipopts.h"
#include "lwip/debug.h"

创建文件lwipopts.h,内容参考如下:


#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__

/**
 * SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
 * critical regions during buffer allocation, deallocation and memory
 * allocation and deallocation.
 */
#define SYS_LIGHTWEIGHT_PROT    0

/**
 * NO_SYS==1: Provides VERY minimal functionality. Otherwise,
 * use lwIP facilities.
 
 NO_SYS==1: Use lwIP without OS-awareness (no thread, semaphores, mutexes or mboxes). 
 This means threaded APIs cannot be used (socket, netconn, i.e. everything in the 'api' folder), 
 only the callback-style raw API is available (and you have to watch out for yourself that 
 you don't access lwIP functions/structures from more than one context at a time!)

 */
#define NO_SYS                  1

/* ---------- Memory options ---------- */
/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
   lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
   byte alignment -> define MEM_ALIGNMENT to 2. */
#define MEM_ALIGNMENT           4

/* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE                (10*1024)

/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
   sends a lot of data out of ROM (or other static memory), this
   should be set high. */
#define MEMP_NUM_PBUF           10
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
   per active UDP "connection". */
#define MEMP_NUM_UDP_PCB        6
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
   connections. */
#define MEMP_NUM_TCP_PCB        10
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
   connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 6
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
   segments. */
#define MEMP_NUM_TCP_SEG        12
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
   timeouts. */
// #define MEMP_NUM_SYS_TIMEOUT    3


/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE          12

/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE       512


/* ---------- TCP options ---------- */
#define LWIP_TCP                1
#define TCP_TTL                 255

/* Controls if TCP should queue segments that arrive out of
   order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ         0

/* TCP Maximum segment size. */
#define TCP_MSS                 (1500 - 40)	  /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */

/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF             (4*TCP_MSS)

/*  TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
  as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. */

#define TCP_SND_QUEUELEN        (2* TCP_SND_BUF/TCP_MSS)

/* TCP receive window. */
#define TCP_WND                 (2*TCP_MSS)


/* ---------- ICMP options ---------- */
#define LWIP_ICMP                       1


/* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
   interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
   turning this on does currently not work. */
#define LWIP_DHCP               1


/* ---------- UDP options ---------- */
#define LWIP_UDP                1
#define UDP_TTL                 255


/* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1


/*
   --------------------------------------
   ---------- Checksum options ----------
   --------------------------------------
*/

/* 
The STM32F4x7 allows computing and verifying the IP, UDP, TCP and ICMP checksums by hardware:
 - To use this feature let the following define uncommented.
 - To disable it and process by CPU comment the  the checksum.
*/
#define CHECKSUM_BY_HARDWARE 


#ifdef CHECKSUM_BY_HARDWARE
  /* CHECKSUM_GEN_IP==0: Generate checksums by hardware for outgoing IP packets.*/
  #define CHECKSUM_GEN_IP                 0
  /* CHECKSUM_GEN_UDP==0: Generate checksums by hardware for outgoing UDP packets.*/
  #define CHECKSUM_GEN_UDP                0
  /* CHECKSUM_GEN_TCP==0: Generate checksums by hardware for outgoing TCP packets.*/
  #define CHECKSUM_GEN_TCP                0 
  /* CHECKSUM_CHECK_IP==0: Check checksums by hardware for incoming IP packets.*/
  #define CHECKSUM_CHECK_IP               0
  /* CHECKSUM_CHECK_UDP==0: Check checksums by hardware for incoming UDP packets.*/
  #define CHECKSUM_CHECK_UDP              0
  /* CHECKSUM_CHECK_TCP==0: Check checksums by hardware for incoming TCP packets.*/
  #define CHECKSUM_CHECK_TCP              0
#else
  /* CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.*/
  #define CHECKSUM_GEN_IP                 1
  /* CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.*/
  #define CHECKSUM_GEN_UDP                1
  /* CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.*/
  #define CHECKSUM_GEN_TCP                1
  /* CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.*/
  #define CHECKSUM_CHECK_IP               1
  /* CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.*/
  #define CHECKSUM_CHECK_UDP              1
  /* CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.*/
  #define CHECKSUM_CHECK_TCP              1
#endif


/*
   ----------------------------------------------
   ---------- Sequential layer options ----------
   ----------------------------------------------
*/
/**
 * LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
 */
#define LWIP_NETCONN                    0

/*
   ------------------------------------
   ---------- Socket options ----------
   ------------------------------------
*/
/**
 * LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
 */
#define LWIP_SOCKET                     0


/*
   ----------------------------------------
   ---------- Lwip Debug options ----------
   ----------------------------------------
*/
//#define LWIP_DEBUG                      1

#endif /* __LWIPOPTS_H__ */

/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

配置中NO_SYS = 1,LWIP_NETCONN = 0,LWIP_SOCKET = 0。关于NO_SYS这个选项的详细介绍参考LwIP的在线API:http://www.nongnu.org/lwip/2_0_x/group__lwip__opts__nosys.html,里面有一段介绍:“NO_SYS==1: Use lwIP without OS-awareness (no thread, semaphores, mutexes or mboxes). This means threaded APIs cannot be used (socket, netconn, i.e. everything in the 'api' folder), only the callback-style raw API is available (and you have to watch out for yourself that you don't access lwIP functions/structures from more than one context at a time!)“,这段话告诫我们,如果使用RAW API模式,Raw API函数都是不可重入的,或者说是线程不安全的,例如不可以在主程序和中断程序中同时调用LwIP的API函数。还有一种情况就是我们的系统其实有RTOS,但是我们依然使用的是RAW API模式,这时候我们不可以在多个线程中同时调用API函数,当然,我们可以使用程序技巧避免这个问题,可以创建一个专门的网络线程,这个线程处理所有的网络连接任务,并负责数据的收发工作,其它线程和网络线程通过RTOS的线程间通讯机制进行数据交换,其实这种方式就是Sequential API和BSD socket API两种模式的实现方法。我更倾向去使用Raw API加上自己创建网络线程处理网络事务。

配置文件中有很多以MEM为前缀的配置选项,这也是LwIP的内存管理模块的配置选项,参考每个选项的注释信息。例如MEM_SIZE表示LwIP的内存堆大小。关于更多LwIP的内存分配机制可以参考其他文章。其它配置意义可以查看LwIP的在线API文档。

接下来还有一件事要做,就是一开始说明的,我们需要将网卡收到的数据传入到LwIP协议栈,并将LwIP产生的数据通过网卡发送出去。发送工作在 ethernetif_stm32f407.c 文件中的low_level_output函数中已经实现了,而low_level_output也被赋给了网卡的linkoutput函数指针,协议栈使用某网卡进行数据发送的时候会调用该网卡的linkoutput函数。接收工作在low_level_input中也做了,low_level_input又是被ethernetif_input函数所调用,并将接收到的数据传入到协议栈中去,所以我们需要在收到一帧MAC帧数据的时候调用ethernetif_input函数,可以使用轮询的方式进行数据查询或者使用中断。这里我(我参考的例程)使用的是中断方式,在ETH_IRQHandler中调用ethernetif_input函数:

void ETH_IRQHandler(void)
{
    u8 err;
    /* Handles all the received frames */
    /* check if any packet received */
    while(ETH_CheckFrameReceived())
    { 
        /* process received ethernet packet */
        LwIP_Pkt_Handle();
    }
    /* Clear the Eth DMA Rx IT pending bits */
    ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
    ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
}

void LwIP_Pkt_Handle(void)
{
  /* Read a received packet from the Ethernet buffers and send it to the lwIP for handling */
  ethernetif_input(&netif);
}

但是我再想这会不会产生线程间不安全的调用问题?前面关于NO_SYS这个选项的问题中已经提到了,在中断中调用网络数据接收和协议栈会导致context conflict,可以使用前面说的方法解决这个问题,创建一个网络专用线程,中断和网络线程使用二值信号量或者标志位进行同步,或者使用轮询的方式。到这里LwIP的Raw API已经可以使用了,至于High level的API,后面在移植到FreeRTOS上测试。下面进行Raw API的测试,使用的polling的方式进行网络包数据读取。

主函数如下:

int main(void)
{ 
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组2
    uart_init(115200);		//初始化串口波特率为115200
    printf("system start\r\n");
		
    /* Start the tasks defined within this file/specific to this demo. */
    xTaskCreate( Task_network, "Task network", 512, NULL, 2, NULL );
	
    /* Start the scheduler. */
    vTaskStartScheduler();

    while(1);
}

  task_network文件内容如下:

extern err_t ethernetif_init(struct netif *netif);
extern err_t ethernetif_input(struct netif *netif);
extern int GetNetifLinkStatus(void);

struct tcp_pcb *tcp_client_pcb1 = NULL;
struct tcp_pcb *tcp_client_pcb2 = NULL;
enum network_status
{
	STA_IDLE = 0,
	STA_INIT,
	STA_ERROR,
	STA_WAITCON,
	STA_RUNNING,
};

void StateMachine_1(void *pvParameters);
void StateMachine_2(void *pvParameters);
static __IO int status_con1 = STA_IDLE;
static __IO int status_con2 = STA_IDLE;

//tcp客户端初始化
int TcpClientInit(struct tcp_pcb **pcb,u16_t local_port)
{
	err_t err;
	if (*pcb != NULL)
		tcp_close(*pcb);
	*pcb = tcp_new();	//建立通信的TCP控制块(Clipcb)
	printf("%d,%.8X\r\n",__LINE__,(int)*pcb);
	if (!*pcb)
	{
		printf("%d\r\n",__LINE__);
		return 1;
	}		
	err = tcp_bind(*pcb,IP_ADDR_ANY,local_port);	//绑定本地IP地址和端口号 ,本地ip地址在LwIP_Init()中已经初始化
	if(err != ERR_OK)
	{
		printf("%d\r\n",__LINE__);
		return 1;
	}    
	return 0;
}

//发送数据函数
err_t TcpSendBuff(struct tcp_pcb *pcb,unsigned char *buff,unsigned int length)
{
	err_t err;
	err = tcp_write(pcb,buff,length,TCP_WRITE_FLAG_COPY);	//发送数据
	if(err == ERR_OK)
	{
		err = tcp_output(pcb);
		return err;
	}
	//tcp_close(tcp_client_pcb);				//发送完数据关闭连接,根据具体情况选择使用	
	return err;					
}

void Task_network( void *pvParameters )
{
	__IO uint32_t LocalTime = 0; /* this variable is used to/ create a time reference incremented by 10ms */
	__IO uint32_t status_main = 0;
	int PhyLinkStatus = 0;
	ETH_BSP_Config();
	LwIP_Init();

	printf("Task network started\r\n");

	TickType_t xLastWakeTime;
	xLastWakeTime = xTaskGetTickCount();
	while(1)
	{  
		/***************************从网卡中读取数据并送入到网卡***************************/
// 		while(xSemaphoreTake(xSemaphore_EthPktRecv,0) == pdPASS)
// 			ethernetif_input(&netif_st);
		while(ETH_CheckFrameReceived())
		{ 
			/* process received ethernet packet */
// 			LwIP_Pkt_Handle();
			ethernetif_input(&netif_st);
		}
		
		PhyLinkStatus = GetNetifLinkStatus();
		if(status_main == 0)
		{
			if(!PhyLinkStatus)
			{
				printf("Physical Disconnected\r\n");
				status_main = 1;
			}
		}
		else if(status_main == 1)
		{
			if(PhyLinkStatus)
			{
				printf("Physical Connected\r\n");
				status_main = 0;
				//做一些恢复物理连接后的操作
				status_con1 = STA_INIT;
			}
		}
		
		/***************************执行所有网络连接的状态机程序***************************/
		StateMachine_1(&PhyLinkStatus);
// 		StateMachine_2(&PhyLinkStatus);
		
		/***************************执行LwIP需要周期性执行的事务***************************/
		LocalTime += 10;
		LwIP_Periodic_Handle(LocalTime);
		
		vTaskDelayUntil(&xLastWakeTime,10);	//10ms运行周期
	}
}

err_t cbConnected(void *arg,struct tcp_pcb *pcb,err_t err)
{
	if(pcb == tcp_client_pcb1)
	{
		printf("TCP client1 Connected\r\n");
		status_con1 = STA_RUNNING;
		err = ERR_OK;
		return err;
	}
	else if(pcb == tcp_client_pcb2)
	{
		printf("TCP client2 Connected\r\n");
		status_con2 = STA_RUNNING;
		err = ERR_OK;
		return err;
	}
	return ERR_OK;
}

err_t cbReceived(void *arg, struct tcp_pcb *pcb,struct pbuf *p,err_t err)
{
	if(p == NULL)
		return ERR_BUF;
	struct pbuf *p_temp = p;
	tcp_recved(pcb, p_temp->tot_len);//通知LwIP内核该数据包已被处理
	if(pcb == tcp_client_pcb1)
	{
		while(p_temp != NULL)
		{
			TcpSendBuff(pcb,p_temp->payload,p_temp->len);
			p_temp = p_temp->next;
		}
		pbuf_free(p);
		err = ERR_OK;
		return err;
	}
	else if(pcb == tcp_client_pcb2)
	{
		while(p_temp != NULL)
		{
			TcpSendBuff(pcb,p_temp->payload,p_temp->len);
			p_temp = p_temp->next;
		}
		pbuf_free(p);
		err = ERR_OK;
		return err;
	}
	return ERR_OK;
}

void StateMachine_1(void *pvParameters)
{
	if(status_con1 == STA_IDLE)
	{
		status_con1 = STA_INIT;
	}
	else if(status_con1 == STA_INIT)
	{
		struct ip_addr ipaddr;
		IP4_ADDR(&ipaddr,192,168,2,1);           //服务器IP地址
		TcpClientInit(&tcp_client_pcb1,TCP_LOCAL_PORT);
		tcp_connect(tcp_client_pcb1,&ipaddr,TCP_SERVER_PORT,cbConnected);	//注册回调函数
		tcp_recv(tcp_client_pcb1,cbReceived);	/* 设置tcp接收回调函数 */
// 		status_con1 = STA_WAITCON;
	}
	else if(status_con1 == STA_WAITCON)
	{
	}
	else if(status_con1 == STA_ERROR)
	{
		printf("Error status\r\n");
		while(1);
	}
	else if(status_con1 == STA_RUNNING)
	{
		if(tcp_client_pcb1->state == ESTABLISHED)
		{
			static int i = 0;
			if(i++ == 100)
			{
				i = 0;
				printf("a");
				TcpSendBuff(tcp_client_pcb1,"qwertyuiop",10);
			}
		}
		else if(tcp_client_pcb1->state == CLOSE_WAIT)
		{
			printf("Disconnected:%d\r\n",tcp_client_pcb1->state);
			tcp_abort(tcp_client_pcb1);//Aborts the connection by sending a RST (reset) segment to the remote host. The pcb is deallocated. This function never fails.
			status_con1 = STA_INIT;
		}
		if(*(int*)pvParameters == 0)
		{
			tcp_abort(tcp_client_pcb1);//Aborts the connection by sending a RST (reset) segment to the remote host. The pcb is deallocated. This function never fails.
			status_con1 = STA_INIT;
		}
	}
	else
	{
		printf("Wrong status\r\n");
		while(1);
	}
}

void StateMachine_2(void *pvParameters)
{
	if(status_con2 == STA_IDLE)
	{
		status_con2 = STA_INIT;
	}
	else if(status_con2 == STA_INIT)
	{
		struct ip_addr ipaddr;
		IP4_ADDR(&ipaddr,192,168,2,1);           //服务器IP地址
		TcpClientInit(&tcp_client_pcb2,789);
		tcp_connect(tcp_client_pcb2,&ipaddr,8888,cbConnected);	//注册回调函数
		tcp_recv(tcp_client_pcb2,cbReceived);	//设置tcp接收回调函数
	}
	else if(status_con2 == STA_WAITCON)
	{
	}
	else if(status_con2 == STA_ERROR)
	{
		printf("Error status\r\n");
		while(1);
	}
	else if(status_con2 == STA_RUNNING)
	{
		if(tcp_client_pcb2->state == ESTABLISHED)
		{
			static int i = 0;
			if(i++ == 100)
			{
				i = 0;
				printf("a");
				TcpSendBuff(tcp_client_pcb2,"tcp connection 2",10);
			}
		}
		else
		{
			printf("Disconnected\r\n");
			tcp_abort(tcp_client_pcb2);//Aborts the connection by sending a RST (reset) segment to the remote host. The pcb is deallocated. This function never fails.
			status_con2 = STA_INIT;
		}
		if(*(int*)pvParameters == 0)
		{
			tcp_abort(tcp_client_pcb2);//Aborts the connection by sending a RST (reset) segment to the remote host. The pcb is deallocated. This function never fails.
			status_con2 = STA_INIT;
		}
	}
	else
	{
		printf("Wrong status\r\n");
		while(1);
	}
}

task_network文件里面,设计了两个状态机,分别运行了两个TCP Client的连接,功能是将收到的TCP服务器数据返回给服务器,同时间隔一定的时间向TCP服务器发送数据,实验效果OK:

 

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