davidce 发表于 2011-12-10 23:19:19

自制基于Arduino移动式城市环境信息采集器

本帖最后由 迷你强 于 2013-12-22 19:45 编辑

制作灵感来源于国外一个叫做 urban feeds 的项目。仪器能够采集:
[*]温度
[*]湿度
[*]CO2浓度
[*]O3浓度
[*]光照强度
[*]人流量
[*]大于1微米空气悬浮粒子
[*]大于2.5微米空气悬浮粒子
[*]日期时间
[*]位置信息
采用SD卡存储数据,锂电池供电,外观如图:
全部配件包括:
[*]密封盒 1个 115×90×55

[*]arduino UNO核心板 1块

[*]SD卡模块1个及SD卡1张(容量不限,但由于FAT16文件分区格式的限制单个分区应小于2G)

[*]SHT10温湿度传感器 1个

[*]BH1750FVI光照传感器 1个

[*]MG811二氧化碳(CO2)传感器(无需底板) 1个

[*]MQ131臭氧(O3)传感器(无需底板) 1个

[*]七星座 2个

[*]DSM501A粉尘传感器 1个

[*]SIRF II GPS模块 1个

[*]HC-SR501 人体红外感应模块 1个

[*]0.5A 单锂电池充电、升压板(保护+充电+升压+充电指示)1块

[*]606168P 聚合物锂电池 3.7V 2800mAH 1块

[*]CA3140 阻抗变换 1个
[*]二极管1N4007 1个
[*]3mm 发光二极管LED 绿(颜色随意) 1个
[*]电容 10nF(104贴片) 1个
[*]电阻 220 1个、10K(104贴片) 1个、22K 1个、10K 2个、1K 1个
[*]双面洞洞板 2×8cm 1块
[*]单排针 间距2.54mm 高11mm和高17mm 若干
[*]彩色杜邦线建议最短20cm 若干
[*]KN3-3 拨动开关(六脚) 1个
[*]M3螺母 若干
[*]6+12 M3铜柱子 若干
[*]M3螺丝 包括10mm和6mm(非必需) 若干
[*]M3空心铜柱(非必需) 若干

电路部分:

[*]SD卡接法请参考:arduino学习笔记18 - SD卡读写实验
[*]SHT10 data脚接D8,clock脚接D9
[*]BH1750FVI 是I2C协议,参见:arduino学习笔记27 - DS1307 RTC时钟芯片与DS18B20数字温度传感器实验中DS1307 与UNO板的接法
[*]MG811二氧化碳传感器阻抗变换电路如图,接A3:

[*]MQ131臭氧传感器辅助电路如图,接A2:

[*]DSM501A粉尘传感器接线如图,接输出1接D7,输出2接D6:

[*]SIRF II GPS模块TX接D0,RX接D1
[*]HC-SR501 人体红外感应模块输出接A1
[*]发光二极管串联220欧姆电阻接在电源地和D2之间
[*]锂电池充电、升压板与锂电池接法按板子说明连接
[*]六角拨动开关一组控制仪器电源开关,一组控制锂电池充电开关,两组控制状态互斥如示意图:

[*]电源供电从锂电池充电、升压板升压输出端+5v经拨动开关仪器电源一组连接到UNO Vin脚;电池充电从UNO +5v输出经拨动开关仪器充电一组接到锂电池充电、升压板充电输入端
[*]从锂电池充电、升压板充电输入端引出线到A0并为A0设置10K下拉电阻
[*]所有传感器(除SD卡、温湿度)的+5v输入接UNO Vin脚,SD卡和温湿度传感器接UNO +5v输出脚;所有传感器的GND和锂电池充电、升压板的充电输入、+5V升压输出的GND接UNO GND脚

仪器组装:

DSM501A粉尘传感器 需垂直安装
仪器内部:


程序代码://davidce 20111211

// Include
#include <SHT1x.h>
#include <SD.h>
#include <Wire.h>

// Digital pin
// SHT1x
#define ShtDataPin8//data
#define ShtClockPin 9//clock
SHT1x sht1x(ShtDataPin, ShtClockPin);
// system light
#define systemLightPin 2
//DSM501A DUST
#define DSMPin2_56
#define DSMPin1_07
//SD card
File myFile;
#define SD_CSPin10
char filename[] = "result.txt";
String order= "";
//GPS
#define rxPin 0                  // RX PIN
#define txPin 1                  // TX TX

