【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:

 

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