基于ZigBee设计的天气监测系统

2022-07-12 16:41:12 浏览数 (1)

伴随我国经济的高速发展,大气环境污染问题也随之诞生,针对日益严重的大气污染问题,各种监测手段应运而生。经过对现有监测手段进行详细研究后,本文提出一种基于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();
      }
    }
}

0 人点赞