// Analog pin
const int powerInPin = A0;    // check power state for the bettery charge
const int infrRayPin = A1;    //infrared ray
const int o3Pin = A2;          //MQ131
const int co2Pin = A3;          //MQ811

// Variable
boolean inCharge = false;      //bettery charging mark
const unsigned long warmTime = 60000;   //system warmming 60000 ms
const unsigned long scanTime = 0;   //get data fre scanTime = scanTime + flashTime
const unsigned long flashTime = 250;
unsigned long previousMillis = 0;      //前一次判断时间点
unsigned long partMillis=0;            //到溢出时计算的时间
const unsigned long sectev = 30;         // 时间间隔(秒)
const unsigned long interval = 30000;         // 时间间隔(毫秒) = sectev * 1000
const unsigned long mintev = 30000000; // 时间间隔(微秒) =interval * 1000
//BH1750 IIC Mode
const int BH1750address = 0x23; //setting i2c address
byte buff;
//GPS
int byteGPS = -1;
char linea = "";
char comandoGPR = "$$$$GPRMC";
int cont=0;
int bien=0;
int conta=0;
int indices;

void setup() {
if(!SD.begin(SD_CSPin))
{
    return;
}
// read the value from the power
int powerValue = analogRead(powerInPin); // variable to store the value coming from the power
if(powerValue>1000)
{
    inCharge=true;
    Serial.begin(4800);//port speed for GPS
}
else
{
    //check if the log file exists and add name of items to the new file
    if (!SD.exists(filename))
    {
      myFile = SD.open(filename, FILE_WRITE);
      if (myFile)
      {
      myFile.print("date_UTC");
      myFile.print(9,BYTE);
      myFile.print("time_UTC");
      myFile.print(9,BYTE);
      myFile.print("lat");
      myFile.print(9,BYTE);
      myFile.print("lon");
      myFile.print(9,BYTE);
      myFile.print("temp_C");
      myFile.print(9,BYTE);
      myFile.print("hum_PER");
      myFile.print(9,BYTE);
      myFile.print("pcs_1");
      myFile.print(9,BYTE);
      myFile.print("pcs_2_5");
      myFile.print(9,BYTE);
      myFile.print("peop_tra");
      myFile.print(9,BYTE);
      myFile.print("O3");
      myFile.print(9,BYTE);
      myFile.print("CO2");
      myFile.print(9,BYTE);
      myFile.println("light_lx");
      myFile.close();
      }
    }
    for (int i=0;i<300;i++)
    {       // Initialize a buffer for received data
      linea=' ';
    }
    pinMode(systemLightPin, OUTPUT);
    pinMode(DSMPin2_5, INPUT);
    pinMode(DSMPin1_0, INPUT);
    pinMode(rxPin, INPUT);
    pinMode(txPin, OUTPUT);
    Wire.begin();
    Serial.begin(4800);//port speed for transform
    digitalWrite(systemLightPin, HIGH);   
    delay(warmTime);
}
}

