基于ZigBee的无线数据采集系统方案

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

.专业整理.
无线数据采集控制系统的设计课程名称专业综合课程设计
课程设计总评成绩
学生XX、学号
学生专业班级
指导教师XX
课程设计起止日期2016.11.7-2016.12.1
无线数据采集控制系统的设计
第1章需求分析
1.1 课程设计题目
无线电子开关
1.2 课程设计任务与要求
无线电子开关的设计
PC端用java编写程序通过串口给CC2530模块A发送开关等指令,CC2530模块A通过射频模块将指令以无线方式发送给CC2530模块B,CC2530模块B根据指令开灯或关灯。

针对给定的任务,结合专业课程和专业知识完成系统的硬件或软件设计,对硬件设计:要求完成系统和接口设计,并能动手制作和调试,对测量结果进行分析处理。

设计须提供实物成果。

对软件设计:要求能够熟悉软件工具,设计其算法或者是系统结构,实现该算法和软件,能够对其计算过程进行推导或者是说明软件系统结构,并能调试成功,对实验结果进行分析处理。

通过专业综合的课程设计,使学生能够综合掌握无线传感网技术、java语言程序设计、数据库等课程的专业知识, 要求学生经过课程设计的教学环节进一步理解无线传感器网络的结构和组成原理,掌握数据采集节点,无线传输,串口通信等模块的基本设计方法,完成系统应用程序的设计。

通过专业综合课程设计,提高学生电子信息系统综合设计能力。

掌握电子信息系统的基本开发过程与应用方法。

要求学生经过课程设计的教学环节进一步理解电子信息系统的设计方法,根据所选择的对象进行应用系统的硬件和软件设计,提高学生专业的综合素质与专业能力。

1.3 软硬件运行环境与开发工具
软件开发环境为eclipse+IAR Embedded Workbench
硬件开发环境为CC2530
1.4主要芯片说明
CC2530是ZigBee无线数据传输其中的一个核心芯片,它能够以非常低的总材料成本建立强的网络节点。

CC2530芯片有四种不同的闪存版本:分别具32/64/128/256KB的闪存。

CC2530芯片工作实具有不同的运行模式,使得它尤其适应超低功耗要求的系统。

运行模式之间的转换时间短进一步确保了低能源消耗。

在业界内,CC2530结合了XX仪器的业界领先的黄金单元ZigBee协议栈,提供了一个强大和完整的ZigBee解决方案。

CC2530芯片共包含了40个引脚,引脚的排布如图1所示:
图1:cc2530引脚图
CC2530芯片模块大致可以分为三类:CPU和内存相关的模块;外设、时钟和电源管理相关的模块以与无线电相关的模块。

〔1〕CPU和内存:CC253x芯片系列中使用的8051CPU内核是一个单周期的8051兼容内核。

〔2〕调试接口:执行一个专有的两线串行接口,用于内电路调试。

〔3〕I/O控制器:负责所有通用I/O引脚。

〔4〕五通道DMA控制器:系统可以使用一个多功能的五通道DMA控制器,使用XDATA存储空间访问存储器,因此能够访问所有物理存储器。

〔5〕定时器1:是一个16位定时器,具有定时器PWM功能。

〔6〕内置MAC定时器:是专门为支持IEEE802.15.4,MAC或软件中其他时槽的协议设计。

〔7〕定时器3和定时器4:是8位定时器,具有定时器/计数器/PWM功能。

〔8〕睡眠定时器:是一个超低功耗的定时器,计算32kHz晶振或32kHzRC振荡器的周期。

〔9〕看门狗:一个内置的看门狗,允许CC2530在固件挂起的情况下复位自身。

第2章系统总体设计
2.1 系统组成方案
由eclipse编写上位机程序,TAR编写下位机程序,通过无线方式发送命令。

