转自:http://blog.csdn.net/ygrx/article/details/8020516
好久没有做过技术工作了,前几天因为一些需要,要在ST的OS20平台上进行了LWIP的移植,有一些心得,写出来供大家参考。
LWIP的背景我就不介绍了,相信能看到这篇文章的人都对其背景有过了解了。
LWIP的模块化还是很强的,所以移植起来没有想象的那么多困难,一个协议栈在某个平台上移植,其实主要来说包括两个大的部分接口,注意一下,我使用的是最新的LWIP1.4.0。
1.与系统相关的接口,比如多线程,信号量,互斥锁,系统时间等,当然,LWIP还有一种无操作系统模式,那这些接口就不用实现了。
2.与硬件相关的接口,实际上就是网卡驱动啦,当然,如果你的物理层不是网卡而是别的什么,那就需要的是另外那个硬件设备的驱动啦,比如Docsis设备之类的。
好了,开始移植吧。
首先是操作系统部分:
1.opt.h
首先看这个文件,这个文件里面包含了LWIP的模块选项,可以在这里选择哪些模块需要编译,那些模块不编译,分成几个部分,mem,arp,icmp,igmp,ppp,dhcp等,这里可以根据自己的需要修改编译选项,如果是带操作系统的,还要修改栈空间,优先级之类的选项。
2.cc.h
这个文件是没有的,你需要建立一个目录arch,然后在下面添加cc.h,这里里面主要是一些定义,包括数据类型,大小头端之类的,我的cc.h比较简单
/*数据类型宏定义*/
typedef unsigned char u8_t;
typedef unsigned short u16_t;
typedef unsigned int u32_t;
typedef char s8_t;
typedef short s16_t;
typedef int s32_t;
typedef int mem_ptr_t;
typedef semaphore_t sys_sem_t;
typedef mutex_t sys_mutex_t;
typedef message_queue_t sys_mbox_t;
typedef u8_t sys_thread_t;
#define BYTE_ORDER LITTLE_ENDIAN //大小头端选择
/*调试信息宏定义*/
#define U16_F "hu"
#define S16_F "d"
#define X16_F "hx"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"
#define SZT_F "uz"
就这些。
3.arch.h
这个地方需要注意就是字节对齐,字节对齐在某些编译器上是用的#pragam pack(n)来实现的,那需要把相应的宏定义修改:
#ifndef PACK_STRUCT_BEGIN
#define PACK_STRUCT_BEGIN #pragma pack(1)
#endif /* PACK_STRUCT_BEGIN */
#ifndef PACK_STRUCT_END #pragma pack()
#define PACK_STRUCT_END //#
而ST的st2cc编译器是在编译的时候加了个参数来进行字节对齐的,所以这里不用修改
4.sys.h
这个文件就比较关键了,这是你移植过程中要写最多代码的地方,在LWIP中,如果是有操作系统的编译版本,那么需要实现线程和与线程通信的相关代码,其中包括:线程建立,互斥锁,信号量,消息队列(一般叫MailBox),系统当前时间,需要实现的东西都在sys.h中,一般情况下,我们为了文件清晰,会在arch目录下新建一个文件sys_arch.c,当然,你也可以直接在根目录下建立这个文件,注意的是要在该文件中include sys.h,然后你就开始挨个实现sys.h中的系统函数吧,实际上也挺简单,就是把系统的API调用封装成LWIP的模式。
线程部分:
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio) 线程建立
互斥锁:
err_t sys_mutex_new(sys_mutex_t *mutex) 新建锁
void sys_mutex_lock(sys_mutex_t *mutex) 获取锁
void sys_mutex_unlock(sys_mutex_t *mutex) 释放锁
void sys_mutex_free(sys_mutex_t *mutex) 删除锁
int sys_mutex_valid(sys_mutex_t *mutex) 查询锁是否可用
void sys_mutex_set_invalid(sys_mutex_t *mutex) 设置锁为失效
信号量:
err_t sys_sem_new(sys_sem_t *sem, u8_t count) 新建信号量
void sys_sem_signal(sys_sem_t *sem) 发送信号量
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout) 等待信号量
void sys_sem_free(sys_sem_t *sem) 删除信号量
int sys_sem_valid(sys_sem_t *sem) 查询是否可用
void sys_sem_set_invalid(sys_sem_t *sem) 设置为失效
消息队列(MailBox):
这个东西稍微复杂一点,如果操作系统的API中没有现成的消息队列机制,那么你需要自己实现,其实无非就是个链表的带锁操作,在OS20中是自己带有MailBox的API的,所以我也没自己实现,直接使用了,需要实现的函数在sys.h中也都有,我就不列举了。
系统时间:
这个我返回的是系统调用的系统时间,就是系统启动后多少秒
u32_t sys_now(void)
好了,这几个文件都实现完了,那基本上操作系统部分就差不多了,我的建议是,在opt.h和cc.h修改完以后,你就开始编译,如果有错误几乎都应该是没有include某个文件造成的,及时修改错误,然后直到编译得没有错误了,再来添加sys_arch.c,这样可以减少一些麻烦。
总得来说就是,首先,你要修改opt.h,把你需要的模块添加上去,把栈空间,mailbox大小之类的定义好,然后编译,因为LWIP是标准的C写的,理论上应该很容易编译成功,我只是有两个地方没有.h文件,添加一下就编译过了。
当然,编译过了只是第一步,完了以后就要手写sys_arch.c了,sys.h里面的所有函数都要实现,当然,你要偷懒的话有些也可以直接返回。这个文件是和你系统API相关的,写好以后记得加入到Makefile里面,然后再编译,找错误,编译,直到成功。
其实还有临界保护,主要是
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev)
#define SYS_ARCH_UNPROTECT(lev)
这三个宏,定义成你的系统的临界保护调用就行了,我偷懒定义的空的,暂时没发现问题。
好了,操作系统部分大概就这么一些,接下来就是底层的硬件驱动了,驱动的具体实现我就不说了,各自的平台不一样,网卡硬件也不一样,但是基本的三个东西一定要有,1.初始化网卡,2.接收数据,3.发送数据。
在LWIP1.4.0中,其实已经跟我们写好驱动的框架,我们所需要做的就是填充这个框架。框架对应的文件是:ethernetif.c
打开这个文件后你会发现这个文件被#if 0包起来了,首先改成#if 1,这样,你会发现这个文件中有如下几个函数:
static void low_level_init(struct netif *netif) //底层硬件初始化
static err_t low_level_output(struct netif *netif, struct pbuf *p) //底层硬件发送数据函数
static struct pbuf * low_level_input(struct netif *netif) //底层硬件接收数据函数
err_t ethernetif_input(struct pbuf *pp,struct netif *netif) //硬件抽象层接收数据
err_t ethernetif_init(struct netif *netif) //硬件抽象层初始化
这些函数里面已经实现了一部分代码,主要是硬件抽象层的代码,我们需要把具体的驱动程序添加进去,我们按调用的顺序来说一下:
当LWIP协议栈启动的时候,协议栈本身会首先调用ethernetif_init,然后在ethernetif_init函数中会调用low_level_init,在low_level_init中实际上就是你的网卡的初始化程序了,当然,为了更加结构化,你可以另外写一个函数比如DM9003_init,在low_level_init调用他,总之low_level_init中就要实现你的网卡初始化,参数就是netif,用来告诉你这个设备需要绑定一个句柄。
当协议栈要有数据发送出去的时候,协议栈会自动调用low_level_output,参数包括netif,这是的句柄,用来区分应该从哪个网卡发走,p就是数据包内容。
当有数据进来的时候,这时就和你的实现有关了,如果你底层获取数据是中断形式,那么就是中断来了以后在中断中调用ethernetif_input,然后在ethernetif_input会调用low_level_input去硬件获取数据,然后把数据通过消息队列的形式发给LWIP协议栈,所以这一部分中,low_level_input是需要实现的实际的硬件接收数据驱动程序,并且,需要驱动在适当的时候调用ethernetif_input来把数据传给上层协议栈,参数还是netif,实际上这个netif一直是个全局变量,稍后我们会说在哪里声明他。(ethernetif_input 里调用 ethernet_input)
数据接收这一部分有很大的灵活性,总的原则就是当数据确实的到达以后,由驱动在适当的时候调用ethernetif_input,而low_level_input是具体的获取数据函数,完了ethernetif_input会简单的判断一下数据类型然后决定是否传到上层协议栈。
由于OS20的速度有限,在中断中直接调用ethernetif_input时间消耗很长,我的实现是,在初始化时启动了中断模式,并且在初始化时同时启动了一个轮询线程,中断来了数据以后直接把网卡的数据存到内存中去,然后给轮询的线程发一个信号量,轮询线程先阻塞在信号量上,收到信号以后,查询该内存块,有数据的话就在线程中调用ethernetif_input把数据发给上层协议栈,然后重新阻塞住。
好了,至此,底层部分结构也说完了,现在让我们看看初始化,LWIP是如何工作的,其实上面两部分移植好了以后,要使用LWIP很简单,只有两个步骤。
1.找到netif.c,在前面声明一个全局变量: struct netif eth_netif;
然后找到void netif_init(void)函数,在该函数最后面加上(静态IP):
ip_addr_t loop_ipaddr, loop_netmask, loop_gw;
IP4_ADDR(&loop_gw, 192,168,3,1); 设置网关的IP
IP4_ADDR(&loop_ipaddr, 192,168,3,100);设置本机IP
IP4_ADDR(&loop_netmask, 255,255,255,0);设置子网掩码
/*将IP绑定到eth_netif上,并且告诉协议栈,初始化网卡的函数是ethernetif_init,网卡接收到数据以后调用tcpip_input来处理数据*/
netif_add(ð_netif, &loop_ipaddr, &loop_netmask, &loop_gw, 0, ethernetif_init, tcpip_input);
netif_set_up(ð_netif); 启动该网络设备
这里的eth_netif就是上一部分中的netif参数,设置成一个全局变量,驱动就能用了,具体是怎么对接的仔细看看netif_add就明白了,tcpip_input函数是协议栈自带的,不用管,填上就行。
2.在你的main函数里调用tcpip_init();记得include "tcpip.h"。
OK,结束。
如果你按这个步骤一步一步来的话,并且驱动程序正确,那么这时候你应该可以ping通你的设备了,接下来你可以试试网络上传下载的速度啦,后面的东西你就自己微调吧,怎么样,够简单吧?