void loop(){
if(!inCharge)                  // work state
{
    float temp_c =sht1x.readTemperatureC();
    float humidity = sht1x.readHumidity();

    unsigned long currentMillis;
    boolean goloop=true;
    partMillis=0;
    unsigned long duration1_0=0;
    unsigned long duration2_5=0;
    long temp1_0=0;
    long temp2_5=0;
    unsigned long rayMark=0;      //人流量计数
    double rayFreq = 0.0;      //人流量频率

    while(goloop)      //loop
    {
      currentMillis = micros();
      if(currentMillis<previousMillis)
      {
      partMillis = 4294967295 -previousMillis +1;
      previousMillis = 0;
      }
      if(currentMillis - previousMillis - partMillis < mintev)
      {
      //1.0
      if(temp1_0==0)
      {
          temp1_0=-1;
          temp1_0=pulseIn(DSMPin1_0, LOW);
      }
      if(temp1_0>0)
      {
          duration1_0 =duration1_0 + temp1_0;
          temp1_0=0;
      }
      //2.5
      if(temp2_5==0)
      {
          temp2_5=-1;
          temp2_5=pulseIn(DSMPin2_5, LOW);
      }
      if(temp2_5>0)
      {
          duration2_5 =duration2_5 + temp2_5;
          temp2_5=0;
      }
      }
      else
      {
      goloop=false;
      previousMillis=currentMillis;
      }
      //human transform
      int rayState = analogRead(infrRayPin);
      if(rayState>500)
      {
      rayMark = rayMark + 1;
      }
    }
    double per =double(duration1_0)/double(interval);// had multiply 1000
    int pcs1_0 = -1;
    pcs1_0 =per * 50.0;
    per =double(duration2_5)/double(interval);// had multiply 1000
    int pcs2_5 = -1;
    pcs2_5 =per * 50.0;
    rayFreq = double(rayMark) /double(sectev);

    // light
    uint16_t lightval=0;
    BH1750_Init(BH1750address);
    delay(200);
    if(2==BH1750_Read(BH1750address))
    {
      lightval=((buff<<8)|buff)/1.2;
    }

    //MQ131
    int O3v=analogRead(o3Pin);
    float O3ppb=float(O3v) * 0.0049;    //not realy value
    float O3mg_m3 = O3ppb * 48 / 22.4 / 1000;//need ajaust
    //MG811
    int CO2v=analogRead(co2Pin);
    float CO2ppb=float(CO2v) * 0.0049;    //not realy value
   
    //GPS
    String datestr = "";    //date UTC (ddmmyy)
    String timestr = "";    //time UTC (hhmmss.sss)
    String latstr = "";    //Latitude (ddmm.mmmm)
    String lonstr = "";    //Longitude (dddmm.mmmm)
    boolean isGPSOK = false;
    bien=0;
    while(bien!=6)
    {
      byteGPS=Serial.read();
      if(byteGPS == -1)
      {
      delay(100);
      }
      else
      {
      linea=byteGPS;      // If there is serial port data, it is put in the buffer
      conta++;
      if(byteGPS==13)
      {
          cont=0;
          bien=0;
          for (int i=1;i<7;i++)
          {   // Verifies if the received command starts with $$$$GPRMC
            if (linea==comandoGPR)
            {
            bien++;
            }
          }
          if(bien==6)// If yes, continue and process the data
          {
            for (int i=0;i<300;i++)
            {
            if (linea==',')
            {    // check for the position of the"," separator
                indices=i;
                cont++;
            }
            if (linea=='*')
            {    // ... and the "*"
                indices=i;
                cont++;
            }
            }
            String dataString;
            int outindex;
            outindex=1;
            for (int j=indices;j<(indices-1);j++)
            {
            if(linea=='A')
            {
                isGPSOK = true;
            }
            }
            outindex=8;//Date UTC (ddmmyy)
            dataString="";
            for (int j=indices;j<(indices-1);j++)
            {
            dataString = dataString + linea;
            }
            datestr=dataString;
            outindex=0;//time UTC (hhmmss.sss)
            dataString="";
            for (int j=indices;j<(indices-1);j++)
            {
            dataString = dataString + linea;
            }
            timestr=dataString;
            outindex=2;//Latitude (ddmm.mmmm)
            dataString="";
            for (int j=indices;j<(indices-1);j++)
            {
            dataString = dataString + linea;
            }
            latstr=dataString;
            outindex=4;//Longitude (dddmm.mmmm)
            dataString="";
            for (int j=indices;j<(indices-1);j++)
            {
            dataString = dataString + linea;
            }
            lonstr=dataString;
          }
          // Reset the buffer
          conta=0;                  
          for (int i=0;i<300;i++)
          {   
            linea=' ';            
          }
      }
      }
    }

    //output result
    Serial.print(datestr);
    Serial.print(9,BYTE);
    Serial.print(timestr);
    Serial.print(9,BYTE);
    Serial.print(latstr);
    Serial.print(9,BYTE);
    Serial.print(lonstr);
    Serial.print(9,BYTE);
    Serial.print(temp_c);
    Serial.print(9,BYTE);
    Serial.print(humidity);
    Serial.print(9,BYTE);
    Serial.print(pcs1_0);
    Serial.print(9,BYTE);
    Serial.print(pcs2_5);
    Serial.print(9,BYTE);
    Serial.print(rayFreq);
    Serial.print(9,BYTE);
    Serial.print(O3ppb);
    Serial.print(9,BYTE);
    Serial.print(CO2ppb);
    Serial.print(9,BYTE);
    Serial.println(lightval,DEC);

    //writer the result to SD card
    myFile = SD.open(filename, FILE_WRITE);
    if (myFile)
    {
      // to sd file
      myFile.print(datestr);
      myFile.print(9,BYTE);
      myFile.print(timestr);
      myFile.print(9,BYTE);
      myFile.print(latstr);
      myFile.print(9,BYTE);
      myFile.print(lonstr);
      myFile.print(9,BYTE);
      myFile.print(temp_c);
      myFile.print(9,BYTE);
      myFile.print(humidity);
      myFile.print(9,BYTE);
      myFile.print(pcs1_0);
      myFile.print(9,BYTE);
      myFile.print(pcs2_5);
      myFile.print(9,BYTE);
      myFile.print(rayFreq);
      myFile.print(9,BYTE);
      myFile.print(O3ppb);
      myFile.print(9,BYTE);
      myFile.print(CO2ppb);
      myFile.print(9,BYTE);
      myFile.println(lightval,DEC);
      myFile.close();

      //flash the light
      if(isGPSOK)
      {
      digitalWrite(systemLightPin, HIGH);
      delay(flashTime);
      digitalWrite(systemLightPin, LOW);
      }
      else
      {
      digitalWrite(systemLightPin, LOW);
      delay(flashTime);
      digitalWrite(systemLightPin, HIGH);
      }
    }
    delay(scanTime);
}
else               //bettery charging and data translation
{
    while(Serial.available() > 0)
    {
      int incomingByte = Serial.read();
      if(incomingByte==10)    //order end
      {
      if(order == "list")
      {
          myFile = SD.open(filename);
          if (myFile)
          {
            while (myFile.available())
            {
            Serial.write(myFile.read());
            }
            myFile.close();
          }
          else
          {
            Serial.println("open file failure.");
          }
      }
      else if(order.length()>0)
      {
          Serial.println("The available command is:");
          Serial.println("list");
      }
      //reset order
      order="";
      }
      else
      {
      if(incomingByte!=13)
      {
          order = order + char(incomingByte);
      }
      }
    }
}
}

