DIY 空气质量检测表

前几天逛淘宝看到有空气颗粒物浓度测量的传感器,直接是 3.3V TTL 电压串口输出的,也不贵,也就 100 多一点。觉得挺好就买了个,这两天自己捣鼓了个小程序,搞了个软件界面的空气质量检测表。程序写的很简单,但是感觉这个小软件还是挺实用的,所以就写篇博客,大家用我的代码很容易就自己 DIY 一套。

硬件准备

传感器用的是 攀藤科技 PMS7003M 。除了攀藤科技,还有几家这种传感器做的应该也不错,不过我没去用过,也没仔细调研。(之所以用的这家,不过是因为在 newsmth 上看到一个帖子,有人这么用了。)这个传感器买了之后其实我就后悔了,因为我发现还有个 pm2.5、甲醛、温湿度三合一传感器 PMS5003ST。这几个功能合一起就差不多可以做完整的空气质量监控了。

下面是 PMS7003M 的照片。

这款传感器的接口非常的小,联线很费劲,所以我还买了个专用测试转接板。下面是照片。



有了这两个之后还要一根 USB 转 TTL 的转接线。淘宝上很多,随便选一根就行,成本十几块钱吧。需要注意的是USB 转 TTL 转接线上用的芯片,据说用 FT232 的质量最好,也最贵。我买时缺货,只买到了几根使用 PL2303 芯片的。到是也没用出什么毛病。

当然还要连4根线,5V、GND、TXD、RXD。需要注意的是传感器的TXD要接到转接线的RXD上,传感器的RXD要接到转接线的TXD上.

代码

传感器上电后默认状态为主动输出,即传感器主动向主机发送串行数据,时间间隔为200——800ms,空气中颗粒物浓度越高,时间间隔越短。主动输出又分为两种模式:平稳模式和快速模式。在空气中颗粒物浓度变化较小时,传感器输出为平稳模式,即每三次输出同样的一组数值,实际数据更新周期约为2s。当空气中颗粒物浓度变化较大时,传感器输出自动切换为快速模式,每次输出都是新的数值,实际数据更新周期为200——800ms。

PMS7003M 默认波特率:9600bps 校验位:无 停止位:1位

协议总长度:32字节

字节 数据
起始符1 0x42
起始符2 0x4D
帧长度高八位 帧长度=2x13+2(数据+校验位)
帧长度低八位
数据1高八位 数据1表示PM1.0浓度(CF=1,标准颗粒物)单位μg/m3
数据1低八位
数据2高八位 数据2表示PM2.5浓度(CF=1,标准颗粒物)单位μg/m3
数据2低八位
数据3高八位 数据3表示PM10浓度(CF=1,标准颗粒物)单位μg/m3
数据3低八位
数据4高八位 数据4表示PM1.0浓度(大气环境下)单位μg/m3
数据4低八位
数据5高八位 数据5表示PM2.5浓度(大气环境下)单位μg/m3
数据5低八位
数据6高八位 数据6表示PM10浓度 (大气环境下)单位μg/m3
数据6低八位
数据7高八位 数据7表示0.1升空气中直径在0.3um以上颗粒物个数
数据7低八位
数据8高八位 数据8表示0.1升空气中直径在0.5um以上颗粒物个数
数据8低八位
数据9高八位 数据9表示0.1升空气中直径在1.0um以上颗粒物个数
数据9低八位
数据10高八位 数据10表示0.1升空气中直径在2.5um以上颗粒物个数
数据10低八位
数据11高八位 数据11表示0.1升空气中直径在5.0um以上颗粒物个数
数据11低八位
数据12高八位 数据12表示0.1升空气中直径在10um以上颗粒物个数
数据12低八位
数据13高八位 版本号
数据13低八位 错误代码
数据和校验高八位 校验码=起始符1+起始符2+……..+数据13低八位
数据和校验低八位