如图2
图2 :设计方案
2.2系统工作原理
上位机程序以轮询方式通过PC串口向ZigBee节点A发送命令light:1011,节点A通过无线方式向节点B转发命令light:1011,节点B接收到命令后点亮蓝灯,黄灯和绿灯〔1点亮,0熄灭〕。

上位机程序以轮询方式通过PC串口向ZigBee节点A发送命令light:1011,节点A通过无
线方式向节点C转发命令light:1011,节点C接收到命令后点亮蓝灯,红灯和绿灯〔1点亮,0熄灭〕。

2.3系统构建
PC端用java编写程序通过串口给CC2530模块A发送开关等指令,CC2530模块A通过射频模块将指令以无线方式发送给CC2530模块B和模块C,CC2530模块B和模块C根据指令开灯或关灯。

根据指令〔light1或light2〕第5位判断后缀为1或者为2,节点B和节点C分别依据指令开灯或者关灯
第3章系统硬件设计
3.1 主模块电路设计
本系统采用TI公司生产的CC2530为核心器件。

CC2530是一个真正的片上系统〔SoC〕解决方案,它能够以非常低的材料成本建立强大的网络节点,它结合了领先的RF收发器的优良性能、业界标准的增强型8051CPU、系统内可编程闪存、8KBRAM、A/D转换器以与许多其他强大的功能,并且其具有不同的运行模式,使得它尤其适应超低功耗要求的系统。

围绕着CC2530芯片,系统大致可分为三大模块:CPU和内存相关的模块,外设、时钟和电源管理相关的模块以与无线电相关的模块。

3.2 显示模块电路设计
设计通过A发送的指令来控制4个LED灯的亮灭,LED模块电路与灯的引脚图,如图3、图4所示。

图3:LED模块电路
图4:LED灯对应引脚
若要点亮LED灯,CC2530的通用IO口需要配置三个寄存器:P1SEL,P1DIR,P1INP,P2INP 功能选择寄存器PxSEL,其中x为端口标号0~2,用来设置端口的每个引脚为通用I/O或外部设备I/O。

默认为通用I/O。

方向寄存器PxDIR,其中x为端口标号0~2,用来设置端口的每个引脚为输入或输出。

默认为输入。

输入模式寄存器P1INP,用来设置P1端口用作输入时为上拉、下拉模式或三态模式。

默认为上、下拉模式。

具体是上拉还是下拉,由P2INP来设置。

输入模式寄存器P2INP,用来设置P0、P1、P2端口用作输入时为上拉、下拉模式。

默认为上拉模式。

完整配置:
•P1SEL&=~0x03;//P1_0、P1_1通用IO
•P1DIR|=0x03;// P1_0、P1_1输出
•P1INP&=~0x03;// P1_0、P1_1上下拉
•P2INP&=~0x40;// P1上拉
简化配置:
•P1DIR|=0x03;// P1_0、P1_1输出
3.3 通信模块电路设计
CC2530 是符合802.15.4 标准的无线收发芯片,但是本设计并没有遵守802.15.4 协议规则,在发送过程中忽略了网络ID、源地址和目标地址等参数,在接收的过程中禁止了帧过滤。

通过发送和接收过程的处理使得CC2530 无线部分的使用尽可能的简单清晰,通过最少的代码说明问题。

无线芯片的调试具有一定的难度,一般存在发送设备和接收设备。

为了通过最简单的代码说明无线芯片的使用,只编写一个设备的代码同时实现发送和接收功能。

设备代码的功能也相对简单,CC2530 从串口接收数据并把数据通过RF 部分原分不动地发送出去,于此同时CC2530 把从RF 部分接收的数据原分不动的通过串口发送出去,通过这样的方式实现无线串口。

发送的数据编号以与控制指令,来控制灯的亮度和开关。

串口数据属于"流"型数据包,RF 部分属于"帧"型数据包。

在串口数据处理与分析中,一般采用特定的串口头和长度的方式解析数据,但是本文采用通过串口时间间隔的方式解析数据,这种方法等同于modbus-RTU 串口数据处理的方法。