int BH1750_Read(int address)
{
int i=0;
Wire.beginTransmission(address);
Wire.requestFrom(address, 2);
while(Wire.available())
{
    buff = Wire.receive();// receive one byte
    i++;
}
Wire.endTransmission();
return i;
}
void BH1750_Init(int address)
{
Wire.beginTransmission(address);
Wire.send(0x10);//1lx reolution 120ms
Wire.endTransmission();
}
代码说明:
[*]传感器需要预热时间,在变量warmTime 中设置,这里设置为60秒
[*]仪器根据inCharge 变量判断系统处于采集状态或充电状态,在采集状态时如插上usb可通过arduino IDE的串口监视实现数据显示;仪器通过arduino的usb口充电,在充电的同时可通过arduino IDE的串口监视实现交互,如输入list命令可列出SD卡存储的数据
[*]DSM501A灰尘传感器有两路输出分别对应不同的灰尘粒径检出量,检测到灰尘是在输出口产生时间不等的低脉冲,通过计算30秒内的低脉冲率和对于关系得到灰尘粒子量(请参考DSM501A datasheet),程序中通过micros()函数提供计时,micros()每70分钟左右会归零,通过 4294967295 -previousMillis +1 得出归零前的计数加到归零后的计数上实现连续计数
[*]程序通过pulseIn()返回低脉冲时间,虽然pulseIn()函数可以设置超时(默认1秒)但并不产生中断,代码中通过轮询pulseIn()的返回变量的值判读pulseIn()是否完成读取
[*]人体红外线模块的脉冲电平是3.3v,通过模拟读取时设置读数大于500为有输出
[*]当GPS模块可靠定位时系统状态灯常灭,采集数据时亮250ms;GPS不可靠定位时系统状态灯常亮,采集数据时灭250ms,GPS上集成电池,定位可靠与非都有日期时间输出(UTC时间,中国时区 +8小时)
[*]BH1750光照传感器在太阳光直接照射的情况下有可能爆表(输出为0),建议避免强光直射

关于传感器标定:
每个传感器在出厂后的性能是不同的,在实际的应用中需要进行标定。仪器中需要标定的传感器是MG811二氧化碳、MQ131臭氧和DSM501A灰尘传感器,其它的传感器是数字输出,在出厂前经过标定。标定方法参考:

