ZLIP使用简介

李章林1

1 南开大学电子应用实验室,http://www.zlmcu.com,版本:2005-11-28)

1 目录结构

TCP/IP协议栈程序所在目录

Icmp协议。

IP层。

:网络接口层。

TCP协议层。

TCPIP内存管理程序。

 

 

 

 

 

 

 

 

:网络接口协议所在目录。

ARP协议。

:以太网接口协议。

RTL8019AS以太网接口芯片驱动程序。

 

 

 

 

 

 

:全局函数和宏定义所在目录

 

 

 

 

:应用层协议所在目录

:主程序,这里包含一个如何使用的例子程序。

KeilC目录下是KeilC51的工程文件所在目录。用KeilC51打开Ex1.Uv2

MCU目录下是各种类型的51单片机的头文件。

2 概述

单片机上网技术,是当前的一个热门技术。单片机上网技术中的一个重要部分是在单片上实现TCP/IP协议栈。现在可获得的TCP/IP源代码一般并不为51单片机设计,而51单片机和KeilC51编译器有其自身的特点:存储类型、函数指针、重入函数等,ZLIP就是针对这些特点设计的TCP/IP协议栈。

ZLIP设计的目标是:

1)              精简TCP/IP协议栈,以减小代码量。ZLIP目前没有支持UDP协议,ICMP协议也只支持其中的echo协议(响应ping数据包)lwIP是一个功能全面的TCP/IP协议栈,但是相对51来说代码量较大。

2)             应用层接口简单,以兼容通用的socket接口。uIP有很小的代码量和减小代码量(选择AVR为目标器件时,代码为5K左右)和RAM使用量(100字节左右)。uIP采用了不保存需要应答的数据包的RAM使用方案,没有和BSD的套接字接口兼容,应用层接口较复杂。

3)             针对KeilC51编译器设计。所有的外部变量都使用了xdata类型,全部指针都为明确存储类型的指针,需要重入的函数已经声明为reentant,使用KeilC的小模式下编译。

使用12M晶振、KeilC编译器、89C55单片下测试的技术参数如下:

1:技术参数

代码量(字节)

外部RAM使用量(字节)

发送速度(字节/秒)

14841

11068

5.892K

ZLIP的特点如下:

1)有适中代码量和RAM使用量。

2)使用类似MFC的CScoket的套接字接口,使用方便。

3)支持多TCP连接、多网络设备。能方便地移植到多任务操作系统和其它CPU下。能方便地替换网络接口协议和网卡驱动设备。

4)支持ping命令的响应。

5)为单片机设计:所有的外部变量都使用了xdata类型,全部指针都为明确存储类型的指针,需要重入的函数已经声明为reentant,使用KeilC的小模式编译。

3电路图

1RTL8019AS电路左半部分

2RTL8019AS电路右半部分

该程序不能在KeilC下软件仿真,因为程序的运行需要外部电路配合。该51系统的外部电路主要有:以太网接口芯片RTL8019AS电路、外部RAM电路。

       以太网接口芯片RTL8019AS电路图,如图1和图2表示。A0A4接地址线,D0D7接数据线,CSRTL是片选线(低电平有效),RD-WR-接读写信号线。

4 应用层接口

zlIP接口函数基本和BSD的套接字接口相同。

41提供的用户接口函数:

TCPSocket()

函数原型:socket xdata * TCPSocket(IP_ADDR ScrIP)

功能:申请一个套接字。ScrIP是这个套接字的本地IP地址。返回socket类型指针,如果申请失败返回NULL

 

TCPConnect()

函数原型:BOOL TCPConnect(socket xdata * pTCB, IP_ADDR DestIP, WORD DestPort,void (code * recv)(void xdata * buf,WORD size),void (code * close)(socket xdata * pSocket))

功能:向IP地址为DestIP的服务器的DestPort端口发起连接。参数recvclose用于设置当接收到数据包和对方要求关闭TCP连接时应该调用的回调函数指针。连接成功返回TRUE,否则返回FALSE

 

TCPSend()

函数原型:BOOL TCPSend(socket xdata * pTCB,void xdata *buf,WORD DataSize)

功能:发送数据。发送数据的TCP连接是套接字指针pTCB对应的连接,发送的数据的起始地址为buf,大小为DataSize。发送成功返回TRUE,否则返回FALSE

 

TCPSendEx()

函数原型:BOOL TCPSendEx(socket xdata * pTCB,struct SMemHead xdata *MemHead)

功能:快速发送数据。在使用TCPSend函数时,你首先需要将数据放入buf指向的内存中,然后调用TCPSend函数,接着该函数会将buf指向的内存区数据拷贝到TCP缓冲区中。使用TCPSendEx 时你首先用TCPAllocate(DATA_SIZE)获得一个TCP缓冲区,然后直接将数据放入TCP缓冲区中,从而比TCPSend函数少一次数据拷贝,提高发送速度。

