Blog Archives

Home Posts by “孔德翰
DSC_0863

UAV大氣剖面觀測系統

No Comments

專案動機 – 霧林的重要性

潮濕、涼爽、陰暗,是大部分人進入山地霧林的第一印象,山地霧林多半位於熱帶或亞熱帶臨海的山地區域,其特徵是每天週期性雲霧、以及充滿附生植物的森林樹冠層,由海岸來的潮濕空氣在沿著山地爬升之後,由於溫度下降,而形成濃厚雲霧帶。

霧林的獨特由於其水文特性 – 攔截雨霧,進而提升森林的涵水能力,但同時缺點也是因為倚賴霧水的水份供給,因此分布面積十分狹窄,此外由於山地霧林的獨特性,所以特有種比率非常高,許多物種只能生存在這樣的環境之中,在近年來氣候變遷下,有可能使雲霧帶生成高度上升,進而造成現有霧林帶乾旱的現象,最後使霧林帶的植物與特有種面臨威脅。

目前服務於國立臺灣大學實驗林管理處的賴彥任先生,以溪頭地區為例子,溪頭是著名的森林遊樂景點,因此長年有相當多的遊客照訪,到去年已經超過200萬的遊客,這還不包括去妖怪村等周遭景點但沒有進去溪頭自然園區的遊客數。也因此,我們也開始關注這麼多的遊客是否可能造成氣候與生態衝擊。

在南投溪頭發現了近幾年霧發生率快速減少的情況,因而提出UAV大氣剖面觀測的計劃,想藉由觀測雲霧的結構與時空分佈,進而分析雲霧的成因與導致雲霧快速減少的原因。

5591218236_f8412d5112

霧的觀測

霧通常採用能見度觀測,若能見度低於1KM則稱為起霧,我們也利用縮時攝影來觀測,但夜間是個問題。雲霧發生率通常有小時或日的統計,也就是若該觀測小時或日若有能見度低於1KM,則視為霧時或霧日,我們將霧日/365得上述的比例。

U1

觀測方法

1.安裝地點

由於溪頭是個峽谷的地形,道路也是沿著峽谷而上至溪頭,因此目前認為沿著路旁的國小放置裝置,計劃在雲林國小、初鄉國小、鹿谷國小、廣興國小、鳳凰國小、文昌國小、內湖國小、和雅國小、溪頭辦公室、溪頭天文台共10個點設置,主要是考量他們有電有網路。

U2

在固定的觀測點,系統會在沒有 Wifi AP 時自動記錄,等 Wifi 出現時,會自動連上線,將資料傳出來。所以只要定期走到設備旁邊,就可以將資料上傳到 server。U3

無人機天空感測,飛行過程中記錄資料,回來的時候就上傳,目前飛天感測感測是每五秒感測一次。

DSC_0847

感測點安裝完成之後,可以用比對觀測資料的方式,觀察雲霧形成的時間差。

2. 機構裝置

在原本的LASS觀測系統上進行改裝,需要將PM2.5感測器與濕度感測器(SHT31)放在標準的通風筒觀測。為能長期觀測設備會加入電瓶來長期供電,並加入Watchdog功能當系統有異常時執行重開機的能力。

Resource

DSC_0850

農業感測計畫Sensors for Farm

No Comments

緣起

LASS 是套環境感測器系統,經過社群的努力已整合感測設備、網路通訊連線與雲端資料庫,為追求其他突破,除了系統功能上的增強,也積極尋求其他相關領域的應用需求,突破目前LASS系統架構僅是應用在PM2.5空汙監控之上,此農業感測計劃便是將LASS環境感測的理念應用於農業之上。

大家都知道,農業是看天吃飯,這個『天』包括陽光、空氣、水,所以日照、溫、濕度、雨量土壤酸鹼度,這氣候氣象因素都會影響植物生長,而我們的農友可以透過獲得這些資訊,來創造適合生長的環境,以得到更好的收穫與農作品質。

114

計劃的實踐

  • 計劃的目標
  1. 產銷履歷氣候資料:加入當地農場的氣候資料到產銷履歷,目前市面上的產銷履歷提供有生產者與生產地的相關訊息,但這些資料對於消費者只能說是個保障,當出問題時利於追蹤到生產者,而不是一個選擇的依據,消費者在意的是這項商品是在什麼樣的情況下生產出來的,因此有加入氣候資料到產銷履歷的想法,由我們的機構收集數據整合於雲端系統(以彌補農場到鄰近氣象站的距離出產生的數值誤差)。
  2. 環境監控與預報:系統非常適合應用於溫室,以提供科學數據創造出作物良好的生長環境,而變因較多的戶外則是採專注於收集雨量資訊的策略,雨量常隨地理環境不同就有很大區別,氣象局預報的範圍還是太大,對於小區域性的農友不夠精確,山的一邊下雨,翻過山又是晴空萬里,因此可利用雨量數據做為土石流,淹水預警系統。
  • 應用區域:菜園、水田、魚菜共生
  • 系統功能概述:將農田的感測值用 LoRa 傳回來,在網頁或是 App 中顯示出來,並開放資料給大家取得。可以有效長期觀察農田的感測值變化,以及減少所需要的巡田次數

DSC_0849

機構 – Weather Box

設計概念

  • 攜帶提箱式:Weather Box設計成提箱式讓使用者方便攜帶,可以便於記錄不同地區菜園的微氣候,並且附註在生產履歷上提供消費者參考。

lass.hackpad.com_c8meILUnsyi_p.525653_1452911709683_1452743523755

lass.hackpad.com_c8meILUnsyi_p.525653_1452911723895_1452743508415

  • 腳架可移動式:爲了快速架設風力、雨量感測器設計的移動型架構。

