2012-7-27 13:47:42 [显示全部楼层]
6879浏览
查看: 6879|回复: 0

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

[复制链接]
制作灵感来源于国外一个叫做 urban feeds 的项目。仪器能够采集:
  • 温度
  • 湿度
  • CO2浓度
  • O3浓度
  • 光照强度
  • 人流量
  • 大于1微米空气悬浮粒子
  • 大于2.5微米空气悬浮粒子
  • 日期时间
  • 位置信息

自制基于Arduino移动式城市环境信息采集器图1
采用SD卡存储数据,锂电池供电,外观如图:
自制基于Arduino移动式城市环境信息采集器图2

全部配件包括:

  • 密封盒 1个 115×90×55
    自制基于Arduino移动式城市环境信息采集器图3
  • Arduino UNO核心板 1块
    自制基于Arduino移动式城市环境信息采集器图4自制基于Arduino移动式城市环境信息采集器图5自制基于Arduino移动式城市环境信息采集器图6
  • SD卡模块1个及SD卡1张(容量不限,但由于FAT16文件分区格式的限制单个分区应小于2G)
    自制基于Arduino移动式城市环境信息采集器图7


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


    自制基于Arduino移动式城市环境信息采集器图9
  • SHT10温湿度传感器 1个
    自制基于Arduino移动式城市环境信息采集器图10
  • BH1750FVI光照传感器 1个
    自制基于Arduino移动式城市环境信息采集器图11
  • 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:
    自制基于Arduino移动式城市环境信息采集器图12


    自制基于Arduino移动式城市环境信息采集器图13
  • MQ131臭氧传感器辅助电路如图,接A2:
    自制基于Arduino移动式城市环境信息采集器图14
  • DSM501A粉尘传感器接线如图,接输出1接D7,输出2接D6:
    自制基于Arduino移动式城市环境信息采集器图15
  • 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脚

仪器组装:

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


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

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


DSM501A粉尘传感器 需垂直安装
仪器内部:
自制基于Arduino移动式城市环境信息采集器图19
自制基于Arduino移动式城市环境信息采集器图20




