LWIP(Lightweight IP)是一个开源的TCP/IP协议栈实现,特别设计用于嵌入式系统,它注重减少内存使用和代码大小,以适应资源受限的环境。以下是您提到的LWIP的一些主要特性的详细说明:
由于LWIP的轻量级设计,它非常适合在嵌入式系统中使用,特别是在那些内存和处理器资源有限的设备上。此外,LWIP的高度可配置性使得开发人员可以根据特定应用的需求来定制协议栈的功能和大小。
1. 内存管理
TCP/IP协议栈中的各层都需要处理具有特定格式的数据包,并在数据包通过各层时添加或移除头部和尾部信息。这种对数据缓冲区的频繁操作可能会对嵌入式系统的性能和稳定性产生影响,特别是在资源受限的情况下。所以对于TCP/IP协议栈而言,内存管理始终是最重要的一环,内存管理的选择将从根本上决定内 存分配和回收效率,最终决定系统的性能。
在LwIP中,动态内存管理机制包括了几种不同的方式,如使用标准C库函数(如malloc()和free())、自定义的动态内存堆(heap)分配以及动态内存池(pool)分配。具体使用哪种机制,通常是通过配置LwIP的opt.h(或类似的配置头文件)中的宏定义来决定的。
以下是与LwIP动态内存管理相关的宏定义示例和解释:
标准C库内存分配:
如果LwIP配置为使用标准C库函数进行内存分配,那么它会直接调用malloc()和free()。这种配置在操作系统环境中比较常见,但在嵌入式系统中可能不太适用,因为标准C库的内存管理函数可能不适合嵌入式系统的特定需求。
通常,这种配置不需要特定的宏定义,因为默认情况下LwIP可能就会使用它们(除非明确禁用了它们)。
LwIP动态内存堆分配:
LwIP提供了一个自己的内存堆管理实现,它通常用于在没有操作系统或需要更精细控制内存分配的情况下。这种堆管理实现通常会在mem.c和memp.c文件中定义。
为了启用LwIP的内存堆分配,你需要在opt.h中定义相关的宏,例如:
#define MEM_LIBC_MALLOC 0 // 禁用标准C库malloc
#define MEM_USE_POOLS 0 // 禁用内存池分配
// 可能还需要配置MEM_SIZE等宏来定义堆的大小
LwIP动态内存池分配:
内存池分配是LwIP中用于优化性能和减少内存碎片化的一种策略。它预先分配了固定大小的内存块,并在需要时从池中分配和释放这些块。
为了启用内存池分配,你需要在opt.h中定义相关的宏,例如:
#define MEM_LIBC_MALLOC 0 // 禁用标准C库malloc
#define MEM_USE_POOLS 1 // 启用内存池分配
// 还需要配置一些其他的宏来定义内存池的大小和数量,例如MEMP_NUM_PBUF等
在opt.h中,你会看到很多与内存管理相关的宏定义,它们允许你精细地控制LwIP的内存使用。这些宏通常包括:
请注意,具体的宏定义和配置可能因LwIP的不同版本而有所变化。因此,在配置LwIP的内存管理时,最好参考你所使用的LwIP版本的官方文档或源代码中的注释。
2 . 网络接口
在lwIP中,struct netif是用于描述一个硬件网络接口的数据结构。对于单网卡系统,通常只会有一个netif结构体实例。然而,对于多网卡系统,可以创建多个netif结构体实例,每个实例对应一个物理网络接口。
这些netif结构体实例可以通过一个链表来组织,以便lwIP可以遍历所有的网络接口。在struct netif中,如果定义了LWIP_SINGLE_NETIF宏为0(或者未定义),则会包含一个指向下一个netif结构体的指针next,用于构建链表。
/** ETHARP_TABLE_MATCH_NETIF==1: Match netif for ARP table entries.
* If disabled, duplicate IP address on multiple netifs are not supported
* (but this should only occur for AutoIP).
*/
#if !defined ETHARP_TABLE_MATCH_NETIF || defined __DOXYGEN__
#define ETHARP_TABLE_MATCH_NETIF !LWIP_SINGLE_NETIF
#endif
当lwIP需要发送或接收数据时,它会遍历这个链表,找到正确的网络接口来处理数据。例如,当收到一个数据包时,lwIP会检查数据包的源地址或目标地址,以确定应该使用哪个网络接口来处理这个数据包。同样地,当lwIP需要发送一个数据包时,它会选择一个合适的网络接口来发送数据。
/** Generic data structure used for all lwIP network interfaces.
* The following fields should be filled in by the initialization
* function for the device driver: hwaddr_len, hwaddr[], mtu, flags */
struct netif {
#if !LWIP_SINGLE_NETIF
/** pointer to next in linked list */
struct netif *next;
#endif
#if LWIP_IPV4
/** IP address configuration in network byte order */
ip_addr_t ip_addr;
ip_addr_t netmask;
ip_addr_t gw;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
/** Array of IPv6 addresses for this netif. */
ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];
/** The state of each IPv6 address (Tentative, Preferred, etc).
* @see ip6_addr.h */
u8_t ip6_addr_state[LWIP_IPV6_NUM_ADDRESSES];
#if LWIP_IPV6_ADDRESS_LIFETIMES
/** Remaining valid and preferred lifetime of each IPv6 address, in seconds.
* For valid lifetimes, the special value of IP6_ADDR_LIFE_STATIC (0)
* indicates the address is static and has no lifetimes. */
u32_t ip6_addr_valid_life[LWIP_IPV6_NUM_ADDRESSES];
u32_t ip6_addr_pref_life[LWIP_IPV6_NUM_ADDRESSES];
#endif /* LWIP_IPV6_ADDRESS_LIFETIMES */
#endif /* LWIP_IPV6 */
/** This function is called by the network device driver
* to pass a packet up the TCP/IP stack. */
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() */
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. */
netif_linkoutput_fn linkoutput;
#if LWIP_IPV6
/** This function is called by the IPv6 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 ethip6_output() */
netif_output_ip6_fn output_ip6;
#endif /* LWIP_IPV6 */
#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 */
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) */
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是一个链表结构,链表中的元素通过next指针连接。个网络硬件接口对应一个netif结构的变量,当存在多个网络硬件接口的时候,这些netif结构的变量通过 next指针相互连接,构成了一个链表。因此,用户程序应当建立一个全局变量,以维护该链表。
3 . LwIP 的应用程序接口
LwIP提供了一套应用程序接口(API),这些接口使得应用程序能够方便地使用TCP/IP协议栈进行网络通信。
以下是LwIP的主要应用程序接口及其功能的简要介绍:
在嵌入式系统中,由于资源有限(如内存、处理器速度等),使用传统的BSD API(如UNIX或Linux中的套接字API)可能会导致资源消耗过多,从而不适合这些环境。为此,LwIP(Lightweight IP)被设计为轻量级的TCP/IP协议栈,特别针对嵌入式系统进行了优化。
LwIP API的优势
LwIP API在设计上充分考虑了嵌入式系统的特点,它与BSD API类似,但操作更为低级,能够充分利用LwIP的内部结构来避免BSD API对系统资源的过度依赖。这一特点使得LwIP API在嵌入式系统中具有显著的优势:
BSD Socket兼容层
尽管LwIP API具有诸多优势,但BSD套接字API的易用性和普及性仍然不容忽视。为了充分利用现有的代码库和工具,LwIP保留了一个BSD Socket兼容层。这使得开发人员能够轻松地将为BSD套接字编写的应用程序迁移到LwIP环境中,同时享受LwIP带来的性能优势。
3 . LwIP 的应用程序接口
在将LWIP协议栈移植到GD32H7处理器上时,底层驱动的移植工作确实主要集中在两个方面:修改ethernetif.c文件以适配GD32H7的网络硬件,以及编写GD32H7的网络驱动程序。以下是这两部分工作的详细描述:
ethernetif.c
文件ethernetif.c文件是LWIP协议栈提供的一个示例文件,用于连接网络硬件驱动和LWIP的网络协议栈。这个文件需要被修改以适配GD32H7的特定网络硬件接口。主要的工作包括:
接口定义:定义用于与GD32H7网络硬件通信的接口函数。这些函数通常包括初始化函数(如low_level_init)、发送函数(如low_level_output)和接收函数(如low_level_input)。
初始化函数:在low_level_init函数中,配置GD32H7的网络硬件接口,如设置MAC地址、初始化DMA描述符、配置中断等。
发送函数:在low_level_output函数中,实现将数据帧从LWIP缓冲区发送到网络硬件的功能。这通常涉及将数据帧复制到网络硬件的发送缓冲区,并启动发送过程。
接收函数:在low_level_input函数中,从网络硬件的接收缓冲区读取数据帧,并将其传递给LWIP的协议栈。这个函数可能会被设置为网络硬件接收中断的处理程序。
3.2编写GD32H7的网络驱动程序
编写GD32H7的网络驱动程序是实现网络底层功能的关键部分。这个驱动程序需要实现网络接口的初始化、收发报文等功能。具体的工作包括:
硬件初始化:在驱动程序中,需要配置GD32H7的网络硬件接口,包括设置MAC地址、配置时钟、初始化DMA等。
中断处理:网络硬件通常使用中断来通知CPU数据已经到达或发送完成。在驱动程序中,需要编写中断处理程序来响应这些中断,并执行相应的操作(如读取接收缓冲区、清除发送完成标志等)。
数据收发:驱动程序需要实现数据的发送和接收功能。发送功能通常涉及将数据帧复制到网络硬件的发送缓冲区,并启动发送过程;接收功能则涉及从网络硬件的接收缓冲区读取数据帧,并将其传递给上层协议栈。
错误处理:在网络通信过程中,可能会出现各种错误(如发送失败、接收缓冲区溢出等)。驱动程序需要能够检测这些错误,并采取相应的措施(如重试发送、丢弃接收到的数据帧等)。
性能优化:为了提高网络通信的性能,驱动程序可能需要实现一些优化措施,如使用DMA进行数据传输、使用中断合并来减少中断次数等。
本帖最后由 尹小舟 于 2024-6-5 14:05 编辑
如果从官方代码移植的话,容易吗?大老有没有这方面的计划?
引用: lugl4313820 发表于 2024-6-6 10:13 如果从官方代码移植的话,容易吗?大老有没有这方面的计划?
LWIP和单片机的以太网驱动,这个协议还是很复杂,我也是才开始了解,根据我的学习体会,一开始不要去尝试移植LWIP或编写以太网驱动。先通过学习和理解其工作原理和基本概念,为后续的实践打下坚实的基础。学习别人已经编写好的Demo是快速上手的有效途径。通过阅读和分析Demo代码,了解如何配置和使用LWIP以及以太网驱动。更形象,不了解就移植的话,有问题会有很多挫败感,在理解了基本概念和Demo代码之后,在尝试在自己的硬件平台上进行实践。从简单的网络应用程序开始,逐步增加功能和复杂性,你的心态会顺很多
引用: 尹小舟 发表于 2024-6-6 10:40 LWIP和单片机的以太网驱动,这个协议还是很复杂,我也是才开始了解,根据我的学习体会,一开始不要去尝试 ...
多谢大佬的指导。