具体的代码其实就是个串口通讯加上数据解析。需要注意的是这个传感器传回来的数据是大端的。也就是对于一个 16bit 的数据是先传高 8 位的,然后再传低 8 位。所以收到的数据需要颠倒一下。

我 Qt 用的比较熟,所以下面的代码都是 Qt (C++) 的。传感器相关的代码都封装到一个类里,类名叫 PMS7003M。下面是头文件。

#ifndef PMS7003M_H
#define PMS7003M_H
#include <QVector>
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
class PMS7003M : public QObject
{
    Q_OBJECT
public:
    explicit PMS7003M(QObject *parent = 0);
    void set(unsigned char index,unsigned char input);
    QList<QString> getPortsList();

    void open(QString com);
    void close();
    QVector<int> getPM() const;
    QVector<int> getCount() const;
private:
    QSerialPort m_port;
    QVector<unsigned char> m_data;
    int m_pm1_factory;
    int m_pm25_factory;
    int m_pm10_factory;
    int m_pm1_outdoor;
    int m_pm25_outdoor;
    int m_pm10_outdoor;

    int m_count03;
    int m_count05;
    int m_count1;
    int m_count25;
    int m_count5;
    int m_count10;

    unsigned char m_lenHighByte;
    unsigned char m_lenLowByte;
    unsigned short m_length;
    unsigned short m_version;
    unsigned short m_errorno;
    enum {STATE_0X42, STATE_0X4D, STATE_FRAME_LEN_H, STATE_FRAME_LEN_L, STATE_DATA, STATE_CHECKSUM} m_state;
    void stateMachine(unsigned char x);
    bool checksum();
    void updateValue();
    int m_dataCount;
signals:
    void dataReady();
private slots:
    void readCom();

};

#endif // PMS7003M_H

之后是 cpp 文件。

#include "pms7003m.h"
#include <QDebug>
#include <QMessageBox>
PMS7003M::PMS7003M(QObject *parent) : QObject(parent)
{
    m_state = STATE_0X42;
    m_data.resize(30);
    getPortsList();
}

void PMS7003M::close()
{
    m_port.close();
}

void PMS7003M::open(QString COM)
{
    m_port.setPortName(COM);
    qDebug() << "PortName:"<<m_port.portName() ;
    if(m_port.open(QIODevice::ReadWrite))
    {
        qDebug() << "m_port.open:" ;
        m_port.setBaudRate(9600);
        m_port.setParity(QSerialPort::NoParity);
        m_port.setDataBits(QSerialPort::Data8);
        m_port.setStopBits(QSerialPort::OneStop);
        m_port.setFlowControl(QSerialPort::NoFlowControl);

        m_port.clearError();
        m_port.clear();
        connect(&m_port, SIGNAL(readyRead()), this, SLOT(readCom()));
    }
}

void PMS7003M::stateMachine(unsigned char x)
{
    switch(m_state)
    {
    case STATE_0X42:
        if(x == 0X42)
        {
            m_state = STATE_0X4D;
        }
        break;
    case STATE_0X4D:
        if(x == 0x4d)
        {
            m_state = STATE_FRAME_LEN_H;
        }
        else
        {
            m_state = STATE_0X42;
        }
        break;
    case STATE_FRAME_LEN_H:
        m_lenHighByte = x;
        m_state = STATE_FRAME_LEN_L;
        break;
    case STATE_FRAME_LEN_L:
        m_lenLowByte = x;
        m_length = (m_lenHighByte << 8) + m_lenLowByte;
        if(m_length == 28)
        {
            m_dataCount = 0;
            m_state = STATE_DATA;
        }
        else
        {
            m_state = STATE_0X42;
        }
        break;
    case STATE_DATA:
        m_data[m_dataCount] = x;
        m_dataCount++;
        if(m_dataCount == 28)
        {
            if(checksum())
            {
                updateValue();
                emit dataReady();
            }
            else
            {
                qDebug() << "checksum failed";
            }
            m_state = STATE_0X42;
        }
    }
}

