常见的嵌入式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),起始优先级,临界访问函数,信号量邮箱队列变量或结构的定义
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2
3 #define LWIP_TASK_START_PRIO 8
4
5 #define SYS_ARCH_PROTECT(lev) OS_ENTER_CRITICAL()
6 #define SYS_ARCH_UNPROTECT(lev) OS_EXIT_CRITICAL()
7
8 typedef struct
9 {
10 OS_EVENT * pQ; // uCOSII中指向事件控制块的指针
11 void * pvQEntries[MAX_QUEUE_ENTRIES]; // MAX_QUEUE_ENTRIES消息队列中最多的队列数
12 } Q_DESCR, * Q_DESCRPt;
13
14
15 typedef OS_EVENT * sys_sem_t;
16 typedef Q_DESCRPt sys_mbox_t;
17 typedef INT8U sys_thread_t;
(2)sys_init()
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 {
3 // 初始化了一个内存缓冲池,用于放MAX_QUEUES个ucosii的消息队列。
4 unsigned char err;
5 QueueMemPt = OSMemCreate(( void * )QueueMemoryPool, MAX_QUEUES, sizeof (Q_DESCR), & err);
6 curr_prio_offset = 0 ;
7 }
(3)任务创建sys_thread_new
在我们的应用环境下,只创建了一个任务tcpip_thread,在tcpip.c中tcpip_init创建
sys_thread_new(tcpip_thread, NULL, TCPIP_THREAD_PRIO);
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 {
3 u8_t CreatState,TaskPrio;
4 // 计算实际的优先级数值
5 // 这个值由起始优先级LWIP_TASK_START_PRIO,偏移量和参数传递的prio相加而得.
6 // 这里传递过来的是TCPIP_THREAD_PRIO(定义在opt.h,值为1)
7 // 其他任务定义的值也都为1(虽然这里并未创建其他任务),由于uCOSII不支持相同优先级
8 // 因此使用一个全局变量curr_prio_offset来累加,每创建一个任务,实际的优先级就加1
9 TaskPrio = LWIP_TASK_START_PRIO + curr_prio_offset + prio;
10 // 判断是否超过定义的最大任务数
11 if (curr_prio_offset < LWIP_TASK_MAX)
12 {
13 // 创建uCOSII任务
14 CreatState = OSTaskCreateExt(( void ( * )( void * )) thread,
15 ( void * ) 0 ,
16 (OS_STK * ) & LwipTaskStack[curr_prio_offset][LWIP_TASK_STK_SIZE - 1 ],
17 (INT8U ) TaskPrio,
18 (INT16U ) TaskPrio,
19 (OS_STK * ) & LwipTaskStack[ 0 ],
20 (INT32U ) LWIP_TASK_STK_SIZE,
21 ( void * ) 0 ,
22 (INT16U )(OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR));
23
24 // 创建失败
25 if (CreatState)
26 {
27 return 0 ;
28 }
29 // 打印调试信息, 优先级偏移量加1
30 print_string( " task prio = %d created...\r\n " ,TaskPrio);
31 curr_prio_offset ++ ;
32
33 }
34 else
35 {
36 print_string( " lwip task prio out of range ! error! " );
37 }
38 // 返回创建成功的任务优先级
39 return TaskPrio;
40 }
(4)邮箱操作sys_mbox_new(),sys_mbox_free(),sys_mbox_post(),sys_arch_mbox_fetch()
这里邮箱实际上用到的是uCOSII中的消息队列,邮箱只能存放一个消息,而消息队列可以存放多个消息
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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;
}
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
unsigned char err;
OSQFlush( mbox -> pQ ); // 清除队列事件
OSQDel( mbox -> pQ, OS_DEL_NO_PEND, & err); // 删除队列
err = OSMemPut( QueueMemPt, mbox); // 把队列所占内存放入内存池
}
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
// 由于lwip会发空消息,而ucosii对空消息会当错处理,所以我们把lwip的空做成ucosii的一个特定值。
{
unsigned char err;
if ( ! data)
data = ( void * ) 0xffffffff ;
err = OSQPost(mbox -> pQ, data);
}
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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 SemPt;
SemPt = OSSemCreate(count);
if (SemPt != NULL)
return SemPt;
return SYS_SEM_NULL;
}
[2]删除信号量
{
unsigned char err;
OSSemDel((OS_EVENT * )sem, OS_DEL_NO_PEND, & err );
}
[3]发送信号量
{
OSSemPost((OS_EVENT * )sem);
}
[4]等待信号量
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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]设置超时事件
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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)驱动了.
发表于 @ 2009年09月01日