程序代码:
ARDUINO CODE
  1. //davidce 20111211
  2. // Include
  3. #include <SHT1x.h>
  4. #include <SD.h>
  5. #include <Wire.h>
  6. // Digital pin
  7. // SHT1x
  8. #define ShtDataPin  8  //data
  9. #define ShtClockPin 9  //clock
  10. SHT1x sht1x(ShtDataPin, ShtClockPin);
  11. // system light
  12. #define systemLightPin 2
  13. //DSM501A DUST
  14. #define DSMPin2_5  6
  15. #define DSMPin1_0  7
  16. //SD card
  17. File myFile;
  18. #define SD_CSPin  10
  19. char filename[] = "result.txt";
  20. String order= "";
  21. //GPS
  22. #define rxPin 0                    // RX PIN
  23. #define txPin 1                    // TX TX
  24. // Analog pin
  25. const int powerInPin = A0;    // check power state for the bettery charge
  26. const int infrRayPin = A1;    //infrared ray
  27. const int o3Pin = A2;          //MQ131
  28. const int co2Pin = A3;          //MQ811
  29. // Variable
  30. boolean inCharge = false;      //bettery charging mark
  31. const unsigned long warmTime = 60000;     //system warmming 60000 ms
  32. const unsigned long scanTime = 0;     //get data fre scanTime = scanTime + flashTime
  33. const unsigned long flashTime = 250;  
  34. unsigned long previousMillis = 0;        //前一次判断时间点
  35. unsigned long partMillis=0;              //到溢出时计算的时间
  36. const unsigned long sectev = 30;           // 时间间隔(秒)
  37. const unsigned long interval = 30000;           // 时间间隔(毫秒) = sectev * 1000
  38. const unsigned long mintev = 30000000; // 时间间隔(微秒) =interval * 1000
  39. //BH1750 IIC Mode
  40. const int BH1750address = 0x23; //setting i2c address
  41. byte buff[2];
  42. //GPS
  43. int byteGPS = -1;
  44. char linea[300] = "";
  45. char comandoGPR[7] = "$GPRMC";
  46. int cont=0;
  47. int bien=0;
  48. int conta=0;
  49. int indices[13];
  50. void setup() {
  51.   if(!SD.begin(SD_CSPin))
  52.   {
  53.     return;
  54.   }
  55.   // read the value from the power
  56.   int powerValue = analogRead(powerInPin); // variable to store the value coming from the power
  57.   if(powerValue>1000)
  58.   {
  59.     inCharge=true;
  60.     Serial.begin(4800);  //port speed for GPS
  61.   }
  62.   else
  63.   {
  64.     //check if the log file exists and add name of items to the new file
  65.     if (!SD.exists(filename))
  66.     {
  67.       myFile = SD.open(filename, FILE_WRITE);
  68.       if (myFile)
  69.       {
  70.         myFile.print("date_UTC");
  71.         myFile.print(9,BYTE);
  72.         myFile.print("time_UTC");
  73.         myFile.print(9,BYTE);
  74.         myFile.print("lat");
  75.         myFile.print(9,BYTE);
  76.         myFile.print("lon");
  77.         myFile.print(9,BYTE);
  78.         myFile.print("temp_C");
  79.         myFile.print(9,BYTE);
  80.         myFile.print("hum_PER");
  81.         myFile.print(9,BYTE);
  82.         myFile.print("pcs_1");
  83.         myFile.print(9,BYTE);
  84.         myFile.print("pcs_2_5");
  85.         myFile.print(9,BYTE);
  86.         myFile.print("peop_tra");
  87.         myFile.print(9,BYTE);
  88.         myFile.print("O3");
  89.         myFile.print(9,BYTE);
  90.         myFile.print("CO2");
  91.         myFile.print(9,BYTE);
  92.         myFile.println("light_lx");
  93.         myFile.close();
  94.       }
  95.     }
  96.     for (int i=0;i<300;i++)
  97.     {       // Initialize a buffer for received data
  98.       linea<i>=' ';
  99.     }
  100.     pinMode(systemLightPin, OUTPUT);  
  101.     pinMode(DSMPin2_5, INPUT);
  102.     pinMode(DSMPin1_0, INPUT);
  103.     pinMode(rxPin, INPUT);
  104.     pinMode(txPin, OUTPUT);
  105.     Wire.begin();
  106.     Serial.begin(4800);  //port speed for transform
  107.     digitalWrite(systemLightPin, HIGH);   
  108.     delay(warmTime);
  109.   }
  110. }
  111. void loop(){
  112.   if(!inCharge)                  // work state
  113.   {
  114.     float temp_c =sht1x.readTemperatureC();
  115.     float humidity = sht1x.readHumidity();
  116.     unsigned long currentMillis;
  117.     boolean goloop=true;
  118.     partMillis=0;
  119.     unsigned long duration1_0=0;
  120.     unsigned long duration2_5=0;
  121.     long temp1_0=0;
  122.     long temp2_5=0;
  123.     unsigned long rayMark=0;        //人流量计数
  124.     double rayFreq = 0.0;        //人流量频率
  125.     while(goloop)      //loop
  126.     {
  127.       currentMillis = micros();
  128.       if(currentMillis<previousMillis)
  129.       {
  130.         partMillis = 4294967295 -  previousMillis +1;
  131.         previousMillis = 0;  
  132.       }
  133.       if(currentMillis - previousMillis - partMillis < mintev)
  134.       {
  135.         //1.0
  136.         if(temp1_0==0)
  137.         {
  138.           temp1_0=-1;
  139.           temp1_0=pulseIn(DSMPin1_0, LOW);
  140.         }
  141.         if(temp1_0>0)
  142.         {
  143.           duration1_0 =duration1_0 + temp1_0;
  144.           temp1_0=0;
  145.         }
  146.         //2.5
  147.         if(temp2_5==0)
  148.         {
  149.           temp2_5=-1;
  150.           temp2_5=pulseIn(DSMPin2_5, LOW);
  151.         }
  152.         if(temp2_5>0)
  153.         {
  154.           duration2_5 =duration2_5 + temp2_5;
  155.           temp2_5=0;
  156.         }
  157.       }
  158.       else
  159.       {
  160.         goloop=false;
  161.         previousMillis=currentMillis;
  162.       }
  163.       //human transform
  164.       int rayState = analogRead(infrRayPin);
  165.       if(rayState>500)
  166.       {
  167.         rayMark = rayMark + 1;
  168.       }
  169.     }
  170.     double per =double(duration1_0)/double(interval);// had multiply 1000
  171.     int pcs1_0 = -1;
  172.     pcs1_0 =  per * 50.0;
  173.     per =double(duration2_5)/double(interval);// had multiply 1000
  174.     int pcs2_5 = -1;
  175.     pcs2_5 =  per * 50.0;
  176.     rayFreq = double(rayMark) /double(sectev);
  177.     // light
  178.     uint16_t lightval=0;
  179.     BH1750_Init(BH1750address);
  180.     delay(200);
  181.     if(2==BH1750_Read(BH1750address))
  182.     {
  183.       lightval=((buff[0]<<8)|buff[1])/1.2;
  184.     }
  185.     //MQ131
  186.     int O3v=analogRead(o3Pin);
  187.     float O3ppb=float(O3v) * 0.0049;    //not realy value
  188.     float O3mg_m3 = O3ppb * 48 / 22.4 / 1000;  //need ajaust
  189.     //MG811
  190.     int CO2v=analogRead(co2Pin);
  191.     float CO2ppb=float(CO2v) * 0.0049;    //not realy value
  192.    
  193.     //GPS
  194.     String datestr = "";    //date UTC (ddmmyy)
  195.     String timestr = "";    //time UTC (hhmmss.sss)
  196.     String latstr = "";    //Latitude (ddmm.mmmm)
  197.     String lonstr = "";    //Longitude (dddmm.mmmm)
  198.     boolean isGPSOK = false;
  199.     bien=0;
  200.     while(bien!=6)
  201.     {
  202.       byteGPS=Serial.read();
  203.       if(byteGPS == -1)
  204.       {
  205.         delay(100);
  206.       }
  207.       else
  208.       {
  209.         linea[conta]=byteGPS;        // If there is serial port data, it is put in the buffer
  210.         conta++;
  211.         if(byteGPS==13)
  212.         {
  213.           cont=0;
  214.           bien=0;
  215.           for (int i=1;i<7;i++)
  216.           {     // Verifies if the received command starts with $GPRMC
  217.             if (linea<i>==comandoGPR[i-1])
  218.             {
  219.               bien++;
  220.             }
  221.           }
  222.           if(bien==6)  // If yes, continue and process the data
  223.           {
  224.             for (int i=0;i<300;i++)
  225.             {
  226.               if (linea<i>==',')
  227.               {    // check for the position of the  "," separator
  228.                 indices[cont]=i;
  229.                 cont++;
  230.               }
  231.               if (linea<i>=='*')
  232.               {    // ... and the "*"
  233.                 indices[12]=i;
  234.                 cont++;
  235.               }
  236.             }
  237.             String dataString;
  238.             int outindex;
  239.             outindex=1;
  240.             for (int j=indices[outindex];j<(indices[outindex+1]-1);j++)
  241.             {
  242.               if(linea[j+1]=='A')
  243.               {
  244.                 isGPSOK = true;
  245.               }
  246.             }
  247.             outindex=8;  //Date UTC (ddmmyy)
  248.             dataString="";
  249.             for (int j=indices[outindex];j<(indices[outindex+1]-1);j++)
  250.             {
  251.               dataString = dataString + linea[j+1];
  252.             }
  253.             datestr=dataString;
  254.             outindex=0;  //time UTC (hhmmss.sss)
  255.             dataString="";
  256.             for (int j=indices[outindex];j<(indices[outindex+1]-1);j++)
  257.             {
  258.               dataString = dataString + linea[j+1];
  259.             }
  260.             timestr=dataString;
  261.             outindex=2;  //Latitude (ddmm.mmmm)
  262.             dataString="";
  263.             for (int j=indices[outindex];j<(indices[outindex+1]-1);j++)
  264.             {
  265.               dataString = dataString + linea[j+1];
  266.             }
  267.             latstr=dataString;
  268.             outindex=4;  //Longitude (dddmm.mmmm)
  269.             dataString="";
  270.             for (int j=indices[outindex];j<(indices[outindex+1]-1);j++)
  271.             {
  272.               dataString = dataString + linea[j+1];
  273.             }
  274.             lonstr=dataString;
  275.           }
  276.           // Reset the buffer
  277.           conta=0;                    
  278.           for (int i=0;i<300;i++)
  279.           {   
  280.             linea<i>=' ';            
  281.           }
  282.         }
  283.       }
  284.     }
  285.     //output result
  286.     Serial.print(datestr);
  287.     Serial.print(9,BYTE);
  288.     Serial.print(timestr);
  289.     Serial.print(9,BYTE);
  290.     Serial.print(latstr);
  291.     Serial.print(9,BYTE);
  292.     Serial.print(lonstr);
  293.     Serial.print(9,BYTE);
  294.     Serial.print(temp_c);
  295.     Serial.print(9,BYTE);
  296.     Serial.print(humidity);
  297.     Serial.print(9,BYTE);
  298.     Serial.print(pcs1_0);
  299.     Serial.print(9,BYTE);
  300.     Serial.print(pcs2_5);
  301.     Serial.print(9,BYTE);
  302.     Serial.print(rayFreq);
  303.     Serial.print(9,BYTE);
  304.     Serial.print(O3ppb);
  305.     Serial.print(9,BYTE);
  306.     Serial.print(CO2ppb);
  307.     Serial.print(9,BYTE);
  308.     Serial.println(lightval,DEC);
  309.     //writer the result to SD card
  310.     myFile = SD.open(filename, FILE_WRITE);
  311.     if (myFile)
  312.     {
  313.       // to sd file
  314.       myFile.print(datestr);
  315.       myFile.print(9,BYTE);
  316.       myFile.print(timestr);
  317.       myFile.print(9,BYTE);
  318.       myFile.print(latstr);
  319.       myFile.print(9,BYTE);
  320.       myFile.print(lonstr);
  321.       myFile.print(9,BYTE);
  322.       myFile.print(temp_c);
  323.       myFile.print(9,BYTE);
  324.       myFile.print(humidity);
  325.       myFile.print(9,BYTE);
  326.       myFile.print(pcs1_0);
  327.       myFile.print(9,BYTE);
  328.       myFile.print(pcs2_5);
  329.       myFile.print(9,BYTE);
  330.       myFile.print(rayFreq);
  331.       myFile.print(9,BYTE);
  332.       myFile.print(O3ppb);
  333.       myFile.print(9,BYTE);
  334.       myFile.print(CO2ppb);
  335.       myFile.print(9,BYTE);
  336.       myFile.println(lightval,DEC);
  337.       myFile.close();
  338.       //flash the light
  339.       if(isGPSOK)
  340.       {
  341.         digitalWrite(systemLightPin, HIGH);
  342.         delay(flashTime);
  343.         digitalWrite(systemLightPin, LOW);
  344.       }
  345.       else
  346.       {
  347.         digitalWrite(systemLightPin, LOW);
  348.         delay(flashTime);
  349.         digitalWrite(systemLightPin, HIGH);
  350.       }
  351.     }
  352.     delay(scanTime);
  353.   }
  354.   else               //bettery charging and data translation
  355.   {
  356.     while(Serial.available() > 0)
  357.     {
  358.       int incomingByte = Serial.read();
  359.       if(incomingByte==10)    //order end
  360.       {
  361.         if(order == "list")
  362.         {
  363.           myFile = SD.open(filename);
  364.           if (myFile)
  365.           {
  366.             while (myFile.available())
  367.             {
  368.               Serial.write(myFile.read());
  369.             }
  370.             myFile.close();
  371.           }
  372.           else
  373.           {
  374.             Serial.println("open file failure.");
  375.           }
  376.         }
  377.         else if(order.length()>0)
  378.         {
  379.           Serial.println("The available command is:");
  380.           Serial.println("list");
  381.         }
  382.         //reset order
  383.         order="";
  384.       }
  385.       else
  386.       {
  387.         if(incomingByte!=13)
  388.         {
  389.           order = order + char(incomingByte);
  390.         }
  391.       }
  392.     }
  393.   }
  394. }
  395. int BH1750_Read(int address)
  396. {
  397.   int i=0;
  398.   Wire.beginTransmission(address);
  399.   Wire.requestFrom(address, 2);
  400.   while(Wire.available())
  401.   {
  402.     buff<i> = Wire.receive();  // receive one byte
  403.     i++;
  404.   }
  405.   Wire.endTransmission();  
  406.   return i;
  407. }
  408. void BH1750_Init(int address)
  409. {
  410.   Wire.beginTransmission(address);
  411.   Wire.send(0x10);//1lx reolution 120ms
  412.   Wire.endTransmission();
  413. }</i></i></i></i></i></i>