QVector<int> PMS7003M::getPM() const
{
    QVector<int> pm;
    pm << m_pm1_factory;
    pm << m_pm25_factory;
    pm << m_pm10_factory;
    pm << m_pm1_outdoor;
    pm << m_pm25_outdoor;
    pm << m_pm10_outdoor;

    return pm;
}

QVector<int> PMS7003M::getCount() const
{
    QVector<int> count;
    count << m_count03;
    count << m_count05;
    count << m_count1;
    count << m_count25;
    count << m_count5;
    count << m_count10;
    return count;
}

void PMS7003M::updateValue()
{
    m_pm1_factory = (m_data[0] << 8) + m_data[1];
    m_pm25_factory = (m_data[2] << 8) + m_data[3];
    m_pm10_factory = (m_data[4] << 8) + m_data[5];
    m_pm1_outdoor = (m_data[6] << 8) + m_data[7];
    m_pm25_outdoor = (m_data[8] << 8) + m_data[9];
    m_pm10_outdoor = (m_data[10] << 8) + m_data[11];

    m_count03 = (m_data[12] << 8) + m_data[13];
    m_count05 = (m_data[14] << 8) + m_data[15];
    m_count1 = (m_data[16] << 8) + m_data[17];
    m_count25 = (m_data[18] << 8) + m_data[19];
    m_count5 = (m_data[20] << 8) + m_data[21];
    m_count10 = (m_data[22] << 8) + m_data[23];

    m_version = m_data[24];
    m_errorno = m_data[25];
}

bool PMS7003M::checksum()
{
    unsigned short sum = 0x42 + 0x4D + m_lenHighByte + m_lenLowByte;
    for(int i = 0; i < 26; i++)
    {
        sum += m_data[i];
    }
    if(sum == (m_data[26] << 8) + m_data[27])
    {
        return true;
    }
    return false;
}

QList<QString> PMS7003M::getPortsList()
{
    QList<QString>ports;
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        ports.append(info.portName());
    }
    qDebug()<<"ports:"<<ports;
    return ports;
}

void PMS7003M::readCom()
{
    QByteArray data = m_port.readAll();
    for (int i = 0; i < data.size(); ++i)
    {
        stateMachine(data.at(i));
    }
    //qDebug() << "x";
}

界面代码很简单,就不贴了。下面给几个我的软件界面截图。





