基于无线传感器网络的远程环境监测系统的设计与实现(共34页).doc
精选优质文档-倾情为你奉上基于无线传感器网络的远程环境监测系统的设计与实现1 系统概述1.1 系统背景近年来,由于计算机技术、网络技术、现代电子技术的迅猛发展,无线通信技术在医疗、自动化控制、远程监测等多个领域得到了广泛的应用。其中环境远程监测有其特定的应用背景,一般在比较偏僻、环境恶劣的无人居住区域(如沙漠、高山、丛林等危险地区),这使得很难通过架设电缆来完成数据传输,因此使用无线通信是一种很好的选择。对环境的监测是十分重要且有意义的,获得生存环境的实时的数据,有助于我们预测环境变化的趋势以及更好的掌握自己生活的环境现状并加以改善。1.2 系统简介本系统在实验环境中搭建一个ZigBee网络,该网络由一个中心节点和多个终端节点以自组织方式构成。终端节点负责采集环境温湿度等数据信息,并通过 ZigBee 网络把采集的数据信息发送至中心节点;与中心节点相连的控制器(ARM单片机)将数据发送到串口,再通过socket 通信将数据传到远程主机,并将获得的信息存入数据库中。设计一个界面,可通过界面显示出所需要的信息以及提供相关查询服务。整个系统设计综合利用ZigBee网络的低成本、低功耗、自组织、灵活等优良特性,提出一个实时、高效的远程环境监测解决方案。1.3 关键词解释1.3.1无线传感器网络无线传感器网络(Wireless Sensor Network, WSN),就是由部署在监测区域内大量的廉价微型传感器节点组成,通过无线通信方式形成的一个多跳的自组织的网络系统,其目的是协作地感知、采集和处理网络覆盖区域中被感知对象的信息,并发送给观察者2。传感器、感知对象和观察者构成了无线传感器网络的三个要素。无线传感器网络所具有的众多类型的传感器,可探测包括地震、电磁、温度、湿度、噪声、光强度、压力、土壤成分、移动物体的大小、速度和方向等周边环境中多种多样的现象。基于MEMS的微传感技术和无线联网技术为无线传感器网络赋予了广阔的应用前景。这些潜在的应用领域可以归纳为:军事、航空、反恐、防爆、救灾、环境、医疗、保健、家居、工业、商业等领域34。其主要特点有:1) 低速率传感器网络节点通常只需定期传输温度、湿度、压力、流量、电量等被测参数,相对而言,被测参数的数据量小,采集数据频率较低。2) 低功耗通常,传感器节点利用电池供电,且分布区域复杂、广阔,很难通过更换电池方式来补充能量,因此,要求传感器网络节点的功耗要低,传感器的体积要小。3) 低成本应用无线传感器网络,监测区域广、传感器的节点多,且有些区域环境的地形复杂,甚至连工作人员都无法进入,一旦安装传感器器则很难更换,因而要求传感器的成本低廉。4) 短距离为了方便组网和传递数据,两个传感器节点之间的距离通常要求在几十米到几百米之间。5) 高可靠性无线传感器网络的信息获取是靠分布在监测区域内的各个传感器检测到的,如传感器本身不可靠,则其信息的传输和处理是没有任何意义的。6) 大容量要求网络能容纳上千、上万个节点。7) 动态性对于复杂环境的组网,其覆盖区域往往会遇到各种电、磁环境的干扰,加之供电能量的不断损耗,易引起传感器节点故障,因此要求传感器网络具有自组网、智能化和协同感知等功能。1.3.2 ZigBee技术ZigBee技术是一种结构简单、低功耗、低数据率、低成本和高可靠性的双向微功率网格式无线接入技术,介于RFID和蓝牙之间的技术提案,此前被称作“Hom2eRFLite”或“FireFly”无线技术,主要用于近距离无线连接5。最重要的是ZigBee技术支持地理定位功能,它工作于无需注册的2.4 GHz ISM频段,传输速率为250 kb/s,传输距离可以从标准的75米,到扩展后的几百米,甚至几千米,利用ZigBee技术可由多到65535个无线微功率收发机组成一个庞大而有效的无线网络平台6。其主要特点有:1) 低功耗 由于ZigBee的传输速率低,发射功率仅为1mW,而且采用了休眠模式,功耗低,因此ZigBee设备非常省电。据估算,ZigBee设备仅靠两节5号电池就可以维持长达6个月到2年左右的使用时间,这是其它无线设备望尘莫及的。2) 成本低 ZigBee模块的初始成本在6美元左右,估计很快就能降到1.5-2.5美元,并且ZigBee协议是免专利费的。低成本对于ZigBee也是一个关键的因素。3) 时延短 通信时延和从休眠状态激活的时延都非常短,典型的搜索设备时延30ms,休眠激活的时延是15ms,活动设备信道接入的时延为15ms。因此ZigBee技术适用于对时延要求苛刻的无线控制(如工业控制场合等)应用。4) 网络容量大 一个星型结构的Zigbee网络最多可以容纳254个从设备和一个主设备,一个区域内可以同时存在最多100个ZigBee网络,而且网络组成灵活。5) 可靠 采取了碰撞避免策略,同时为需要固定带宽的通信业务预留了专用时隙,避开了发送数据的竞争和冲突。MAC层采用了完全确认的数据传输模式,每个发送的数据包都必须等待接收方的确认信息。如果传输过程中出现问题可以进行重发7。6) 安全 ZigBee提供了基于循环冗余校验(CRC)的数据包完整性检查功能,支持鉴权和认证, 采用了AES-128的加密算法,各个应用可以灵活确定其安全属性。ZigBee 网络中存在三种逻辑设备类型:协调器、路由器和终端设备。1) 协调器 协调器包含所有的网络消息,是3种设备类型中最复杂的一种,也是该网络的第一个设备。协调器具有存储容量大、计算能力强的特点,其主要任务包括发送网络信标、建立一个网络、管理网络节点、存储网络节点信息、寻找一对节点间的路由消息和不断地接收信息8。2) 路由器 路由器的功能主要有允许其他设备加入网络、多跳路由和协助终端设备的通讯。3) 终端设备 终端设备没有特定的维持网络结构的责任,它可以处于睡眠或者唤醒状态,因此它可以是一个电池供电设备。ZigBee 网络有三种网络拓扑结构:星型、树状和网状拓扑结构。在星型拓扑结构中,整个网络由一个称为ZigBee协调器的设备来控制,ZigBee协调器负责发起和维持网络正常工作,保持同网络终端设备通信;在网状型和树型拓扑结构中,ZigBee 协调器负责启动网络以及选择关键的网络参数,同时,也可以使用ZigBee 路由器来扩展网络结构;在树型网络中,路由器采用分级路由策略来传送数据和控制信息。树型网络可以采用基于信标的方式进行通信;网状型网络中,设备之间使用完全对等的通信方式,ZigBee路由器不发送通信信标。1.3.3 Linux 交叉编译通常,程序是在一台计算机上编译,然后再分布到将要使用的其他计算机上。当主机系统(运行编译器的系统)和目标系统(产生的程序将在其上运行的系统)不兼容时,该过程就叫做交叉编译。除了兼容性这个原因之外,以下两种情况也需要进行交叉编译:(1) 目标系统对其可用的编译工具没有本地设置;(2) 主机系统比目标系统要快得多,或者具有更多的可用资源。系统中使用的是基于ARM架构的Linux平台交叉编译工具arm-linux-gcc,其版本为arm-linux-gcc-4.3.3。需要说明的是不同Linux内核要使用相应的交叉编译器编译生成正确的内核文件。1.3.4 socket 通信Windows Sockets是广泛应用的、开放的、支持多种协议的网络编程接口。其中套接字(Socket)是通信的基石,是支持TCP/IP协议网络通信的基本操作单元,可以将套接字看作是不同主机之间的进程进行双向通信的端点。套接字可以分为两类:流套接字和数据报套接字14。1) 流套接字流套接字提供双向的、有序的、无重复并且无记录边界的数据流服务,它适用于处理大量数据。流套接字是面向连接的,通信双方进行数据交换前必须建立一条路径。这样既确定了它们之间存在的路由,又保证了双方是活动的、可彼此响应的,但在通信双方之间建立一个通信信道需要很多开销。除此之外,大部分面向连接的协议为保证发送无误,可能会需要执行额外的计算来验证正确性,因此还会进一步增加开销。2) 数据报套接字数据报套接字支持双向的数据流,但并不保证数据传输的可靠性、有序性和无重复性。数据报套接字是无连接的,通信双方在进行通信前不需要事先建立连接,也不需要维护通信链路,因此可以节省开销,但不适合大量、有序数据的传输15。考虑到本系统中需要传输的数据只是一些环境信息,其特点是数据量小且变化连续,不易突发。因此系统采用数据报套接字完成数据传输。2 系统总体分析与设计2.1 系统总体分析由于本系统要实现环境监测的功能,而实验室所使用的嵌入式实验箱CVT6410已经集成了监测环境中各项指标(芯片温度、实验板温度、环境温度、环境湿度等)模块。因此,在本次课程设计中已经有了能够实时接收环境信息的功能,还有许多其他的功能也要实现,具体包括将接收到的信息转换为可读的数据,并利用socket通信将数据传到PC端(即服务器端)。此外,在PC机端接收到数据后,要能够将数据显示出来,并将这些数据传入构建的数据库中,以便于备份与查询。2.2 系统总体设计(1)获取环境信息通过集成在嵌入式实验箱CVT6410中的温度传感器、湿度传感器、光照传感器以及烟雾传感器等传感器获取所处环境的相关信息。(2)实时接收环境信息利用Socket网络编程,实时从串口监听并接收远端控制器发来的环境数据。在通信过程中,PC机作为服务器端,实验箱作为客户机端进行通信。(3)将接收到的数据存储到数据库中新建一个数据库用于存储接收到的数据,数据库中可以只有一个表,每一个属性代表一个检测的指标(温度、湿度、光照等)。(4)将环境数据显示出来用C#做一个界面,既可以直接显示从客户机端接收到的数据,也可以像在数据库中一样以表格的形式显示出环境参数。2.3 系统流程图开始服务器与客户机是否处于同一网段?是否可以相互通信?客户机向服务器发送环境数据服务器端接收到数据?数据库连接成功?环境数据存入数据库将数据显示在界面中结束3 开发实现过程3.1 安装VMware虚拟机在Windows下安装虚拟机VMware。VMware (Virtual Machine ware)是一个“虚拟PC”软件公司。它的产品可以使你在一台机器上同时运行二个或更多Windows、DOS、LINUX系统。与“多启动”系统相比,VMware采用了完全不同的概念。多启动系统在一个时刻只能运行一个系统,在系统切换时需要重新启动机器。VMware是真正“同时”运行,多个操作系统在主系统的平台上,就象标准Windows应用程序那样切换。而且每个操作系统你都可以进行虚拟的分区、配置而不影响真实硬盘的数据,你甚至可以通过网卡将几台虚拟机用网卡连接为一个局域网,极其方便。虚拟机装好以后出现如下界面:3.2 安装Fedora操作系统Fedora 是一个知名的发行版,是一款由全球社区爱好者构建的面向日常应用的快速、稳定、强大的。它允许任何人自由地使用、修改和重发布,无论现在还是将来。它由一个强大的社群开发,这个社群的成员以自己的不懈努力,提供并维护自由、的软件和开放的标准。Fedora 项目由 Fedora 基金会管理和控制,得到了 Red Hat, Inc. 的支持。Fedora 是一个独立的操作系统,可运行的体系结构包括 x86(即i386-i686), x86_64 和 PowerPC。Fedora安装过程:Fedora开机过程:Fedora 开机的登录界面:3.3 配置编译环境1.以root身份登录ubuntu2.然后将光盘中的4.3.3.tar.gz拷到桌面上3.解压并安装,具体请执行如下命令:# tar zxvf 4.3.3.tar.gz -C /4.设置环境变量,执行如下命令:# gedit /etc/profile在最后面添加一行export PATH=$PATH:/home/zzx/Desktop/6410/4.3.3/bin如图所示:重新启动Fedora5.测试是否配置成功,打开终端,输入以下命令:# arm-linux-gcc v倒数第二行显示了版本信息,至此,编译器已安装好。3.4 Linux交叉编译环境的配置1.安装交叉编译器2.配置Fedora10的IP地址为192.168.1.12进入root用户:supassword/sbin/ifconfig eth0 192.168.1.12 netmask 255.255.255.0gedit /etc/exports写入:/tftpboot 192.168.1.12/255.255.255.0(rw)保存并退出/sbin/service portmap restart/sbin/service nfs restart/sbin/service iptables stop3.创建tftpboot文件夹cd /mkdir tftpbootchmod 777 /tftpboot4.将6410实验箱和PC机用网线相连,启动实验箱ping 192.168.1.63.5 超级终端1. Windows 7系统安装HyperTerminal,即将HyperTerminal文件夹下的HyperTerminal_English_ANSI 里的hticons.dll和hypertrm.dll拷贝至C:/Windows/System32下;2. 使用usb转串口线(或普通RS232线)将6410实验箱和PC机相连。打开HyperTerminal软件,选择正确的串口。设置完成后点击确认,正确连接至6410实验箱后,多次回车,出现如下结果:3. 挂载tftpboot文件夹中文件至嵌入式Linux的mnt文件夹中。输入如下命令:mount 192.168.1.12:/tftpboot /mnt/ -o nolock挂在成功后,输入cd /mnt查看挂载结果。3.6 socket 通信Linux系统是通过提供套接字(socket)来进行网络编程的。网络程序通过socket和其它几个函数的调用,会返回一个通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的好处。我们可以通过向描述符读写操作实现网络之间的数据交流。1) socketint socket(int domain, int type,int protocol)domain:说明网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等)。AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信。type:网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等)。SOCK_STREAM表明使用的是TCP协议,这样会提供按顺序的、可靠、双向、面向连接的比特流。SOCK_DGRAM表明使用的是UDP协议,这样只会提供定长的、不可靠、无连接的通信。protocol:由于指定了type,所以这个地方一般只要用0来代替就可以了。socket为网络通讯做基本的准备。成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况。2) bindint bind(int sockfd, struct sockaddr *my_addr, int addrlen)sockfd:是由socket调用返回的文件描述符。addrlen:是sockaddr结构的长度。my_addr:是一个指向sockaddr的指针。sockaddr的定义如下:struct sockaddr unisgned short as_family; char sa_data14; ; 不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构(struct sockaddr_in) 来代替。sockaddr_in的定义如下: struct sockaddr_in unsigned short sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero8; sin_family一般为AF_INET,sin_addr设置为INADDR_ANY表示可以和任何的主机通信,sin_port是我们要监听的端口号。sin_zero8是用来填充的。Bind函数将本地的端口同socket返回的文件描述符捆绑在一起。成功则返回0,失败的情况和socket一样。3) listenint listen(int sockfd,int backlog) sockfd:是bind后的文件描述符。backlog:设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。listen函数将bind的文件描述符变为监听套接字。返回的情况和bind一样。4) acceptint accept(int sockfd, struct sockaddr *addr,int *addrlen)sockfd:是listen后的文件描述符. addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了。bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了。失败时返回-1。5) connectint connect(int sockfd, struct sockaddr * serv_addr,int addrlen) sockfd:socket返回的文件描述符。serv_addr:储存了服务器端的连接信息。其中sin_add是服务端的地址。addrlen:serv_addr的长度。connect函数是客户端用来同服务端连接的。成功时返回0,sockfd是同服务端通讯的文件描述符失败时返回-1。到第三阶段,可以存取资料了,要读取资料,我们可以用 recv() 函式: 6) recv:接收数据int recv(int sockfd, void* buf, int maxbuf, int options)sockfd:socket返回的文件描述符。buf:是收到数据后存放的缓冲位置。maxbuf:是缓冲区buf的大小。 recv()会回传收到信息的大小值,如有错误,会回传负数值。7) send:发送数据int send(int sockfd, void *buffer, int msg_len, int options)其中sockfd、buffer和msg_len和recv()的相同,只不过是这次把要传输的信息先放buf。而options有MSG_OOB,MSG_DONTROUTE,MSG_DONTWAIT,MSG_NOSIGNAL,send()会回传传输的总大小值。 查看 socket 文件夹里的文件:对文件夹里的这些文件进行编译:查看Makefile 文件:对于每个程序需要分别编译arm平台的执行文件,同时,为了在PC端进行测试,也编译的PC版本的执行文件,Makefile文件如下所示:CC = arm-linux-gccLD = arm-linux-ldEXEC = client.armOBJS = client.o EXEC1 = server.armOBJS1 = server.oEXEC2 = listener.armOBJS2 = listener.oEXEC3 = talker.armOBJS3 = talker.oCFLAGS +=LDFLAGS += all: $(EXEC) $(EXEC1) $(EXEC2) $(EXEC3)$(EXEC): $(OBJS)$(CC) $(LDFLAGS) -o $ $(OBJS) $(LDLIBS$(LDLIBS_$)cp $(EXEC) /tftpboot/$(EXEC1): $(OBJS1)$(CC) $(LDFLAGS) -o $ $(OBJS1) $(LDLIBS$(LDLIBS_$)cp $(EXEC1) /tftpboot/$(EXEC2): $(OBJS2)$(CC) $(LDFLAGS) -o $ $(OBJS2) $(LDLIBS$(LDLIBS_$)cp $(EXEC2) /tftpboot/$(EXEC3): $(OBJS3)$(CC) $(LDFLAGS) -o $ $(OBJS3) $(LDLIBS$(LDLIBS_$)cp $(EXEC2) /tftpboot/gcc -o client client.cgcc -o server server.cgcc -o listener listener.cgcc -o talker talker.cclean:-rm -f $(EXEC) $(EXEC1) $(EXEC2) $(EXEC3) *.elf *.gdb *.o此时在超级终端中查看一下 /mnt文件夹:3.7 向服务器端发送数据#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/time.h>#include <signal.h>#include <fcntl.h>#include <termios.h>#include <errno.h>#include <ctype.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <netdb.h>#include <stddef.h>#define FALSE0#define TRUE1/#define MYPORT 4950 / the port users will be connecting toint MYPORT;int nread=0;char Recbuff1024;int sRecDataLen = 0;char send_buf256;static int serial_fd;int speed_arr = B, B, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300, B38400, B19200, B9600, B4800, B2400, B1200, B300;int name_arr = , , 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300, 38400, 19200, 9600, 4800, 2400, 1200, 300;/-void set_speed(int fd,int speed)int i;int status;struct termios Opt;tcgetattr(fd, &Opt);/tcsetattr函数用于设置终端的相关参数。参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数。for( i = 0; i < sizeof(speed_arr)/sizeof(int); i+)if(speed = name_arri)tcflush(fd, TCIOFLUSH);/清空终端未完成的输入/输出请求及数据.TCOFLUSH:清除正写入的数据,且不会发送至终端。cfsetispeed(&Opt, speed_arri);cfsetospeed(&Opt, speed_arri);status = tcsetattr(fd, TCSANOW, &Opt);if(status != 0)perror("tcsetattr fd1");/perror( ) 用来将上一个函数发生错误的原因输出到标准设备return;tcflush(fd, TCIOFLUSH);int set_Parity(int fd,int databits,int stopbits,int parity)struct termios options;if(tcgetattr(fd, &options) != 0)perror("SetupSerial 1");return(FALSE);options.c_cflag &=CSIZE;switch(databits)case 7:options.c_cflag |= CS7;break;case 8:options.c_cflag |= CS8;break;default:fprintf(stderr,"Unsupported data sizen");return(FALSE);switch(parity)case 'n':case 'N':options.c_cflag &= PARENB;options.c_iflag &= INPCK;break;case 'o':case 'O':options.c_cflag |= (PARODD | PARENB);options.c_iflag |= INPCK;break;case 'e':case 'E':options.c_cflag |= PARENB;options.c_cflag &= PARODD;options.c_iflag |= INPCK;break;case 'S':case 's':options.c_cflag &= PARENB;options.c_cflag &= CSTOPB;break;defaultfprintf(stderr, "Unsupported parityn");return(FALSE);switch(stopbits)case 1:options.c_cflag &= CSTOPB;break;case 2:options.c_cflag |= CSTOPB;break;default:fprintf(stderr,"Unsupported stop bitsn");return(FALSE);if(parity != 'n'&& parity != 'N')options.c_iflag |= INPCK;options.c_cflag &= CRTSCTS;options.c_iflag &= IXOFF;options.c_iflag &= IXON;if(parity != 'n'&& parity != 'N') options.c_iflag |= INPCK;tcflush(fd, TCIOFLUSH);options.c_lflag &= (ICANON|ECHO|ECHOE|ISIG);options.c_oflag &= OPOST;options.c_iflag &= (ICRNL|IGNCR); options.c_ccVTIME = 0; options.c_ccVMIN = 8;tcflush(fd, TCIFLUSH); fcntl(fd, F_SETFL, 0);if(tcsetattr(fd, TCSANOW, &options) != 0)perror("SetupSerial 3");return(FALSE);return(TRUE);int OpenDev(char *Dev)int fd = open(Dev, O_RDWR);if(-1 = fd)perror("Can't Open Serial Port");return -1;elsereturn fd;int serial_init(void)char *Dev = "/dev/s3c2410_serial1"serial_fd = OpenDev(Dev);if(serial_fd > 0)set_speed(serial_fd,);elseprintf("Can't Open Serial Port!n");exit(0);if(set_Parity(serial_fd,8,1,'N') = FALSE)printf("Set parity Errorn");exit(1);return 0;int main(int argc, char *argv) int sockfd; int numbytes; struct sockaddr_in their_addr; / connector's address information struct hostent *he; serial_init(); /* parameters check */ if (argc != 3) /判断参数值的个数 fprintf(stderr,"usage: talker hostname portn"); exit(1); /* argv1 = server ip address */ if (he=gethostbyname(argv1) = NULL) / get the host info perror("gethostbyname"); exit(1); /* argv2 = server port */MYPORT=atoi(argv2); /* setup socket */ if (sockfd = socket(AF_INET, SOCK_DGRAM, 0) = -1) /socket初始化 perror("socket"); exit(1); char buff1024; their_addr.sin_family = AF_INET; / 表示TCP/IP协议 their_addr.sin_port = htons(MYPORT); / short, network byte order their_addr.sin_addr = *(struct in_addr *)he->h_addr); memset(&(their_addr.sin_zero), '0', 8); / zero the rest of the struct while(1) while(1) nread = read(serial_fd,buff,1024);/读串口文件中的数据,放入buff中,并将长度返回给nreadif(nread >0)printf("n%dn",nread);buffnread = '0'/*int i;for(i=0;i<nread;i+)RecbuffsRecDataLen + i = buffi;sRecDataLen = sRecDataLen + nread;scan(sockfd,their_addr);*/printf("n2530's Temperature=%d Board's Temperature=%d Temperature=%d Humidity=%dn",buff0,buff1,buff2,buff3); if (numbytes=sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&their_addr, sizeof(struct sockaddr) = -1) /sendto 函数