伴随我国经济的高速发展,大气环境污染问题也随之诞生,针对日益严重的大气污染问题,各种监测手段应运而生。经过对现有监测手段进行详细研究后,本文提出一种基于ZigBee技术的空气质量监测系统。本系统利用ZigBee技术进行组网,使用协调器通过串口向数据管理上位机传递数据,提供监测方法。
基于ZigBee设计的天气监测系统
一、上位机运行效果
软件打开后,会显示默认数据,接上CC2530单片机后,数据会实时刷新。
第二个页面显示日志数据,也就是串口收到的原始数据,直接将CC2530传上来的数据实时显示出来。
二、上位机设计思路
上位机采用Qt5设计,Qt5是一套基于C 语言的跨平台软件库,性能非常强大,目前桌面端很多主流的软件都是采用QT开发。比如: 金山办公旗下的-WPS,字节跳动旗下的-剪映,暴雪娱乐公司旗下-多款游戏登录器等等。Qt在车联网领域用的也非常多,比如,哈佛,特斯拉,比亚迪等等很多车的中控屏整个系统都是采用Qt设计。
上位机通过串口与CC2530单片机进行通信,上位机的波特率固定为115200。与上位机通信时,CC2530的串口波特率需要设置为115200. 通信传递的数据格式: #17.6,28,78,90,123
其中#号固定,表示开始传递数据,随后的数据分别是:温度,湿度,空气质量(PM2.5),气压值,雨滴传感器 的数据值。 单片机上采集好数据之后,按照上面的格式组合成字符串发送出来即可,上位机收到数据即可实时显示出来。
三、安装编译环境、完成代码设计
如果需要自己编译运行源代码,需要先安装Qt5开发环境。 下载地址: https://download.qt.io/official_releases/qt/5.12/5.12.6/
下载之后,先将电脑网络断掉(不然会提示要求输入QT的账号),然后双击安装包进行安装。 安装可以只需要选择一个MinGW 32位编译器就够用了,详情看下面截图,选择“MinGW 7.3.0 32-bit”后,就点击下一步,然后等待安装完成即可。
软件安装好之后,将源代码解压放在英文目录下,然后双击“EnvironmentDisplay.pro”打开工程。
第一次打开工程需要选择编译器,选择“MinGW 7.3.0 32-bit”即可。 然后点击左下角的绿色三角形,编译运行工程就可以了。
主要的代码如下: 如果不想自己建立工程,需要整个工程的代码和资料也可去这里下载: https://download.csdn.net/download/xiaolong1126626497/85892653
代码语言:javascript复制#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//刷新串口 设备
on_pushButton_flush_uart_clicked();
/*4. 设置串口默认的配置*/
UART_Config =new QSerialPort; //新建串口对象
UART_Config->setBaudRate(115200); //默认波特率
UART_Config->setDataBits(QSerialPort::Data8); //数据位
UART_Config->setParity(QSerialPort::NoParity);//奇偶校验
UART_Config->setStopBits(QSerialPort::OneStop);//停止位
UART_Config->setFlowControl(QSerialPort::NoFlowControl); //流控开关
//关联串口读信号
connect(UART_Config, SIGNAL(readyRead()),this, SLOT(ReadUasrtData()));
//显示时间
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(time_update()));
timer->start(1000);
}
Widget::~Widget()
{
delete ui;
}
//实时时间显示
void Widget::time_update()
{
//拼接名称
QDateTime dateTime(QDateTime::currentDateTime());
//时间效果: 2020-03-05 16:25::04 周一
QString qStr;
qStr=dateTime.toString(" yyyy/MM/dd hh:mm:ss ddd");
this->setWindowTitle("天气检测-数据展示上位机-" qStr);
}
//刷新串口
void Widget::on_pushButton_flush_uart_clicked()
{
QList<QSerialPortInfo> UartInfoList=QSerialPortInfo::availablePorts(); //获取可用串口端口信息
ui->comboBox_ComSelect->clear();
if(UartInfoList.count()>0)
{
for(int i=0;i<UartInfoList.count();i )
{
if(UartInfoList.at(i).isBusy()) //如果当前串口 COM 口忙就返回真,否则返回假
{
QString info=UartInfoList.at(i).portName();
info ="(占用)";
ui->comboBox_ComSelect->addItem(info); //添加新的条目选项
}
else
{
ui->comboBox_ComSelect->addItem(UartInfoList.at(i).portName()); //添加新的条目选项
}
}
}
else
{
ui->comboBox_ComSelect->addItem("无可用COM口"); //添加新的条目选项
}
}
//打开串口
void Widget::on_pushButton_OpenUart_clicked()
{
if(ui->pushButton_OpenUart->text()=="打开串口") //打开串口
{
ui->pushButton_OpenUart->setText("断开连接");
/*配置串口的信息*/
UART_Config->setPortName(ui->comboBox_ComSelect->currentText()); //COM的名称
if(!(UART_Config->open(QIODevice::ReadWrite))) //打开的属性权限
{
QMessageBox::warning(this, tr("状态提示"),
tr("串口打开失败!n请选择正确的COM口"),
QMessageBox::Ok);
ui->pushButton_OpenUart->setText("打开串口");
return;
}
}
else //关闭串口
{
ui->pushButton_OpenUart->setText("打开串口");
/*关闭串口-*/
UART_Config->clear(QSerialPort::AllDirections);
UART_Config->close();
}
}
//温度,湿度,空气质量(PM2.5),气压值,雨滴传感器
//串口传递的数据格式:
//#17.6,28,78,90,123
//#18.5,58,68,80,456
//读信号
void Widget::ReadUasrtData()
{
/*返回可读的字节数*/
if(UART_Config->bytesAvailable()<=0)
{
return;
}
/*定义字节数组*/
QString rx_data;
/*读取串口缓冲区所有的数据*/
rx_data=UART_Config->readAll();
if(rx_data.at(0)=='#')
{
rx_data=rx_data.remove('#');
//提取单片机串口上传的数据
temperature=rx_data.section(',',0,0);
humidity=rx_data.section(',',1,1);
quality=rx_data.section(',',2,2);
pressure=rx_data.section(',',3,3);
rain=rx_data.section(',',4,4);
//设置显示的数据
ui->label_temperature->setText(temperature "℃");
ui->label_humidity->setText(humidity);
ui->label_quality->setText(quality);
ui->label_pressure->setText(pressure);
ui->label_rain->setText(rain);
}
Log_Text_Display(rx_data "n");
qDebug()<<rx_data;
}
//清空日志
void Widget::on_pushButton_clicked()
{
ui->plainTextEdit->clear();
}
/*日志显示*/
void Widget::Log_Text_Display(QString text)
{
QPlainTextEdit *plainTextEdit_log=ui->plainTextEdit;
//设置只读
if(!plainTextEdit_log->isReadOnly())
{
plainTextEdit_log->setReadOnly(true);
}
//设置光标到文本末尾
plainTextEdit_log->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
//当文本数量超出一定范围就清除
if(plainTextEdit_log->toPlainText().size()>1024*4)
{
plainTextEdit_log->clear();
}
plainTextEdit_log->insertPlainText(text);
//移动滚动条到底部
QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar();
if(scrollbar)
{
scrollbar->setSliderPosition(scrollbar->maximum());
}
}
//窗口关闭事件
void Widget::closeEvent(QCloseEvent *event)
{
int ret = QMessageBox::question(this, tr("提示"),
tr("是否需要退出程序?"),QMessageBox::Yes | QMessageBox::No);
if(ret==QMessageBox::Yes)
{
UART_Config->close();
event->accept();
timer->stop();
}
else
{
event->ignore();
}
/*
其中accept就是让这个关闭事件通过并顺利关闭窗口,
ignore就是将其忽略回到窗口本身。这里可千万得注意在每一种可能性下都对event进行处理,
以免遗漏。
*/
}
void Widget::paintEvent(QPaintEvent *p1)
{
//绘制样式
QPainter p(this);
//绘制样式
QStyleOption opt;
opt.initFrom(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);//绘制样式
}
四、CC2530代码
4.1 32MHZ频率下配置串口0实现数据发送
代码语言:javascript复制#include <ioCC2530.h>
#include <string.h>
//定义LED灯的端口
#define LED1 P1_2
#define LED2 P1_3
//定义KEY按键的端口
#define KEY1 P1_0 //定义按键为P1_0口控制
#define KEY2 P1_1 //定义按键为P1_1口控制
/*
函数功能:LED灯IO口初始化
硬件连接:LED1-->P1_2 , LED2-->P1_3
*/
void LED_Init(void)
{
P1DIR |=0x3<<2; //配置P1_2、P1_3为输出模式
LED1 = 1;
LED2 = 1;
}
/*
函数功能:按键IO口初始化
硬件连接:KEY1-->P1_0 KEY2-->P1_1
*/
void KEY_Init(void)
{
P1SEL&=~(0x3<<0); //配置P1_0,P1_1处于通用GPIO口模式
P1DIR&=~(0x3<<0); //配置P1_0,P1_1为输入模式
P1INP|= 0x3<<0; //上拉
}
void delay10ms(void) //误差 0us
{
unsigned char a,b,c;
for(c=193;c>0;c--)
for(b=118;b>0;b--)
for(a=2;a>0;a--);
}
/*
函数功能:按键扫描
返 回 值:按下的按键值
*/
unsigned char Key_Scan(void)
{
static unsigned char stat=1;
if((KEY1==0||KEY2==0)&&stat)
{
stat=0;
delay10ms();
if(KEY1==0)return 1;
if(KEY2==0)return 2;
}
else
{
if(KEY1&&KEY2)stat=1;
}
return 0;
}
/*
函数功能:串口0初始化
*/
void Init_Uart0(void)
{
PERCFG&=~(1<<0); //串口0的引脚映射到位置1,即P0_2和P0_3
P0SEL|=0x3<<2; //将P0_2和P0_3端口设置成外设功能
U0BAUD = 216; //32MHz的系统时钟产生115200BPS的波特率
U0GCR&=~(0x1F<<0);//清空波特率指数
U0GCR|=11<<0; //32MHz的系统时钟产生115200BPS的波特率
U0UCR |= 0x80; //禁止流控,8位数据,清除缓冲器
U0CSR |= 0x3<<6; //选择UART模式,使能接收器
}
/*
函数功能:UART0发送字符串函数
*/
void UR0SendString(char *str,unsigned int len)
{
int j;
for(j=0;j<len;j )
{
U0DBUF = *str ; //将要发送的1字节数据写入U0DBUF
while(UTX0IF == 0);//等待数据发送完成
UTX0IF = 0; //清除发送完成标志,准备下一次发送
}
}
/******************************************
* 函数描述:32M系统时钟下的毫秒延时函数
******************************************/
void Delay_ms(unsigned int ms)
{
unsigned int i,j;
for(i = 0; i < ms; i )
{
for(j = 0;j < 1774; j );
}
}
/*主函数*/
void main(void)
{
char buff[]="-----万邦易嵌嵌入式开发-----rn";
unsigned char key;
CLKCONCMD &= ~0x40; //设置系统时钟源为32MHz晶振
for(; CLKCONSTA & 0x40;); //等待晶振稳定
CLKCONCMD &= ~0X47; //设置系统主时钟频率为32MHz
LED_Init();//初始化LED灯控制IO口
KEY_Init();//按键初始化
Init_Uart0(); //初始化串口0
while(1)
{
key=Key_Scan();
if(key)
{
//先发送一个字符串,测试串口0数据传输是否正确
UR0SendString(buff,strlen(buff));
LED2 = !LED2;
}
}
}
4.2 16MHZ频率下配置串口实现中断接收数据
代码语言:javascript复制#include <ioCC2530.h>
#include <string.h>
//定义LED灯的端口
#define LED1 P1_2
#define LED2 P1_3
//定义KEY按键的端口
#define KEY1 P1_0 //定义按键为P1_0口控制
#define KEY2 P1_1 //定义按键为P1_1口控制
unsigned char dataRecv;
unsigned char Flag = 0;
/*
函数功能:LED灯IO口初始化
硬件连接:LED1-->P1_2 , LED2-->P1_3
*/
void LED_Init(void)
{
P1DIR |=0x3<<2; //配置P1_2、P1_3为输出模式
LED1 = 1;
LED2 = 1;
}
/*
函数功能:按键IO口初始化
硬件连接:KEY1-->P1_0 KEY2-->P1_1
*/
void KEY_Init(void)
{
P1SEL&=~(0x3<<0); //配置P1_0,P1_1处于通用GPIO口模式
P1DIR&=~(0x3<<0); //配置P1_0,P1_1为输入模式
P1INP|= 0x3<<0; //上拉
}
void delay10ms(void) //误差 0us
{
unsigned char a,b,c;
for(c=193;c>0;c--)
for(b=118;b>0;b--)
for(a=2;a>0;a--);
}
/*
函数功能:按键扫描
返 回 值:按下的按键值
*/
unsigned char Key_Scan(void)
{
static unsigned char stat=1;
if((KEY1==0||KEY2==0)&&stat)
{
stat=0;
delay10ms();
if(KEY1==0)return 1;
if(KEY2==0)return 2;
}
else
{
if(KEY1&&KEY2)stat=1;
}
return 0;
}
/*
函数功能:串口0初始化
*/
void Init_Uart0(void)
{
PERCFG&=~(1<<0); //串口0的引脚映射到位置1,即P0_2和P0_3
P0SEL|=0x3<<2; //将P0_2和P0_3端口设置成外设功能
U0BAUD = 216; //16MHz的系统时钟产生115200BPS的波特率
U0GCR&=~(0x1F<<0);//清空波特率指数
U0GCR|=12<<0; //16MHz的系统时钟产生115200BPS的波特率
U0UCR |= 0x80; //禁止流控,8位数据,清除缓冲器
U0CSR |= 0x3<<6; //选择UART模式,使能接收器
UTX0IF = 0; //清除TX发送中断标志
URX0IF = 0; //清除RX接收中断标志
URX0IE = 1; //使能URAT0的接收中断
EA = 1; //使能总中断
}
/*
函数功能:UART0发送字符串函数
*/
void UR0SendString(char *str,unsigned int len)
{
int j;
for(j=0;j<len;j )
{
U0DBUF = *str ; //将要发送的1字节数据写入U0DBUF
while(UTX0IF == 0);//等待数据发送完成
UTX0IF = 0; //清除发送完成标志,准备下一次发送
}
}
/*================UR0接收中断服务函数================*/
#pragma vector = URX0_VECTOR
__interrupt void UART0_RecvInterrupt()
{
URX0IF = 0; //清除RX接收中断标志
dataRecv = U0DBUF; //将数据从接收缓冲区读出
Flag = 1; //设置接收指令标志
}
/*================执行上位机的指令=================*/
void ExecuteTheOrder()
{
Flag = 0 ; //清除接收指令标志
switch(dataRecv)
{
case 'A':
UR0SendString("选择1!rn",9);
break;
case 'B':
UR0SendString("选择2!rn",9);
break;
case 'C':
UR0SendString("选择3!rn",9);
break;
case 'D':
UR0SendString("选择4!rn",9);
break;
}
}
/*主函数*/
void main(void)
{
char buff[]="-----嵌入式开发-----rn";
unsigned char key;
LED_Init();//初始化LED灯控制IO口
KEY_Init();//按键初始化
Init_Uart0(); //初始化串口0
while(1)
{
key=Key_Scan();
if(key)
{
//先发送一个字符串,测试串口0数据传输是否正确
UR0SendString(buff,strlen(buff));
LED2 = !LED2;
}
if(Flag == 1) //查询是否收到上位机指令
{
ExecuteTheOrder();
}
}
}