
可运行在单片机上的udp通讯协议的实现.doc
18页可运行在单片机上的 UDP通讯协议的实现2010年 04月 22日 星期四 19:46本文可以说是在我之前写的《单片机驱动 DM9000网卡芯片(详细调试过程)》基础上增加了一些协议代码来实现具体 UDP通信传输在这里我重新强调一下,上篇文章是在介绍如何调硬件,目的是为了让应用程序可以使用这个芯片而具体的使用就是使用三个函数:初始化、数据包发送和数据包接收数据包接收是否基于中断还需要用户根据需要自行设置总之,我们可以通过上篇文章了解到,对硬件的调试可以得到这三个有用的函数不同网卡芯片的驱动可能略有不同这里不一一例举,所以首先需要说明的是,本文所讲的内容主要是如何用 C代码来实现协议,并利用数据包发送、接收函数来实现通讯,基本是与硬件无关的除中断外,本文唯一与硬件相关的地方就是大端或小端格式,这也在之前的文章中有提到过,在本文涉及到的地方会再次说明也就是说使用不同网卡芯片都可以应用本文所写的代码其次,本文所写出的协议部分是已经过简化的,代码较少,不需要操作系统支持但仅能实现数据的收发,而且没有验证可靠性(需要时可自行验证)适合用在资源有限的单片机系统中,或者需要用网络代替 RS232通信的情况,当然也可以在操作系统中使用。
可根据情况来选择或增减顺便提一下资源要求:ram 最好大于 2KB,实在不行也得要 1KB(需要一定技巧,传输的数据内容很少,不需要全部读出数据包的情况);flash 或 rom4KB以上,基本的单片机都能达到;可用 IO怎么也得有 12个,控制个一般的芯片也需要这么多的在进行正文之前,我再啰嗦几句,本文是讲协议的实现这里的协议部分可以从《TCP/IP协议 第一卷 —— 协议》这本书中看到最详细最权威的讲解,如果有兴趣研究协议的话可以参考这本书(网上可以找到电子版的)下面进入主题1、UDP 通讯的实现过程简述涉及到协议部分,很多人会感觉摸不清头绪,不知如何下手所以看一看上面说的那本书还是很有帮助的当然看了以下部分,你也会对协议有些了解的1)初始化网卡芯片和其他外设(在网卡驱动部分已经做好了,这里重新说了一遍);(2)arp 通讯获得目标机地址信息;(3)udp 通讯收发数据(利用 IP协议作媒介)看到这会不会有些失望呢,可实际上 udp通讯就是这么简单的UDP是 User Datagram Protocol的简称,中文名是用户数据包协议,是 OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务(这句话是在网上抄的)。
说的再简单点,udp 通讯与 RS232一样,只管将数据发送出去而不管对方是否正确的接收到了在一些简单应用中,我们似乎也不大关心数据是否被对方正确的接收到了,因为传输过程中数据包损坏的情况也不多,肯定能被正确的接收到或者我们有其他的办法去验证这和TCP协议不一样,TCP 协议是可靠的链接,发送一次数据需要三次握手来反复确认数据被正确无误的接收到了,否则会重新发送一遍,实现起来比较繁琐有点跑题,不过看到这里应该可以明白 udp协议是一种很简单的网络通讯协议简单解释一下上面三个过程初始化不用解释了ARP 通讯是整个网络传输的开始,而且只需要运行一次在《单片机驱动 DM9000网卡芯片(详细调试过程)【下】》最后的部分已经讲清楚了主要是解释第三条,udp 通讯收发数据OSI参考模型中 arp协议属于链路层(最底层),ip 协议比 arp协议高一层属于网络层(这一层还包括 icmp和 igmp协议),在往上一层是运输层,包括 tcp协议和 udp协议但是按我的理解,从数据包格式的角度看,我把 arp协议与 ip协议放在同一级别,我们接收到的数据包中的前几十个字节用来判断是 arp协议还是 ip协议,也就是说这两个协议是互补相容的(下面程序中会做个过滤,数据包只接收 arp或 ip协议,最后处理的数据包中只能是 arp协议或者是 ip协议)。
这种互补相容的协议同样也适用于 tcp协议和 udp协议上既然有互不相容的协议,那么也就有相容的协议了,这种相容的协议就是指一个协议必须依赖于另一种协议才能实现,udp 协议就是这样我们可以这样理解,ip 协议就像一件外套,udp 协议好比一件衬衫,而真正的数据可以看做是穿衣服的人穿衣服的人先穿上衬衫再穿外套,这两个协议之间的关系就是这样:udp 协议将数据包起来,ip 协议又将 udp协议连同其中的数据一起包起来也就是说,实际的数据经过 udp协议的包装,在经过 ip协议的包装之后才能发送出去虽然看起来有些繁琐,但实际计算机端就是这样识别数据的所谓的包装就是在被包装数据前加上一小段首部数据,一般几十个字节左右2、ARP 协议的实现这部分内容在《单片机驱动 DM9000网卡芯片(详细调试过程)【下】》的后半部分已经讲过,这里为了完整性再重复一次在写所有协议之前,有些全局变量需要事先设定一下,如 ip地址、mac 地址等信息另外,统一规定一下我们的单片机系统为“基板”,计算机端为“上位机”,以下叫起来方便再规定一下:char 型是 8位,short 型是 16位,long 型是 32位OK!/********************************************/unsigned char my_macaddr[6] = { 0x00, 0x0a, 0x00, 0x01, 0x02, 0x03 };//基板上 mac地址,这里随便写 6个字节。
unsigned char my_ipaddr[4] = { 192, 168, 1, 207 };//基板上 ip地址,根据网关写入合适值unsigned char server_macaddr[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };//上位机mac地址,通过 arp协议获取后改变unsigned char server_ipaddr[4] = {192, 168, 1, 122};//上位机 ip地址,看自己的电脑,这个应该会,不会就问问旁边的人吧unsigned char transmit_buffer[2048] = { 0 };//发送数据包缓存区unsigned char receive_buffer[2048] = { 0 };//接收数据包缓存区/*如果为了节省 ram,这两个区可以共用一个(因为发送和接收数据可以分开进行,而且一般网卡芯片内部都会有自己独立的接收和发送缓存的),容量也可以改小,写成“unsigned char data_buffer[1000] = { 0 };”我的 ram比较多,所以用了两个区,而且占用很大空间。
/unsigned long send_packet_length = 0;//发送数据包长度unsigned long receive_packet_length = 0;//接收数据包长度/*如果上面的数据缓存区共用一个的话,这两个存储数据包长度的变量也可以共用一个,写成“packet_length”*//********************************************/这部分是全局变量,需要在所有函数之前定义在写 arp协议之前先定义两个结构体://以太网地址首部,我们用的网络就是以太网struct eth_hdr {unsigned char d_mac[6]; //目标 mac地址unsigned char s_mac[6]; //源 mac地址unsigned short type; //协议类型,判断 arp协议还是 ip协议};//ARP首部=以太网首部+arp 协议首部struct arp_hdr {struct eth_hdr ethhdr; //以太网首部结构体,如上unsigned short hwtype; //硬件地址,“1”表示 mac地址unsigned short protocol; //协议地址,“0x0800”表示 ip地址unsigned char hwlen; //硬件地址长度,mac 地址为 6unsigned char protolen; //协议地址,ip 地址为 4unsigned short opcode; //操作码“1”表示 arp请求,“2”表示 arp应答unsigned char smac[6]; //源 mac地址unsigned char sipaddr[4]; //源 ip地址unsigned char dmac[6]; //目标 mac地址unsigned char dipaddr[4]; //目标 ip地址};#define ETH_TRBUF ((struct eth_hdr *)&transmit_buffer[0])#define ETH_REBUF ((struct eth_hdr *)&receive_buffer[0])/*宏定义均是为了书写方便,以以太网首部的方式指向数据包缓存#define ARP_TRBUF ((struct arp_hdr *)&transmit_buffer[0])#define ARP_REBUF ((struct arp_hdr *)&receive_buffer[0])接下来是 arp协议的代码,具体含义在前一篇文章中有,这里对 arp实现原理不做详细介绍了。
另外,本文认为数据包发送和接收函数已经像前篇文章中那样写好了:void sendpacket(unsigned char* datas, unsigned long length);//发送数据包//要发送的数据包存放在“transmit_buffer[]”中unsigned short receivepacket(unsigned char* datas);//接收数据包,返回协议类型(arp或 ip)//接收到的数据包存放在“receive_buffer[]”中可以与发送数据包公用//arp请求void arp_request(void){memcpy(ARP_TRBUF->ethhdr.d_mac, server_macaddr, 6);memcpy(ARP_TRBUF->ethhdr.s_mac, my_macaddr, 6);/*用到上面的函数,在文件中要写上“#include ”*/ARP_TRBUF->ethhdr.type = 0x0806;//arp协议,此处按大端格式/*注意!这里涉及到大端格式和小端格式问题,小端格式需要写成 0x0608,也就是 16位的高低两个字节转换一下,这个很重要,一定要弄清楚自己处理器的编译器的存储格式。
我记得51、AVR 等单片机的编译器是小端格式,所以要高低字节交换我这里和下面的程序中都按大端格式写*/ARP_TRBUF->hwtype = 1;//此处按大端格式ARP_TRBUF->protocol = 0x0800;//ip协议,此处按大端格式ARP_TRBUF->hwlen = 6;ARP_TRBUF->protolen = 4;ARP_TRBUF->opcode = 1;//arp请求,此处按大端格式/*以下凡是 16位宽的数据我都按大端格式处理不在重复啦*/memcpy(ARP_TRBUF->smac, my_macaddr, 6);memcpy(ARP_TRBUF->sipaddr, my_ipaddr, 4);memcpy(ARP_TRBUF->dipaddr, server_ipaddr, 4);send_packet_length = 42;//14字节以太网首部+28 字节 arp首部=42 字节sendpacket(tr_data, send_packet_length);}//arp应答。