通过这种检测字节数据时间间隔的方法使得CC2530 的串口部分可以接收无特殊格式要求的数据,真正实现无线串口功能。

第4章系统软件设计
4.1上位机程序设计
上位机程序通过两个类来实现,第一个SerialPort类用来实现控制程序面板,第二个DSerialPort类来实现串口通信。

图5所示为Java的两个类:
图5:Java的两个类
SerialPort类中,通过网格布局管理器来实现面板的布局,设置各个标签以与文本框,复选框,组合框,窗口的大小位置以与标题等。

4个灯的设置大致相同,通过jcheckbox来表示灯选中以与未选中时的设置。

jlabel1=new JLabel<"全关1:">;
jcheckbox1=new JCheckBox<"绿">;
jcheckbox2=new JCheckBox<"红">;
jcheckbox3=new JCheckBox<"黄">;
jcheckbox4=new JCheckBox<"蓝">;
jlabel2=new JLabel<"全关2:">;
jcheckbox5=new JCheckBox<"绿">;
jcheckbox6=new JCheckBox<"红">;
jcheckbox7=new JCheckBox<"黄">;
jcheckbox8=new JCheckBox<"蓝">;
//灯一的设置
jcheckpanel1=new JPanel<>;
gridbagconstraints.anchor=GridBagConstraints.EAST;
gridbagconstraints.gridwidth=1;
gridbaglayout.setConstraints<jlabel1,gridbagconstraints>;
add<jlabel1>;
gridbagconstraints.anchor=GridBagConstraints.WEST;
gridbagconstraints.gridwidth=GridBagConstraints.REMAINDER;
gridbaglayout.setConstraints<jcheckpanel1, gridbagconstraints>;
add<jcheckpanel1>;
jcheckpanel1.add<jcheckbox4>;
jcheckpanel1.add<jcheckbox3>;
jcheckpanel1.add<jcheckbox2>;
jcheckpanel1.add<jcheckbox1>;
//灯一开关的设置
jcheckbox1.addActionListener<new ActionListener<>{
publicvoid actionPerformed<ActionEvent e>{
if<getLightState1<>.equals<"0000">>
jlabel1.setText<"全关1:">;
elseif<getLightState1<>.equals<"1111">>
jlabel1.setText<"全开1:">;
else
jlabel1.setText<"开灯1:">;
}
}>;
通过switch语句,串口打开后轮流发送Data的参数以与设置参数异常时的命令:void startRun<>{
Timer timer = new Timer<>;
TimerTask task =new TimerTask<>{
int i=0;
publicvoid run<>{
if<com_open>{//串口打开则发送
try{
switch<i>{
case 0:{sp.write<lightcommand1+getLightState1<>>;break;}
case 1:{sp.write<lightcommand2+getLightState2<>>;break;}
}
if<i==2>{
i=0;
}
else
i++;
}
catch<Exception e>{
System.out.println<"发送异常">;
}
}
}
};
timer.schedule<task,1000,200>;//在200毫秒后执行此任务,每次间隔2秒执行一次,如果传递一个Data参数,就可以在某个固定的时间执行这个任务.
}
串口通信DSerialport类中,通过listport方法,列出了所有可用的串口,并设置返回值类型为void:
publicvoid listPort<JComboBox jcombox1>{
CommPortIdentifier cpid;
Enumeration en = CommPortIdentifier.getPortIdentifiers<>;
System.out.println<"now to list all Port of this PC:" +en>;
while<en.hasMoreElements<>>{
cpid = <CommPortIdentifier>en.nextElement<>;
if<cpid.getPortType<> == CommPortIdentifier.PORT_SERIAL>{
jcombox1.addItem<cpid.getName<>>;
System.out.println<cpid.getName<> + ", " + cpid.getCurrentOwner<>>;
}
}
}
通过selectport方法,选择一个端口,设置返回值类型void:
publicvoid selectPort<String portName,int rate>{
this mPort = null;
CommPortIdentifier cpid;
Enumeration en = CommPortIdentifier.getPortIdentifiers<>;
while<en.hasMoreElements<>>{
cpid = <CommPortIdentifier>en.nextElement<>;
if<cpid.getPortType<> == CommPortIdentifier.PORT_SERIAL
&& cpid.getName<>.equals<portName>>{
this mPort = cpid;
break;
}
}
openPort<rate>;
}
通过openPort方法,打开SerialPort,设置返回值类型void:
privatevoid openPort<int rate>{
if<commPort == null>
System.out.println<String.format<"无法找到名字为'%1$s'的串口!", commPort.getName<>>>;
else{
System.out.println<"端口选择成功,当前端口:"+commPort.getName<>+",现在实例化SerialPort:">;
try{
serialPort = <SerialPort>commPort.open<appName, timeout>;
System.out.println<"实例SerialPort 成功!">;
}catch<PortInUseException e>{
thrownew RuntimeException<String.format<"端口'%1$s'正在使用中!",
commPort.getName<>>>;
}
try {
serialPort.setSerialPortParams<rate,8,1,0>;//设置波特率等参数
}
catch <UnsupportedCommOperationException e> {
// TODO Auto-generated catch block
e.printStackTrace<>;
}
}
}
通过checkport方法检查端口是否正确连接:
privatevoid checkPort<>{
if<commPort == null>
thrownew RuntimeException<"没有选择端口,请使用" +
"selectPort<String portName> 方法选择端口">;
if<serialPort == null>{
thrownew RuntimeException<"SerialPort 对象无效!">;
}
}
通过write方法向端口发送数据,在调用此方法前需要先选择端口并确定serialport正常打开。

publicvoid write<String message> {
checkPort<>;
try{
outputStream = serialPort.getOutputStream<>;
}catch<IOException e>{
thrownew RuntimeException<"获取端口的OutputStream出错:"+e.getMessage<>>;
}
try{
outputStream.write<message.getBytes<>>;
System.out.println<"信息发送成功!">;
}catch<IOException e>{
thrownew RuntimeException<"向端口发送信息时出错:"+e.getMessage<>>;
}finally{
try{
outputStream.close<>;
}catch<Exception e>{
}
}
}
通过startread开始监听从端口中接受的数据:
publicvoid startRead<int time>{
checkPort<>;
try{
inputStream = serialPort.getInputStream<>;
}catch<IOException e>{
thrownew RuntimeException<"获取端口的InputStream出错:"+e.getMessage<>>;
}
try{
serialPort.addEventListener<this>;
}catch<TooManyListenersException e>{
thrownew RuntimeException<e.getMessage<>>;
}
serialPort.notifyOnDataAvailable<true>;
System.out.println<String.format<"开始监听来自'%1$s'的数据--------------", commPort.getName<>>>;
if<time > 0>{
this.threadTime = time*1000;
Thread t = new Thread<this>;
t.start<>;
System.out.println<String.format<"监听程序将在%1$d秒后关闭。

", threadTime>>;
}
}
使用close方法关闭serialport:
publicvoid close<>{
serialPort.close<>;
serialPort = null;
commPort = null;
}
/**
* 数据接收的监听处理函数
*/
publicvoid serialEvent<SerialPortEvent arg0> {
byte[] readBuffer = newbyte[20];
int numBytes=0;
String readStr="";
s2 = "";
try {
while<inputStream.available<>>0> {
try {
Thread.sleep<100>;//休眠100ms
} catch <InterruptedException e> {
e.printStackTrace<>;
}
numBytes = inputStream.read<readBuffer>; //从串口上读取数据上的流}
for<int i=0;i<numBytes;i++>{
readStr=readStr +<char>readBuffer[i];
}
s2=readStr;
System.out.println<"接收的数据:"+readStr>;
}
catch <IOException e> {
e.printStackTrace<>;
}
}
/**
* 读取接收的数据
*/
public String readData<>{
return s2;
}
publicvoid run<> {
try{
Thread.sleep<threadTime>;
serialPort.close<>;
System.out.println<String.format<"端口''监听关闭了!",
commPort.getName<>>>;
}catch<Exception e>{
e.printStackTrace<>;
}
}
}
4.2下位机程序设计
发送过程大致可分为侦听SFD 清除信道,关闭接收中断,填充缓冲区,启动发送并等待发送完成,最后恢复接收中断。

