源码版本:lwip-1.4.1、STM32F107_ETH_LwIP_V1.0.0(ST官方LwIP的移植例程)
硬件平台:STM32F103xx
IDE: MDK KEIL5
工程添加LwIP源码
1 前言
2 源码准备
3 分析源码包
4 添加文件到工程
5 编译工程
6 硬件平台支持LwIP
写LwIP移植的文章主要记录个人的移植技巧和思路。有一段时间看了大量的LwIP的移植教程和博客,都没有很好地阐明移植的思路和方法,让初学者无法完全的掌握移植技巧,同时导致一大部分初学者对移植产生了畏惧。从本文开始,将详细讲明怎样在没有国内教程的情况下去移植LwIP。
Lwip的源码可以通过该链接进行下载:http://savannah.nongnu.org/projects/lwip/。本次记录采用的是lwip-1.4.1版本。
1. 进入lwip-1.4.1文件夹,文件组织如下:
2. doc、src、test文件夹的用途:
doc:单从文件夹名称可以知道该文件夹是供给用户使用的移植说明文档。
src:该文件包含了LwIP的源码。
test:该文件是官方提供的测试程序,我们可以不使用。
如果单从文件夹名称还是看不出这几个文件夹的用途,我们可以查看lwip-1.4.1目录下的一些说明文档。
通过上述两点,我们大概了解到移植从来未接触过的源码主要通过官方提供的文档说明进行移植。
3. doc目录下的文件
同理,我们首先查看FILES文件,其内容如下:
FILES文档已经为我们解释了很清楚。如果英文水平不好的话,请查查翻译。
笔者前期主要将LwIP移植到无系统的工程上,因此只需要用到rawapo.txt参考文档。至于其他文档,根据实际需求进行使用,可能后期文章将会使用到。对于rawapi.txt的内容,此处不进行展开,请自己硬着头皮去翻译去读。
4. src目录下
同样,进入src目录下首先查看的是FILES文档,其内容如下:
该文档也解释了src目录下文件夹的作用,仅仅采用低层次的raw API方法使用LwIP的话,只需要用到core、include、netif三个文件夹。至于api文件夹在使用高层次的API接口时才需用到。
1. 在工程目录下创建文件夹LWIP,把lwip-1.4.1整个文件夹拷贝到LWIP下。如下图所示:
2. 在keil平台添加3个分组,如下图所示:
3. 添加文件到指定分组中
(1) 由于本次移植是不带OS的,因此LWIP/API暂时不加入文件。
(2) 将协议栈相关文件添加到LWIP/CORE分组下,如下图所示:
(3) 将netif文件夹下的部分文件加入/LWIP/NETIF分组中
(4) 设置头文件路径
编译 整个工程,当然肯定会报错,这也是我们想要的结果,我们需根据报错信息修改整个工程目录,看看LwIP还需要什么文件。
1. 缺少lwipopts.h头文件,这个官方没给我们说明要自己创建的,根据文件名可以知道这个是用户的LwIP配置文件。我们在LWIP目录下创建USER文件夹,并在该文件夹中新建lwipopts.h,同时在keil平台中加入分组“LWIP/USER”,把lwipopts.h加入该分组,方便修改。当然,别忘了添加头文件路径。再次编译工程,新的报错信息如下:
2. 缺少cc.h文件,从arch关键字可以看出,这个文件与平台相关,涉及到平台相关的一般都是关于编译器、数据类型的声明等。这个我们不用自己写,直接引用ST官方的LWIP移植工程。
3. 将port文件夹下的arch文件夹拷贝到工程目录下的LWIP文件夹中。把arch的路径添加到keil的头文件路径。再次编译。
4. 将工程中的"arch/cc.h"改为"cc.h" ,再次编译,又是出现上面类同的错误信息,同理。
5. 上述报错,缺少sys_arch.h头文件,这个头文件与OS有关,我们在arch下 创建一个空头文件即可。
6. 从上述的错误可见,缺少与OS相关的通信机制,我们在lwipopts.htouw头文件中添加“#define NO_SYS 1”,然后编译。
7. 在lwipopts.h文件中再将LWIP_SOCKET和LWIP_NETCONN宏定义为0,再次编译。
8.我们在arch目录下新建sys_arch.c文件,把该文件加入keil中的LWIP/ARCH分组中,并往该文件夹加入如下代码:
#include "lwip/sys.h"
#include "sys_arch.h"
u32_t sys_now(void)
{
return 0;
}
9.再次编译,工程不再报错,只有警告。这说明我们移植成功一大半了。
LwIP已经加入到了我们的工程中,虽说编译通过了,但还不能直接用,毕竟我们还没有给LwIP提供与硬件平台相关的驱动。说到驱动的话,我们就应该找到LwIP提供的驱动接口框架文件。这个文件不难找,前面提过netif文件夹就是底层接口相关的,从netif目录下的FILE文件可知ethernetif.c文件是我们所找的文件,同时也是我们必须修改的文件。
粗略地阅读ethernetif.c文件,我们可以发现如下所示的一条语句:
这条语句又叫我们看前面的注释...如下:
翻译就不翻译了,大概知道这是网络底层接口的框架就行了。由于该文件被宏条件给屏蔽了,也就是说之前我们编译通过的LwIP并没有网络底层接口框架。我们把"#if 0"改为"#if 1",然后我们就开始完善这个框架。
由上往下地阅读ethernetif.c文件,不放过每一个函数的注释,因为这些注释会告诉你函数的作用,以及需要用户修改什么地方。
(1) low_level_init接口
static void
low_level_init(struct netif *netif)
{
struct ethernetif *ethernetif = netif->state;
/* set MAC hardware address length */
netif->hwaddr_len = ETHARP_HWADDR_LEN;
/* set MAC hardware address */
netif->hwaddr[0] = ;
...
netif->hwaddr[5] = ;
/* 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;
/* Do whatever else is needed to initialize interface. */
}
从注释可以知道,low_level_init是硬件初始化接口,会被ethernetif_init接口调用。再看low_level_init接口内部语句,需要我们完善的有:
(2)low_level_output
/**
* 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 ethernetif *ethernetif = netif->state;
struct pbuf *q;
initiate transfer();
#if ETH_PAD_SIZE
pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif
for(q = p; q != NULL; q = q->next) {
/* Send the data from the pbuf to the interface, one pbuf at a
time. The size of the data in each pbuf is kept in the ->len
variable. */
send data from(q->payload, q->len);
}
signal that packet should be sent();
#if ETH_PAD_SIZE
pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
LINK_STATS_INC(link.xmit);
return ERR_OK;
}
这个接口将网络层数据包通过以太网硬件发送出去,因此该接口我们需将网络层数据包封装为帧数据包并通过物理层发送出去。以太网芯片实现了链路层和网络层,因此我们在此加入向下封装和发送数据的过程。如何向下封装和发送,此处不加多解说,根据实际使用的硬件平台自行添加。实现这一部分,需要我们了解struct pbuf数据结构。