一 引言
随着信息技术的不断发展,以及人们对日常生活舒适度、方便度要求的提高,信息家电、智能仪表等产品越来越频繁的出现在我们的生活当中;人们也越来越热衷于把家电、仪表等设备连接到Internet 中,从而可以方便、及时的对它们进行远程察看、远程控制。把这些设备接入Internet ,就需要考虑TCP/IP 网络协议的实现。
51单片机是对目前所有兼容Intel 8031指令系统的单片机的统称。该系列单片机的始祖是Intel的8031单片机,后来随着Flash rom技术的发展,8031单片机取得了长足的进展,成为目前应用最广泛的8位单片机之一,其代表型号是ATMEL公司的AT89系列,它广泛应用于工业测控系统之中。目前很多公司都有51系列的兼容机型推出,在目前乃至今后很长的一段时间内将占有大量市场。51单片机是基础入门的一个单片机,还是应用最广泛的一种。需要注意的是52系列的单片机一般不具备自编程能力。本文将简要描述uIP的实现方法,分析uIP协议栈的应用接口,并讨论如何将其应用到51系列单片机上。
二 uIP协议栈的实现方法简述
uIP协议栈主要提供了三个函数供系统底层调用。即uip_init(), uip_input() 和uip_periodic()。其与应用程序的主要接口是UIP_APPCALL( )。ip_init()是系统初始化时调用的,主要初始化协议栈的侦听端口和默认所有连接是封闭的。当网卡驱动收到一个输进包时,将放进全局缓冲区uip_buf中,包的大小由全局变量uip_len约束。同时将调用uip_input()函数,这个函数将会根据包首部的协议处理这个包和需要时调用应用程序。当uip_input()返回时,一个输出包同样放在全局缓冲区uip_buf里,大小赋给uip_len.假如uip_len是0,则说明没有包要发送。否则调用底层系统的发包函数将包发送到网络上。uIP周期计时是用于驱动所有的uIP内部时钟事件。当周期计时激发,每一个TCP连接都会调用uIP函数uip_periodic()。类似于uip_input()函数。uip_periodic()函数返回时,输出的IP包要放到uip_buf中,供底层系统查询uip_len的大小发送。
uIP实现了TCP/IP协议集的四个基本协议:ARP地址解析协议,IP网际互联协议, ICMP网络控制报文协议和TCP传输控制协议。为了在8位16位处理器上应用,uIP协议栈在各层协议实现时采用有针对性的方法,保持代码大小和存储器使用量最小。
1 实现ARP地址解析协议时为了节省存储器,ARP应答包直接覆盖ARP请求包。
2 实现IP网络协议时对原协议进行了极大的简化,它没有实现分片和重组。
3 实现ICMP网络控制报文协议时,只实现echo(回响)服务。uIP在生成回响报文时并不重新分配存储器空间,而是直接修改echo请求报文来生成回响报文。将ICMP类型字段从"echo"类型改变成 "echo reply"类型,重新计算校验和修改校验和字段。
4 uIP里的TCP没有实现发送和接收数据的滑动窗口。每个TCP连接的状态由uip_conn结构保存,uip_conn结构包括当地和远端的TCP端口编号,远程主机的IP地址,重发时间值,上一段重发的编号,和连接的段的最大尺寸等信息。一个uip_conn结构数组用于保存所有的连接,数组的大小为支持的同时连接的最大数量。为了减少储存器的使用量,在处理重发时uIP并不缓存发送的数据包,而是由应用程序在需要重发时重新生成发送的数据。
三 uIP协议栈的接口
uIP协议栈为了具有最大的通用性,在实现时将底层硬件驱动和顶层应用层之外的所有协议集"打包"在一个"库"里。协议栈通过接口与底层硬件和顶层应用"通信".通过这种方式,uIP具有极高的通用性和独立性,移植到不同系统和实现不同的应用都很方便,很好的体现了TCP/IP协议平台无关性的特点。uIP协议栈与系统底层和应用程序之间的接口关系如图(一)所示:
1 uIP协议栈与系统底层的接口
uIP与系统底层的接口包括与设备驱动的接口和与系统定时器的接口两类。
1.1 uIP与设备驱动接口
uIP通过函数uip_input()和全局变量uip_buf、uip_len来实现与设备驱动的接口。uip_buf用于存放接收到的和要发送的数据包,为了减少存储器的使用,接收数据包和发送数据包使用相同的缓冲区。uip_len表明接收发送缓冲区里的数据长度,通过判断uip_len的值是否为0来判断是否接收到新的数据,是否有数据要发送。当设备驱动接收到一个IP包并放到输入包缓存里(uip_buf)后,应该调用uip_input()函数。uip_input()函数是uIP协议栈的底层入口,由它处理收到的IP包。当uip_input()返回,若有数据要发送,则发送数据包放在包缓冲区里。包的大小由全局变量uip_len指明。如果uip_len是0,没有包要发送;如果uip_len大于0则调用网络设备驱动发送数据包。
1.2 uIP与系统计时接口
TCP/IP协议要处理许多定时事件,例如包重发、ARP表项更新。系统计时用于为所有uIP内部时钟事件计时。当周期计时激发,每一个TCP连接应该调用uIP函数uip_periodic()。TCP连接编号作为参数传递给uip_periodic()函数。uip_periodic()函数检查参数指定的连接的状态,如果需要重发则将重发数据放到包缓冲区(uip_buf)中并修改uip_len的值。当uip_periodic()函数返回后,应该检查uip_len的值,若不为0则将uip_buf缓冲区中的数据包发送到到网络上。
ARP协议对于构建在以太网上的TCP/IP协议是必须的,但对于构建与其他网络接口(例如:串行链路)上的TCP/IP则不是必需的。为了结构化的目的,uIP将ARP协议作为一个可添加的模块单独实现。因此,ARP表项的定时更新要单独处理。系统定时器对ARP表的更新进行定时,定时时间到则调用uip_arp_timer()函数对过期表项进行清除。
2 uIP协议栈与应用程序的接口
应用程序作为单独的模块由用户实现,uIP协议栈提供一系列接口函数供用户程序调用。用户需将应用层入口程序作为接口提供给uIP协议栈,定义为宏UIP_APPCALL()。uIP在接收到底层传来的数据包后,若需要送上层应用程序处理,它就调用UIP_APPCALL()。uIP提供给应用程序的接口函数按功能描述如下:
2.1 接收数据接口:应用程序利用uip_newdata()函数检测是否有新数据到达。全局变量uip_appdata指针指向实际数据。数据的大小通过uip_datalen()函数获得。
2.2 发送数据接口:应用程序通过使用uIP函数uip_send()发送数据。uip_send()函数采用两个参数;一个指针指向发送数据起始地址,另一个指明数据的长度。
2.3 重发数据接口:应用程序通过测试函数uip_rexmit()来判断是否需要重发数据,如果需要重发则调用uip_send()函数重发数据包。
2.4 关闭连接接口:应用程序通过调用uip_close()函数关闭当前连接。
2.5 报告错误接口:uIP提供错误报告函数检测连接中出现的错误。应用程序可以使用两个测试函数uip_aborted()和uip _timedout() 去测试那些错误情况。
2.6 轮询接口:当连接空闲时,uIP会周期性地轮询应用程序,判断是否有数据要发送。应用程序使用测试函数uip_poll()去检查它是否被轮询过。
2.7 监听端口接口:uIP维持一个监听知名TCP端口的列表。通过uip_listen()函数,一个新的监听端口打开并添加到监听列表中。当在一个监听端口上接收到一个新的连接请求时,uIP产生一个新的连接和调用该端口对应的应用程序。
2.8 打开连接接口:在uIP里面通过使用uip_connect()函数打开一个新连接。这个函数打开一个新连接到指定的IP地址和端口,返回一个新连接的指针到uip_conn结构。如果没有空余的连接槽,函数返回空值。
2.9 数据流控制接口:uIP提供函数uip_stop()和uip_restart()用于TCP连接的数据流控制。应用程序可以通过函数uip_stop()停止远程主机发送数据。当应用程序准备好接收更多数据,调用函数uip_restart()通知远程终端再次发送数据。函数uip_stopped()可以用于检查当前连接是否停止。
四 uIP在51系列单片机上的应用
51系列单片机具有悠久的历史和广泛的应用,许多公司推出了具有更高的处理速度的51内核的8位单片机,被应用在各个领域内。因此使用uIP这种免费的TCP/IP协议栈解决由51内核的单片机构建的低端嵌入式设备的网络接入问题具有一定的代表性。下面将讨论利用uIP协议栈在51单片机上实现简单的WEB SERVER,远端用户可以通过浏览器访问存储在单片机系统上的WEB页面。
硬件平台结构如图(二)所示:其中单片机选用PHILIPS公司的P89C51RD2,64K字节的串行EEPROM可以用于存储WEB页面。采用ISA接口的以太网接口芯片RTL8019AS连接到以太网上。通过MAX232实现与PC机的串行连接,可以显示调试信息。
uIP协议栈是以函数库的形式提供的,本身不提供底层网络驱动和上层应用程序。因此为了完成指定的功能,开发者必须添加以下几个模块:底层RTL8019AS网卡芯片的驱动、应用层基于HTTP协议的WEB SERVER的实现、系统定时器。
RTL8019AS的驱动主要包括三部分:init_8019as()函数完成网卡芯片的上电初始化,包括设定网卡物理地址,设定收发缓冲区位置和大小等;eth_send()函数完成数据的发送;eth_rcve()函数完成以太网数据的接收。底层网络设备驱动程序与uIP协议栈通过两个全局变量进行接口:变量uip_buf为收发缓冲区的首地址;uip_len为收发的数据长度。eth_send()函数将uip_buf里的uip_len长度的数据发送到以太网上。eth_rcve()函数将接收到的数据存储到uip_buf指定的缓冲区中,同时修改uip_len的值。
uIP提供的源代码中包括一个基于HTTP协议的WEB SERVER示例,该WEB SERVER通过简单的文件系统在数据存储器中存储静态页面,同时具有CGI功能。用户可以参照该示例以及uIP提供给应用程序的接口函数说明实现自己的应用层功能。用户的应用程序中必须将 UIP_APPCALL宏定义为该层的服务程序。例如:在示例程序中WEB SERVER的处理程序为httpd()函数,则要进行如下的宏定义#define UIP_APPCALL httpd.
51系列单片机上都有2到3个定时计数器,可以选择其中的一个来为TCP/IP协议中与时间有关的事件定时。需要由用户处理的定时事件包括:为uip_periodic()函数的执行提供基准,还要为ARP表项的更新定时。uip_periodic()函数每0.5秒执行一次,ARP表项每10秒更新一次。
uIP的设置单独包含在一个叫uipopt.h的头文件里,都是以宏的形式定义方便于修改。用户应根据自己的应用在uipopt.h文件里设置本地的物理地址、IP地址、网关地址、收发缓冲区的大小、支持的最大连接数、ARP表大小等等选项。
添加了必须的模块,对uIP进行了正确地配置后,需要编写主程序函数。针对基于以太网的WEB SERVER应用,主程序在完成初始化后将不停的进行查询,如果有新数据包到达则送uip_input()函数处理;如果没有新数据包到达则处理定时事件。框架代码如下所示:
通过实际的代码说明uIP协议栈的主控制循环。
以上实例在keil C51编译器下设置大模式,优化等级6(速度优先)进行编译,对uIP代码部分可以不做任何修改,对HTTP示例代码仅需针对类型表达进行极少量的修改即可编译通过。在硬件平台上运行良好。
五 总结
uIP协议栈采用有效的方法和结构化的代码,使其存储器占用量很小并且可以很方便的应用到不同的工程项目中。同时它又是免费的可以自由使用于商业和非商业目的。uIP为低端嵌入式设备的网络接入提供了很好的解决方案,具有很高的应用价值。