复制代码



    代码说明:
  • 传感器需要预热时间,在变量warmTime 中设置,这里设置为60秒
  • 仪器根据inCharge 变量判断系统处于采集状态或充电状态,在采集状态时如插上usb可通过arduino IDE的串口监视实现数据显示;仪器通过arduino的usb口充电,在充电的同时可通过arduino IDE的串口监视实现交互,如输入list命令可列出SD卡存储的数据
  • DSM501A灰尘传感器有两路输出分别对应不同的灰尘粒径检出量,检测到灰尘是在输出口产生时间不等的低脉冲,通过计算30秒内的低脉冲率和对于关系得到灰尘粒子量(请参考DSM501A datasheet),程序中通过micros()函数提供计时,micros()每7分钟左右会归零,通过 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/ ... 12/C02-Calibration/ 中的方法,另外二氧化碳是温室气体,特性稳定,所以在全球很长时间都是均匀分布在同一水平,全球二氧化碳实时浓度见:http://co2now.org/,可以参考此网站上的值作为标定浓度之一,同时呼吁大家节能减排,减少温室气体排放
  • MQ131臭氧传感器标定需要在标准条件50ppb浓度臭氧下进行,请参考datasheet中说明
  • DSM501A灰尘传感器标定也可参考datasheet中的说明,需要注意的是DSM501A灰尘传感器测量粒子浓度与PM2.5、PM10和TSP(PM100)的检测原理和概念不同,不可混淆,只可做参考
    在仪器中以上传感器均未做标定,MQ131、MG811的输出值为对应的电压值

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

  • 温湿度曲线:
    自制基于Arduino移动式城市环境信息采集器图21
  • 灰尘粒子曲线:
    自制基于Arduino移动式城市环境信息采集器图22
  • 气体浓度曲线:
    自制基于Arduino移动式城市环境信息采集器图23

移动采集:
  • 采集路线:
    自制基于Arduino移动式城市环境信息采集器图24
  • 温度分布:
    自制基于Arduino移动式城市环境信息采集器图25
  • 湿度分布:
    自制基于Arduino移动式城市环境信息采集器图26
  • CO2分布:
    自制基于Arduino移动式城市环境信息采集器图27
  • 1微米以上粒子:
自制基于Arduino移动式城市环境信息采集器图28
图中红色代表高值,蓝色代表低值。


下一步工作:

MQ系列传感器辅助电路类似,仪器只需更换传感器探头和标定可实现其它气体测量
由于MQ131和MG811属于加热型传感器耗电量大,2800mAh的锂电池只能连续工作3.5小时左右,同时二氧化碳和臭氧气体不适合移动观测,下一步打算将两种气体传感器设置在固定监测仪器上,在留出的面板位置上安装1602液晶显示,仪器同时实现便携式GPS功能。


http://www.geek-workshop.com/thread-297-1-1.html

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail