常见的嵌入式TCPIP协议栈有LwIP,uIP,uC/TCPIP,TinyTcp等,相对来说LwIP功能较uIP(uIP更多用在8位51上),TinyTCP强点,但代码量小于uC-TCPIP,之前也尝试过移植uC-TCPIP,不过一直有点问题,当然uC-TCPIP还不是免费的.加上网上关于LwIP的资料也比较多.
1.LwIP简介
LwIP是瑞士计算机科学院(Swedish Institute of Computer Science)的Adam Dunkels等开发的一套用于嵌入式系统的开放源代码TCP/IP协议栈。LwIP的含义是Light Weight(轻型)IP协议,相对于uip。LwIP可以移植到操作系统上,也可以在无操作系统的情况下独立运行。LwIP TCP/IP实现的重点是在保持TCP协议主要功能的基础上减少对RAM的占用,一般它只需要几十K的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端嵌入式系统中使用。LwIP的特性如下:支持多网络接口下的IP转发,支持ICMP协议 ,包括实验性扩展的的UDP(用户数据报协议),包括阻塞控制,RTT估算和快速恢复和快速转发的TCP(传输控制协议),提供专门的内部回调接口(Raw API)用于提高应用程序性能,并提供了可选择的Berkeley接口API。http://www.sics.se/~adam/lwip/或http://savannah.nongnu.org/projects/lwip/
2.采用LwIP1.2,1.3的接口函数有些不一样,为了移植的方便还是使用1.2,毕竟基于1.2的参考资料更多些,移植的很多代码参考并借用了网上的例子,在此表示感谢!
更详细的信息可以参考我上传的工程http://download.csdn.net/source/1661278
将LwIP1.2加入到我们的IAR工程中,因为我们并不需要SLIP,PPP以及IPv6的支持,这些相关代码就不必加入到工程里了,将头文件路径添加到IAR include directories.
3.添加移植代码,如cc.h, lwipopts.h, sys_arch.h, sys_arch.c, 网卡驱动等
cc.h:定义数据类型,大小端格式等
lwipopts.h:lwip的配置文件
sys_arch.h/c:实现lwip与操作系统(这里是uCOSII)的接口,如任务创建,信号量邮箱操作等
rtl8019.h/c:网卡驱动程序,SMARTARM2200使用的网卡芯片是RTL8019AS
cc.h和lwipopts.h的内容就不详细介绍了,具体内容可参考上面链接给的工程,都是些宏定义,配置定义等
4.操作系统模拟层的实现(sys_arch.c)
因为我们使用操作系统,因此在sys_arch.c中需要实现任务,信号量,邮箱操作的函数.LwIP提供了这样的接口以供不同的操作系统去实现.
(1)sys_arch.h
头文件定义了LwIP最大任务数(其实LwIP只创建了一个任务tcpip_thread),起始优先级,临界访问函数,信号量邮箱队列变量或结构的定义
#define LWIP_TASK_MAX 2 #define LWIP_TASK_START_PRIO 8 #define SYS_ARCH_PROTECT(lev) OS_ENTER_CRITICAL() #define SYS_ARCH_UNPROTECT(lev) OS_EXIT_CRITICAL() typedef struct { OS_EVENT * pQ; //uCOSII中指向事件控制块的指针 void * pvQEntries[MAX_QUEUE_ENTRIES]; //MAX_QUEUE_ENTRIES消息队列中最多的队列数 } Q_DESCR, *Q_DESCRPt; typedef OS_EVENT* sys_sem_t; typedef Q_DESCRPt sys_mbox_t; typedef INT8U sys_thread_t;
(2)sys_init()
void sys_init(void) { //初始化了一个内存缓冲池,用于放MAX_QUEUES个ucosii的消息队列。 unsigned char err; QueueMemPt = OSMemCreate((void *)QueueMemoryPool, MAX_QUEUES, sizeof(Q_DESCR), &err); curr_prio_offset = 0; }
(3)任务创建sys_thread_new
在我们的应用环境下,只创建了一个任务tcpip_thread,在tcpip.c中tcpip_init创建
sys_thread_new(tcpip_thread, NULL, TCPIP_THREAD_PRIO);
sys_thread_t sys_thread_new(void (* thread)(void *arg), void *arg, int prio) { u8_t CreatState,TaskPrio; //计算实际的优先级数值 //这个值由起始优先级LWIP_TASK_START_PRIO,偏移量和参数传递的prio相加而得. //这里传递过来的是TCPIP_THREAD_PRIO(定义在opt.h,值为1) //其他任务定义的值也都为1(虽然这里并未创建其他任务),由于uCOSII不支持相同优先级 //因此使用一个全局变量curr_prio_offset来累加,每创建一个任务,实际的优先级就加1 TaskPrio = LWIP_TASK_START_PRIO+curr_prio_offset+prio; //判断是否超过定义的最大任务数 if(curr_prio_offset < LWIP_TASK_MAX) { //创建uCOSII任务 CreatState = OSTaskCreateExt((void (*)(void *)) thread, (void *) 0, (OS_STK *) &LwipTaskStack[curr_prio_offset][LWIP_TASK_STK_SIZE - 1], (INT8U ) TaskPrio, (INT16U ) TaskPrio, (OS_STK *)&LwipTaskStack[0], (INT32U ) LWIP_TASK_STK_SIZE, (void *) 0, (INT16U )(OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR)); //创建失败 if(CreatState) { return 0; } //打印调试信息, 优先级偏移量加1 print_string("task prio = %d created.../r/n",TaskPrio); curr_prio_offset++; } else { print_string(" lwip task prio out of range ! error! "); } //返回创建成功的任务优先级 return TaskPrio; }
(4)邮箱操作sys_mbox_new(),sys_mbox_free(),sys_mbox_post(),sys_arch_mbox_fetch()
这里邮箱实际上用到的是uCOSII中的消息队列,邮箱只能存放一个消息,而消息队列可以存放多个消息
sys_mbox_t sys_mbox_new(void) { unsigned char err; Q_DESCRPt QDescPt; QDescPt = OSMemGet(QueueMemPt, &err); //在内存池中分配出一个队列结构的空间,并将此空间赋成这个结构。 if(err == OS_NO_ERR){ QDescPt->pQ = OSQCreate(&(QDescPt->pvQEntries[0]), MAX_QUEUE_ENTRIES); //创建队列,但是队列只是lwip队列结构的一个成员。 if(QDescPt->pQ != NULL){ return QDescPt; } } return SYS_MBOX_NULL; }
void sys_mbox_free(sys_mbox_t mbox) { unsigned char err; OSQFlush( mbox->pQ ); //清除队列事件 OSQDel( mbox->pQ, OS_DEL_NO_PEND, &err); //删除队列 err = OSMemPut( QueueMemPt, mbox); //把队列所占内存放入内存池 }
void sys_mbox_post(sys_mbox_t mbox, void *data) //由于lwip会发空消息,而ucosii对空消息会当错处理,所以我们把lwip的空做成ucosii的一个特定值。 { unsigned char err; if(!data) data = (void *)0xffffffff; err = OSQPost(mbox->pQ, data); }
unsigned long sys_arch_mbox_fetch(sys_mbox_t mbox, void **msg, unsigned long timeout) { unsigned char err; unsigned short ucos_timeout = 0; /*由于lwip中的timeout时间是ms,而ucosii的是timer tick,所以要转换,将timeout转换成ucos_timeout*/ //首先确保输入参数的合理性 if(timeout){ ucos_timeout = (timeout * OS_TICKS_PER_SEC)/1000; if(ucos_timeout < 1) ucos_timeout = 1; } if(!mbox) return SYS_ARCH_TIMEOUT; //因为OSQPend()有返回值,则看有没有可返回的地址,有则返回,无则不返回。 if(msg != NULL){ *msg = OSQPend(mbox->pQ, (unsigned short)ucos_timeout, &err); } else{ OSQPend(mbox->pQ, (unsigned short)ucos_timeout, &err); } if(err == OS_TIMEOUT){ timeout = SYS_ARCH_TIMEOUT; } else{ if(*msg == (void *)0xffffffff) *msg = NULL; timeout = (ucos_timeout - err) * (1000/OS_TICKS_PER_SEC); } return timeout; }
(5)信号量操作sys_sem_new(),sys_sem_free,sys_sem_signal(),sys_arch_sem_wait()
[1]创建信号量
sys_sem_t sys_sem_new(unsigned char count) { sys_sem_t SemPt; SemPt = OSSemCreate(count); if(SemPt != NULL) return SemPt; return SYS_SEM_NULL; }
[2]删除信号量
void sys_sem_free(sys_sem_t sem) { unsigned char err; OSSemDel((OS_EVENT *)sem, OS_DEL_NO_PEND, &err ); }
[3]发送信号量
void sys_sem_signal(sys_sem_t sem) { OSSemPost((OS_EVENT *)sem); }
[4]等待信号量
unsigned long sys_arch_sem_wait(sys_sem_t sem, unsigned long timeout) //同队列 { unsigned char err; unsigned short ucos_timeout = 0; /*由于lwip中的timeout时间是ms,而ucosii的是timer tick,所以要转换,将timeout转换成ucos_timeout*/ //首先确保输入参数的合理性 if(timeout){ ucos_timeout = (timeout * OS_TICKS_PER_SEC)/1000; if(ucos_timeout < 1) ucos_timeout = 1; } OSSemPend ((OS_EVENT *)sem,(u16_t)ucos_timeout, (u8_t *)&err); if(err == OS_TIMEOUT){ return 0; } else{ return 1; } }
[5]设置超时事件
struct sys_timeouts *sys_arch_timeouts(void) { unsigned char CurrPrio; signed short err, offset; OS_TCB CurrTaskPcb; NullTimeouts.next = NULL; err = OSTaskQuery(OS_PRIO_SELF, &CurrTaskPcb); CurrPrio = CurrTaskPcb.OSTCBPrio; offset = CurrPrio - LWIP_TASK_START_PRIO; if(offset<0 || offset >= LWIP_TASK_MAX){ return &NullTimeouts; } return &LwipTimeouts[offset]; }
至此,LwIP与uCOSII相关的部分就移植完成了,下一步就是实现网卡(RTL8019AS)驱动了.