114-3
13389056_10205084860160112_1336685981_o

  • 百葉箱:百葉箱適合加上太陽能板、電瓶及網路,持續提供一定方圓公里內的氣候資訊。

lass.hackpad.com_c8meILUnsyi_p.525653_1464884983968_20160527_152733

13383974_10205084856880030_1294285006_o

機構零件

相關報導

科技農夫陳幸延──農田裡的開源自造者

RESOURCE

photo

空氣盒子

No Comments

空氣盒子背景介紹

空氣盒子計劃是LASS核心概念的延伸,由華碩雲端、瑞昱半導體與中研院資訊所合作,發展一套簡易安裝的空氣品質監控設備,對於缺乏軟硬開發能力的人,提供一個監測PM2.5、溫度與濕度的管道,目前已提供300個空氣盒子給市民與學校,再將環境資訊上傳至雲端平台進行彙整,開放給研究機構分析汙染來源以及Makers進行創新加值應用,真正落實民眾參與、共同提升城市生活品質。

IMAG0021

a3

空氣盒子特色

  • 簡易安裝:簡單幾個步驟即可安裝完成,EdiGreen AirBox
  • 支援手機APP:用戶可以透過手機下載APP,即可在手機觀看即時空汙監測數據。
  • 視覺化的網頁數據顯示:空氣盒子

IMAG0017

規格

大小尺寸 148.4mm(W) x 111.5mm(H) x 45mm(D)
Wifi IEEE 802.11b/g/n
溫度感測範圍 0~60°C, Accuracy: ± 1 °C
濕度感測範圍 0~100%RH, Accuracy: ± 5% RH
PM2.5 量測範圍 minimum 0.3μm
PM2.5 計量效率 50%@0.3um, 98%@>=0.5 um
供電方式 Micro USB power port x 1 DC 5V
參考文件 快速安裝手冊 ,   FAQ

相關報導

Resource

未命名40

智慧家庭:PM2.5空氣感測器(電路設計下篇)

No Comments

作者:曹永忠/vMaker

本篇是接續上篇文章『智慧家庭:PM2.5空氣感測器(電路設計上篇)』,延續未完成的硬體電路組裝下篇,教大家如何組立空氣粒子感測裝置電子電路組裝。上文中我們介紹空氣粒子感測裝置的開發板安裝、空氣懸浮粒子感測器安裝,麵包板安裝、溫溼度模組安裝、RTC 時鐘模組安裝…等等,本篇將介紹空氣懸浮粒子感測器、LCD  2004 顯示模組等電路安裝,並進行第一階段的整合測試。

安裝 RTC 時鐘模組

我們加入 RTC 時鐘模組,如下圖所示進行電路連接。

未命名

(圖 1)安裝 RTC 時鐘模組

由於時間因素對本設計是一個非常重要的因素,由於阿米巴開發板並沒有內置時間模組,所以我們加入 RTC 時鐘模組。所以增加下表之接腳表,讓讀者更加了解。

(表 1)RTC 時鐘模組接腳表(累加接腳表)

未命名

我們將下表之 PMS 3003 空氣懸浮粒子感測器測試程式一攥寫好之後,編譯完成後上傳到 Ameba 開發板。

(表 2)PMS 3003 空氣懸浮粒子感測器測試程式一

PMS3003空氣懸浮粒子感測器測試程式一(PMS3003AirQualityV51A)
/*

This example demonstrate how to read pm2.5 value on PMS 3003 air condition sensor

 

PMS 3003 pin map is as follow:

PIN1  :VCC, connect to 5V

PIN2  :GND

PIN3  :SET, 0:Standby mode, 1:operating mode

PIN4  :RXD :Serial RX

PIN5  :TXD :Serial TX

PIN6  :RESET

PIN7  :NC

PIN8  :NC

 

In this example, we only use Serial to get PM 2.5 value.

 

The circuit:

* RX is digital pin 0 (connect to TX of PMS 3003)

* TX is digital pin 1 (connect to RX of PMS 3003)

 

*/

#define turnon HIGH

#define turnoff LOW

#include <WiFi.h>

#include “PMType.h”

 

 

#include <Wire.h>  // Arduino IDE 內建

// LCD I2C Library,從這裡可以下載:

// https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads

#include <LiquidCrystal_I2C.h>

 

#include <SoftwareSerial.h>

 

#include “RTClib.h”

RTC_DS1307 RTC;

//DateTime nowT = RTC.now();

 

uint8_t MacData[6];

 

SoftwareSerial mySerial(0, 1); // RX, TX

IPAddress  Meip ,Megateway ,Mesubnet ;

String MacAddress ;

int status = WL_IDLE_STATUS;

boolean ParticleSensorStatus = true ;

#define pmsDataLen 32

uint8_t buf[pmsDataLen];

int idx = 0;

int pm25 = 0;

uint16_t PM01Value=0;          //define PM1.0 value of the air detector module

uint16_t PM2_5Value=0;         //define PM2.5 value of the air detector module

uint16_t PM10Value=0;         //define PM10 value of the air detector module

int NDPyear, NDPmonth, NDPday, NDPhour, NDPminute, NDPsecond;

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // 設定 LCD I2C 位址

 

void setup() {

Serial.begin(9600);

 

mySerial.begin(9600); // PMS 3003 UART has baud rate 9600

lcd.begin(20, 4);      // 初始化 LCD,一行 20 的字元,共 4 行,預設開啟背光

lcd.backlight(); // 開啟背光

//  while(!Serial) ;

WiFi.status();    //this method must be used for get MAC

MacAddress = GetWifiMac() ;

ShowMac() ;

ShowDateTime() ;

 

}

 