参数:发送数据的TCP连接是套接字指针pTCB对应的连接,发送的数据放在TCP缓存MemHead中。发送成功返回TRUE,否则返回FALSE

 

TCPListen()

函数原型:BOOL TCPListen(socket xdata *pTCB,WORD ScrPort,void (code * accept)(socket xdata *pNewTCB))

功能:使用套接字pTCBScrPort端口监听。参数accept是当有客户端向这个监听端口连接成功时调用的回调函数指针。

 

TCPClose()

函数原型:void TCPClose(socket xdata *pTCB)

功能:我方主动关闭连接时调用TCPClose函数,它将要求关闭套接字pTCB对应的连接。TCPClose返回以后这个TCP连接可能保持,因为另一方还没有发起关闭请求。

 

TCPAbort()

函数原型:void TCPAbort(socket xdata *pTCB)

功能:当使用完这个套接字以后,调用TCPAbort,将这个套接字释放,还给系统。

42使用步骤

使用ZLIP时,在你的主程序中(请看示例程序的main.c文件)需要做的步骤如下:

1)首先设置一个25ms的定时中断函数(示例程序为Timer函数)。请在中断函数中调用NetIfTimer(); ARPTimer(); TCPTimer();三个函数。

 

       2)OnReceive函数,它应该有如下的参数和返回值,函数名可以任意:

void OnReceive1(void DT_XDATA * buf,WORD size)  REENTRANT_MUL

在使用TCPConnect函数时,OnReceive1将作为TCPConnect函数的一个参数,也就是设置该socket的接收函数。当TCP连接接收到对方数据时,将自动调用OnReceive1函数。buf指向接收的数据,size是接收的数据量的大小。你可以在OnReceive1中处理接收的数据。当程序中有多个TCP连接同时存在时,你需要给每个连接准备一个OnReceive函数。

 

       3)OnClose函数,它应该有如下的参数和返回值,函数名可以任意:

void OnClose1(socket DT_XDATA * pSocket) REENTRANT_MUL

类似于OnReceive函数,当TCP连接的另一方首先向我方发起关闭连接的请求时,系统将自动调用OnClose函数。pSocket指向将要关闭的socket。如果你想立即关闭这个连接则在OnClose函数中调用TCPClose函数。当程序中有多个TCP连接同时存在时,你需要给每个连接准备一个OnClose函数。

 

4)OnAccept函数。如果你的程序中用到TCPListen函数监听某端口,这时需要写OnAccept函数。它应该有如下的参数和返回值,函数名可以任意:

void OnAccept1(socket DT_XDATA *pNewSocket) REENTRANT_MUL

当一个正在listensocket接受了对方的连接以后将会自动调用该函数。pNewSocket是将要获得这个连接的控制权的socket指针。一般在OnAccept函数中做以下处理:

       ExAccept = pNewSocket;            //保存pNewSocket,以后可以用ExAccept发送数据

       pNewSocket->recv = OnAcceptRecv;  //设置pNewSocketOnReceive函数。

       pNewSocket->close = OnClose;          //设置pNewSocketOnClose函数。

当程序中有多个处于listensocket时,你需要给每个socket准备一个OnAccept函数

 

       5)在主程序中做初始化工作:

/* init. the order is not important */

       NetIfInit();     //初始化网络接口

       ARPInit();      //初始化ARP

       TCPInit();      //初始化TCP

       MemInit();      //初始化内存模块

       RTLInit(EtherAddr);      //初始化RTL8019ASEtherAddr为以太网地址

 

       /* init Devcie struct and init this device */

/* 初始化一个以太网接口设备,并设置这个设备的发送和接收驱动函数。如果你的系统中以太网接口芯片的驱动不一样,只要替换这里的发送和接口驱动函数就可以了*/

       EtherDevInit(&DevRTL,EtherAddr,RTLSendPacket,RTLReceivePacket);

 

       /* add this device to NetIf */

/* 添加一个网络接口设备。参数含义是:该设备的IP地址、子网掩码、网关、输入函数指针、输出函数指针、该设备的指针。如果你的系统中有多个网络设备,比如moden,可以编写moden的输入输出函数,使用NetIfAdd函数添加这个设备。*/

       NetIfAdd(IPAddr,NetMask,GateWay,EtherInput,EtherOutput,&DevRTL);

 

       6)启动25ms的定时中断

 

       7)使用类似

ExConn = TCPSocket(IPAddr);

       语句分配一个socket,并且绑定这个socket的源IP地址。

 

       8)

如果我方作为服务器方,监听某一端口则:

                            TCPListen(ExConn,Port1,OnAccept1);

当另一方向我方Port1端口进行连接时,系统自动调用OnAccpet1函数。

       如果我方作为客户端,向另一方的某个端口进行连接则:

TCPConnect(ExConn,IPAddr2,Port2,OnReceive2,OnClose2);

即向IP地址为IPAddr2的服务器的Port2端口进行连接。在连接成功以后,如果接收到另一方的数据则自动调用OnReceive1函数,如果接收到另一方的关闭请求则自动调用OnClose1函数。

 

       9)当某个socket处于连接状态时,可以使用TCPSend或者TCPSendEx函数发送数据。

 

       10)需要关闭连接的时候,使用TCPClose关闭连接。

 

       11)当一个socket不再需要时,使用TCPAbort将这个socket还给系统。

5 移植相关修改

5.1 RTL8019AS的基地址

修改Netif\RTL8019.h中的

#define RTL_BASE_ADDRESS 0xb000

默认的基地址为0xb000。当单片机访问0xb000开始的地址的时候,CSRTL信号线应给低电平,以选通RTL8019AS

5.2 TCP缓冲区大小设置

修改TCIPIP\TCPIPmem.h中的

#define TCPIP_BUF_SIZE    0x2000

默认为8K,建议大于4K。缓冲区过小,将会影响发送和接收速度。

5.3 多网络设备

如果你的系统中有多个网络设备。修改TCPIP\NetIf.h中的

#define NET_IF_MAX_NUM       1

默认情况下为最多一个设备。

在主程序中使用NetIfAdd函数添加网络设备。

5.4 TCP连接数的设置

修改TCPIP\TCP.h中的

#define TCP_CONNECTION_MAX_NUM         10

默认情况下最多支持10socket同时工作。

5.5 网络接口层协议的最大帧头长度

只有当你的程序使用以太网以外的网络接口协议时,才需要修改。修改TCPIP\NetIf.h中的

#define NETIF_HEAD_MAX_LEN      14

默认是以太网帧头长度,即14个字节。

5.6 ARP表的大小设置

修改Netif\ARP.h中的

#define ARP_ENTRY_MAX_NUM              4

默认情况下ARP表大小为4个记录。当ARP表已经满的时候,新的记录将会覆盖最老的那个记录。

5.7响应ping命令

如果不希望系统能够响应ping命令,则修改TCPIP\icmp.h中的

#define ICMP_EN  1

默认情况下该开关是打开的。如果不需要此功能将其设置为0

5.8 到其它CPU的移植

zlIP虽然为51单片机设计,但是也可以被移植到其它的CPU上。系统中的GloblDef\GlobleDef.h记录了CPU的信息,主要修改这个文件。

1)      设置BYTE,WORD,DWORD,BOOL等类型的定义

2)      注释掉#define MCU_C51这一行。注释掉这个选项开关以后将程序从C51变为ANSIC,程序中将没有C51特有的关键字。

3)      字节顺序设置。即设置多字节变量的高字节存在于低地址还是高地址。51单片机的字节顺序和0x8086CPU不一样。删除#   define HOST_ORDER_AS_NET,如果字节顺序和网络字节顺序不一样。

4)      是否移植到具有多线程的51单片机程序中。比如单片上运行了RTOS51uc/OS-IITiny51等单片上的多线程操作系统,则需要打开#   define MULTI_THREAD开关,此时程序中几乎所有的函数都声明为reentrant类型的。

5)      如果需要运行在调试状态打开#   define DEBUG开关。

6)      对于IORAM不是统一编址的系统需要修改RTL8019.c文件中的#define ReadReg(port) (*((BYTE DT_XDATA *)port))#define WriteReg(port,value) (*((BYTE DT_XDATA *)port) = value),使程序能够访问IO端口。

6 后记

       公布此源代码,旨在将我的心得和成果和大家共享,共同学习和进步。由于本人水平有限,错误和疏漏之处难免,还请各位同行指正。

参考文献

[1] 李章林,张立民. TCP/IP在51单片上的实现特点和方法. “2003年全国单片机和嵌入式系统年会”论文集.2003年

[2] (电子文献)Adam Dunkels.uIP - A Free Small TCP/IP Stack[Z].  http://dunkels.com/adam/uip/index.html. 2002-1-15.1

[3] (电子文献)Adam Dunkels.lwIP - News Archive[Z].http://www.sics.se/~adam/lwip/news.html.2001-1-9.

[4] 李章林,张立民. ANSIC程序到KeilC51的移植心得. “2003年全国单片机和嵌入式系统年会”论文集.2003年