Category Archives: 教學

Home Posts Categorized as “教學”
擷取

LASS Field Try 第二彈:即時數據呈現系統

No Comments

前情提要
  • 使用者需要一個即時知道自己感測器資料的方法,例如:
  1. FT1_001 的即時資料顯示介面(https://thingspeak.com/channels/61022
  1. FT1_001 的PM2.5 即時資訊儀表板介面(https://thingspeak.com/apps/plugins/25091
  • 以下介紹如何建立一個和上面例子中一樣的網頁
準備步驟
 Windows 作業系統
  • 要先確定電腦有安裝 python
  • WINDOWS 要注意環境變數要設定好
  • 開啟命令提示字元
  • 開始 –> 執行 –> cmd
  • 安裝 pip (Python套件管理程式ˇ)
  • 輸入 python get-pip.py
  • 利用 pip 來安裝 mqtt 函式庫
  • pip install paho-mqtt
  • pip install httplib2
  • 在 ThingSpeak.com 上建立一個帳號(若已有帳號可略過)
  • 使用ThingSpeak.com帳號登入系統後,選擇 Channels -> My Channels -> N
  • ew Channel
  • 在 New Channel 的設定網頁中依序填入
  • Name: 您所要呈現的 LASS Field Try 1 的設備 ID (FT1_XXX 格式)
  • Field 1: 輸入 PM2.5
  • Field 2: 先在旁邊方格打勾,接著在文字框輸入 Temperature
  • Field 3: 先在旁邊方格打勾,接著在文字框輸入 Humidity
  • Field 4: 先在旁邊方格打勾,接著在文字框輸入 Battery Level
  • Tags: 輸入 PM2.5, LASS, LinkIt One
  • Make Public?: 在方格打勾
  • 最後點擊 “Save Channel"
  • 在 My Channels 中選擇剛剛設定的 Channel,接著選取 API Keys,記下上面的 Write API Key (共16個英數字元)
  • 下載  lass_pm25_to_thingspeak.py
  • 執行 lass_pm25_to_thingspeak.py
  • 輸入 python lass_pm25_to_thingspeak.py FT1_0XX WRITE_ API_KEY
  • FT1_0XX 為裝置名稱
  • WRITE_ API_KEY 為寫入資料到 thinkspeak 所需要的 key
MAC 作業系統
  • 在 ThingSpeak.com 上建立一個帳號(若已有帳號可略過)
  • 使用ThingSpeak.com帳號登入系統後,選擇 Channels -> My Channels -> New Channel
  • 在 New Channel 的設定網頁中依序填入
  • Name: 您所要呈現的 LASS Field Try 1 的設備 ID (FT1_XXX 格式)
  • Field 1: 輸入 PM2.5
  • Field 2: 先在旁邊方格打勾,接著在文字框輸入 Temperature
  • Field 3: 先在旁邊方格打勾,接著在文字框輸入 Humidity
  • Field 4: 先在旁邊方格打勾,接著在文字框輸入 Battery Level
  • Tags: 輸入 PM2.5, LASS, LinkIt One
  • Make Public?: 在方格打勾
  • 最後點擊 “Save Channel"
  • 在 My Channels 中選擇剛剛設定的 Channel,接著選取 API Keys,記下上面的 Write API Key (共16個英數字元)
  • 執行LASS資料轉ThingSpeak程式:./lass_pm25_to_thingspeak.py LASS_DEVICE_ID ThingSpeak_API_Key
  • 其中 LASS_DEVICE_ID 為 LASS Field Try 1 的設備 ID (FT1_XXX 格式)
  • ThingSpeak_API_Key 為新設 ThingSpeak Channel的 ThingSpeak Channel (共16個英數字元)
  • 在unix like系統上,可用下列指令將程式丟到背景執行(除非關機,否則即使logout也會繼續執行): nohup ./lass_pm25_to_thingspeak.py LASS_DEVICE_ID ThingSpeak_API_Key &
  • python library installation
  • pip install paho-mqtt
  • pip install httplib2
  • 回到 ThingSpeak 網站,在 My Channels 中選擇剛剛設定的 Channel,接著選 Private View 或 Public View 即可見到即時更新的感測資料,其中 Public View 的網頁網址可以分享給其他朋友(不需要 ThingSpeak 帳號即可瀏覽)
其他功能
  • 若要自行撰寫資料相關程式,可參考 ThingSpeak API 網頁
  • 若要設定「斷線通知」,可在 ThingSpeak 下依照下列步驟設定
  • ThingSpeak 主畫面 -> Apps -> React
  • 點擊 「New React」
  • 在 React Name 輸入這個 Reack 的名稱,例如:"LASS FT1_XXX Offline"
  • 在 Condition Type 選擇 No Data Check
  • 在 Test Frequeny 選擇 Every 10 minutes
  • 在 Condition 的第一個選單中,選擇對應的 ThingSpeak Channel
  • 在 Condition 的第二個方框中,輸入 “2″
  • 在  Action 的選單中選擇 ThingTweek 
  • 在 then tweet 方框中輸入當設備斷線時,您希望收到的訊息,例如: “[LASS] Your device FT1_XXX is offline"
  • 在 using Twitter account 中,選取您的 Twitter 帳號 (註:您必須事先設定好 ThingSpeak 和您的 Twitter 帳號連結)
  • 在 Options 中選擇 Run action only the first time the condition is met
  • 點擊 「Save React」
  • 按照以上設定,倘若LASS設備出現當機現象時,最遲在 12 分鐘內,在您的 Twitter 帳號上便會收到 ThingSpeak 傳來的通知
  • 若要設定「PM2.5警報」,可在 ThingSpeak 下依照下列步驟設定
  • ThingSpeak 主畫面 -> Apps -> React
  • 點擊 「New React」
  • 在 React Name 輸入這個 Reack 的名稱,例如:"LASS FT1_XXX Alarm"
  • 在 Condition Type 選擇 Numeric
  • 在 Test Frequeny 選擇 Every 30 minutes
  • 在 Condition 的第一個選單中,選擇對應的 ThingSpeak Channel
  • 在 Condition 的第二個選單中,選擇 1 (PM2.5)
  • 在 Condition 的第三個選單中,選擇 is greather than
  • 在 Condition 的輸入方框中,輸入您想要設定的PM2.5警報臨界值,例如 35
  • 在  Action 的選單中選擇 ThingTweek 
  • 在 then tweet 方框中輸入當PM2.5超過臨界值時,您希望收到的訊息,例如: “[LASS] PM2.5 of FT1_XXX is %%trigger%% now!",其中 %%trigger%% 將來在送出訊息時,會自動被取代成 PM2.5 的量測值
  • 在 using Twitter account 中,選取您的 Twitter 帳號 (註:您必須事先設定好 ThingSpeak 和您的 Twitter 帳號連結)
  • 在 Options 中選擇 Run action only the first time the condition is met
  • 點擊 「Save React」
  • 按照以上設定,倘若LASS設備量測到超過臨界值的 PM2.5濃度時,最遲在 30 分鐘內,在您的 Twitter 帳號上便會收到 ThingSpeak 傳來的通知
lass.hackpad1

LASS Field Try #1: Device Instructions(English)

No Comments

Hardware Instructions
  • Please prepare the following units
  • LinkIt One development board (including battery, GPS antenna, and WiFi antenna)
  • Note that the all-in-one package (including everything above) is available in the following links:
  • Linkit ONE HW block diagram:
  • Please connect the battery, GPS receiver (the square one), and WiFi antenna (the thin rectangle one) to the LinkIt One board. You will find small text under the connectors noting GPS and WiFi on the board.
  • Please refit the 1st (purple), 2nd (orange), and 5th (green) wiring of the G3 sensor iknto the Dupont connector. (If you buy the all-in-one package from the above link, you can skip this step).
  • Please connect the following wirings of the G3 sensor to the corresponding pins on the LinkIt One board:
  • the 1st (purple) wiring to the 5V pin;
  • the 2nd (orange) wiring to the GND pin; and 
  • the 5th (green) wiring to the Rx pin.
  • Please connect the following wirings of the Grove – Temperature and Humidity Sensor Pro to the corresponding pins on the LinkIt One board:
  • the 1st (yellow) wiring to the D2 pin;
  • the 2nd (white) wiring to the D3 pin (this wiring is NOT used, and may have been removed in the all-in-one package provided in the above link); 
  • the 3rd (red) wiring to the 3V3 pin; and 
  • the 4th (black) wiring to the GND pin.
  • Now, the hardware is ready to work! Feel free to adjust the sensor and antenna to your favorite relative positions. You can also find a good cover/enclosure for your set. If you have a 3D printer, you may want to print out one of the existing design from the LASS archive (https://github.com/LinkItONEDevGroup/LASS/tree/master/3dp).
Software Instructions
  • If this your first time using LinkIt One, please refer to the official tutorial provided by MediaTek (MS Windows plafformMac OS platform). Note that, it is strongly recommend to use the Arduino IDE version indicated in the tutorial, otherwise you may experience a number of hardware related issues.
  • Please install the following libraries (from here) into your Arduino environment. In case you have trouble installing libraries into your Arduino environment, you may find this tutorial useful: Installing Additional Arduino Libraries.
  • Under Arduino IDE, please open the LASS.ino project, and edit the configuratin.h file as follows:
  • #define APP_ID (APPTYPE_SYSTEM_BASE+X): please change ‘X’ to ‘1’
  • #define DEVICE_ID “YOUR_DEVICE_NAME": please change “YOUR_DEVICE_NAME" to your own device name. It is suggested to report your device name on the LASS page in order to prevent duplicate, which may result in issues in future data collection/analysis/visualization.
  • #define WIFI_SSID “LASS": please change “LASS" to your WiFi SSID
  • #define WIFI_PASS “LASS123456″: please change “LASS123456″ to your WiFi password (you can skip this step if there is no WiFi password)
  • #define WIFI_AUTH LWIFI_WPA: please keep it as it is (if you are using WPA encryption), or change it to “LWIFI_WEP" (if you are using WEP encryption) or “LWIFI_OPEN" (if there is no encryption used)
  • Now, the software is ready to go! Please compile the codes and upload to your LinkIt One board. (please make sure the board is connected to your PC).
Testing
  • You can also find the GIS visualization of your device at your location:
  • LASS also provides a number of approaches to access the measurement data of your device and other device. For more detailed information, please check out the webpage: LASS Data Platform
FAQ
  • Where can I find help if I need technical help?
  • You are welcome to post on LASS FB. Please do remember to include the error messages and other useful information so people in the community can help you.
  • You are also encouraged to help maintain this page, and make it more helpful for people in the LASS community.
未命名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空氣感測器』系列的文章,讓我們在未來可以創造出更優質、智慧化的家庭。

參考資料:

 

post_56e916a97b250

智慧家庭:PM2.5 空氣感測器(硬體組裝上篇)

No Comments

作者:曹永忠/vMaker

本篇主要是教大家如何組立空氣粒子感測裝置硬體組裝,但是為了讓讀者簡化實驗,並不讓讀者自行設計與製作空氣粒子感測裝置外部產品硬體結構,而是採用市面上各個相關模組來設計出空氣粒子感測裝置。

空氣粒子感測裝置基本組成要素

首先,我們先將購買所需的元件,由下圖可以見到以下所有零件的一覽圖:

未命名

未命名

未命名

本文所有的零件都會以零件包的方式出版,並跟國內最大的電子零件供應商 iCShop 合作,讀者若有任何需要,請逕行與該公司接洽。對於零件使用上,若讀者仍不熟悉,可以參考文末的參考資料。

安裝阿米巴開發板

首先,我們先行安裝瑞昱半導體公司的阿米巴(Ameba)開發板(圖(b)),如下圖所示,我們先拿出外殼(圖(z))的四顆塑膠螺絲柱與四顆塑膠螺絲。

未命名

(圖 2)阿米巴開發板固定柱

如下圖所示,我們先拿出外殼底板,並將上圖所示之的四顆塑膠支柱與四顆塑膠螺絲,鎖於如下圖所示之底板上。

未命名

(圖 3)將固定柱鎖於外殼底板

將瑞昱半導體公司的阿米巴(Ameba)開發板置於上圖所示之的塑膠支柱上,並拿出與四顆塑膠螺絲帽,將之固定鎖緊。

未命名

(圖 4)將阿米巴開發板鎖於外殼底板

如上圖所示,我們完成出瑞昱半導體公司的阿米巴(Ameba)開發板之裝設。

安裝偵測空氣懸浮粒子感測器

下一步我們將裝設明攀藤科技(Plantower)的 PMS3003 空氣懸浮粒子感測器(註 1),首先我們拿出外殼元件內的小螺絲與小螺絲帽,如下圖所示,先放於桌面上。

未命名

(圖 5)固定 PM 感測器之螺絲

如下圖所示,我們拿出如圖(a)所示之偵測空氣懸浮粒子感測器(PMS3003),並且先將塑膠膜拆開,可以看到如下圖所示之對稱的兩個螺絲孔。

未命名

(圖 6)空氣懸浮粒子感測器

如下圖所示,我們將如上圖所示之對稱的兩個螺絲孔,對齊外殼底板之右上方兩個螺孔,位置如下圖所示之位置。

未命名

(圖 7)安裝空氣懸浮粒子感測器於底板上

如下圖所示,我們將螺絲由外殼底板之右上方兩個螺孔往上插入後,如下圖所示,將螺絲帽旋緊。

未命名

(圖 8)鎖緊空氣懸浮粒子感測器螺絲

完成裝置偵測空氣懸浮粒子感測器(PMS3003)之後,整個外觀如下圖所示。

未命名

(圖 9)空氣懸浮粒子感測器安裝完成

安裝麵包板

下一步我們將裝設小型麵包板(圖(f)),因為我們必須裝設許多元件,如果所有元件都直接連接到阿米巴開發板,恐怕無法同時間連接這麼多必要的元件,所以我們必須透過麵包板來擴充可以連接的元件,特別是每一個元件都需要電力供應,而阿米巴開發板無法同時供應這麼多的元件電源插孔。

未命名

(圖 10)麵包板

為了擴充麵包板,我們先拿出單心線(圖(g)),如下圖所示,先將單心線進行裁剪。

未命名

(圖 11)單心線

如下圖所示,我們先把上圖所示之單心線,進行裁剪單心線為五條 45 mm~55 mm 長度,並使用撥線鉗或斜口鉗將五條單心線雙邊各剝去 6 mm~8 mm 長度的外皮,使之露出金屬的單心銅線。

未命名

(圖 12)五條單心線

將五條剝去外皮單心線依序插入下圖所示之麵包板,使之橫跨五個雙邊通道,使之導通。

未命名

(圖 13)裝置單心線於麵包板

如下圖所示,我們再剪單心線一條 25 mm~35 mm 長度,並使用撥線鉗或斜口鉗將一條單心線雙邊各剝去 6 mm~8 mm 長度的外皮,使之露出金屬的單心銅線。

未命名

(圖 14)補上單心線

如下圖所示,將一條剝去外皮單心線依序插入麵包板,使之連接第二條與第三條單心線的通道,使之導通。

未命名

(圖 15)連接 VCC 雙線

最後,我們將完成單心線布置的麵包板放置外殼底板之右下方,整個外觀如下圖所示。

未命名

(圖 16)安裝麵包板於底殼上

安裝溫溼度模組

因為我們需要量測溫度、濕度,如果使用單一功能的感測器,會增加裝置元件,所以我們採用溫溼度合一感測元件:DHT22溫濕度模組(圖(e))。

首先,我們將外殼之右側板如下圖所示放置。

未命名

(圖 17)外殼右側

如下圖所示,我們取出一個小螺絲與小螺帽以及 DHT22 溫濕度模組。

未命名

(圖 18)DHT22 與螺絲

由於外殼設計因素,我們將 DHT22 溫濕度模組的接腳解焊,重新焊接一個 180 度的三 pin 的接腳。

未命名

(圖 19)改裝腳位的 DHT22

將 DHT22 溫濕度模組塞入右側板的下方,並將 DHT22 溫濕度模組上方孔洞,對準右側板的孔洞,方便鎖螺絲。

未命名

(圖 20)將 DHT22 裝於外殼上

如下圖所示,將 DHT22 溫濕度模組於右側板的孔洞,將螺絲鎖入後,用螺帽旋緊。

未命名

(圖 21)將 DHT22 鎖緊於外殼上

取出三條一公一母杜邦線(圖(h)),準備連接 DHT22 溫濕度模組。

未命名

(圖 22)取出 DHT22 的杜邦線

三條一公一母杜邦線,連接於 DHT22 溫濕度模組之電源接腳與訊號接腳。

未命名

(圖 23)將杜邦線插於 DHT22 上

完成將 DHT22 溫濕度模組置於外殼右板上。

安裝 RTC 時鐘模組

由於本裝置需要精準的時間與持續不斷的時間資訊,如果耗用阿米巴開發板於計算時間會大材小用,而且可能增加程式的複雜度。所以我們引入了時鐘模組,採用 Tiny RTC DS1307 時鐘模組(圖(d))。

首先,我們將外殼的下側板如下圖所示放置。

未命名

(圖 24)外殼下板

如下圖所示,我們取出一個 DS1307 RTC 時鐘模組,再取出如泡棉膠(圖(m))。

未命名

(圖 25)取出 RTC 時鐘模組

我們將泡棉膠撕開一面,黏到 DS1307 RTC 時鐘模組上。

未命名

(圖 26)黏上雙面膠於 RTC 時鐘模組

我們再將泡棉膠另一面撕開,黏到外殼的中間偏左方的位置上。

未命名

(圖 27)將 RTC 時鐘模組黏到外殼

如下圖所示,我們取出四條一公一母杜邦線,準備連接 DS1307 RTC 時鐘模組。

未命名

(圖 28)取出 RTC 用的杜邦線

我們將四條一公一母杜邦線,連接於 DS1307 RTC 時鐘模組之電源接腳與 I2C 訊號接腳。

未命名

(圖 29)將杜邦線裝於 RTC 時鐘模組

預期完成組裝目標

當完成硬體、電路與軟體安裝後,只要再接上電源就可以完成 PM2.5 空氣感測器的裝置了。

未命名

(圖 59)正常開機之成品圖

本文為『PM 2.5 空氣感測器』系列第二篇:硬體組裝篇上,主要介紹之裝置所有元件,並且一步一步教導讀者如何將這些元件組裝,完成 PM 2.5 空氣感測器的硬體安裝。後續筆者還會繼續發表『PM 2.5 空氣感測器』系列的文章,讓我們在未來可以創造出更優質、智慧化的家庭。

註 1:北京攀藤科技有限公司是一家專注於空氣品質感測器研發、生產與銷售的高科技企業,作為行業領跑者,公司通過不斷創新和品質追求,已與國內外多家知名企業建立了良好的戰略合作夥伴關係。

參考資料:

  • 曹永忠. (2016). 智慧家庭:PM2.5 空氣感測器(感測器篇).   Retrieved fromhttp://vmaker.tw/project/view/695
  • 曹永忠, 许智诚, & 蔡英德. (2014). Arduino 电风扇设计与制作: Using Arduino to Develop a Controller of the Electric Fan. 台湾、彰化: 渥玛数位有限公司.
  • 曹永忠, 許智誠, & 蔡英德. (2013). Arduino 電風扇設計與製作: The Design and Development of an Electronic Fan by Arduino Technology (初版 ed.). 台灣、彰化: 渥瑪數位有限公司.
  • 曹永忠, 許智誠, & 蔡英德. (2014a). Arduino EM-RFID 門禁管制機設計:The Design of an Entry Access Control Device based on EM-RFID Card (初版 ed.). 台灣、彰化: 渥瑪數位有限公司.
  • 曹永忠, 許智誠, & 蔡英德. (2014b). Arduino RFID 门禁管制机设计: Using Arduino to Develop an Entry Access Control Device with RFID Tags. 台湾、彰化: 渥瑪數位有限公司.
  • 曹永忠, 許智誠, & 蔡英德. (2014c). Arduino RFID 門禁管制機設計: The Design of an Entry Access Control Device based on RFID Technology (初版 ed.). 台灣、彰化: 渥瑪數位有限公司.
  • 曹永忠, 許智誠, & 蔡英德. (2015a). Ameba 空气粒子感测装置设计与开发(MQTT篇):Using Ameba to Develop a PM 2.5 Monitoring Device to MQTT (初版 ed.). 台湾、彰化: 渥瑪數位有限公司.
  • 曹永忠, 許智誠, & 蔡英德. (2015b). Ameba 空氣粒子感測裝置設計與開發(MQTT篇)):Using Ameba to Develop a PM 2.5 Monitoring Device to MQTT (初版 ed.). 台湾、彰化: 渥瑪數位有限公司.
57721adbe03b3948839f97f732c6981d.image_.530×397

Linkit Smart 7688 Duo上使用PMS3003 (G3) 空汙感測器

No Comments

作者:

Behind the project…

我是最近一段時間才開始摸索 IoT 生態的,是興趣;但相對我過去的工作經驗,應該是算是業餘的嘗鮮族。然後,發現雖然不是電子背景出身要動動手當個【創客】好像不是件太困難的事。於是,我跟在智慧家電領域的同學 Harvey 聊了這個想法,他毫不考慮了丟了個 Arduino 套件讓我嘗試。這真是有趣的世界,我好像可以去做一些事,一些以前不認為可以獨力幹的事。這也開始了我的創客生活。

回到我那個搞智慧家電的同學 Harvey,在玩弄他丟給我的 Arduino 一段間後,有一天他又問我說能不能搞個空氣品質觀測的應用,因為… 有這個需求。聽起來很有趣,於是有了這個 project 的想法。

他說就很簡單,弄個空氣品質感測器,然後把測得的數據顯示出來,然後…
最好可以把數據資料給保存起來 (Database/Cloud?)
最好可以把數據資料整理再呈現 (Analysis and vistualization?)
最好可以透過不同的介面被呈現 (LCD/TFT/Web/App?)
最好可以盡可能地把體積小型化 (7688/Duo?)
最好可以把整體費用壓到最便宜 (Arduino+Wifi or 7688/Duo?)
最好可以… 明天醒來這一切都實現了。

Arduino 讓很多創客可以輕易的踏出第一步,而且獲得成就感。

但是隨著想法和功能越趨複雜,就發現 Arduino 開發板好像要疊的積木更多,要寫的 sketch code 更長,然後 Flash Rom 的 size 好像也開始不夠用了。於是,我開始尋找具備相當整合性的開發板:我想至少幫我把網路(Ethernet 或 Wifi 都好)整合在開發板吧,或至少讓我除了有 Arduino C 以外,還具備更有伸展性的開發環境吧(可以來點跟互聯網相關的嗎)!

我在想把這類裝置和互聯網連接上是一件有趣的事(我在講什麼!IoT 不就是為了這檔事嗎?),事實上我很喜歡這樣的結果。

於是我幾番考慮後決定使用【LinkIt Smart 7688 Duo】。當然,其他開發板也做得到,而且功能也更強大,只是我比較背骨偏偏選擇這個新上市的薄薄的一片來玩。

事實上,因為 7688/Duo 才上市(2015.12.25)不久,網路上的資源有限,摸索 MCU 和 MPU 的互動過程中也讓我吃了不少閉門羹,畢竟我只是幾個月的創客生手,這摸索的路是必經的過程就不再多做文章了。

有感這一路學習的資源都是透過互聯網來的,很多觀念和程式技巧的養成都是分享自許多前人的貢獻,我真的很感激(鞠躬+讚嘆)。那天跟 LASS 的哈爸在臉書訊息上聊了一下,發現除了 7688/Duo 資源的短缺著實讓很多人學習過程受挫外,開源共享的模式也還未遍地開花結果,很多成功的經驗自然無法繼續被擴充和延續。有鑑於此,既然自己是喝互聯網的奶水長大的,就也分享自己的奶水(呵)給大家吧,我決定把自己這走這一遭的經驗全部給紀錄並公開給大家參考,希望可以幫助更多初心者一路好走,ㄟ 是起步的順利一點。

Ready? Let’s Go!

有了之前一些嘗試的經驗,這次我決定要以簡潔和滿足最低功能需求的前提下去實作這個 project。

一開場就梭哈了,以下是這次硬體上我所用上的元件:

  • Planter PMS3003(G3)/PMS5003(G5)PMS3003
  • LinkIt Smart 7688 DuoLinkit Smart 7688 Duo
  • Grove Breakout for LinkIt Smart 7688 DuoGrove Breakout for LinkIt Smart 7688 Duo
  • Hitachi HD44780U 1602 LCD (I2C)Hitachi HD44780U 1602 LCD
  • DS1307 RTC(I2C)DS1307_RTC

選擇 PMS3003(或是叫 G3)的原因是有以下特點:

  • 數據準確:鐳射檢測,穩定、一致性好
  • 回應速度快速:使用環境變更時回應時間小於 10 秒
  • 獨立運作:使用標準的通訊埠輸出量測資料
  • 不須外加元件:自備風扇,不須外加。
  • 分析能力高:分辨顆粒最小直徑達 0.3 微米

為了降低實作過程中硬體配置的接線複雜度(另一方面是偷懶),我選擇搭配了 Grove Breakout for LinkIt Smart 7688 Duo 這塊擴充版來串接另外兩個 I2C 設備(用在 1602 LCD 和 RTC);當然這不是必要的,你也可以有另外兩個替代方案:

  • 如果想降低成本或是先做開發測試,可以使用麵包板(breadboard)用杜邦線來處理配線。
  • 如果想簡化整個硬體的體積,可以考慮剪裁一張適當的PCB來焊接處理配線。

接下來的步驟就像玩樂高積木一樣,請把它們組合起來。

  • 7688/Duo 開發板直接對準腳位插上 Grove Breakout就可以。
  • 另外 LCD 和 RTC 因為都是選用 grove I2C 規格,也直接插上去 Grove Breakout 就可以。
  • 加工(1),因為 PMS3003 的排線是 Molex 1.25mm 8P 的規格,為了方便和開發板那端接,我自己裁了一小塊PCB然後把這8條線焊上標準的 2.54mm 排插(參考附圖)。
  • 加工(2),非必要,請視實際情況參考,因為我選用LCD元件供電是5v,另外 PMS3003 的供電也是 5v,但是開發板上的 5v 只有一個插槽,我就直接利用加工(1)裁的那塊PCB焊上一條5v共腳的單排插,把所有要用到5v的供電的元件都一起接過來,然後再接一條線過去開發板上的5v。
  • 非必要,請視實際情況參考,把LCD元件的 VCC/5v 改接到PCB 剛剛焊的 5v 共腳排插。
  • 參考 PMS3003 的 datasheet 把: PIN(1) VCC/5v 接到 PCB 剛剛焊的 5v 共腳排插。
  • 參考 PMS3003 的 datasheet 把: PIN(2) GND 接到開發板的 GND 端。
  • 參考 PMS3003 的 datasheet 把: PIN(4) RXD 接到開發板的 D10 端。
  • 參考 PMS3003 的 datasheet 把: PIN(5) TXD 接到開發板的 D09 端。

PMS3003 PIN out

pms3003_pinout

PMS3003 G3 PM2.5 粉塵感測器接腳圖。資料出處:https://www.dfrobot.com/wiki/index.php?title=PM2.5_laser_dust_sensor_SKU:SEN0177

8 Pin 接腳和 5v共腳排插加工PCB

PCB for 8 PIN

整體實物佈線配置(由於我還沒有整理線路,所以圖片凌亂的線路傷眼睛請不要怪我)
另外圖中配線有一大把接到 LCD 的原因是那塊 LCD 和 I2C 控制晶片模組是我分開買自己再用杜邦排線焊接的,直接選購帶I2C控制晶片模組的 1602 LCD 省事多了,又免掉一堆線路。
The System
=================================================

Plan for the software architecture

接下來我們可以開始來聊聊程式架構和佈局了。

但是,在此之前我想先借用 Linkit Smart 7688 Duo Developers’ Guide 上面的一張圖片來引導7688/Duo程式架構可能的方式有哪些,以及我選擇用什麼模式來開發。

development model of Linkit Smart 7688 Duo

如果你選擇了 Linkit Smart 7688 Duo 這個開發板,你應該(或許)知道這個板子上面比 Linkit Smart 7688 多了一顆 ATMega32u4 MCU 晶片。這意味著兩件事,(1)你可以在 7688/Duo 上面實現大部分 Arduino(UNO) 可以做的事。(2)你的軟體開發模式可以有更多元的模式,如上圖(右)揭露的三種方式。

我的開發模式選擇第二種模型,也就是預設的方法,因為在這裡我不需要 MPU 下任何溝通指導給 MCU,單單僅要用它丟過來的資料即可:
SerialPort Model

我的想法是,既然開發板同時有 MPU(MT7688) 和 MCU(ATMega32u4),那麼就讓兩顆晶片分工做他們擅長的事吧;也就是對sensors 讀取資料的事完全交給 MCU 做,讀到的資料透過內部 Serial Port 協定傳給 MPU,然後所有邏輯處理和聯網作業就交給 MPU。以上理由是我自己掰的,不管有沒有道理聽聽就好,重點他真的可以跑得動(初心者,我好感動!)。

接下就是整體的架構規劃了,我把整個實作又再切成兩大塊,第一部分是純粹發生在 Linkit Smart 7688 Duo 身上的程式,這也是我會多著墨的地方,以及第二部分是在網頁呈現的部分這部分就偏離 7688/Duo 的探討範圍,我會僅就 UI 的呈現說明,不細究程式的撰寫。

在 Linkit Smart 7688 Duo 上面運行的程式分 MCU 端以及 MPU 端的工作,我們可以用兩隻獨立的程式來滿足不同的功能需求:

  • MCU 主要的工作有3件事,(1)讀取 sensors 資料,(2)把讀取到的 sensors 資料顯示在 LCD,(3)打包 sensors 資料給 MPU
  • MPU 的工作有就只有一件事,把收到來自 MCU 的 sensors 資料寫入資料庫。

在資料保存和網頁呈現端有兩種可以選擇,就端看你熟悉怎樣的程式技巧了。如果熟 PHP 可以考慮用第一種,如果非常懂Node.js 可以走第二種方法。我比較偷懶,用第一種架構(PHP)來實作資料保存。
Development Model 1
Development Model 2

MCU(ATMega32u4) side programming
MCU 程式下載 (.ino)

我要說好在 7688/Duo 用的 MCU 是和 Arduino(UNO) 相同的 ATMega32u4 晶片,所以我們可以把 Arduino IDE 開發的那套經驗,完全套用在這裡甚至不用修改程式碼(我試過了,移植性是 100%)。有關 Arduino IDE 的環境操作就請自己練習了,我唯一要提醒的是:請務必記得依照 Linkit Smart 7688 Duo Developer’s Guide 把 Arduino IDE 的開發板環境設定成 7688/Duo 啊~ 不然你的紅色錯誤訊息會一直出現…

我們直接切入主題,上面說我們規劃的時候,就只打算讓 MCU 負責以下3件事:

  • (1)讀取 sensors 資料
  • (2)把讀取到的 sensors 資料顯示在 LCD
  • (3)打包 sensors 資料給 MPU

(1)讀取 sensors 資料

RTC時間的讀取 和 LCD 資料的顯示應該算超簡單的練習,相信直接看 sketch code 就可以懂,這裡就不贅述了。我們直接來看 PMS3003 (G3) 這顆 sensor 的參數特性:

首先,它可以採樣測定的空氣懸浮微粒有三種規格 0.3-1.0um/1.0-2.5um/2.5-10um,也就是說我們可以拿到 PM1.0/PM2.5/PM10 的測定資料(ug/m3)。而且PMS3003 的 datasheet 寫到他有兩套檢定空氣品質濃度的方法,分別是可以獲得「大氣環境下」和「標準顆粒物」兩組資料值,所以程式裡面每一次從 sensor 那邊得到的資料就會有2組,6個測定值。(這裡我會在意的是「大氣環境下」測得的這組)

這裡我要補充說明的是,範例裡面使用程式去讀取 PMS3003 資料那段邏輯在網路上有多種不同的實作(讀到的資料值都相同啦),我是參考一個程式寫的很簡潔的網友的(雖然他的方法也偷懶少做了checksum),如果看不懂建議您直接看一下 PMS3003 的 datasheet。或是參考下面這另一個出處的邏輯:https://www.dfrobot.com/wiki/index.php?title=PM2.5_laser_dust_sensor_SKU:SEN0177

再來,它的資料介面使用標準序列阜(Serial port baudrate: 9600; Parity: None; Stop Bits: 1; packet length is fixed at 24 bytes.)讀取,從PIN(4)和PIN(5)接腳可以和他溝通 (RX/TX)。這裡有個注意的地方,如果我們直接以 Arduino 的 Serial1 類別去讀取 RX/TX 將會有問題。原因是 7688 把預設的第一組 UART(D0/D1) 保留給 MCU 和 MPU 的溝通使用,所以把 PMS3003的 RX/TX 插上 7688/Duo 的 D0/D1 是讀不到資料的,請記得預設情況下在 7688/Duo 裡面,Serial1是MCU丟資料給MPU的通道口。

我使用另一個替代方法即用 SoftwareSerial() 類別去實作 serialport 和他溝通,這個方法可以選用 7688/Duo 上面有限定可用的數位腳位去指定(RX/TX)。所以記得程式一開始必須把SoftwareSerial.h 這句宣告(#include)包含進來。

(2)打包 sensors 讀取的資料

在這裡我選用 JSON 資料格式來打包 PMS3003 讀取到的資料和 DS1307 RTC 得到的日期和時間。所以在程式裡面一開始就會看到關於 ArduinoJson.h 宣告(#include)就是這個原因。

這裡我舉一個採集到的資料,他被包成 JSON 格式後的範例給大家參考:

{
"sensor": "PMS3003",
"date": "2016-03-13",
"time": "05:34:11",
"pm010_TSI": "07",
"pm010_ATM": "07",
"pm025_TSI": "10",
"pm025_ATM": "10",
"pm100_TSI": "16",
"pm100_ATM": "16"
}

資料名稱 _ATM 指的就是「大氣環境下」方法測得的數據。
資料名稱 _TSI 指的就是「標準顆粒物」方法測得的數據。

接下來打包完的 JSON 資料,就是要把資料往 MPU 送。


之前寫個這一段,後來我重新看過程式,發現 FIRMATA protocol 只被我宣告,但是在我的 code 裡面宣告後一直沒有使用(因為我發現那個是我移植另一個測試用的),所以下面粉紅色這塊注釋裡面,我把之前 sketch code 裡面這兩行給直接刪除掉(程式已經更新了)。

Firmata.h 的宣告那行(#include)
Firmata.begin(Serial1);

設定MCU/MPU溝通(Serial Port) 的 Baud Rate Rate = 57600,然後把打包的 JSON 資料物件(這裡的範例是 Jasonroot)直接寫進去 Serial1即可:


Serial1.begin(57600);
Jasonroot.printTo(Serial1);

最後我們把範例程式 compiler 完 upload 到 7688/Duo 板子後,打開 Arduino IDE 的 Serial Monitor 觀看執行結果,應該會如同下圖所示。
SerialPort_Monitor

LCD 端的顯示應該如下圖:
LCD 1602 Display

補充說明,程式中設定每隔 10 秒 (10,000 ms) 去讀取 sensors 資料一次。

MPU(MT7688AN) side programming
MPU 程式下載 (.js)

我們複習一下,在這個設定好的故事裡面 MPU 的工作有就只有一件事,把揭開來自 MCU 的 sensors 資料寫入資料庫。

當我們把 MCU 的 sketch 給 upload 後,sensor 所讀到的值經包裝成 JSON 後將會源源不絕的被送到 Serial1。既然我們知道源頭的水進入 Serial1 這條水管後,我就可以在另一頭接水使用了,MPU 的工作就從接水開始。

MPU 端的程式我選擇用 Node.js 來實作,好處就是他已經是出廠就安裝好的開發環境,不用自己去煩心安裝主程式和相關套件的問題。

在 Node.js 中先宣告一個序列阜 serialport 的物件 ==> var com = require(“serialport");
把這個序列阜物件設定以 Baud Rate = 57600 的速度連到 /dev/ttyS0 這個 port 串口, /dev/ttyS0 就是接水口,水源就是剛剛在 MCU 持續每隔10秒鐘寫入的 JSON 資料。

既然我們在 MCU 端包裝資料就是以 JSON 格式包裝,這裡我們就可以用 Node.js 的內建 JSON 函數把它解(parse)出來。這裡我非常簡單的檢查收到的資料是不是有無效的資料( if (jobj.xxxxx=="undefined")),如果有這一包資料我就不要了,直接忽略他。 當然你應用更嚴僅的方法去過濾垃圾資料,我這裡只是偷個懶,千萬別學我,不然程式執行過程是會有機會被無緣無故的垃圾操作給中斷的。

寫到這裡,當千辛萬苦的資料都到手了,現在要殺要刮就輕而易舉了,是不?(耶!下課了)在這個階段可以做的事太多了,但是以我的規劃,當MPU把資料拿到手後的下一步是寫進去資料庫存起來,我選用的辦法是,用 HTTP Client 去發動一個 PHP 程式帶必要的資料過去,然後由 PHP 處理資料建檔。

會喜歡 Node.js 的原因是,他剛好有內建 HTTP Server/Client 的服務,我只要把資料以合乎URL規範的格式編輯好再交付給 HTTP Client 去觸發執行即可。

比方說,我們用下的例子當範例:

MPU 當下收到的資料解開如下


[sensor]= PMS3003
[date]= 2016-03-13
[time]= 09:10:07
[pm010_TSI]= 10
[pm025_TSI]= 15
[pm100_TSI]= 18
[pm010_ATM]= 10
[pm025_ATM]= 15
[pm100_ATM]= 18

那我就會利用一隻位在 pm25.com 這部 web server 上面一隻已經編程好的 PHP 程式 cgi_insert_sensor_data.php 幫我做建檔資料,最簡單的 URL 字串可以寫成如下:(資料的傳遞是以 key=value 的方式用一個 & 符號串起組成)


http://pm25.com/cgi_insert_sensor_data.php?input_SID=PMS3003&input_datetime=2016-03-13T09:10:07&pm010_TSI=10&pm025_TSI=15&pm100_TSI=18&pm010_ATM=10&pm025_ATM=15&pm100_ATM=18

而當位在 pm25.com 這部 web server 上的 cgi_insert_sensor_data.php 收到後,我會把資料轉成如下 SQL 語言,然後呼叫資料庫函數寫入資料庫:


SQL Command String =INSERT INTO Resort.Weather(Sensor_Id, Sensor_Datetime, pm010_TSI, pm025_TSI, pm100_TSI, pm010_ATM, pm025_ATM, pm100_ATM) VALUES (‘PMS3003’, ‘2016-03-13T09:10:07’, 10, 15, 18, 10, 15, 18 )

這樣一來,資料建檔就可以被存放在任何有接通資料庫的互聯網主機上了。當然,如果資料庫是在近端的網域內,你可以直接考慮以 Node.js 直接寫入資料庫,而不用再維護一支遠端主機上的 cgi 程式。

實際操作方面,我假設你已經會用 SSH 遠端登錄到你的 7688/Duo ,並且會一些很基本的 Linux 指令,這是因為 7688/Duo 的 OS 是採用 Linux 近親 OpenWrt的緣故,如果你一點都不懂的話會很吃力,先找幾帖 Linux 大補帖惡補一下吧。

待 login root 後,請建立一個目錄例如:
root@mylinkit:~#mkdir app

切換到該目錄:
root@mylinkit:~#cd app

用任何拿手的 text editor 編輯一個新的文字檔如:
root@mylinkit:~/app#vi PM25_7688Duo.js

把範例程式碼貼上去,存檔後離開。
接下來我們要讓這個程式碼發生作用,必須用 node 啟動執行它:
root@mylinkit:~/app#node PM25_7688Duo.js

等個幾秒後,畫面應該就會 prompt 出一些資料,這是我把執行過程中的資料片段丟到畫面來觀測的緣故,當然這個階段程式會佔據你的控制權,您沒辦法做其他的事,事實上您也不需要在這時間做任何事,因為 node 程式正很忠實地幫你一分一秒做你要他做的事,那到底他做了什麼?我們上面的說明已經解釋過了(要終止這隻程式的獨佔模式而回到命令列可以同時按下 [control]+[c] 兩個按鍵,程式就會立即中止了)。

下面是啟動 node 程式執行的畫面截圖,可以供參考。
MPU_screenshot

Data Visualisation

網頁端的資料呈現不是這個題目的範圍,我等有空在講個概念吧!
這裡我就先分享一個我初步弄出來的樣子供大家參考,各位可以構思自己的視覺呈現。

數據視覺化輸出展示

以上簡單的說明,希望對 Linkit Smart 7688 Duo 抱有期待又沒辦法突破的你有所幫助。

lass.hackpad1

LASS Field Try 第一彈:PM2.5量測裝設指南

No Comments

硬體安裝說明
  • 準備下列硬體設備
  • 以上硬體之套件包可以在下列網站購得:
Linkit ONE HW block diagram示意圖
  • 將LinkIt One的電池、GPS(方的那顆)與WiFi天線(寬與細的皆可)完成組裝
  • (上圖表下方天線接口, 接口端有描述GPS與Wifi)
  • 將 G3 PM2.5感測器的排線剪斷(沒錯,不要懷疑),並將第1,2,5根接線(分別為紫色、橘色、色)改裝成杜邦接頭
  • 將 G3 PM2.5感測器的第一根接線(紫)插入LinkIt One上的 5V 接腳,將第二根接線(橘)插入 GND 接腳,將第根接線()插入 RX 接腳 , 第三根(藍) 線插入TX接腳
  • 將 Grove 溫濕度感測器接上 Grove 對杜邦接頭轉接線,並將第一根接線(黃)插入 D2 接腳,將第二根接線(白)插入 D3 接腳(這根線其實不會用到,沒有插也無所謂),將第三根接線(紅)插入 3V3 接腳(本來應該要使用 5V 接腳,但因為已經被 PM2.5 感測器使用,所以改用 3.3 V,這個感測器也同時支援 3.3V 電壓),將第四根接線(黑)插入 GND 接腳
  • 硬體安裝完畢,可將感測器與天線調整成喜歡的相對位置,也可以加上適當的外殼
  • 接線圖(  注意下面的圖藍色與綠色的線, 綠色為RX 藍色為TX  ,接反會造成 PM2.5 感應器無法正常啟動)
  • 抱歉, 圖面錯誤部份更正 
  • 感謝 協助
軟體安裝說明
  • (步驟執行至 Configure the Arduino IDE 結束即可)
  • 附註: 
  • Arduino IDE 請使用1.5.6與1.5.7的版本,以免inkIt ONE Firmware Updater無法更新
  • 謹記LASS與Libraries這兩個資料夾,之後會使用到這兩個資料夾的檔案
  • Import剛剛下載的以下七個Library (Sketch > Import Library… >新增函式)
  • 溫濕度感測器的library: DHT_linkit.zip
  • KalmanFilter :HP20x_dev.zip
  • 直接點擊開啟LASS.INO, 請修改分頁 configuration.h 中下列的變數設定
  • #define APP_ID (APPTYPE_SYSTEM_BASE+X) : 請將 X 改成 1
  • #define DEVICE_ID “YOUR_DEVICE_NAME": 請將YOUR_DEVICE_NAME改成您自定的裝置名稱(任何字串皆可), Field Try 建議為 FT1_xxx
  • #define WIFI_SSID “LASS": 請將 LASS 改成您的 WiFi 名稱
  • #define WIFI_PASS “LASS123456″: 請將 LASS123456 改成您的 WiFi 密碼(若不需要密碼可以跳過)
  • #define WIFI_AUTH LWIFI_WPA: 請設定與網關相同之認證設定,需要時請將 LWIFI_WPA 改為 LWIFI_WEP (若使用 WEP安全機制)或 LWIFI_OPEN(若不使用加密)
  • Ctrl+R重新編譯 LASS.ino 程式,確認有USB連線與Link modem port, 並上傳至 LinkIt One 即可執行
  • Verify與上傳圖示
  •     確認設備連線
測試與資料接收
  • 若要觀看即時呈現感測器的資料,可參考下列網頁之說明(看自己的需要決定需要看多少)
  • 系統提供的部分(使用者只需使用,不需建置)
  • 使用者客製化(只有在使用者有相關需求的時候才需要建置)
  • 偵錯才需要
  • 要檢查資料是否已經上傳 MQTT, 這個最直接,檢查 server 是否有將資料記錄在 log 中
  • 若使用 Apple 手機,可下載 MQTT Tester/Client (需付費),並設定 MQTT server 為 gpssensor.ddns.net,Port number 為 1883,Topic 為 LASS/Test/PM25,即可接收所有 Field Try 訊息
  • 若使用Android手機, 可下載MyMQTT,設定值同上
常見問題與回答
  • 建議開機方式: 
  • 如果遭遇當機,燈號 TX和RX都會間歇閃爍,這有可能是目前已知的Wifi 問題 也有可能是Power on reset 的問題。建議開機時,將USB插上後,先用BAT供電位置等待所有燈號熄滅 ,再切回USB供電位置。目前這樣開機 都還不太會遇到當機。
  • 請更新程式碼到 0.7.13 或之後的最新版本,版本的程式已針對WiFi導致的當機問題進行處理,大幅減低因為WiFi導致的當機機會
  • 在系統的圖表沒看到的 DeviceID
  • 檢查 server 有沒有收到 
  • 如果沒收到,請檢查 USB console 的 debug output
  • 如果顯示 wifi not ready
  • 一般表示沒有連上 wifi ap, 請檢查 wifi id, wifi pwd, wifi_auth, 或是 wifi AP 是否正常
  • 如果顯示 wifi ready
  • 表示 AP 連接正常,如果 AP 連上 internet 是正常的,一般 server 應該已經收到。如果還沒收到,請確認 MQTT 設定是否被更動到
  • 另外可將PC網卡設為AP mode, WireShark Linkit ONE,看有無MQTT封包在上面
  • 如果看到時間不正常 (如上圖 2080-01-06)
  • 一般是 GPS 沒有定到位。常見原因是在室內,或是GPS 天線沒有安裝。
  • GPS 時間為 GMT+0, 所以和台灣時間差八小時是正常的
  • 如果 server 有收到,而且資料格式正確(如果沒被更動到,一定是正確的),應該就可以在系統提供的功能報表上看到資料
  • 當有問題時,如何取得協助
  • 如果您有長期監測的打算,LASS 有 Field Try 的 FB 討論串,找個有在 Field Try 的夥伴,請他幫您加入
  • 請確定查詢過 hackpad 相關的資料,以及上述的自我偵錯方式皆已無效
  • 請將 USB console 的關鍵輸出,發文到 LASS FB ,提醒如果沒有提供關鍵的所需資料,人家很難幫助上您
  • 覺得安裝與偵錯步驟寫得不夠正確,貼心?
  • 每個人都有盲點,對系統越熟的人,越容易少寫東西,請隨手更新文件,因為前人的幫助,目前才能有這樣的系統,隨手做出貢獻,才是參與開源專案的良好習慣
LASS 7688

用7688 Duo遠端控制PM2.5偵測裝置

No Comments

作者:阿海

搭給厚!哇希肉多多A阿海(台語)。最近常常有 LASS 的社群夥伴問我:「我可以在自己的PM2.5偵測裝置上,加上LCD ,或者控制空氣清淨機嗎?」相信各位專業的 Maker 一定可以馬上異口同聲『有何不可!』,沒錯!有何不可呢?

只是情境上,LASS 的PM2.5偵測裝置的設置,應該是在屋頂或者是窗外,在窗外的裝置裝上一個螢幕顯示,並且還有智慧插座,要觀看數值還要跑到戶外,這樣的情境,阿海怎麼都想不通啊!阿海心中,怎麼都想放一個獨立的抬頭顯示器在室內,並且讓它默默的工作才對啊!

大多數的物聯網開發版範例,很多都是將自己的資料,上傳到一個資料平台來閱覽,或者只是透過手機操作平台,來遠端操作裝置,而這樣和 MQTT 當初提供點對點相連的概念,可能稍微有點差距。如果我們能夠在屋頂裝設感測器,在屋內放一個能夠顯示、又可以連動控制的面板,不就能解決這個問題了嗎?

剛好轟動 Maker林、驚動 Arduino板海市場的 LinkIt Smart 7688 Duo 剛上市不久,不如阿海就繼續透過這片好棒棒的開發板,結合128 x 64的 OLED,來實做一個全新的 LASS M2M節點裝置吧!以下,阿海詳述了設計和製作的過程,分享如何一步一步的完成所需要的功能。

必備物品

  • Mediatek LinkItSmart 7688 Duo x1
  • SSD1306相容的128 x 64 I2C OLED模組 x1
SSD1306相容的128x64 I2C OLED模組

SSD1306相容的128×64 I2C OLED模組

選配物品(控制空氣清淨機)

  1. 5V Relay 模組  x1
  2. 電力配線用一連明盒附插座  x1
  3. 有插頭的多芯電線  x1
選配物品(控制空氣清淨機)

選配物品(控制空氣清淨機)

設計步驟一:分析系統架構

想要了解LASS的架構,必定要先了解MQTT,如果要把MQTT用一個簡單的說法來解釋,就是把它想成無線電台,想要跟對方互相通訊,就要轉到正確的頻道,當大家的頻道都相同時,別人廣播什麼你就聽得見了(就像收音機一般)。

而在MQTT裡面,頻道的概念就叫做「Topic」,所以想要接收來自另外一端的消息,我們就得先去訂閱你想知道的Topic,目前所有的PM2.5公開測試,都定義在「LASS/TEST/PM2.5」這一個Topic上,我得知原先哈爸是有規劃Private Topic的欄位的,但是因為陰錯陽差在Field Try釋出的版本並沒能帶上,因此想要找到自身站台的訊息,就勢必要從所有站點的訊息海裡面「撈」出來了。

LASS與MQTT系統架構

LASS與MQTT系統架構

設計步驟二:取得,並測試線上正在運作的各種資料

要開發和測試 MQTT,Chrome 上有一套很簡單的 App 叫做 MQTTLens,透過這個工具你可以輕鬆的在 MQTT上的頻道訂閱/發佈 訊息,來看看如何安裝並且啟動它吧!

Chrome App 上的 MQTTlens

Chrome App 上的 MQTTlens

接下來就要連上 LASS 的了,在啟動畫面後左上角 Connection 右邊,有一個「+」號,點擊後會帶出新增連線的畫面。

MQTT伺服器

MQTT伺服器

LASS的MQTT伺服器位址是 「gpssensor.ddns.net」,服務Port一樣是 「1883」,填寫完後點擊右下角的「Create Connection」。
填寫範例如下:

MQTT伺服器填寫範例-1

MQTT伺服器填寫範例-1

MQTT伺服器填寫範例-2

MQTT伺服器填寫範例-2

填寫完畢來訂閱主題,測試看看通訊會不會成功吧!請在對話框內輸入訂閱 Topic「LASS/Test/+」 ,然後按下「Subscribe」,如果連線成功就會如下圖這樣,畫面會顯示站台上的訊息,陸陸續續湧進。

MQTTlens操作成功畫面

MQTTlens操作成功畫面

觀察此頻道的固定格式如下:

|ver_format=3|fmt_opt=0|app=PM25|ver_app=0.7.13|device_id=FT1_004|tick=260989326|date=2016-01-21|time=06:27:23|device=LinkItONE|s_0=67852.00|s_1=100.00|s_2=1.00|s_3=0.00|s_4=662.00|s_d0=66.00|s_t0=21.40|s_h0=72.50|s_d1=81.00|gps_lat=23.284105|gps_lon=120.275816|gps_fix=1|gps_num=16|gps_alt=5

其中 ,「Device_ID 是站台編號」、「s_d0 就是PM2.5的資料欄位」、「s_d1是PM10的資料」「s_t0 為溫度的資料」、「s_h0 則是溼度的資料」,以及日期和時間,這是驗證裝置上線情形和觀察原始資料最直接的辦法了!

為了開發時方便,等等先不要訂閱主頻道,因為這樣會面對海量的訊息,所以我們來創建一個新的頻道叫「DeveloperTest」,仿照上面的格式來測試發佈訊息,並且按下「Subscribe」訂閱這個頻道後按下「Publish」發佈一段訊息,確認伺服器收送正常工作。

測試一個獨立的 MQTT頻道

測試一個獨立的 MQTT頻道

設計步驟三:分析開發版的資源並規劃實作方式

一說到 Linkti Smart 7688 Duo,就來複習一下它的架構:它是由兩個主要單元組成,上半部能網路連線的通訊端為 OpenWRT 系統,於500Mhz 的 MT7628 MPU 上運行,第二是 Arduino 相容的 ATMEGA32U4 在 8Mhz 的速度運行。

直觀上我們會選擇在 OpenWRT 端訂閱 MQTT,並且在海量的訊息中,找到想要特定站台傳來的資料處理好,再透過 UART 傳送到 Arduino 端,Arduino 收到 UART(Serial1) 的資料後,對這些資料進行處理,並進行相對應的控制,最後顯示在 OLED 上。

本專案的架構規劃流程圖-1

本專案的架構規劃流程圖-1

本專案的架構規劃流程圖-2

本專案的架構規劃流程圖-2

設計步驟四:於Linux端取得MQTT資料分封傳送

LASS的Github上,已經有陳伶志博士提供:MQTT資料轉送到Thingspeak的程式,只要將它進行稍微的修改就可以達到本次需要的目標了,開源專案的最大好處就是如此,站在巨人的肩膀上,輕輕一跳大家就可以到更高的地方!

為了要使用 Serial Library 和 MQTT 這兩個 library,在設備上要安裝 pyserial 和 paho-mqtt 這兩個套件,我們連線進 7688 Duo 下以下兩個指令:你可以使用 PuTTy 連線 mylinkit.local 這個位址,連線成功之後,操作安裝指令。

pip install pyserial paho-mqtt

接下來,將 lass-pm25-friend.py 這隻程式放到 7688 Duo 上,Windows上可以用SCP這套軟體來傳輸。

用SCP這套軟體來傳輸

用SCP這套軟體來傳輸-1

用SCP這套軟體來傳輸-2

用SCP這套軟體來傳輸-2

在Linux-like系統上,直接在Terminal 終端機程式。

scp lass-pm25-friend.py root@mylinkit.local:/root/

阿海將這隻程式修改,讓收到訂閱的MQTT Message程式部分,再加幾行代碼,使收到的資料「原封不動」,只加分行符號「’\n’」轉送下去。也許有人會疑惑,為什麼要這樣設計呢?

明明都已經正確的解析出資料了,卻不傳送已經解析過的資料,是的,這是因為:Arduino 上有許多板子可以直接支援MQTT的(訂閱/傳送),為了未來移植到其他版子方便,阿海就將字串解析的工作交給 MCU 了。(詳細的程式碼請見 Github 上的原始碼會比較清楚)

python 程式內部有一些相關的參數可以調整,說明如下:

################################################
# Please configure the following settings for your environment

MQTT_SERVER = "gpssensor.ddns.net" #伺服器位址
MQTT_PORT = 1883 #伺服器連接阜
MQTT_ALIVE = 90 #連接Timeout的時間
MQTT_TOPIC = "LASS/Test/#" #訂閱的主題開頭 '#'代表全部的任意字串
SERIALPORT="/dev/ttyS0" #所使用的Serial port名稱
BUADRATE=57600 #LinkitSmart 7688 Duo Serial1 端的 buadrate
###############################################

接下來要執行這隻程式,指令如下:

python lass-pm25-friend.py [你的站號] [你的ThingspeakAPI key] [模式]

其中模式 0 是只使用thingspeak(原本的功能)、1是只使用Serial、2是兩種功能都使用。
執行後的畫面如下:

Trying To Connect:gpssensor.ddns.net
MQTT Connected with result code 0
Got your MQTT channel
LASS/Test/PM25|ver_format=3|fmt_opt=0|app=PM25|ver_app=0.7.13|device_id=FT1_004|tick=378198422|date=2016-01-22|time=15:01:45|device=LinkItONE|s_0=69813.00|s_1=100.00|s_2=1.00|s_3=0.00|s_4=692.00|s_d0=27.00|s_t0=20.10|s_h0=75.30|s_d1=28.00|gps_lat=23.284026|gps_lon=120.275832|gps_fix=1|gps_num=16|gps_alt=6

為了讓每次通電開機時,都能夠自動啟動這一隻程式,先將啟動語法複製好設定在 /etc/rc.local/ 之下,你必須先輸入指令:

chmod 777 /etc/rc.local
vim /etc/rc.local

並在VIM中編輯如下:

python /root/lass_pm25_friend.py [FT1_ID] [APIKEY] 2 >lasslog.out &

設計步驟五:於 Arduino 端接收 MQTT 資料字串,並取得想要的數值

完成上一個步驟之後,接下來要對 Serial 1進行測試,確保資料在傳送的過程中不會因為電路的誤差而「糟精」(台語:偏差)的疑慮,這種時候,阿海都會直接打開 Arduino IDE 中的範例 「MultiSrerialMEGA」,這個好用的工具範例,修改兩端到要使用的 Buadrate,隨即測試接收資料看看。

Arduino IDE 中 MultiserialMEGA

Arduino IDE 中 MultiserialMEGA

透過 Serial Console確認資料無誤

透過 Serial Console確認資料無誤

當確認傳來的資料是相同的之後,就開始著手撰寫通訊程式,Arduino中,剛好能夠利用String 物件輕鬆的操作字串,而MQTT訊息串並不是太大,於是就將所有的資料流存,放到字串裡面。

得到字串之後,就來設計解析字串程式,這邊要取得的有「日期」、「時間」、「溫度」、「溼度」、「PM2.5」、「PM10」這六項資料。其中前兩項並不是數字,所以撰寫一段額外的程序,來分別放置在字串變數,和浮點數陣列之中。而解析時只要找到tag的前置名稱加上「=」號的開頭位置,以及結尾的「|」符號,就能夠取出這段資料。

首先宣告想要截取的特徵前導字元:

#include <avr/pgmspace.h>
  const char data_datep[] PROGMEM = "date";
  const char data_timep[] PROGMEM = "time";
  const char interestValue_1[] PROGMEM = "s_d0";
  const char interestValue_2[] PROGMEM = "s_t0";
  const char interestValue_3[] PROGMEM = "s_h0";
  const char interestValue_4[] PROGMEM = "s_d1";
  const char* const interestValue[] PROGMEM = {data_datep, data_timep , interestValue_1, interestValue_2, interestValue_3, interestValue_4, };

然後尋找特徵碼的開頭,並使用 String.indexOf 定位,將所需要的部分轉換成數值,存放在浮點數陣列之中。

float sensorValue[4];
char buffer[8];

boolean onMessage(){
  Serial.println("OnMessage");
  for(int i=0;i<=data_num-1;i++){
    #ifdef BOARD_AVR
      strcpy_P(buffer, (char*)pgm_read_word(&(interestValue[i])));
    #else
      strcpy(buffer,interestValue[i]);
    #endif
   byte startpos=msg.indexOf(buffer);
   String temp =msg.substring(msg.indexOf(assigner,startpos)+1,msg.indexOf(seperator,startpos)); //From tagname after'=' to seperator'|'
   if(temp==""){
    return 0; //if failed to match tags....
    }
   if(i==0){
    data_date = temp; 
   } else if(i==1){
    data_time = temp;
   } else {
    sensorValue[i-2]=temp.toFloat();
   }
    Serial.println(temp);
  }
  return 1; // I count 2440 message/second with static process , so maybe perfomence is about 100~1000 message/s.
}

 

將這些資料印在Serial上,確認解析字串成功之後,就要準備來顯示在 128 x 64 的繪圖型 OLED 上了。

於Serial觀察解析字串的結果

於Serial觀察解析字串的結果

設計步驟六:打造Arduino端 OLED顯示單元

在寫文章的第一天,阿海就把 Adafruit 買來的1.3吋 OLED 打破了,於是阿海只能屈就一片 Library 相容的0.96寸模組了,這片 SSD1306 OLED 模組在 Library 內有兩種顯示模式:一種是引用內建的字型,直接給字串顯示他的做法類似 Print,而另外一種是直接讀取一段單色的byte array bm p圖檔,會一直變動的數字,必須使用字型來顯示比較省事。

而中文的部分,因為我預定只顯示幾個特定的字,因此不如直接把「字」畫成「圖」吧,有一種大家的電腦都有內建的:神人級繪圖修圖軟體「小畫家」(PTT Mobile01的人這樣稱呼的),是產生單色點陣圖最方便的工具了!在 SSD1306 裡面,每一段 Byte Array 圖檔都有寬度8的倍數的限制,一般顯示中文的點陣字體是「16 x 15」,因此要顯示「溫度」這兩個字,我們大概需要32 x 16像素的畫框。

在小畫家裡面設定好畫框,輸入中文字:

在小畫家裡面設定好畫框,輸入中文字

在小畫家裡面設定好畫框,輸入中文字

接下來我們要將圖檔轉成ByteArray,網路上有一個免費的軟體叫「LCD Assistant」,是Adafruit推薦使用的,既然有人推,阿海就用用看,將剛剛畫好的圖檔導入,就可以產生一個內含Byte Array的檔案了,我將這個檔案另外放置在 Bitmap.c 裡面,放在相同的專案資料夾,讓IDE能夠識別,其他的文字和圖案,都是這樣如法炮製來產生圖檔,你就能在OLED上顯示這些字了,不過遺憾的是,繪圖的定位和文字模式會有一點差別,這時候只能多嘗試幾次,很快就可以對齊了。

使用 LCD Assistant轉換字體

使用 LCD Assistant轉換字體

使用Adafruit Library 顯示一段文字的程式(Arduino)

在程式燒錄進去之前,先將OLED組裝上去,他是使用I2C介面的,請將模組上SDA、SCL分別接到 7688 Duo上的 D2、D3兩腳,VCC接上3V3,GND接上GND。如使用Relay模組,GND再將線路T接出來,Relay版上的VCC再接上5V。

電路示範圖

電路示範圖

設計步驟七:打造Arduino端 空氣清淨機智慧插座控制單元

最後來撰寫一段簡單的控制程式,實現在空氣污染太高的時候,能夠自動啟動的空氣清淨機,一般在工程現場,最常見的控制器都是「窗型控制」的,所謂窗型控制,就是數值高到某一個界線,就會啟動機器,而要一直到啟動後產生效用,到達另一個比啟動值較低的界限,才會關閉,這樣的控制方式就稱為窗型控制,例如這邊寫入當PM2.5大於50ug/m^3 ,就讓Relay通電來啟動空氣清淨機,而啟動後要到40ug/m^3以後才會停止運作。

此外,為了預防網路連結失效時,機器運轉不停,阿海也讓程式十分鐘沒有數字更新,就得強迫關閉空氣清淨機,同時也為了避免感測器數值跳動幅度過大,也新增一個變數,來設定最短的啟動時間,才不會讓機器頻繁啟動而損壞。

使用Relay來控制的程式碼如下:

 if(logichandle){
  //Run your logic here;example give you standard windows control
  if(sensorValue[0] > ONvalue){
    digitalWrite(PINOUT,HIGH); //Turn ON
    lastOnTime =nowtime;
  }

  if(sensorValue[0] <OFFvalue && (nowtime - lastOnTime) > LEASTONTIME){
    digitalWrite(PINOUT,LOW); //Turn OFF
  }
  heartbeat =nowtime;
  //after done place logic handle 0;
  logichandle=0;  
 }

 if((nowtime - heartbeat) >MOSTONTIMME){
  digitalWrite(PINOUT,LOW); //Turn OFF Because no connection;
 }

 

然後依照大部分智慧插座的教學,進行Relay的連接和插座的組裝。

由於阿海寫完稿,又把插座拿去做別的東西了,我只能放一張它的「紀念照」了:
p7.1

設計步驟八-執行結果

以上,是本專案所有的設計的思考及實作過程,希望留下這樣的紀錄,對於未來想要開發LASS應用裝置的人,會有所幫助,最後來看看完整的執行結果吧!

執行結果-1

執行結果-1

執行結果-2

執行結果-2

執行結果-3

執行結果-3

執行結果-4

執行結果-4

(本文原發表於MakerPRO,原文連結

相關資訊

本文所有程式公開放置在:
https://github.com/LinkItONEDevGroup/LASS/tree/master/Companion-module

加入討論請到LASS社群:
https://www.facebook.com/groups/1607718702812067/

阿海特別感謝兩位社群朋友,於撰寫本文時提供協助:哈爸、陳伶志 博士

作者介紹:關於肉多多

肉多多-多肉植物科技農場為三個在物聯網公司退役的人組成,平時在雲嘉南搞工程拼經濟,下班就種多肉怡情,偶爾參展交朋友,如果你喜歡肉多多分享的文章,就到粉絲專頁點個讚吧,畢竟點讚一點都不會花你錢,還有可能讓阿海多寫幾篇文章跟大家分享喔!

http://www.facebook.com/rododo.farm