void loop() { // run over and over

idx = 0;

memset(buf, 0, pmsDataLen);

 

while (mySerial.available())

{

buf[idx++] = mySerial.read();

}

 

// check if data header is correct

if (buf[0] == 0x42 && buf[1] == 0x4d)

{

pm25 = ( buf[12] << 8 ) | buf[13];

Serial.print(“pm2.5: “);

Serial.print(pm25);

Serial.println(” ug/m3″);

ShowPM(pm25) ;

}

}

 

void ShowMac()

{

lcd.setCursor(0, 0); // 設定游標位置在第一行行首

lcd.print(“MAC:”);

lcd.print(MacAddress);

 

}

 

void ShowDateTime()

{

lcd.setCursor(0, 2); // 設定游標位置在第一行行首

lcd.print(StrDate());

lcd.setCursor(11, 2); // 設定游標位置在第一行行首

lcd.print(StrTime());

//  lcd.print();

 

}

 

String  StrDate() {

String ttt ;

//nowT  = now;

DateTime now = RTC.now();

ttt = print4digits(now.year()) + “-” + print2digits(now.month()) + “-” + print2digits(now.day()) ;

//ttt = print4digits(NDPyear) + “/” + print2digits(NDPmonth) + “/” + print2digits(NDPday) ;

return ttt ;

}

 

String  StringDate(int yyy,int mmm,int ddd) {

String ttt ;

//nowT  = now;

ttt = print4digits(yyy) + “-” + print2digits(mmm) + “-” + print2digits(ddd) ;

return ttt ;

}

 

 

String  StrTime() {

String ttt ;

// nowT  = RTC.now();

DateTime now = RTC.now();

ttt = print2digits(now.hour()) + “:” + print2digits(now.minute()) + “:” + print2digits(now.second()) ;

//  ttt = print2digits(NDPhour) + “:” + print2digits(NDPminute) + “:” + print2digits(NDPsecond) ;

return ttt ;

}

 

String  StringTime(int hhh,int mmm,int sss) {

String ttt ;

ttt = print2digits(hhh) + “:” + print2digits(mmm) + “:” + print2digits(sss) ;

return ttt ;

}

 

 

 

String GetWifiMac()

{

String tt ;

String t1,t2,t3,t4,t5,t6 ;

WiFi.macAddress(MacData);

 

Serial.print(“Mac:”);

Serial.print(MacData[0],HEX) ;

Serial.print(“/”);

Serial.print(MacData[1],HEX) ;

Serial.print(“/”);

Serial.print(MacData[2],HEX) ;

Serial.print(“/”);

Serial.print(MacData[3],HEX) ;

Serial.print(“/”);

Serial.print(MacData[4],HEX) ;

Serial.print(“/”);

Serial.print(MacData[5],HEX) ;

Serial.print(“~”);

 

t1 = print2HEX((int)MacData[0]);

t2 = print2HEX((int)MacData[1]);

t3 = print2HEX((int)MacData[2]);

t4 = print2HEX((int)MacData[3]);

t5 = print2HEX((int)MacData[4]);

t6 = print2HEX((int)MacData[5]);

tt = (t1+t2+t3+t4+t5+t6) ;

Serial.print(tt);

Serial.print(“\n”);

 

return tt ;

}

String  print2HEX(int number) {

String ttt ;

if (number >= 0 && number < 16)

{

ttt = String(“0″) + String(number,HEX);

}

else

{

ttt = String(number,HEX);

}

return ttt ;

}

String  print2digits(int number) {

String ttt ;

if (number >= 0 && number < 10)

{

ttt = String(“0″) + String(number);

}

else

{

ttt = String(number);

}

return ttt ;

}

 

String  print4digits(int number) {

String ttt ;

ttt = String(number);

return ttt ;

}

 

 

void ShowPM(int pp25)

{

lcd.setCursor(0, 3); // 設定游標位置在第一行行首

lcd.print(”  PM2.5:     “);

lcd.setCursor(9, 3); // 設定游標位置在第一行行首

lcd.print(pp25);

 

}

資料下載:https://github.com/brucetsao/makerdiwo/tree/master/201604/PMS3003AirQualityV51A

由於,我們可以連上網際網路,也可以讀取時鐘模組,所以我們可以顯示網路資訊與時間資訊。我們將下表之 PMS 3003 空氣懸浮粒子感測器測試程式一攥寫好之後,編譯完成後上傳到 Ameba 開發板。

未命名

(圖 1)PMS 3003 空氣懸浮粒子感測器測試程式一畫面結果

擴充 RTC 時鐘模組網路校時能力

由於 RTC 時鐘模組是一個可以信賴的即時時鐘模組,但是如果新安裝裝置、更換電池或地區變更等等,都必須要將空氣粒子感測裝置帶回原開發者的研究室方可以更正於 RTC 時鐘模組的時間,雖然 Ameba 開發版有強大的無線網路連接網際網路的能力,但是在六秒中,除了重新讀取空氣粒子感測裝置的資料,還必須完成許多其他的工作,這些都必須耗費 Ameba 開發版的時間,與無線網路連接網際網路取得時間的成本,這樣對一個完善的空氣粒子感測裝置,太耗費在無線網路連接網際網路取得時間的成本。

所以我們如果能將系統修正,在每一次空氣粒子感測裝置開機後,即使用無線網路連接網際網路取得時間,並動態校正 RTC 時鐘模組,往後的時間就完全依靠內部的 RTC 時鐘模組運作,如此空氣粒子感測裝置可以更加完備,所以我們修改上述程式來達到此功能。

我們將下表之整合空氣懸浮粒子感測器測試程式二攥寫好之後,編譯完成後上傳到 Ameba 開發板。

(表 3)整合空氣懸浮粒子感測器測試程式二

