基于STM32F407MAC与DP83848实现以太网通讯五(裸机移植LwIP协议栈)
LwIP协议移植参考文件:
上一节实现了STM32ETH MAC数据链路层的数据收发,本节在上一节的基础上移植LwIP协议栈,实现网络层收发数据。
LwIP协议栈的移植主要参考ST的官方参考文件STSW-STM32070的LwIP例程实现。
一、LwIP的启动流程
在LwIP的官方文档中找到System initalization,文档中说明了LwIP的正常启动流程。
lwip_init()
: 初始化lwip内核与其所有子系统(无操作系统),使用操作系统时则使用tcpip_init()
初始化所有系统netif_add()
: 添加一个网卡(结构体抽象)到网卡列表中,使用DHCP时需要将网卡的ip_addr
字段清零。下面演示了网卡的初始化。netif_set_default()
:设置默认的的网卡netif_set_link_up()
:设置网络连接状态,当检测到有网线连接时调用此函数(不知道是否连接上网线时也需要调用(DHCP、IPV6发现))netif_set_up()
:打开网络接口,在配置完网卡后使用dhcp_start()
:开启DHCP第一次调用时会创建一个DHCP客户端sys_check_timeouts()
:处理协议栈中的定时器比如DHCP、TCP超时,这个函数必须放在主循环周期调用。
二、lwip源码
以上为lwip2.1.2/src文件夹中的文件
- api: NETCONN 和 Socket 的API文件,不带操作系统移植使用的是RAW(回调函数)编程,本节未用到
- apps: 网络应用程序的源文件
- core: lwip的内核源码
- include: lwip模块的头文件
- netif: 网卡移植相关的文件
三、LwIP协议栈移植
3.1、将源文件和头文件路径添加到工程
- 将api文件夹的所有.c文件添加到工程
- 将core/ipv4的所有.c文件添加到工程
- 将core文件夹下的所有.c文件添加到工程
- 将netif文件夹的ethernet.c文件添加到工程,其他文件使用的时候再添加
添加好的工程文件如下(ethernetif.c
后面添加)
添加相关的头文件路径如下
3.2、移植头文件lwipopts.h
lwipopts.h
文件为lwip的配置文件,opt.h
是lwip的默认配置文件在lwip的源码中,在lwipopt.h
文件中我们对部分的参数重新配置以适应使用的硬件。
需要注意的是以下硬件校验和的配置,这个是基于硬件的,也就是说当STM32ETH外设开启了硬件检验和时,这个才能配置,否者lwip通讯会失败。
#define CHECKSUM_BY_HARDWARE
lwipopt.h
的代码如下:
lwipopts.h
/*
* Copyright (c) 2001-2003 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 <adam@sics.se>
*
*/
#ifndef LWIP_LWIPOPTS_H
#define LWIP_LWIPOPTS_H
#ifdef LWIP_OPTTEST_FILE
#include "lwipopts_test.h"
#else /* LWIP_OPTTEST_FILE */
#define LWIP_IPV4 1
#define LWIP_IPV6 0
#define NO_SYS 1
#define LWIP_SOCKET (NO_SYS==0)
#define LWIP_NETCONN (NO_SYS==0)
#define LWIP_NETIF_API (NO_SYS==0)
#define LWIP_IGMP LWIP_IPV4
#define LWIP_ICMP LWIP_IPV4
#define LWIP_SNMP LWIP_UDP
#define MIB2_STATS LWIP_SNMP
#ifdef LWIP_HAVE_MBEDTLS
#define LWIP_SNMP_V3 (LWIP_SNMP)
#endif
#define LWIP_DNS LWIP_UDP
#define LWIP_MDNS_RESPONDER LWIP_UDP
#define LWIP_NUM_NETIF_CLIENT_DATA (LWIP_MDNS_RESPONDER)
#define LWIP_HAVE_LOOPIF 1
#define LWIP_NETIF_LOOPBACK 1
#define LWIP_LOOPBACK_MAX_PBUFS 10
#define TCP_LISTEN_BACKLOG 1
#define LWIP_COMPAT_SOCKETS 1
#define LWIP_SO_RCVTIMEO 1
#define LWIP_SO_RCVBUF 1
#define LWIP_TCPIP_CORE_LOCKING 1
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_NETIF_EXT_STATUS_CALLBACK 1
#ifdef LWIP_DEBUG
#define LWIP_DBG_MIN_LEVEL 0
#define PPP_DEBUG LWIP_DBG_OFF
#define MEM_DEBUG LWIP_DBG_OFF
#define MEMP_DEBUG LWIP_DBG_OFF
#define PBUF_DEBUG LWIP_DBG_OFF
#define API_LIB_DEBUG LWIP_DBG_OFF
#define API_MSG_DEBUG LWIP_DBG_OFF
#define TCPIP_DEBUG LWIP_DBG_OFF
#define NETIF_DEBUG LWIP_DBG_OFF
#define SOCKETS_DEBUG LWIP_DBG_OFF
#define DNS_DEBUG LWIP_DBG_OFF
#define AUTOIP_DEBUG LWIP_DBG_OFF
#define DHCP_DEBUG LWIP_DBG_OFF
#define IP_DEBUG LWIP_DBG_OFF
#define IP_REASS_DEBUG LWIP_DBG_OFF
#define ICMP_DEBUG LWIP_DBG_OFF
#define IGMP_DEBUG LWIP_DBG_OFF
#define UDP_DEBUG LWIP_DBG_OFF
#define TCP_DEBUG LWIP_DBG_OFF
#define TCP_INPUT_DEBUG LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF
#define TCP_RTO_DEBUG LWIP_DBG_OFF
#define TCP_CWND_DEBUG LWIP_DBG_OFF
#define TCP_WND_DEBUG LWIP_DBG_OFF
#define TCP_FR_DEBUG LWIP_DBG_OFF
#define TCP_QLEN_DEBUG LWIP_DBG_OFF
#define TCP_RST_DEBUG LWIP_DBG_OFF
#endif
#define LWIP_DBG_TYPES_ON (LWIP_DBG_ON|LWIP_DBG_TRACE|LWIP_DBG_STATE|LWIP_DBG_FRESH|LWIP_DBG_HALT)
/* ---------- 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. */
/* MSVC port: intel processors don't need 4-byte alignment,
but are faster that way! */
#define MEM_ALIGNMENT 4U
/* 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 (25*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 24
/* MEMP_NUM_RAW_PCB: the number of UDP protocol control blocks. One
per active RAW "connection". */
#define MEMP_NUM_RAW_PCB 3
/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
per active UDP "connection". */
#define MEMP_NUM_UDP_PCB 4
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
connections. */
#define MEMP_NUM_TCP_PCB 5
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 8
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
segments. */
#define MEMP_NUM_TCP_SEG 150
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
timeouts. */
#define MEMP_NUM_SYS_TIMEOUT 17
/* The following four are used only with the sequential API and can be
set to 0 if the application only will use the raw API. */
/* MEMP_NUM_NETBUF: the number of struct netbufs. */
#define MEMP_NUM_NETBUF 2
/* MEMP_NUM_NETCONN: the number of struct netconns. */
#define MEMP_NUM_NETCONN 10
/* MEMP_NUM_TCPIP_MSG_*: the number of struct tcpip_msg, which is used
for sequential API communication and incoming packets. Used in
src/api/tcpip.c. */
#define MEMP_NUM_TCPIP_MSG_API 16
#define MEMP_NUM_TCPIP_MSG_INPKT 16
/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE 50
/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE 1280
/** SYS_LIGHTWEIGHT_PROT
* define SYS_LIGHTWEIGHT_PROT in lwipopts.h if you want inter-task protection
* for certain critical regions during buffer allocation, deallocation and memory
* allocation and deallocation.
*/
#define SYS_LIGHTWEIGHT_PROT (NO_SYS==0)
/* ---------- TCP options ---------- */
#define LWIP_TCP 1
#define TCP_TTL 255
#define LWIP_ALTCP (LWIP_TCP)
#ifdef LWIP_HAVE_MBEDTLS
#define LWIP_ALTCP_TLS (LWIP_TCP)
#define LWIP_ALTCP_TLS_MBEDTLS (LWIP_TCP)
#endif
/* 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 1
/* TCP Maximum segment size. */
#define TCP_MSS (1500 - 40)
/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF (8*TCP_MSS)
/* TCP sender buffer space (pbufs). This must be at least = 2 *
TCP_SND_BUF/TCP_MSS for things to work. */
#define TCP_SND_QUEUELEN (8 * TCP_SND_BUF/TCP_MSS)
/* TCP writable space (bytes). This must be less than or equal
to TCP_SND_BUF. It is the amount of space which must be
available in the tcp snd_buf for select to return writable */
#define TCP_SNDLOWAT (TCP_SND_BUF/2)
/* TCP receive window. */
#define TCP_WND (8 * TCP_MSS)
/* Maximum number of retransmissions of data segments. */
#define TCP_MAXRTX 12
/* Maximum number of retransmissions of SYN segments. */
#define TCP_SYNMAXRTX 4
/* ---------- ARP options ---------- */
#define LWIP_ARP 1
#define ARP_TABLE_SIZE 10
#define ARP_QUEUEING 1
/* ---------- IP options ---------- */
/* Define IP_FORWARD to 1 if you wish to have the ability to forward
IP packets across network interfaces. If you are going to run lwIP
on a device with only one network interface, define this to 0. */
#define IP_FORWARD 1
/* IP reassembly and segmentation.These are orthogonal even
* if they both deal with IP fragments */
#define IP_REASSEMBLY 1
#define IP_REASS_MAX_PBUFS (10 * ((1500 + PBUF_POOL_BUFSIZE - 1) / PBUF_POOL_BUFSIZE))
#define MEMP_NUM_REASSDATA IP_REASS_MAX_PBUFS
#define IP_FRAG 1
#define IPV6_FRAG_COPYHEADER 1
/* ---------- ICMP options ---------- */
#define ICMP_TTL 255
/* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
interfaces. */
#define LWIP_DHCP 0
/* 1 if you want to do an ARP check on the offered address
(recommended). */
#define DHCP_DOES_ARP_CHECK (LWIP_DHCP)
/* ---------- AUTOIP options ------- */
#define LWIP_AUTOIP (LWIP_DHCP)
#define LWIP_DHCP_AUTOIP_COOP (LWIP_DHCP && LWIP_AUTOIP)
/* ---------- UDP options ---------- */
#define LWIP_UDP 1
#define LWIP_UDPLITE LWIP_UDP
#define UDP_TTL 255
/* ---------- RAW options ---------- */
#define LWIP_RAW 1
/* ---------- Statistics options ---------- */
#define LWIP_STATS 1
#define LWIP_STATS_DISPLAY 1
#if LWIP_STATS
#define LINK_STATS 1
#define IP_STATS 1
#define ICMP_STATS 1
#define IGMP_STATS 1
#define IPFRAG_STATS 1
#define UDP_STATS 1
#define TCP_STATS 1
#define MEM_STATS 1
#define MEMP_STATS 1
#define PBUF_STATS 1
#define SYS_STATS 1
#endif /* LWIP_STATS */
/* ---------- NETBIOS options ---------- */
#define LWIP_NETBIOS_RESPOND_NAME_QUERY 1
/* ---------- PPP options ---------- */
#define PPP_SUPPORT 0 /* Set > 0 for PPP */
#if PPP_SUPPORT
#define NUM_PPP 1 /* Max PPP sessions. */
/* Select modules to enable. Ideally these would be set in the makefile but
* we're limited by the command line length so you need to modify the settings
* in this file.
*/
#define PPPOE_SUPPORT 1
#define PPPOS_SUPPORT 1
#define PAP_SUPPORT 1 /* Set > 0 for PAP. */
#define CHAP_SUPPORT 1 /* Set > 0 for CHAP. */
#define MSCHAP_SUPPORT 0 /* Set > 0 for MSCHAP */
#define CBCP_SUPPORT 0 /* Set > 0 for CBCP (NOT FUNCTIONAL!) */
#define CCP_SUPPORT 0 /* Set > 0 for CCP */
#define VJ_SUPPORT 1 /* Set > 0 for VJ header compression. */
#define MD5_SUPPORT 1 /* Set > 0 for MD5 (see also CHAP) */
#endif /* PPP_SUPPORT */
#endif /* LWIP_OPTTEST_FILE */
/* The following defines must be done even in OPTTEST mode: */
#if !defined(NO_SYS) || !NO_SYS /* default is 0 */
void sys_check_core_locking(void);
#define LWIP_ASSERT_CORE_LOCKED() sys_check_core_locking()
void sys_mark_tcpip_thread(void);
#define LWIP_MARK_TCPIP_THREAD() sys_mark_tcpip_thread()
#if !defined(LWIP_TCPIP_CORE_LOCKING) || LWIP_TCPIP_CORE_LOCKING /* default is 1 */
void sys_lock_tcpip_core(void);
#define LOCK_TCPIP_CORE() sys_lock_tcpip_core()
void sys_unlock_tcpip_core(void);
#define UNLOCK_TCPIP_CORE() sys_unlock_tcpip_core()
#endif
#endif
/*
--------------------------------------
---------- 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
/* CHECKSUM_CHECK_ICMP==0: Check checksums by hardware for incoming ICMP packets.*/
#define CHECKSUM_GEN_ICMP 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
/* CHECKSUM_CHECK_ICMP==1: Check checksums by hardware for incoming ICMP packets.*/
#define CHECKSUM_GEN_ICMP 1
#endif
#endif /* LWIP_LWIPOPTS_H */
3.2、系统内核相关的文件cc.h
cc.h中定义了一些对于不同内核处理器使用的有差异的配置,比如大小端模式、数据类型的定义、不同编译平台的宏定义以及lwip断言的宏定义。
需要注意的是类型定义在2.1.x版本不需要在cc.h
中定义,lwip在arch.c
中已经定义了,但是1.4.x版本中就需要添加相关的宏定义。
cc.h
的代码如下:
cc.h
#ifndef __CC_H__
#define __CC_H__
#include "cpu.h"
#include "stdio.h"
/*-------------data type------------------------------------------------------*/
//typedef unsigned char u8_t; /* Unsigned 8 bit quantity */
//typedef signed char s8_t; /* Signed 8 bit quantity */
//typedef unsigned short u16_t; /* Unsigned 16 bit quantity */
//typedef signed short s16_t; /* Signed 16 bit quantity */
//typedef unsigned long u32_t; /* Unsigned 32 bit quantity */
//typedef signed long s32_t; /* Signed 32 bit quantity */
//typedef u32_t mem_ptr_t; /* Unsigned 32 bit quantity */
//typedef int sys_prot_t;
/* 选择小端模式 */
#define BYTE_ORDER LITTLE_ENDIAN
/*-------------critical region protection (depends on uC/OS-II setting)-------*/
#if OS_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev) CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev) CPU_INT_EN()
#endif
#if OS_CRITICAL_METHOD == 3 //method 3 is used in this port.
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev = OS_CPU_SR_Save()
#define SYS_ARCH_UNPROTECT(lev) OS_CPU_SR_Restore(lev)
#endif
/*----------------------------------------------------------------------------*/
/* define compiler specific symbols */
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
/*---define (sn)printf formatters for these lwip types, for lwip DEBUG/STATS--*/
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
/*--------------macros--------------------------------------------------------*/
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \
do \
{ printf("Assertion \"%s\" failed at line %d in %s\r\n", x, __LINE__, __FILE__); \
} while(0)
#endif
#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif
#endif /* __CC_H__ */
3.3、lwip系统测量相关的头文件perf.h
当前移植不需要使用此功能,需要的时候再添加
perf.h
的代码如下:
perf.h
#ifndef __PERF_H__
#define __PERF_H__
#define PERF_START /* null definition */
#define PERF_STOP(x) /* null definition */
#endif /* __PERF_H__ */
3.4、lwip网络接口驱动文件ethernetif.c
lwip协议栈与开发板ETH外设的交互文件,lwip通过调用ethernetif.c
文件中的接收与发送函数实现与外部网络的通信。
源码中netif.c文件就是网卡的抽象,而网卡硬件链路层的数据收发需要我们自己实现,contrib-2.1.0examplesethernetif中由提供的模板,模板中的函数需要我们根据自己使用的硬件去实现。
当然在ST的例程STSW-STM32070中有写好的ethnetif.c文件可以参考,需要注意的是STSW-STM32070文件中使用到的lwip版本为lwip1.4.1版本。
由于使用的是标准库ethernetif.c
的代码部分参考了正点原子探索则开发板的lwip的源码。
ethernetif.c
的代码如下:
ethernetif.c
#include "netif/ethernetif.h"
#include "dp83848.h"
#include "lwip_comm.h"
#include "netif/etharp.h"
#include "string.h"
//由ethernetif_init()调用用于初始化硬件
//netif:网卡结构体指针
//返回值:ERR_OK,正常
// 其他,失败
static err_t low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
int i;
#endif
netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置MAC地址长度,为6个字节
//初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
netif->hwaddr[0]=lwipdev.mac[0];
netif->hwaddr[1]=lwipdev.mac[1];
netif->hwaddr[2]=lwipdev.mac[2];
netif->hwaddr[3]=lwipdev.mac[3];
netif->hwaddr[4]=lwipdev.mac[4];
netif->hwaddr[5]=lwipdev.mac[5];
netif->mtu=1500; //最大允许传输单元,允许该网卡广播和ARP功能
//并且该网卡允许有硬件链路连接
netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;
ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); //向STM32F4的MAC地址寄存器中写入MAC地址
/* 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 //使用硬件帧校验
for(i=0;i<ETH_TXBUFNB;i++) //使能TCP,UDP和ICMP的发送帧校验,TCP,UDP和ICMP的接收帧校验在DMA中配置了
{
ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
}
#endif
ETH_Start(); //开启MAC和DMA
return ERR_OK;
}
//用于发送数据包的最底层函数(lwip通过netif->linkoutput指向该函数)
//netif:网卡结构体指针
//p:pbuf数据结构体指针
//返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
u8 res;
struct pbuf *q;
int l = 0;
u8 *buffer=(u8 *)ETH_GetCurrentTxBuffer();
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)&buffer[l], q->payload, q->len);
l=l+q->len;
}
res=ETH_Tx_Packet(l);
if(res==ETH_ERROR)return ERR_MEM;//返回错误状态
return ERR_OK;
}
//用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p, *q;
u16_t len;
int l =0;
FrameTypeDef frame;
u8 *buffer;
p = NULL;
frame=ETH_Rx_Packet();
len=frame.length;//得到包大小
buffer=(u8 *)frame.buffer;//得到包数据地址
p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
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;
}
}
frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA
if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输
{
ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位
ETH->DMARPDR=0;//恢复DMA接收
}
return p;
}
//网卡接收数据(lwip直接调用)
//netif:网卡结构体指针
//返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
err_t ethernetif_input(struct netif *netif)
{
err_t err;
struct pbuf *p;
p=low_level_input(netif);
if(p==NULL) return ERR_MEM;
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;
}
//使用low_level_init()函数来初始化网络
//netif:网卡结构体指针
//返回值:ERR_OK,正常
// 其他,失败
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif!=NULL",(netif!=NULL));
#if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME
netif->hostname="lwip"; //初始化名称
#endif
netif->name[0]=IFNAME0; //初始化变量netif的name字段
netif->name[1]=IFNAME1; //在文件外定义这里不用关心具体值
netif->output=etharp_output;//IP层发送数据包函数
netif->linkoutput=low_level_output;//ARP模块发送数据包函数
low_level_init(netif); //底层硬件初始化函数
return ERR_OK;
}
ethernetif.h
#ifndef __ETHERNETIF_H__
#define __ETHERNETIF_H__
#include "lwip/err.h"
#include "lwip/netif.h"
//网卡的名字
#define IFNAME0 'e'
#define IFNAME1 'n'
err_t ethernetif_init(struct netif *netif);
err_t ethernetif_input(struct netif *netif);
#endif
3.5、操作系统的接口文件sys_arch.c
sys_arch.c
是在lwip使用操作系统时使用到的文件,现在并不需要,但是要运行lwip还需要给lwip提供一个时基信号(ms),这个时基信号使用STM32的定时器实现,所以现在只需要在sys_arch.c
实现sys_now()
函数。
sys_arch.c
的代码如下:
sys_arch.c
#include "sys_arch.h"
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "timer.h"
//为LWIP提供计时
extern uint32_t lwip_localtime;//lwip本地时间计数器,单位:ms
u32_t sys_now(void){
return lwip_localtime;
}
sys_arch.h
#ifndef __SYS_RTXC_H__
#define __SYS_RTXC_H__
#include "stdlib.h"
#include "lwip/arch.h"
#define SYS_MBOX_NULL 0
#define SYS_SEM_NULL 0
//typedef int sys_sem_t;
//typedef int sys_mbox_t;
//typedef int sys_thread_t;
#define LWIP_RAND rand
u32_t sys_now(void);
#endif /* __SYS_RTXC_H__ */
1ms中断定时器实现代码如下:
timer.c
#include "timer.h"
extern u32 lwip_localtime;
//通用定时器3中断初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器3
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
lwip_localtime +=1; //加1
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
timer.h
#ifndef _TIMER_H
#define _TIMER_H
#include "sys.h"
void TIM3_Int_Init(u16 arr,u16 psc);
#endif
3.6、lwip协议栈启动
上面的程序完成之后接下来就是初始化和启动lwip协议栈了,主要的启动流程参考第一章的Lwip启动流程,这里简单介绍一下。
lwip_comm_init()
函数中首先初始化了ETH的相关硬件ETH_BSP_Config()
,但是没有启动,ETH_Start()
函数在ethernetif.c
的low_level_init()
函数中由lwip协议栈自动调用。
lwip_init()
初始化lwip协议栈,接下来就是给网卡配置IPIP4_ADDR()
或者使用DHCP将IP地址清零ip_addr_set_zero_ip4
,然后添加网卡netif_add()
,设置默认网口netif_set_default()
、打开网口netif_set_up()
,未开启DHCP则已经连接完成,开启DHCP则等待路由器分配IP,完成lwip协议栈的启动。
lwip_pkt_handle()
函数是STM32ETH中断的回调函数,用于接收数据包,如果不使用中断的话,那么ethernetif_input()
函数则需要在主循环中轮询调用。
lwip_comm.c
代码如下:
lwip_comm.c
#include "lwip_comm.h"
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/init.h"
#include "netif/ethernetif.h"
#include "lwip/timeouts.h"
#include "lwip/priv/tcp_priv.h"
#include "lwip/ip4_frag.h"
#include "lwip/tcpip.h"
#include "delay.h"
#include "usart.h"
#include <stdio.h>
__lwip_dev lwipdev; //lwip控制结构体
struct netif lwip_netif; //定义一个全局的网络接口
u32 lwip_localtime; //lwip本地时间计数器,单位:ms
void lwip_comm_default_ip_set(__lwip_dev *lwipx)
{
u32 sn0;
sn0=*(vu32*)(0x1FFF7A10);//获取STM32的唯一ID的前24位作为MAC地址后三字节
//默认远端IP为:192.168.1.100
lwipx->remoteip[0]=192;
lwipx->remoteip[1]=168;
lwipx->remoteip[2]=2;
lwipx->remoteip[3]=2;
//MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
lwipx->mac[0]=2;//高三字节(IEEE称之为组织唯一ID,OUI)地址固定为:2.0.0
lwipx->mac[1]=0;
lwipx->mac[2]=0;
lwipx->mac[3]=(sn0>>16)&0XFF;//低三字节用STM32的唯一ID
lwipx->mac[4]=(sn0>>8)&0XFFF;;
lwipx->mac[5]=sn0&0XFF;
//默认本地IP为:192.168.1.30
lwipx->ip[0]=192;
lwipx->ip[1]=168;
lwipx->ip[2]=2;
lwipx->ip[3]=120;
//默认子网掩码:255.255.255.0
lwipx->netmask[0]=255;
lwipx->netmask[1]=255;
lwipx->netmask[2]=255;
lwipx->netmask[3]=0;
//默认网关:192.168.1.1
lwipx->gateway[0]=192;
lwipx->gateway[1]=168;
lwipx->gateway[2]=2;
lwipx->gateway[3]=1;
}
u8 lwip_comm_init(void)
{
struct netif *Netif_Init_Flag; //调用netif_add()函数时的返回值,用于判断网络初始化是否成功
struct ip4_addr ipaddr; //ip地址
struct ip4_addr netmask; //子网掩码
struct ip4_addr gw; //默认网关
ETH_BSP_Config();
lwip_init(); //初始化LWIP内核
lwip_comm_default_ip_set(&lwipdev); //设置默认IP等信息
#if LWIP_DHCP //使用动态IP
ip_addr_set_zero_ip4(&ipaddr);
ip_addr_set_zero_ip4(&netmask);
ip_addr_set_zero_ip4(&gw);
#else //使用静态IP
IP4_ADDR(&ipaddr,lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
IP4_ADDR(&netmask,lwipdev.netmask[0],lwipdev.netmask[1] ,lwipdev.netmask[2],lwipdev.netmask[3]);
IP4_ADDR(&gw,lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
#endif
Netif_Init_Flag=netif_add(&lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,ðernet_input);//向网卡列表中添加一个网口
if(Netif_Init_Flag==NULL)return 3;//网卡添加失败
else//网口添加成功后,设置netif为默认值,并且打开netif网口
{
if (netif_is_link_up(&lwip_netif))
{
printf("netif_is_link_up is ok\n");
netif_set_default(&lwip_netif); //设置netif为默认网口
netif_set_up(&lwip_netif); //打开netif网口
}
}
#if LWIP_DHCP //若使用了DHCP
int err;
/* Creates a new DHCP client for this interface on the first call.
Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at
the predefined regular intervals after starting the client.
You can peek in the netif->dhcp struct for the actual DHCP status.*/
printf("本例程将使用DHCP动态分配IP地址,如果不需要则在lwipopts.h中将LWIP_DHCP定义为0\n\n");
err = dhcp_start(&lwip_netif); //开启dhcp
if(err == ERR_OK)
printf("lwip dhcp init success...\n\n");
else
printf("lwip dhcp init fail...\n\n");
while(ip_addr_cmp(&(lwip_netif.ip_addr),&ipaddr)) //等待dhcp分配的ip有效
{
delay_ms(1);
}
#endif
printf("MAC: %d.%d.%d.%d.%d.%d\r\n",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);
printf("Static IP: %d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
printf("NETMASK: %d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
printf("Gateway: %d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
return 0;//操作OK.
}
//当接收到数据后调用
void lwip_pkt_handle(void)
{
//从网络缓冲区中读取接收到的数据包并将其发送给LWIP处理
ethernetif_input(&lwip_netif);
}
lwip_comm.h
#ifndef _LWIP_COMM_H
#define _LWIP_COMM_H
#include "DP83848.h"
//lwip控制结构体
typedef struct
{
u8 mac[6]; //MAC地址
u8 remoteip[4]; //远端主机IP地址
u8 ip[4]; //本机IP地址
u8 netmask[4]; //子网掩码
u8 gateway[4]; //默认网关的IP地址
}__lwip_dev;
extern __lwip_dev lwipdev; //lwip控制结构体
void lwip_pkt_handle(void);
void lwip_comm_default_ip_set(__lwip_dev *lwipx);
u8 lwip_comm_init(void);
void lwip_dhcp_process_handle(void);
#endif
3.7、lwip移植验证
在完成了上面的代码移植后,只需要在main.c
中启动lwip并循环调用sys_check_timeouts()
函数就能够运转lwip协议栈了。当然这只是基本的网络协议栈,想要实现UDP/TCP协议的数据收发,还需要写相应的应用程序。本节先验证lwip协议栈的移植。
main.c
的代码如下
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "lwip/timeouts.h"
#include "stm32f4x7_eth.h"
#include "DP83848.h"
#include "lwip_comm.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断分组配置
delay_init(168); // 初始化延时函数
uart_init(); // 初始化串口
LED_Init(); // 初始化LED端口
TIM3_Int_Init(99, 839);
lwip_comm_init();
printf("PHY_SR:0x%04x\n",ETH_ReadPHYRegister(0x01, PHY_SR));
while (1)
{
sys_check_timeouts();
}
}
编译完成后通过网口连接电脑ping开发板结果如下图则lwip移植完成!
四、小结
本节实现了lwip2.1.2协议栈在STM32上的移植。要在本代码的基础上实现UDP和TCP通讯,则在无操作系统的环境下需要使用RAW基于回调函数编程,而在RAW这一块的学习,我打算结合IPerf网络测试软件进行,下一节实现基于STM32F407_lwip的IPerf的网络测试。