[*]MG811二氧化碳标定可参考:http://www.instructables.com/id/Environmental-Mushroom-Control-Arduino-Powered/step12/C02-Calibration/ 中的方法,另外二氧化碳是温室气体,特性稳定,所以在全球很长时间都是均匀分布在同一水平,全球二氧化碳实时浓度见:http://co2now.org/,可以参考此网站上的值作为标定浓度之一,同时呼吁大家节能减排,减少温室气体排放
[*]MQ131臭氧传感器标定需要在标准条件50ppb浓度臭氧下进行,请参考datasheet中说明
[*]DSM501A灰尘传感器标定也可参考datasheet中的说明,需要注意的是DSM501A灰尘传感器测量粒子浓度与PM2.5、PM10和TSP(PM100)的检测原理和概念不同,不可混淆,只可做参考
在仪器中以上传感器均未做标定,MQ131、MG811的输出值为对应的电压值

数据采集与展示:
固定采集:

[*]温湿度曲线:

[*]灰尘粒子曲线:

[*]气体浓度曲线:


移动采集:

[*]采集路线:

[*]温度分布:

[*]湿度分布:

[*]CO2分布:

[*]1微米以上粒子:


图中红色代表高值,蓝色代表低值。
下一步工作:
MQ系列传感器辅助电路类似,仪器只需更换传感器探头和标定可实现其它气体测量
由于MQ131和MG811属于加热型传感器耗电量大,2800mAh的锂电池只能连续工作3.5小时左右,同时二氧化碳和臭氧气体不适合移动观测,下一步打算将两种气体传感器设置在固定监测仪器上,在留出的面板位置上安装1602液晶显示,仪器同时实现便携式GPS功能。
致谢:
仪器在制作过程中得到极客工坊论坛和Arduino 与 ADK(1277738)QQ群热心网友的大力帮助,他们早出晚归,谈天论地,不分主题,有问必答,畅所欲言,谢谢大家!!

完。

Ansifa 发表于 2011-12-11 14:04:31

不错不错!不过全部插的啊。。。不动烙铁小心掉线了。。

jet 发表于 2011-12-11 14:21:13

期待下文,准备自动动手弄一个了:D

davidce 发表于 2011-12-11 17:31:18

Ansifa 发表于 2011-12-11 14:04 static/image/common/back.gif
不错不错!不过全部插的啊。。。不动烙铁小心掉线了。。

头大的问题,所以我现在再也不想打开那个盒子了

弘毅 发表于 2011-12-12 12:37:49

{:soso_e102:}那个不同浓度的分布图好帅

mikeliujia 发表于 2011-12-15 21:57:51

屌爆了!我也想整一套

马丁 发表于 2011-12-17 02:36:01

是上次在美院见过的那个大卫么?
非常贴近现实的咚咚!
有没有兴趣将它整合在一个自动空气净化装置上呢?
这样更加符合处于痛苦之中的国人家庭的实际需求了。

davidce 发表于 2011-12-18 17:54:16

马丁 发表于 2011-12-17 02:36 static/image/common/back.gif
是上次在美院见过的那个大卫么?
非常贴近现实的咚咚!
有没有兴趣将它整合在一个自动空气净化装置上呢? ...

不是美院的

mjgzdnw 发表于 2011-12-20 09:14:20

强人,佩服!!!

534659123 发表于 2012-1-17 23:03:20

我看到了我从淘宝挖出来的空气颗粒物传感器、、、、、那玩意好使么?不过这一套成本相当了得啊。。。

幻生幻灭 发表于 2012-1-18 13:01:21

他们早出晚归,谈天论地,不分主题,有问必答,畅所欲言,..........不分主题

普创通享 发表于 2012-2-21 00:16:22

我和学生正在研究做一个,能实现web发布采集的数据吗!

davidce 发表于 2012-2-21 12:15:17

普创通享 发表于 2012-2-21 00:16 static/image/common/back.gif
我和学生正在研究做一个,能实现web发布采集的数据吗!

Arduino学习笔记:2560+W5100试验实时室温对Pachubbe.com推送可以参考

普创通享 发表于 2012-2-21 14:57:37

davidce 发表于 2012-2-21 12:15 static/image/common/back.gif
Arduino学习笔记:2560+W5100试验实时室温对Pachubbe.com推送可以参考

谢谢您的热心帮助!

tocmattw 发表于 2012-3-1 21:07:40

寫得好啊~~不知道整個成本是多少啊?:D:D
页: [1] 2 3 4 5 6 7 8 9
查看完整版本: 自制基于Arduino移动式城市环境信息采集器