整合空氣懸浮粒子感測器測試程式二(PMS3003AirQualityV71A)
/*

This example demonstrate how to read pm2.5 value on PMS 3003 air condition sensor

 

PMS 3003 pin map is as follow:

PIN1  :VCC, connect to 5V

PIN2  :GND

PIN3  :SET, 0:Standby mode, 1:operating mode

PIN4  :RXD :Serial RX

PIN5  :TXD :Serial TX

PIN6  :RESET

PIN7  :NC

PIN8  :NC

 

In this example, we only use Serial to get PM 2.5 value.

 

The circuit:

* RX is digital pin 0 (connect to TX of PMS 3003)

* TX is digital pin 1 (connect to RX of PMS 3003)

 

*/

#include <math.h>

 

#define turnon HIGH

#define turnoff LOW

 

 

#include “PMType.h”

#include <WiFi.h>

#include <WiFiUdp.h>

#include <Wire.h>  // Arduino IDE 內建

// LCD I2C Library,從這裡可以下載:

// https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads

 

#include “RTClib.h”

RTC_DS1307 RTC;

//DateTime nowT = RTC.now();

 

#include <LiquidCrystal_I2C.h>

#include <SoftwareSerial.h>

 

uint8_t MacData[6];

 

SoftwareSerial mySerial(0, 1); // RX, TX

char ssid[] = “TSAO”;      // your network SSID (name)

char pass[] = “TSAO1234″;     // your network password

#define MAX_CLIENT_ID_LEN 10

#define MAX_TOPIC_LEN     50

char clientId[MAX_CLIENT_ID_LEN];

char outTopic[MAX_TOPIC_LEN];

 

IPAddress  Meip ,Megateway ,Mesubnet ;

String MacAddress ;

int status = WL_IDLE_STATUS;

boolean ParticleSensorStatus = true ;

WiFiUDP Udp;

 

const char ntpServer[] = “pool.ntp.org”;

const long timeZoneOffset = 28800L;

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

