首页资源分类嵌入式处理器51内核 > UIP协议在单片机上的移植与实现

UIP协议在单片机上的移植与实现

已有 445010个资源

下载专区

上传者其他资源

    文档信息举报收藏

    标    签:uip

    分    享:

    文档简介

    单片机的uip协议栈移植

    文档预览

     本科生毕业设计 UIP协议在单片机上的移植与实现 院 系  电气信息工程学院   专 业  电气工程及其自动化 班 级    学 号     学 生 姓 名     联 系 方 式   15939948268     指 导 教 师  赵忠彪 职称: 讲师 2011 年 5 月 独 创 性 声 明 本人郑重声明:所呈交的毕业论文(设计)是本人在指导老师指导下取得的研究成果。除了文中特别加以注释和致谢的地方外,论文(设计)中不包含其他人已经发表或撰写的研究成果。与本研究成果相关的所有人所做出的任何贡献均已在论文(设计)中作了明确的说明并表示了谢意。 签名:                      年  月  日 授 权 声 明 本人完全了解许昌学院有关保留、使用本科生毕业论文(设计)的规定,即:有权保留并向国家有关部门或机构送交毕业论文(设计)的复印件和磁盘,允许毕业论文(设计)被查阅和借阅。本人授权许昌学院可以将毕业论文(设计)的全部或部分内容编入有关数据库进行检索,可以采用影印、缩印或扫描等复制手段保存、汇编论文(设计)。 本人论文(设计)中有原创性数据需要保密的部分为(如没有,请填写“无”):无。 签名:      年  月  日 指导教师签名:                    年  月  日 摘 要 本文针对单片机在嵌入式系统中资源和处理能力有限这一问题,提出了精简的嵌入式协议栈——uIP协议,并以ENC28J60芯片为例,采用c语言编程,经过一些测试,最终实现了uIP协议在单片机上的移植。 关键词:嵌入式系统,uIP协议,移植 ABSTRACT This paper proposes streamlined an embedded protocol—the uIP agreement ,as the SCM is limited at resources and processing power in the embedded system. Taking the ENC28J60 chips for example ,the design achieved the transplant using the c language after some tests finally. Key words:embedded system,uIP agreement, transplant 目 录 3 1 概述 1 1.1 研究背景 1 1.2 研究意义 1 1.3 研究思路 1 2 UIP协议在单片机上的移植过程 2 2.1 UIP特性 2 2.2 UIP 架构 2 2.3 UIP 在MCS-51单片机上的移植 3 3 UIP协议在单片机上的实现 5 3.1 UIP协议关键库函数的功能及使用方法 5 3.1.1 接收数据 6 3.1.2 发送数据 6 3.1.3 重发数据 6 3.1.4 关闭连接 7 3.1.5 报告错误 7 3.1.6 轮询 7 3.1.7 监听端口 7 3.1.8 打开连接 7 3.1.9 数据流控制 8 3.1.10 UIP函数总结 8 3.2 重点代码分析 9 3.2.1 UIP/设备驱动接口 9 3.2.2 UIP/周期计时接口 9 3.2.3 UIP协议栈提供的主要接口 10 3.3 以太网的芯片ENC28J60介绍及编程 11 3.3.1 ENC28J60介绍 11 3.3.2 ENC28J60与单片机的连接 12 设 计 总 结 16 参考文献 17 附 录 18 1.ENC28J60与单片机的外部连接图 18 (1)整体连接图 18 18 (2)MCU部分 18 19 (3)串口与单片机部分 19 19 (4)串口与单片机部分 20 20 (5)电源部分 20 20 2.重点函数程序 20 致 谢 32 1 概述 1.1 研究背景 近些年来,人们对把各种装置连接到一个现有的IP网络比如因特网上产生了浓厚的兴趣。为了能通过因特网通讯,一个可实现的TCP/IP协议栈是很必要的。对于由32位嵌入式处理器构建的中、高端网络接入嵌入式系统,一般会运行一个集成有TCP/IP协议栈的操作系统。但是对于由8位和16位低端处理器构建的系统,因为它所具有的处理能力和资源十分有限,通常不运行操作系统,这就要求系统开发者根据应用的要求和所选用的处理器的实际情况构建自己的TCP/IP协议栈,而TCP/IP协议的透明性掩盖了它实现的复杂性,从无到有构建一个协议栈是一件艰巨的任务,并且缺少有效的调试工具。uIP TCP/IP协议栈是应用于低端8位或16位微处理器构建的嵌入式系统的一个可实现的极小的TCP/IP协议栈,它可以用于商业和非商业目的。为了方便于移植uIP使用C语言编写,而且uIP协议栈的代码大小和RAM的需求比其它一般的TCP/IP栈要小,这就使得它可以方便的应用于各种低端系统上。本文将简要描述uIP的实现方法,分析uIP协议栈的应用接口,并讨论如何将其应用到51系列单片机上。 1.2 研究意义 单片机在嵌入式领域中属于很低端的处理器,它的资源和处理能力有限。首先,它的数据存储空间RAM和程序存储空间ROM都不能超过64K;其次,它的CPU频率通常在12M~40M之间,指令的执行速度相对较慢,导致网络传输速度很难上去。据计算,12M 的51 单片机的最快网络传送速度大概是25KB/s。这些特点导致在单片机上不可能运行大而全的 TCP/IP 协议栈,需要使用精简的嵌入式协议栈。虽然通讯流量和处理速度不允许很大,但对于一般的楼宇、工业、家居智能化等领域也足够应付。更重要的是,它的成本很低,具有很高的经济价值。考虑到这些原因,uIP 协议栈是一个不错的选择。因此本文以基于单片机控制的硬件和uIP 协议栈介绍单片机TCP/IP 网络编程与应用。 1.3 研究思路 本文将简要描述uIP协议的实现方法,分析其在单片机上的移植过程,研究其关键库函数的功能及其使用方法,并讨论如何将其应用到51系列单片机上。 2 UIP协议在单片机上的移植过程 2.1 UIP特性 uIP 协议栈去掉了完整的TCP/IP系统中不常用的功能,简化了通讯流程,只保留了网络通信必须使用的协议,设计重点放在了IP/TCP/ICMP/UDP/ARP这些网络层和传输层的协议上,因此保证了其代码的通用性和结构的稳定性。由于uIP协议栈专门为嵌入式系统而设计,因此还具有以下优越功能: (1)代码非常少,其协议栈代码不到6K,方便阅读与移植。 (2)占用的内存数极少,RAM占用仅几百字节。 (3)它的硬件处理层、协议栈层和应用层共用一个全局缓存区,不存在数据拷贝,而且发送和接收都是依靠这个缓存区,极大的节省了空间和时间。 (4)支持多个主动连接和被动连接并发。 (5)它的源代码中提供一套实例程序:web 客户端,web 服务器,Telnet 服务器,电子邮件发送程序(SMTP 客户端), DNS 主机名解析程序等。通用性强,移植起来基本不用修改就可通过。 (6)在数据的处理上采用轮循机制,不需要操作系统的支持。由于 uIP 对资源的需求少而且移植容易,大部分的8位微控制器都使用过uIP协议栈, 而且很多著名的嵌入式产品和项目(如卫星,Cisco 路由器,无线传感器网络)中均在使用uIP 协议栈。 2.2 UIP 架构 uIP相当于一个代码库,通过一系列的函数实现与底层硬件和高层应用程序之间的通讯,对于整个系统来说它内部的协议组是透明的,从而增加了协议的通用性。 uIP协议栈与系统底层和高层应用之间的关系如图(一)所示: 图(一) uIP在系统中的位置 uIP 协议栈主要提供了三个函数供系统底层调用。即uip_init(), uip_input()和uip_periodic()。其与应用程序的主要接口是UIP_APPCALL( )。uip_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 的大小并发送。由于TCP/IP 的应用场景很多,所以应用程序作为单独的模块由用户实现。uIP 协议栈提供一系列接口函数供用户程序调用,其中大部分函数是作为C的宏命令实现的,主要是为了速度、代码大小、堆栈和效率的使用。用户需要将应用层入口程序作为接口提供给uIP协议栈,并将这个函数定义为UIP_APPCALL()。这样以来,uIP 在接受到底层传来的数据包后,在需要送到上层应用程序处理的地方,调用UIP_APPCALL( ),在不用修改协议栈的情况下可以适配不同的应用程序。 2.3 UIP 在MCS-51单片机上的移植 1.为此项目建立一个keil C 工程,建立src 目录存放源文件。 2.通过阅读uip-1.0\unix\main.c,了解uIP 的的主循环代码架构,并将main.c 放到src 目录下。 3.仿照uip-1.0\unix\tapdev.c 写网卡驱动程序,与具体硬件相关。这一步比较费点时间,不过好在大部分网卡芯片的驱动程序都有代码借鉴或移植。驱动需要提供三个函数。etherdev_init():网卡初始化函数,初始化网卡的工作模式。u16_t etherdev_read(void):读包函数。将网卡收到的数据放入全局缓存区uip_buf 中,返回包的长度,赋给uip_len。void etherdev_send(void):发包函数。将全局缓存区uip_buf 里的数据(长度放在uip_len 中)发送出去。因此,收包和发包主要是操作uip_buf 和uip_len。 4.因为uIP 协议栈需要使用时钟,为TCP 和ARP 的定时器服务,所以使用单片机的定时器0 用作时钟,每20ms 让计数tick_cnt 加1,这样,25 次计数(0.5S)满了后可以调用TCP 的定时处理程序,10S 后可以调用ARP 老化程序。对uIP1.0 版本,增加了timer.c/timer.h,专门用来管理时钟,都放到src 下。 5.uIP 协议栈的主要内容在uip-1.0\uip\下的uip.c/uip.h 中,放到src 下。如果需要ARP 协议,需要将uip_arp.c 和uip_arp.h 也放到src 下。 6.uipopt.h/uip-conf.h 是配置文件,用来设置本地的IP 地址、网关地址、MAC 地址、全局缓冲区的大小、支持的最大连接数、侦听数、ARP 表大小等。需要放在src 下,并且根据需要配置。在V1.00 版本中对配置做了如下修改: (1)配置IP 地址,默认先关IP,在初始化中再设定。 #define UIP_FIXEDADDR 0 #define UIP_IPADDR0 192 #define UIP_IPADDR1 168 #define UIP_IPADDR2 1 #define UIP_IPADDR3 9 #define UIP_NETMASK0 255 #define UIP_NETMASK1 255 #define UIP_NETMASK2 255 #define UIP_NETMASK3 0 #define UIP_DRIPADDR0 192 #define UIP_DRIPADDR1 168 #define UIP_DRIPADDR2 1 #define UIP_DRIPADDR3 1 (2)设置MAC 地址 #define UIP_FIXEDETHADDR 1 #define UIP_ETHADDR0 0x00 #define UIP_ETHADDR1 0x4f #define UIP_ETHADDR2 0x49 #define UIP_ETHADDR3 0x12 #define UIP_ETHADDR4 0x12 #define UIP_ETHADDR5 0x13 (3)ping 功能 #define UIP_PINGADDRCONF 1 (4)关闭主动请求连接的功能 #define UIP_ACTIVE_OPEN 0 (5)将uip_tcp_appstate_t 定位u8_t 类型。 (6)由于单片机是大端结构,因此宏定义需要修改 #define UIP_CONF_BYTE_ORDER UIP_BIG_ENDIAN (7)暂时不移植打印信息,先关闭 #define UIP_CONF_LOGGING 0 (8)定义数据结构类型 typedef unsigned char u8_t; typedef unsigned int u16_t; typedef unsigned long u32_t; 7.若使用 keil C 的小模式编译,则需要在大部分的RAM 的变量前增加xdata。 8.data为keil C 的关键词,代码中所有出现data 的地方(主要是参数、局部变量、结构体成员)改为pucdata或ucdata。 9.解决编译过程中的错误。由于uIP 协议栈是用C 语言编写,所以编译过程中的问题比较少,并且容易解决。 3 UIP协议在单片机上的实现 3.1 UIP协议关键库函数的功能及使用方法 应用程序必须作为C函数去实现,uIP在任何一个事件发生时调用UIP_APPCALL()。表 1 列出了可能的事件和每个事件的对应测试函数,测试函数用于区别不同的事件。函数是作为C宏命令实现的,将会是零或非零值。注意,某些函数可以在互相连接时发生(即新数据可以在数据确应的同时到达)。 表 1 uIP应用事件和对应的测试参数 一个数据包到达,确定先前发送到数据 uip_acked() 应用程序的新数据包已到达 uip_newdata() 一个远程主机连接到监听端口 uip_connected() 一个到达远程主机的连接建立成功 uip_connected() 计时时间满重发 uip_rexmit() 计时时间满周期性轮询 uip_poll() 远程主机关闭连接 uip_closed() 远程主机中断连接 uip_aborted() 由于太多重传,连接中断 uip_timedout() 当应用程序调用时,uIP设置全局变量uip_conn去指向当前连接的uip_conn结构,这可以用于区别不同的服务。一个典型的应用是检查uip_conn->lport (当地TCP端口号)去决定哪个服务连接应该提供。例如,如果值uip_conn->lport等于80,应用程序可以决定启动一个HTTP服务;若值是23,则是启动TELNET服务。 3.1.1 接收数据 如果uIP测试函数uip_newdata()的值为1,则远程连接的主机有发送新数据,uip_appdata指针指向实际数据,数据的大小通过uIP函数uip_datalen()获得。在数据不是被缓冲后,应用程序必须立刻启动。 3.1.2 发送数据 应用程序通过使用uIP函数uip_send()发送数据。uip_send()函数采用两个参数:一个指针指向发送数据和数据的长度。如果应用程序为了产生要发送的实际数据需要RAM空间,包缓存(通过uip_appdata指针指向)可以用于这方面。在一个时间里应用程序只能在连接中发送一块数据,所以不可以在每个应用程序启用中调用uip_send()超过一次,只有上一次调用的数据将会发出后才可以。注意,调用uip_send()后会改变某些全局变量,在应用函数返回前它不能被调用。 3.1.3 重发数据 若数据在网络中丢失,则应用程序必须重新发数据。无论是数据收到还是没有收到,uIP都保持跟踪,并通知应用程序什么时候察觉出数据丢失了。若测试函数uip_rexmit()为真,则应用程序要重发上一次发出的数据。重发就好像原来那样发送,也就是通过uip_send()发送。 3.1.4 关闭连接 应用程序通过调用uip_close()关闭当前连接,这会导致连接干净地关闭。为了指出致命的错误,应用程序可以通过中止连接和调用uip_abort()函数完成这项工作。若连接已经被远端关闭,则测试函数uip_closed()为真,应用程序接着可以做一些必要的清理工作。 3.1.5 报告错误 有两个致命的错误可以发生在连接中:连接由远程主机中止和连接多次重发上一数据而被中止。uIP通过调用函数报告这些问题,应用程序使用两个测试函数uip_aborted()和uip_timedout() 去测试这些错误情况。 3.1.6 轮询 当连接空闲时,uIP周期性地轮询应用程序,应用程序使用测试函数uip_poll()去检查它是否被轮询过。 3.1.7 监听端口 uIP维持一个监听TCP端口列表,通过uip_listen()函数,一个新的监听端口被打开。当一个连接请求在一个监听端口到达,uIP产生一个新的连接。若一个新连接产生,则应用程序被调用,测试函数uip_connected()为真。 3.1.8 打开连接 作为uIP的0.6版,在uIP里面通过使用uip_connect()函数打开一个新连接。这个函数打开一个新连接到指定的IP地址和端口,返回一个新连接的指针到uip_conn结构。若是没有空余的连接槽,则函数返回空值。为了方便,函数uip_ipaddr()可以用于将IP地址打包进两个单元16位数组里,通过uIP去代表IP地址。 接下来用两个例子说明。第一个例子展示了怎样打开一个连接去远端TCP端口8080。若没有足够的TCP连接插槽去允许一个新连接打开,则uip_connect()函数返回NULL并通过uip_abort()中止当前连接。第二个例子展示怎样打开一个新连接去指定的IP地址。 例1:打开一个连接去远端TCP端口8080。 void connect_example1_app(void) { if(uip_connect(uip_conn->ripaddr, 8080) == NULL) { uip_abort(); } } 例2:打开一个连接去当前连接的远端的端口8080。 void connect_example2(void) { u16_t ipaddr[2]; uip_ipaddr(ipaddr, 192,168,0,1); uip_connect(ipaddr, 8080); } 3.1.9 数据流控制 uIP通过函数uip_stop()和函数uip_restart()提供对存取TCP数据流的控制途径。假如一个应用程序下载数据到一个慢速设备,例如磁盘驱动器。当磁盘驱动器的作业队列满时,应用程序不会准备从服务器接收更多的数据,直到队列排出空位。函数uip_stop()可以用于维护流控制和停止远程主机发送数据。当应用程序准备好接收更多数据时,可用函数uip_restart()告知远程终端再次发送数据。函数uip_stopped()可以用于检查当前连接是否停止。 3.1.10 UIP函数总结 表 2 包含了所有uIP提供的函数 表2 uIP 函数总结 系统接口 uip_init() uip_input() uip_periodic() 初始化uIP 处理输入包 处理周期计时事件 应用程序接口 uip_listen() uip_connect() uip_send() uip_datalen() uip_close() uip_abort() uip_stop() uip_stopped() uip_restart() 开始监听端口 连接到远程主机 在当前连接发送数据 输入数据的大小 关闭当前连接 中止当前连接 停止当前连接 查找连接是否停止 重新启动当前连接 测试函数 uip_newdata() uip_acked() uip_connected() uip_closed() uip_aborted() uip_timeout() uip_rexmit uip_poll() 远程主机已经发出数据 确定发出的数据 当前连接刚连上 当前连接刚关闭 当前连接刚中止 当前连接刚超时 数据重发 应用程序循环运行 其它 uip_mss() uip_ipaddr() htons(),ntohs() 获得当前连接的最大段的大小 将IP地址结构打包 在主机和网络之间转换字节次序 3.2 重点代码分析 从系统的立场看,uIP有3个C函数,即uip_init(),uip_input()和 uip_periodic()。uip_init()函数用于初始化uIP堆栈在系统启动期间的调用。当网络设备驱动器读一个IP包到包缓存时,调用函数uip_input()。周期性运行时调用uip_periodic(),代表的是一秒一次。 3.2.1 UIP/设备驱动接口 当设备驱动在包缓存里(uip_buf)放一个输入包,系统应该调用uip_input()函数。函数将会处理这个包并在需要时调用应用程序。当uip_input()返回,一个输出包放在包缓存里,包的大小由全局变量uip_len约束。如果uip_len是0,则没有包要发送。 3.2.2 UIP/周期计时接口 周期计时用于驱动所有uIP内部时钟事件,例如包重发。当周期计时激发,每一个TCP连接应该调用uIP函数uip_periodic()。连接编号传递作为自变量给uip_periodic()函数。类似于uip_input()函数,当uip_periodic()函数返回,输出的IP包要放在包缓存里。在这个特别的例子,函数netdev_send()是网络驱动的部分,将uip_buf数组的目录发出到网上。 for(i = 0; i < UIP_CONNS; ++i) { uip_periodic(i); if(uip_len > 0) { netdev_send(); } } 3.2.3 UIP协议栈提供的主要接口 提供的接口在 uip.h 中,为了减少函数调用造成的额外支出,大部分接口函 数以宏命令实现。 1.初始化uIP协议栈:uip_init() 2.处理输入包:uip_input() 3.处理周期计时事件:uip_periodic() 4.开始监听端口:uip_listen() 5.连接到远程主机:uip_connect() 6.接收到连接请求:uip_connected() 7.主动关闭连接:uip_close() 8.连接被关闭:uip_closed() 9.发出去的数据被应答:uip_acked() 10.在当前连接发送数据:uip_send() 11.在当前连接上收到新的数据:uip_newdata() 12.告诉对方要停止连接:uip_stop() 13.连接被意外终止:uip_aborted() 3.3 以太网的芯片ENC28J60介绍及编程 3.3.1 ENC28J60介绍 ENC28J60 是带有行业标准串行外设接口(SerialPeripheral Interface,SPI)的独立以太网控制器。 它可作为任何配备有SPI 的控制器的以太网接口,ENC28J60 符合IEEE 802.3 的全部规范,采用了一系列包过滤机制以便对传入数据包进行限制。它还提供了一个内部DMA 模块,以实现快速数据吞吐和硬件支持的IP校验和计算,通过两个中断引脚和SPI 实现与主控制器的通信,数据传输速率高达10 Mb/s。两个专用的引脚用于连接LED,进行网络活动状态指示。要将单片机连接到速率为10 Mbps 的以太网,只需ENC28J60、两个脉冲变压器和一些无源元件即可。ENC28J60 由七个主要功能模块组成: 1. SPI 接口——充当主控制器和ENC28J60 之间的通信通道。 2. 控制寄存器——用于控制和监视ENC28J60。 3. 双端口RAM缓冲器——用于发送和接收数据包。 4. 判优器——当DMA、发送和接收模块发出请求时对RAM 缓冲器的访问进行控制。 5. 总线接口——对通过SPI 接收的数据和命令进行解析。 6. MAC (Medium Access Control)模块——实现符合IEEE 802.3 标准的MAC 逻辑。 7. PHY(物理层)模块——对双绞线上的模拟数据进行编码和译码。该器件还包括其他支持模块,如片内稳压器、振荡器、电平变换器(提供可以接受5V 电压的I/O引脚)和系统控制逻辑。 各模块工作特性如下: (1)以太网控制器特性 • IEEE 802.3 兼容的以太网控制器 • 接收器和冲突抑制电路 • 集成MAC 和10 BASE-T PHY • 支持半双工和全双工模式 • 支持一个带自动极性检测和校正的10BASE-T 端口 • 可编程在发生冲突时自动重发 • 可编程填充和CRC 生成 • 可编程自动拒绝错误数据包 • 最高速度可达10 Mb/s (2)缓冲器 • 8 KB 发送/ 接收数据包双端口SRAM • 可配置发送/ 接收缓冲器大小 • 硬件管理的循环接收FIFO • 用于快速数据传送的内部DMA • 字节宽度的随机访问和顺序访问(地址自动递增) • 硬件支持的IP 校验和计算 (2)介质访问控制器(即MAC)特性 • 支持单播、组播和广播数据包 • 可编程数据包过滤,并在以下事件的逻辑“与”和“或”结果为真时唤醒主机: - 单播目标地址 - 组播地址 - 广播地址 - 由64 位哈希表定义的组目标地址 - Magic Packet - 多达64 字节的可编程模式匹配(偏移量可由用户定义) • 环回模式 (3)物理层(PHY)特性 • 环回模式 • 整形输出滤波器 (4)工作特性 • 两个用来表示连接、发送、接收、冲突和全/ 半双工状态的可编程LED 输出 • 25 MHz 时钟 • 使用两个中断引脚的七个中断源 • 带可编程预分频器的时钟输出引脚 • 工作电压范围是3.14V ——3.45V • TTL 电平输入 • 温度范围:-40°C——+85°C (工业级), 0°C ——+70°C (商业级)(仅SSOP 封装) • 28 引脚SPDIP、SSOP、SOIC 和QFN 封装 3.3.2 ENC28J60与单片机的连接 ENC28J60与微控制器MCU的连接是通过SPI实现的,支持10Mbps。对于没有SPI接口的芯片可通过用I/O口模拟SPI接口的方式实现。ENC28J60仅支持SPI模式0,0。 微控制器可通过SPI接口发送命令,访问ENC28J60的寄存器或读写接收/发送缓冲区,完成相关操作。复位也可通过SPI接口用软件实现,其中软件复位不影响RESET引脚的状态。 ENC28J60有两个中断输出,分别用于事件中断触发和网络唤醒主机。 CPU采用LPC2138用宏定义实现SPI口读写操作。SOSPDR为SPI数据寄存器,该双向寄存器为SPI提供发送和接收的数据,发送数据通过写该寄存器提供,SPI接收的数据可从该寄存器读出。SOSPSR为SPI状态寄存器,在对SPI接口进行操作之前需对其初始化。以下是读/写SPI接口的源代码:    #defineREADSPI(Val)   {      S0SPDR=0x00;      while(0==(S0SPSR&0x80))      Val=S0SPDR;    }      #defineWRITESPI(Val)     {      if(0==(S0SPSR&0x40)) {       S0SPDR=Val;       while(0==(S0SPSR&0x80))       }       } uIP的设置单独包含在一个叫uipopt.h的头文件里,都是以宏的形式定义方便修改。用户应根据自己的应用在uipopt.h文件里设置本地的物理地址、IP地址、网关地址、收发缓冲区的大小、支持的最大连接数、ARP表大小等选项。添加必须的模块,对uIP进行了正确地配置后,需要编写主程序函数。针对基于以太网WEBSERVER应用,主程序在完成初始化后将不停的进行查询,若有新数据包到达则送uip_input()函数处理,没有新数据包到达则处理定时事件。流程图如下所示: 设 计 总 结 可以看出,uIP协议栈采用有效的方法和结构化的代码,使其存储器占用量很小并且可以很方便的应用到不同的工程项目中,同时它又是免费的可以自由使用于商业和非商业目的。uIP为低端嵌入式设备的网络接入提供了很好的解决方案,具有很高的应用价值。ENC28J60是极具特色的独立以太网控制器:SPI接口使得小型单片机也能具有网络连接功能;集成MAC和PHY无需其他外设;具有可编程过滤功能,可自动评价、接收或拒收多种信息包,减轻了主控单片机的处理负荷;内部继承可编程的8KB双端口SRAM缓冲器,操作灵活方便。不足之处为仅支持10BASET。 这次设计基本实现了所要设计的内容,但是我也看到了设计过程中的不足和出现的问题。例如,在编程时由于所掌握的知识有限,遇到过不少困难,虽然出了不少错误,但我相信自己在以后越来越多的实践中越来越成熟起来。总的来说这次的毕业设计不仅锻炼了我的实战能力,激发了我的创新思维,提高了我发现问题、解决问题的能力,而且培养了我面对挫折并勇于克服的意志、品质和吃苦耐劳的精神,也使我深深的体会到理论结合实际的重要性,体会到即使以后走到工作岗位,仍然要继续努力汲取知识。 参考文献 [1]李广弟,朱月秀,王秀山.单片机基础[M].北京:北京航空航天大学出版社,2001.7 [2]韩志军,沈晋源,望振波.单片机应用系统设计[M].北京:机械工业出版社,2005 [3]楼然苗,李光飞.51单片机设计实例[M].北京:北京航空航天大学出版社,2003 [4]雷晓平等编.单片机及其应用[M].成都:电子科技大学出版社,2005.6 [5]李兰友等编.单片机开发应用十例[M].北京:电子工业出版社,2006.7 [6]吴微,文军等编.单片机原理及制作[M].武汉:武汉大学出版社,2004.12 [7]靳达.单片机应用系统开发实例导航[M].北京:人民邮电出版社, 2003.10 [8]周志敏,周纪海等编.LED驱动电路设计与应用[M].北京:人民邮电出版社,2004.8 [9]何立民.单片机高级教程[M].北京:北京航空航天大学出版社,2001 [10]夏继强.单片机实验与实践教程[M].北京: 北京航空航天大学出版社,2001 [11]阎石.数字电子技术基础[M].北京:高等教育出版社,2005 附 录 1.ENC28J60与单片机的外部连接图 (1)整体连接图 (2)MCU部分 (3)串口与单片机部分 (4)串口与单片机部分 (5)电源部分 2.重点函数程序 (1) USART函数 #include "USART.h" #include "STC12C5A.h" #define BAUD 9600 //串口初始化 void USART_Init() { PCON &= 0x7f; //波特率不倍速 SMOD=0 SCON = 0x50; //8位数据,可变波特率 AUXR |= 0x40; //定时器1时钟为Fosc,即1T AUXR &= 0xfe; //串口1选择定时器1为波特率发生器 TMOD &= 0x0f; //清除定时器1模式位 TMOD |= 0x20; //设定定时器1为8位自动重装方式 TL1 = 0xB2; //设定定时初值 TH1 = 0xB2; //设定定时器重装值 ET1 = 0; //禁止定时器1中断 TR1 = 1; //启动定时器1 ES = 1; //开串行中断 EA = 1; //开总中断 } //发送一个字符,ASCII码 void SendASC(unsigned char d) { SBUF=d; while(!TI); //等待发送完成,TI置位 TI=0; } //发送字符串 void SendString(unsigned char *str) { while(*str) { SendASC(*str) ; str++; } } //串口中断服务程序 void USART_Interrupt(void) interrupt 4 //放在这里和放在main()里面一样 { unsigned char RxData=0; EA=0; if(RI) //必须判断RI(接收中断标志)是否为1 { RI=0; RxData = SBUF; //在这里添加你的接收代码 SendString("The word you send is "); SendASC(RxData); SendASC('\n'); } EA=1; } (2) uip_arp函数 #include "uip_arp.h" #include struct arp_hdr { struct uip_eth_hdr ethhdr; u16_t hwtype; u16_t protocol; u8_t hwlen; u8_t protolen; u16_t opcode; struct uip_eth_addr shwaddr; u16_t sipaddr[2]; struct uip_eth_addr dhwaddr; u16_t dipaddr[2]; } struct ethip_hdr { struct uip_eth_hdr ethhdr; u8_t vhl; tos; len[2]; ipid[2]; ipoffset[2]; ttl; proto; u16_t ipchksum; u16_t srcipaddr[2]; destipaddr[2]; } #define ARP_REQUEST 1 #define ARP_REPLY 2 #define ARP_HWTYPE_ETH 1 struct arp_entry { u16_t ipaddr[2]; struct uip_eth_addr ethaddr; u8_t time; } struct uip_eth_addr uip_ethaddr = { { UIP_ETHADDR0; UIP_ETHADDR1; UIP_ETHADDR2; UIP_ETHADDR3; UIP_ETHADDR4; UIP_ETHADDR5; } } static struct arp_entry arp_table[UIP_ARPTAB_SIZE]; static u16_t ipaddr[2]; static u8_t i, c; static u8_t arptime; static u8_t tmpage; #define BUF ((struct arp_hdr *)&uip_buf[0]) #define IPBUF ((struct ethip_hdr *)&uip_buf[0]) --------*/ Void uip_arp_init(void) { for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { memset(arp_table[i].ipaddr, 0, 4); } } --------*/ Void uip_arp_timer(void) { struct arp_entry *tabptr; ++arptime; for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { tabptr = &arp_table[i]; if((tabptr->ipaddr[0] | tabptr->ipaddr[1]) != 0 && arptime - tabptr->time >= UIP_ARP_MAXAGE) { memset(tabptr->ipaddr, 0, 4); } } } --------*/ static void uip_arp_update(u16_t *ipaddr, struct uip_eth_addr *ethaddr) { register struct arp_entry *tabptr; for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { tabptr = &arp_table[i]; if(tabptr->ipaddr[0] != 0 && tabptr->ipaddr[1] != 0) { if(ipaddr[0] == tabptr->ipaddr[0] && ipaddr[1] == tabptr->ipaddr[1]) { memcpy(tabptr->ethaddr.addr, ethaddr->addr, 6); tabptr->time = arptime; return; } } } for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { tabptr = &arp_table[i]; if(tabptr->ipaddr[0] == 0 && tabptr->ipaddr[1] == 0) { break; } } if(i == UIP_ARPTAB_SIZE) { tmpage = 0; c = 0; for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { tabptr = &arp_table[i]; if(arptime - tabptr->time > tmpage) { tmpage = arptime - tabptr->time; c = i; } } i = c; } memcpy(tabptr->ipaddr, ipaddr, 4); memcpy(tabptr->ethaddr.addr, ethaddr->addr, 6); tabptr->time = arptime; } --------*/ Void uip_arp_ipin(void) { uip_len -= sizeof(struct uip_eth_hdr); if((IPBUF->srcipaddr[0] & uip_arp_netmask[0]) != (uip_hostaddr[0] & uip_arp_netmask[0])) { return; } if((IPBUF->srcipaddr[1] & uip_arp_netmask[1]) != (uip_hostaddr[1] & uip_arp_netmask[1])) { return; } uip_arp_update(IPBUF->srcipaddr, &(IPBUF->ethhdr.src)); return; } --------*/ Void uip_arp_arpin(void) { if(uip_len < sizeof(struct arp_hdr)) { uip_len = 0; return; } uip_len = 0; switch(BUF->opcode) { case HTONS(ARP_REQUEST): if(BUF->dipaddr[0] == uip_hostaddr[0] && BUF->dipaddr[1] == uip_hostaddr[1]) { BUF->opcode = HTONS(2); memcpy(BUF->dhwaddr.addr, BUF->shwaddr.addr, 6); memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6); memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6); memcpy(BUF->ethhdr.dest.addr, BUF->dhwaddr.addr, 6); BUF->dipaddr[0] = BUF->sipaddr[0]; BUF->dipaddr[1] = BUF->sipaddr[1]; BUF->sipaddr[0] = uip_hostaddr[0]; BUF->sipaddr[1] = uip_hostaddr[1]; BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP); uip_len = sizeof(struct arp_hdr); } break; case HTONS(ARP_REPLY): if(BUF->dipaddr[0] == uip_hostaddr[0] && BUF->dipaddr[1] == uip_hostaddr[1]) { uip_arp_update(BUF->sipaddr, &BUF->shwaddr); } break; } return; } ---------*/ Void uip_arp_out(void) { struct arp_entry *tabptr; if((IPBUF->destipaddr[0] & uip_arp_netmask[0]) != (uip_hostaddr[0] & uip_arp_netmask[0]) || (IPBUF->destipaddr[1] & uip_arp_netmask[1]) != (uip_hostaddr[1] & uip_arp_netmask[1])) { ipaddr[0] = uip_arp_draddr[0]; ipaddr[1] = uip_arp_draddr[1]; } else { ipaddr[0] = IPBUF->destipaddr[0]; ipaddr[1] = IPBUF->destipaddr[1]; } for(i = 0; i < UIP_ARPTAB_SIZE; ++i) { tabptr = &arp_table[i]; if(ipaddr[0] == tabptr->ipaddr[0] &&ipaddr[1] == tabptr->ipaddr[1]) { break; } } if(i == UIP_ARPTAB_SIZE) { memset(BUF->ethhdr.dest.addr, 0xff, 6); memset(BUF->dhwaddr.addr, 0x00, 6); memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6); memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6); BUF->dipaddr[0] = ipaddr[0]; BUF->dipaddr[1] = ipaddr[1]; BUF->sipaddr[0] = uip_hostaddr[0]; BUF->sipaddr[1] = uip_hostaddr[1]; BUF->opcode = HTONS(ARP_REQUEST); BUF->hwtype = HTONS(ARP_HWTYPE_ETH); BUF->protocol = HTONS(UIP_ETHTYPE_IP); BUF->hwlen = 6; BUF->protolen = 4; BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP); uip_appdata = &uip_buf[40 + UIP_LLH_LEN]; uip_len = sizeof(struct arp_hdr); return; } memcpy(IPBUF->ethhdr.dest.addr, tabptr->ethaddr.addr, 6); memcpy(IPBUF->ethhdr.src.addr, uip_ethaddr.addr, 6); IPBUF->ethhdr.type = HTONS(UIP_ETHTYPE_IP); uip_len += sizeof(struct uip_eth_hdr); } (3)主函数 #incldue void main(void) { unsigned char i; unsigned char arptimer=0; USART_init(); //网络初始化函数 nic_init();//uip协议栈初始化 uip_init();// init app 应用程序初始化,如tcp或者udp ,http的应用 httpd_init();// init ARP cache 初始化arp协议的缓冲 uip_arp_init(); // init periodic timer 初始化周期函数定时器 initTimer(); //开放中断 //主循环 while(1) { uip_len = nic_poll(); if(uip_len == 0)//如果没有数据包 { if(timerCounter > TIMERCOUNTER_PERIODIC_TIMEOUT) { timerCounter = 0; for(i = 0; i < UIP_CONNS; i++) { uip_periodic(i);//周期性检查函数 if(uip_len > 0) //如果包长度大于0 发送包 { uip_arp_out();//主动发送和重发数据包在此进行 nic_send(); } } if(++arptimer == 20)//更新arp表 { uip_arp_timer(); arptimer = 0; } } } else // packet received 接收到网络数据包 { // process an IP packet 处理ip数据包 if(BUF->type == htons(UIP_ETHTYPE_IP)) { uip_arp_ipin(); uip_input(); if(uip_len > 0) { uip_arp_out(); nic_send(); } } // process an ARP packet 处理arp包 else if(BUF->type == htons(UIP_ETHTYPE_ARP)) { uip_arp_arpin(); if(uip_len > 0) { nic_send(); } } } } 致 谢 在论文即将完成之际,我希望能够借这个机会对在大学在校期间关心、帮助、支持和鼓励过我的老师、同学、朋友和亲人致以最诚挚的谢意和最衷心的祝福! 首先,我要衷心感谢我的导师—赵忠彪老师的悉心指导。我的论文从选题到撰写以及到最后的完成都凝集了赵老师很多辛勤的汗水,可以说,我的每一点成绩、每一次进步都离不开老师的启发和教诲。赵老师在科研工作中的务实敬业精神深深地感染着我;扎实的理论知识、丰富的工程实践,宏观上启迪着我的思维;渊博的知识、敏锐的洞察力、严谨的治学态度将使我终身难忘。 感谢许昌学院电气信息工程学院的各位老师四年里在我的学习和生活上对我的指导和帮助,同时也感谢系领导和辅导员刘老师在平时生活和学习中给我们创造了良好的条件。 最后,向所有曾给予我关心和帮助的老师和同学致以最衷心的感谢,向在百忙之中参加论文评审、答辩的专家和老师表示衷心的感谢和最崇高的敬意!

    Top_arrow
    回到顶部
    EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高员外,我们会尽快处理。