在这几个过程中唯一需要说明的便是填充缓冲区过程,在初始化过程中提到FRMCTRL0 寄存器,该寄存器中AUTO_CRC 标志位默认为使能状态,CC2530 的物理层负载部分第一个字节为长度域,填充实际负载之前需要先填充长度域,而物理层负载在原长度的基础上增加2。

长度域数值增加2 的原因是由于自动CRC 的存在,CRC 部分占两个字节CC2530 会把这两个字节填充至发送缓冲区。

void rf_send< char *pbuf , int len>
{
RFST = 0xE3; // RF接收使能ISRXON
// 等待发送状态不活跃并且没有接收到SFD
while< FSMSTAT1 & << 1<<1 > | < 1<<5 >>>;
RFIRQM0 &= ~<1<<6>; // 禁止接收数据包中断
IEN2 &= ~<1<<0>; // 清除RF全局中断
RFST = 0xEE; // 清除发送缓冲区ISFLUSHTX
RFIRQF1 = ~<1<<1>; // 清除发送完成标志
// 填充缓冲区填充过程需要增加2字节,CRC校验自动填充
RFD = len + 2;
for <int i = 0; i < len; i++>
{
RFD = *pbuf++;
}
RFST = 0xE9; // 发送数据包ISTXON
while <!<RFIRQF1 & <1<<1>>>; // 等待发送完成
RFIRQF1 = ~<1<<1>; // 清除发送完成标志位
RFIRQM0 |= <1<<6>; // RX接收中断
IEN2 |= <1<<0>;
}
和发送部分略有不同,接收部分可以分为接收中断部分和接收数据帧处理部分。

无线接收部分可以分为两块内容,一块是无线接收中断处理,一块是无线数据帧处理。

在前者中仅需查询标志位即可,RFIRQF0 的第6 位为完整数据包接收中断标志,若CC2530 接收到一个完整的无线数据包,该标志位便会置位。

由于CC2530 存在多种RF中断类型,例如接收到一个完整的帧,帧通过过滤等,那么在进入中断服务函数之后可以通过查询标志位的方法进入相应的处理任务,接收过程中便是采用的这种方式。

进入数据包处理函数之后,首先读取接收缓冲区的第一个字节,第一个字为数据包长度,在这里需要减去2。

长度域减去2 的原理和发送过程相似,最后两个字节原位CRC校验,但是在CC2530 处理过程中填充了更有用的信息,例如RSSI 结果,而CRC 校验只返回结果而不返回数值,CRC 校验的结果只占用一位。

如果CRC 校验成功,那么就依次读取接收缓冲区字节数据,通过串口发送这些字节数据,并附加一个RSSI 结果且RSSI 被中括号包围。

如果CRC 校验失败则通过串口打印CRC Error。

在程序刚开始调试的过程中,原先认为CRC 校验结果是个"花架子",但是实际中却发现当CC2530 处于接收状态时,会不时的收到数据。

这些数据杂乱无章,唯一的特征便是CRC 校验结果错误。

通过CRC 校验结果可以有效的剔除数据,保证系统的可靠性。

void rf_receive_isr<>
{
int rf_rx_len = 0;
int rssi = 0;
char crc_ok = 0;
rf_rx_len = RFD - 2; // 长度去除两字节附加结果
rf_rx_len &= 0x7F;
for <int i = 0; i < rf_rx_len; i++>
{
rf_rx_buf[i] = RFD; // 连续读取接收缓冲区内容}
rssi = RFD - 73; // 读取RSSI结果
crc_ok = RFD; // 读取CRC校验结果BIT7
RFST = 0xED; // 清除接收缓冲区
if< crc_ok & 0x80 >
{
uart0_sendbuf<rf_rx_buf , rf_rx_len>; // 串口发送
//printf<"[%d]",rssi>; //发送强度数据
}
else
{
printf<"\r\nCRC Error\r\n">;
}
}
串口部分的内容和RF部分无关,串口部分的代码包括定时器T1 和UART 两部分,UART 中断中往接收缓冲区中填充数据并重新启动定时器,在定时器中断中指示串口数据接收完毕,改变一个软件标志位is_serial_receive。

void uart0_sendbuf<char *pbuf , int len>
{
for< int i = 0 ; i < len ; i++>
{
while<!UTX0IF>;
UTX0IF = 0;
U0DBUF = *pbuf;
pbuf++;
}
}
#pragma vector=URX0_VECTOR
__interrupt void UART0_ISR<void>
{
URX0IF = 0; // 清除接收中断标志
serial_rxbuf[serial_rxpos] = U0DBUF; // 填充缓冲区
serial_rxpos++;
serial_rxlen++;
timer1_enable<>; // 定时器重新开始计数
}
#pragma vector=T1_VECTOR
__interrupt void Timer1_ISR<void>
{
T1STAT &= ~< 1<< 0>; // 清除定时器T1通道0中断标志is_serial_receive = 1; // 串口数据到达
timer1_disbale<>;
}
模块B和模块C的程序,只需要在串口透传的程序中添加if语句判断接收的指令后缀为1还是为2即可。

因为Java中设置的判断为"xllighjt1:,xllight2:",所以只要判断第0位为‘x’和第7位为1还是为2就可以分别点亮不同模块的灯。

if<<rf_rx_buf[0] == 'x'>&&<rf_rx_buf[7] == '1'>>
//〔if<<rf_rx_buf[0] == 'x'>&&<rf_rx_buf[7] == '2'>>〕
{
if<rf_rx_buf[12] == '1'>
{
LED1=1;
}
else{
LED1=0;
}
if<rf_rx_buf[11] == '1'>
{
LED2=1;
}
else{
LED2=0;
}
if<rf_rx_buf[10] == '1'>
{
LED3=1;
}
else{
LED3=0;
}
if<rf_rx_buf[9] == '1'>
{
LED4=1;
}
else{
LED4=0;
}
}
}
}
第5章实验结果与分析
将程序分别下到3块CC2530实验板上,在上位机中点击并发送命令,下位机对应的节点接收到命令后点亮相应的灯。

实验结果如图6,7,8所示。

图6:点亮黄灯图
图7:点亮红,蓝灯图
图8:点亮模块1图
第6章总结与展望
上文详细介绍了基于Zigbee的无线电子开关的设计过程,设计中将系统分为上位机和下位机两部分。

通过CC2530芯片搭建无线传感器网络,将命令发送到节点处。

控制部分通过eclipse 编写。

本设计是以当下较为流行的Zigbee无线通信技术为基础的,Zigbee技术具有近距离,低复杂度,低功耗,低速率,低成本等优点,因而成本和功耗方面较好,可以广泛用来远程控制灯,具有实际用途,应用前景十分广泛。

当然,这个设计仍然存在一些不足,需要改进和提高,这些在以后的学习生活中都会进一步的探究和实现。

然而,ZigBee技术的应用前景是十分明朗的,成本和功耗方面的优势使其在市场中十分具有竞争力。

尤其在物联网技术已成为当下热点命题之一的时候,ZigBee技术的应用价值就更为重要了,可以想见,伴随着物联网技术的成长,ZigBee技术也将日趋成熟。

课程设计评分表。

相关文档
最新文档