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";
}
界面代码很简单,就不贴了。下面给几个我的软件界面截图。