DIY 空气质量检测表的更多相关文章

  1. scrapy下载中间件结合selenium抓取全国空气质量检测数据

    1.所需知识补充 1.下载中间件常用函数 process_request(self, request, spider): 当每个request通过下载中间件是,该方法被调用 process_reque ...

  2. Arduino 各种模块篇 粉尘传感器 dust sensor 空气质量检测

    Testing a sensor from here. http://www.seeedstudio.com/wiki/Grove_-_Dust_Sensor It's a dust sensor. ...

  3. 洛谷 P2251 质量检测(st表)

    P2251 质量检测 题目提供者ws_ly 标签 难度 普及/提高- 题目描述 为了检测生产流水线上总共N件产品的质量,我们首先给每一件产品打一个分数A表示其品质,然后统计前M件产品中质量最差的产品的 ...

  4. P2251 质量检测(ST表)

    P2251 质量检测 题目描述 为了检测生产流水线上总共N件产品的质量,我们首先给每一件产品打一个分数A表示其品质,然后统计前M件产品中质量最差的产品的分值Q[m] = min{A1, A2, ... ...

  5. 智能家居DIY-空气质量检测篇-获取温度和湿度篇

    目录 智能家居DIY-空气质量检测篇-获取空气污染指数 前言 话说楼主终于升级当爸了,宝宝现在5个月了,宝宝出生的时候是冬天,正是魔都空气污染严重的时候,当时就想搞个自动开启空气净化器,由于种种原因一 ...

  6. 智能家居DIY-空气质量检测篇-获取空气污染指数

    前言 话说楼主终于升级当爸了,宝宝现在5个月了,宝宝出生的时候是冬天,正是魔都空气污染严重的时候,当时就想搞个自动开启空气净化器,由于种种原因一直没有时间搞,最近终于闲下来了这个事情终于提上议程了,现 ...

  7. 如何做好SQLite 使用质量检测,让事故消灭在摇篮里

    本文由云+社区发表 SQLite 在移动端开发中广泛使用,其使用质量直接影响到产品的体验. 常见的 SQLite 质量监控一般都是依赖上线后反馈的机制,比如耗时监控或者用户反馈.这种方式问题是: 事后 ...

  8. OneNET麒麟座应用开发之十:空气质量数据监测站项目总结

    大气质量数据监测站用于测试空气质量监测及数据采集,实现野外或者室内空气质量的检测. 1.项目概述 本项目是一个定制项目,要求采集大气的压力.温度.湿度.PM25.位置等数据并上传到指定的后台服务器.但 ...

  9. 代码质量检测-Sonar

    一. Sonar简介 sonarqube系统是一个代码质量检测工具 由以下四个组件组成(https://docs.sonarqube.org/display/SONAR/Architecture+an ...

随机推荐

  1. JavaScript与WebAssembly进行比较

    本文由云+社区发表 作者:QQ音乐前端团队 在识别和描述核心元素的过程中,我们分享了构建SessionStack时使用的一些经验法则,这是一个轻量级但健壮且高性能的JavaScript应用程序,以帮助 ...

  2. Spring Boot(十一)Redis集成从Docker安装到分布式Session共享

    一.简介 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API,Redis也是技术领域使用最为广泛的存储中间件,它是 ...

  3. Perl一行式:文本编解码、替换

    perl一行式程序系列文章:Perl一行式 文本大小写转换 全部字符转换成大写或小写,有几种方式: # 转大写 $ perl -nle 'print uc' file.log $ perl -ple ...

  4. C#/VB.NET 操作Word批注(二)——如何插入图片、读取、回复Word批注内容

    序 在前面的文章C# 如何插入.修改.删除Word批注一文中介绍了如何操作Word批注的一些方法,在本篇文章中继续介绍操作Word批注的方法.分以下三种情况来介绍: 1. 插入图片到Word批注 2. ...

  5. input type date 解决移动端显示placeholder

    在最近的一个项目中使用到了html5的一个新标签属性,type="date"时,发现placeholder属性失效无法使用. 如果是这样的效果,那么客户体验是可想而知的差了. 最后 ...

  6. https处理的一个过程,对称加密和非对称加密

    一,对称加密 所谓对称加密,就是它们在编码时使用的密钥e和解码时一样d(e=d),我们就将其统称为密钥k. 对称加解密的过程如下: 发送端和接收端首先要共享相同的密钥k(即通信前双方都需要知道对应的密 ...

  7. 关系型数据库中主键(primary key)和外键(foreign key)的概念。

    刚接触关系型数据库的同学,会听过主键和外键的概念.这是关系型数据库的基本概念,需要清楚理解.今天我就以简洁的语言总结一下这个概念. 主键.一句话概括:一张表中,可以用于唯一标识一条记录的字段组(或者说 ...

  8. Dynamics 365 Customer Engagement V9 活动源功能报错的解决方法

    微软动态CRM专家罗勇 ,回复300或者20190120可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 安装好Dynamic ...

  9. 使用python操作XML增删改查

    使用python操作XML增删改查 什么是XML? XML 指可扩展标记语言(EXtensible Markup Language) XML 是一种标记语言,很类似 HTML XML 的设计宗旨是传输 ...

  10. iOS----------网络请求错误

    Error Domain=com.alamofire.error.serialization.response Code=-1016 "Request failed: unacceptabl ...