const byte nptSendPacket[ NTP_PACKET_SIZE] = {

0xE3, 0x00, 0x06, 0xEC, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x31, 0x4E, 0x31, 0x34,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

byte ntpRecvBuffer[ NTP_PACKET_SIZE ];

 

#define LEAP_YEAR(Y)     ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)0) || !((1970+Y)%400) ) )

static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0

uint32_t epochSystem = 0; // timestamp of system boot up

 

 

#define pmsDataLen 32

uint8_t buf[pmsDataLen];

int idx = 0;

int pm25 = 0;

uint16_t PM2_5Value=0;         //define PM2.5 value of the air detector module

int NDPyear, NDPmonth, NDPday, NDPhour, NDPminute, NDPsecond;

unsigned long epoch  ;

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // 設定 LCD I2C 位址

 

 

void setup() {

Serial.begin(9600);

mySerial.begin(9600); // PMS 3003 UART has baud rate 9600

lcd.begin(20, 4);      // 初始化 LCD,一行 20 的字元,共 4 行,預設開啟背光

lcd.backlight(); // 開啟背光

 

MacAddress = GetWifiMac() ;

ShowMac() ;

initializeWiFi();

 

initRTC() ;

ShowDateTime() ;

delay(1500);

}

 

void loop() { // run over and over

ShowDateTime() ;

retrievePM25Value() ;

delay(1000); // delay 1 minute for next measurement

 

 

}

 

void ShowMac()

{

lcd.setCursor(0, 0); // 設定游標位置在第一行行首

lcd.print(“MAC:”);

lcd.print(MacAddress);

 

}

 

void ShowInternetStatus()

{

lcd.setCursor(0, 1); // 設定游標位置

if (WiFi.status())

{

Meip = WiFi.localIP();

lcd.print(“@:”);

lcd.print(Meip);

 

}

else

{

lcd.print(“DisConnected:”);

 

}

 

}

void ShowPM25(int pp25)

{

lcd.setCursor(0, 3); // 設定游標位置在第一行行首

lcd.print(“PM2.5:     “);

lcd.setCursor(9, 3); // 設定游標位置在第一行行首

lcd.print(pp25);

 

}

 

 

 

void ShowDateTime()

{

//  getCurrentTime(epoch, &NDPyear, &NDPmonth, &NDPday, &NDPhour, &NDPminute, &NDPsecond);

lcd.setCursor(0, 2); // 設定游標位置在第一行行首

lcd.print(StrDate());

lcd.setCursor(11, 2); // 設定游標位置在第一行行首

lcd.print(StrTime());

//  lcd.print();

 

}

 

String  StrDate() {

String ttt ;

//nowT  = now;

DateTime now = RTC.now();

ttt = print4digits(now.year()) + “-” + print2digits(now.month()) + “-” + print2digits(now.day()) ;

//ttt = print4digits(NDPyear) + “/” + print2digits(NDPmonth) + “/” + print2digits(NDPday) ;

return ttt ;

}

 

String  StringDate(int yyy,int mmm,int ddd) {

String ttt ;

//nowT  = now;

ttt = print4digits(yyy) + “-” + print2digits(mmm) + “-” + print2digits(ddd) ;

return ttt ;

}

 

 

String  StrTime() {

String ttt ;

// nowT  = RTC.now();

DateTime now = RTC.now();

ttt = print2digits(now.hour()) + “:” + print2digits(now.minute()) + “:” + print2digits(now.second()) ;

//  ttt = print2digits(NDPhour) + “:” + print2digits(NDPminute) + “:” + print2digits(NDPsecond) ;

return ttt ;

}

 

String  StringTime(int hhh,int mmm,int sss) {

String ttt ;

ttt = print2digits(hhh) + “:” + print2digits(mmm) + “:” + print2digits(sss) ;

return ttt ;

}

 

 

String GetWifiMac()

{

String tt ;

String t1,t2,t3,t4,t5,t6 ;

WiFi.status();    //this method must be used for get MAC

WiFi.macAddress(MacData);

 

Serial.print(“Mac:”);

Serial.print(MacData[0],HEX) ;

Serial.print(“/”);

Serial.print(MacData[1],HEX) ;

Serial.print(“/”);

Serial.print(MacData[2],HEX) ;

Serial.print(“/”);

Serial.print(MacData[3],HEX) ;

Serial.print(“/”);

Serial.print(MacData[4],HEX) ;

Serial.print(“/”);

Serial.print(MacData[5],HEX) ;

Serial.print(“~”);

 

t1 = print2HEX((int)MacData[0]);

t2 = print2HEX((int)MacData[1]);

t3 = print2HEX((int)MacData[2]);

t4 = print2HEX((int)MacData[3]);

t5 = print2HEX((int)MacData[4]);

t6 = print2HEX((int)MacData[5]);

tt = (t1+t2+t3+t4+t5+t6) ;

Serial.print(tt);

Serial.print(“\n”);

 

return tt ;

}

String  print2HEX(int number) {

String ttt ;

if (number >= 0 && number < 16)

{

ttt = String(“0″) + String(number,HEX);

}

else

{

ttt = String(number,HEX);

}

return ttt ;

}

String  print2digits(int number) {

String ttt ;

if (number >= 0 && number < 10)

{

ttt = String(“0″) + String(number);

}

else

{

ttt = String(number);

}

return ttt ;

}

 

String  print4digits(int number) {

String ttt ;

ttt = String(number);

return ttt ;

}

 

 

 

// send an NTP request to the time server at the given address

void retrieveNtpTime() {

Serial.println(“Send NTP packet”);

 

Udp.beginPacket(ntpServer, 123); //NTP requests are to port 123

Udp.write(nptSendPacket, NTP_PACKET_SIZE);

Udp.endPacket();

 

if(Udp.parsePacket()) {

Serial.println(“NTP packet received”);

Udp.read(ntpRecvBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

 

unsigned long highWord = word(ntpRecvBuffer[40], ntpRecvBuffer[41]);

unsigned long lowWord = word(ntpRecvBuffer[42], ntpRecvBuffer[43]);

unsigned long secsSince1900 = highWord << 16 | lowWord;

const unsigned long seventyYears = 2208988800UL;

//     epoch = secsSince1900 – seventyYears + timeZoneOffset ;

epoch = secsSince1900 – seventyYears ;

 

epochSystem = epoch – millis() / 1000;

}

}

 

 

void getCurrentTime(unsigned long epoch, int *year, int *month, int *day, int *hour, int *minute, int *second) {

int tempDay = 0;

 

*hour = (epoch  % 86400L) / 3600;

*minute = (epoch  % 3600) / 60;

*second = epoch % 60;

 

*year = 1970;

*month = 0;

*day = epoch / 86400;

 

for (*year = 1970; ; (*year)++) {

if (tempDay + (LEAP_YEAR(*year) ? 366 : 365) > *day) {

break;

} else {

tempDay += (LEAP_YEAR(*year) ? 366 : 365);

}

}

tempDay = *day – tempDay; // the days left in a year

for ((*month) = 0; (*month) < 12; (*month)++) {

if ((*month) == 1) {

if (LEAP_YEAR(*year)) {

if (tempDay – 29 < 0) {

break;

} else {

tempDay -= 29;

}

} else {

if (tempDay – 28 < 0) {

break;

} else {

tempDay -= 28;

}

}

} else {

if (tempDay – monthDays[(*month)] < 0) {

break;

} else {

tempDay -= monthDays[(*month)];

}

}

}

(*month)++;

*day = tempDay+2; // one for base 1, one for current day

}

 

void retrievePM25Value() {

int idx;

bool hasPm25Value = false;

int timeout = 200;

while (!hasPm25Value) {

idx = 0;

memset(buf, 0, pmsDataLen);

while (mySerial.available()) {

buf[idx++] = mySerial.read();

}

 

if (buf[0] == 0x42 && buf[1] == 0x4d) {

pm25 = ( buf[12] << 8 ) | buf[13];

Serial.print(“pm2.5: “);

Serial.print(pm25);

Serial.print(” ug/m3″);

Serial.println(“”);

hasPm25Value = true;

ShowPM25(pm25) ;

}

timeout–;

if (timeout < 0) {

Serial.println(“fail to get pm2.5 data”);

break;

}

}

}

 

void initializeWiFi() {

while (status != WL_CONNECTED) {

Serial.print(“Attempting to connect to SSID: “);

Serial.println(ssid);

// Connect to WPA/WPA2 network. Change this line if using open or WEP network:

status = WiFi.begin(ssid, pass);

//   status = WiFi.begin(ssid);

 

// wait 10 seconds for connection:

delay(10000);

}

 

// local port to listen for UDP packets

Udp.begin(2390);

}

 

void printWifiData()

{

// print your WiFi shield’s IP address:

Meip = WiFi.localIP();

Serial.print(“IP Address: “);

Serial.println(Meip);

 

 

// print your MAC address:

byte mac[6];

WiFi.macAddress(mac);

Serial.print(“MAC address: “);

Serial.print(mac[5], HEX);

Serial.print(“:”);

Serial.print(mac[4], HEX);

Serial.print(“:”);

Serial.print(mac[3], HEX);

Serial.print(“:”);

Serial.print(mac[2], HEX);

Serial.print(“:”);

Serial.print(mac[1], HEX);

Serial.print(“:”);

Serial.println(mac[0], HEX);

 

// print your subnet mask:

Mesubnet = WiFi.subnetMask();

Serial.print(“NetMask: “);

Serial.println(Mesubnet);

 

// print your gateway address:

Megateway = WiFi.gatewayIP();

Serial.print(“Gateway: “);

Serial.println(Megateway);

}

 

void initRTC()

{

Wire.begin();

RTC.begin();

SetRTCFromNtpTime() ;

if (! RTC.isrunning()) {

Serial.println(“RTC is NOT running!”);

// following line sets the RTC to the date & time this sketch was compiled

//    RTC.adjust(DateTime(__DATE__, __TIME__));

 

}

}

void SetRTCFromNtpTime()

{

retrieveNtpTime();

//DateTime ttt;

getCurrentTime(epoch+timeZoneOffset, &NDPyear, &NDPmonth, &NDPday, &NDPhour, &NDPminute, &NDPsecond);

//ttt->year = NDPyear ;

Serial.print(“NDP Date is :”);

Serial.print(StringDate(NDPyear,NDPmonth,NDPday));

Serial.print(“and “);

Serial.print(“NDP Time is :”);

Serial.print(StringTime(NDPhour,NDPminute,NDPsecond));

Serial.print(“\n”);

 

RTC.adjust(DateTime(epoch+timeZoneOffset));

 

 

}

資料下載:https://github.com/brucetsao/makerdiwo/tree/master/201604/ PMS3003AirQualityV71A

由於空氣粒子感測裝置每次開機時,我們可以連上網際網路,使用無線網路連接網際網路取得時間,並動態校正 RTC 時鐘模組,往後的時間就完全依靠內部的 RTC 時鐘模組運作。

未命名

(圖 2-a)開發 IDE 監控畫面

未命名

(圖 2-b)校時之後空氣粒子感測裝置 LCD 顯示畫面

擴充溫溼度感測器

由於完整的空氣粒子感測裝置除了偵測空氣中懸浮微粒的濃度,基本的溫溼度資訊也是必需的需求,所以我們加入溫溼度感測器模組。

未命名

(圖 2)安裝溫溼度感測器

請讀者依照下表之接腳表,進行電路組立。

(表 4)溫溼度感測器接腳表(累加接腳表)

未命名

我們將下表之整合空氣懸浮粒子感測器測試程式三攥寫好之後,編譯完成後上傳到 Ameba 開發板。

(表 5)整合空氣懸浮粒子感測器測試程式三

整合空氣懸浮粒子感測器測試程式三(PMS3003AirQualityV81A)
/*

This example demonstrate how to read pm2.5 value on PMS 3003 air condition sensor

 

PMS 3003 pin map is as follow:

PIN1  :VCC, connect to 5V

PIN2  :GND

PIN3  :SET, 0:Standby mode, 1:operating mode

PIN4  :RXD :Serial RX

PIN5  :TXD :Serial TX

PIN6  :RESET

PIN7  :NC

PIN8  :NC

 

In this example, we only use Serial to get PM 2.5 value.

 

The circuit:

* RX is digital pin 0 (connect to TX of PMS 3003)

* TX is digital pin 1 (connect to RX of PMS 3003)

 

*/

#include <math.h>

 

#define turnon HIGH

#define turnoff LOW

#define DHTSensorPin 7

 

 

#include “PMType.h”

#include <WiFi.h>

#include <WiFiUdp.h>

#include <Wire.h>  // Arduino IDE 內建

// LCD I2C Library,從這裡可以下載:

// https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads

 

#include “RTClib.h”

RTC_DS1307 RTC;

//DateTime nowT = RTC.now();

#include “DHT.h”

// Uncomment whatever type you’re using!

//#define DHTTYPE DHT11   // DHT 11

#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

//#define DHTTYPE DHT21   // DHT 21 (AM2301)

 

 

#include <LiquidCrystal_I2C.h>

#include <SoftwareSerial.h>

 

uint8_t MacData[6];

 

SoftwareSerial mySerial(0, 1); // RX, TX

char ssid[] = “TSAO”;      // your network SSID (name)

char pass[] = “TSAO1234″;     // your network password

#define MAX_CLIENT_ID_LEN 10

#define MAX_TOPIC_LEN     50

char clientId[MAX_CLIENT_ID_LEN];

char outTopic[MAX_TOPIC_LEN];

 

IPAddress  Meip ,Megateway ,Mesubnet ;

String MacAddress ;

int status = WL_IDLE_STATUS;

boolean ParticleSensorStatus = true ;

WiFiUDP Udp;

 

const char ntpServer[] = “pool.ntp.org”;

const long timeZoneOffset = 28800L;

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

const byte nptSendPacket[ NTP_PACKET_SIZE] = {

0xE3, 0x00, 0x06, 0xEC, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x31, 0x4E, 0x31, 0x34,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

byte ntpRecvBuffer[ NTP_PACKET_SIZE ];

 

#define LEAP_YEAR(Y)     ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)0) || !((1970+Y)%400) ) )

static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0

uint32_t epochSystem = 0; // timestamp of system boot up

 

 

#define pmsDataLen 32

uint8_t buf[pmsDataLen];

int idx = 0;

int pm25 = 0;

uint16_t PM2_5Value=0;         //define PM2.5 value of the air detector module

int NDPyear, NDPmonth, NDPday, NDPhour, NDPminute, NDPsecond;

unsigned long epoch  ;

int HumidityData = 0 ;

int TemperatureData = 0 ;

 

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // 設定 LCD I2C 位址

DHT dht(DHTSensorPin, DHTTYPE);

 

void setup() {

Serial.begin(9600);

mySerial.begin(9600); // PMS 3003 UART has baud rate 9600

lcd.begin(20, 4);      // 初始化 LCD,一行 20 的字元,共 4 行,預設開啟背光

lcd.backlight(); // 開啟背光

 

MacAddress = GetWifiMac() ;

ShowMac() ;

initializeWiFi();

 

initRTC() ;

ShowDateTime() ;

ShowInternetStatus() ;

delay(1500);

}

 

void loop() { // run over and over

ShowDateTime() ;

retrievePM25Value() ;

ShowHumidity() ;

delay(1000); // delay 1 minute for next measurement

 

 

}

 

void ShowMac()

{

lcd.setCursor(0, 0); // 設定游標位置在第一行行首

lcd.print(“MAC:”);

lcd.print(MacAddress);

 

}

 

void ShowInternetStatus()

{

lcd.setCursor(0, 1); // 設定游標位置

if (WiFi.status())

{

Meip = WiFi.localIP();

lcd.print(“@:”);

lcd.print(Meip);

 

}

else

{

lcd.print(“DisConnected:”);

 

}

 

}

void ShowPM25(int pp25)

{

lcd.setCursor(0, 3); // 設定游標位置在第一行行首

lcd.print(“PM2.5:     “);

lcd.setCursor(9, 3); // 設定游標位置在第一行行首

lcd.print(pp25);

 

}

 

 

 

void ShowDateTime()

{

//  getCurrentTime(epoch, &NDPyear, &NDPmonth, &NDPday, &NDPhour, &NDPminute, &NDPsecond);

lcd.setCursor(0, 2); // 設定游標位置在第一行行首

lcd.print(StrDate());

lcd.setCursor(11, 2); // 設定游標位置在第一行行首

lcd.print(StrTime());

//  lcd.print();

 

}

 

String  StrDate() {

String ttt ;

//nowT  = now;

DateTime now = RTC.now();

ttt = print4digits(now.year()) + “-” + print2digits(now.month()) + “-” + print2digits(now.day()) ;

//ttt = print4digits(NDPyear) + “/” + print2digits(NDPmonth) + “/” + print2digits(NDPday) ;

return ttt ;

}

 

String  StringDate(int yyy,int mmm,int ddd) {

String ttt ;

//nowT  = now;

ttt = print4digits(yyy) + “-” + print2digits(mmm) + “-” + print2digits(ddd) ;

return ttt ;

}

 

 

String  StrTime() {

String ttt ;

// nowT  = RTC.now();

DateTime now = RTC.now();

ttt = print2digits(now.hour()) + “:” + print2digits(now.minute()) + “:” + print2digits(now.second()) ;

//  ttt = print2digits(NDPhour) + “:” + print2digits(NDPminute) + “:” + print2digits(NDPsecond) ;

return ttt ;

}

 

String  StringTime(int hhh,int mmm,int sss) {

String ttt ;

ttt = print2digits(hhh) + “:” + print2digits(mmm) + “:” + print2digits(sss) ;

return ttt ;

}

 

 

String GetWifiMac()

{

String tt ;

String t1,t2,t3,t4,t5,t6 ;

WiFi.status();    //this method must be used for get MAC

WiFi.macAddress(MacData);

 

Serial.print(“Mac:”);

Serial.print(MacData[0],HEX) ;

Serial.print(“/”);

Serial.print(MacData[1],HEX) ;

Serial.print(“/”);

Serial.print(MacData[2],HEX) ;

Serial.print(“/”);

Serial.print(MacData[3],HEX) ;

Serial.print(“/”);

Serial.print(MacData[4],HEX) ;

Serial.print(“/”);

Serial.print(MacData[5],HEX) ;

Serial.print(“~”);

 

t1 = print2HEX((int)MacData[0]);

t2 = print2HEX((int)MacData[1]);

t3 = print2HEX((int)MacData[2]);

t4 = print2HEX((int)MacData[3]);

t5 = print2HEX((int)MacData[4]);

t6 = print2HEX((int)MacData[5]);

tt = (t1+t2+t3+t4+t5+t6) ;

Serial.print(tt);

Serial.print(“\n”);

 

return tt ;

}

String  print2HEX(int number) {

String ttt ;

if (number >= 0 && number < 16)

{

ttt = String(“0″) + String(number,HEX);

}

else

{

ttt = String(number,HEX);

}

return ttt ;

}

String  print2digits(int number) {

String ttt ;

if (number >= 0 && number < 10)

{

ttt = String(“0″) + String(number);

}

else

{

ttt = String(number);

}

return ttt ;

}

 

String  print4digits(int number) {

String ttt ;

ttt = String(number);

return ttt ;

}

 

 

 

// send an NTP request to the time server at the given address

void retrieveNtpTime() {

Serial.println(“Send NTP packet”);

 

Udp.beginPacket(ntpServer, 123); //NTP requests are to port 123

Udp.write(nptSendPacket, NTP_PACKET_SIZE);

Udp.endPacket();

 

if(Udp.parsePacket()) {

Serial.println(“NTP packet received”);

Udp.read(ntpRecvBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

 

unsigned long highWord = word(ntpRecvBuffer[40], ntpRecvBuffer[41]);

unsigned long lowWord = word(ntpRecvBuffer[42], ntpRecvBuffer[43]);

unsigned long secsSince1900 = highWord << 16 | lowWord;

const unsigned long seventyYears = 2208988800UL;

//     epoch = secsSince1900 – seventyYears + timeZoneOffset ;

epoch = secsSince1900 – seventyYears ;

 

epochSystem = epoch – millis() / 1000;

}

}

 

 

void getCurrentTime(unsigned long epoch, int *year, int *month, int *day, int *hour, int *minute, int *second) {

int tempDay = 0;

 

*hour = (epoch  % 86400L) / 3600;

*minute = (epoch  % 3600) / 60;

*second = epoch % 60;

 

*year = 1970;

*month = 0;

*day = epoch / 86400;

 

for (*year = 1970; ; (*year)++) {

if (tempDay + (LEAP_YEAR(*year) ? 366 : 365) > *day) {

break;

} else {

tempDay += (LEAP_YEAR(*year) ? 366 : 365);

}

}

tempDay = *day – tempDay; // the days left in a year

for ((*month) = 0; (*month) < 12; (*month)++) {

if ((*month) == 1) {

if (LEAP_YEAR(*year)) {

if (tempDay – 29 < 0) {

break;

} else {

tempDay -= 29;

}

} else {

if (tempDay – 28 < 0) {

break;

} else {

tempDay -= 28;

}

}

} else {

if (tempDay – monthDays[(*month)] < 0) {

break;

} else {

tempDay -= monthDays[(*month)];

}

}

}

(*month)++;

*day = tempDay+2; // one for base 1, one for current day

}

 

void retrievePM25Value() {

int idx;

bool hasPm25Value = false;

int timeout = 200;

while (!hasPm25Value) {

idx = 0;

memset(buf, 0, pmsDataLen);

while (mySerial.available()) {

buf[idx++] = mySerial.read();

}

 

if (buf[0] == 0x42 && buf[1] == 0x4d) {

pm25 = ( buf[12] << 8 ) | buf[13];

Serial.print(“pm2.5: “);

Serial.print(pm25);

Serial.print(” ug/m3″);

Serial.println(“”);

hasPm25Value = true;

ShowPM25(pm25) ;

}

timeout–;

if (timeout < 0) {

Serial.println(“fail to get pm2.5 data”);

break;

}

}

}

 

void initializeWiFi() {

while (status != WL_CONNECTED) {

Serial.print(“Attempting to connect to SSID: “);

Serial.println(ssid);

// Connect to WPA/WPA2 network. Change this line if using open or WEP network:

status = WiFi.begin(ssid, pass);

//   status = WiFi.begin(ssid);

 

// wait 10 seconds for connection:

delay(10000);

}

 

// local port to listen for UDP packets

Udp.begin(2390);

}

 

void printWifiData()

{

// print your WiFi shield’s IP address:

Meip = WiFi.localIP();

Serial.print(“IP Address: “);

Serial.println(Meip);

 

 

// print your MAC address:

byte mac[6];

WiFi.macAddress(mac);

Serial.print(“MAC address: “);

Serial.print(mac[5], HEX);

Serial.print(“:”);

Serial.print(mac[4], HEX);

Serial.print(“:”);

Serial.print(mac[3], HEX);

Serial.print(“:”);

Serial.print(mac[2], HEX);

Serial.print(“:”);

Serial.print(mac[1], HEX);

Serial.print(“:”);

Serial.println(mac[0], HEX);

 

// print your subnet mask:

Mesubnet = WiFi.subnetMask();

Serial.print(“NetMask: “);

Serial.println(Mesubnet);

 

// print your gateway address:

Megateway = WiFi.gatewayIP();

Serial.print(“Gateway: “);

Serial.println(Megateway);

}

 

void initRTC()

{

Wire.begin();

RTC.begin();

SetRTCFromNtpTime() ;

if (! RTC.isrunning()) {

Serial.println(“RTC is NOT running!”);

// following line sets the RTC to the date & time this sketch was compiled

//    RTC.adjust(DateTime(__DATE__, __TIME__));

 

}

}

void SetRTCFromNtpTime()

{

retrieveNtpTime();

//DateTime ttt;

getCurrentTime(epoch+timeZoneOffset, &NDPyear, &NDPmonth, &NDPday, &NDPhour, &NDPminute, &NDPsecond);

//ttt->year = NDPyear ;

Serial.print(“NDP Date is :”);

Serial.print(StringDate(NDPyear,NDPmonth,NDPday));

Serial.print(“and “);

Serial.print(“NDP Time is :”);

Serial.print(StringTime(NDPhour,NDPminute,NDPsecond));

Serial.print(“\n”);

 

RTC.adjust(DateTime(epoch+timeZoneOffset));

 

 

}

void ShowHumidity()

{

float h = dht.readHumidity();

// Read temperature as Celsius (the default)

float t = dht.readTemperature();

// Read temperature as Fahrenheit (isFahrenheit = true)

float f = dht.readTemperature(true);

HumidityData = (int)h ;

TemperatureData = (int)t ;

Serial.print(“Humidity :”) ;

Serial.print(h) ;

Serial.print(“%  /”) ;

Serial.print(t) ;

Serial.print(“C  \n”) ;

// Check if any reads failed and exit early (to try again).

if (isnan(h) || isnan(t) || isnan(f)) {

Serial.println(“Failed to read from DHT sensor!”);

return;

}

lcd.setCursor(11, 3); // 設定游標位置在第一行行首

lcd.print((int)h);

lcd.print(“% “);

lcd.print((int)t);

 

}

 

資料下載:https://github.com/brucetsao/makerdiwo/tree/master/201604/ PMS3003AirQualityV81A

我們可以連上網際網路,也可以偵測 PM 2.5 的空氣懸浮粒子、PM 1.0 的空氣懸浮粒子 &  PM 10 的空氣懸浮粒子,溫度、濕度都也可以偵測到值。

未命名

(圖 3-a)讀取完整溫溼度等資料之開發 IDE 監控畫面

未命名

(圖 3-b)空氣粒子感測裝置完整資訊之 LCD 顯示畫面

本文為『PM2.5空氣感測器』系列第四篇:電路設計下篇,主要介紹如何將 PM 2.5 空氣感測器加入 RTC 時鐘模組,溫溼度感測模組,並將『PM2.5空氣感測器』系列第三篇:電路設計上篇與本篇文章一同閱讀與整合,逐一完成 PM 2.5 空氣感測器的電路安裝。

後續筆者還會繼續發表『PM2.5空氣感測器』系列的文章,讓我們在未來可以創造出更優質、智慧化的家庭。

參考資料: