這是我利用Arduino、GPS模組和溫度感測器做出的新竹市熱島效應調查圖
我在Make雜誌37期看到Forrest M. Mims III,寫的這篇Amateur Scientist: Tracking Heat Islands驚豔不已,也很想作一個。
Forris M.Mims 用的是Vernier LabQuest 2加上GPS功能,然後再用熱敏電阻去測溫,不過LabQuest的價錢...所以我要用比較平價的方式去達成。就決定用Arduino加上GPS模組和溫度感測器了。
其實不一定要用GPS模組,只要能接受GPS訊號的手機(要能存軌跡),再加上一個能每秒測溫度和紀錄時間的arduino。帶出去同時測量這些訊號之後,事後再用時間當索引,把每個溫度測定的座標抓出來也是可以用。
我使用的GPS模組是IM120417017 GPS模組,測溫是用LM35溫度感測器,裝在車子上載著繞一圈。得到GPS定位對應氣溫的資料後,再用QGIS這個地理資訊系統的軟體處理出圖。
整個裝置就是這樣一盆,然後把溫度感測器接到車外,GPS天線靠近窗邊。供電用的是行動電源,因為輸出只有5V,所以我是連接到Arduino的USB port。
得到的結果和預想的一樣,在大馬路交叉口(東大路和經國路交叉口)或是鬧區(新竹車站),溫度比較高。而在綠地附近,溫度就低了。當然這樣車載調查的前提是,這段調查期間溫度不至於隨時間變化太大。
放後照鏡旁邊,雖然測的時候是陰天,但是也有可能受到陽光照射影響,而且也有可能受到行車速度影響。陽光照射可以用個容器改善,但是速度要怎麼改善?
再來講一些程式細節的東西,GPS模組輸出的資料較難閱讀,所以需要TinyGPS++的函式庫來解讀。輸出的東西,同時輸出到Serial port、LCD和SD卡裡頭。
最後得到的文字檔,改個檔名變成csv,可以用Google地圖來作資訊視覺化。
從Google地圖的「我的地圖」,找到「建立地圖」,接著用「新增圖層」,用「匯入」的方式把csv檔送進去。然後指定經緯度的欄位之後,可以將不同的溫度範圍用不同色階表示。不過Google地圖一個圖層只能匯入2000個點,所以後來我改用QGIS來處理。
GPS和溫度的文字資料,用「Layer/Add Layer/Add Delimited Text Layer」來匯入
先到Plugins裡安裝OpenLayers Plugin,這樣才能使用Google Map的圖層來套疊(安裝與使用方式看這)
Arduino的程式碼則是在此
#include <SD.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#define DHTPIN 4 // what pin we're connected to
#define DHTTYPE DHT11 // DHT 11
DHT dht(DHTPIN, DHTTYPE);
const int chipSelect = 10;
TinyGPSPlus gps;
//LCD
LiquidCrystal_I2C lcd(0x27,16,2);
void setup()
{
Serial.begin(9600);
pinMode(10, OUTPUT);
lcd.init(); // initialize the lcd
lcd.backlight();
lcd.clear();
if (!SD.begin(chipSelect)) {
lcd.setCursor(0,0);
lcd.print("No Card inserted");
return;
}
Serial.print("lat");
Serial.print(F(","));
Serial.print("lng");
Serial.print(F(","));
Serial.print("date");
Serial.print(F(","));
Serial.print("time");
Serial.print(F(","));
Serial.print("humidity(%)");
Serial.print(F(","));
Serial.print("Temperature(C)");
Serial.print(F(","));
Serial.print("Heat Index(C)");
Serial.print(F(","));
Serial.println();
}
void loop()
{
float h = dht.readHumidity();
// Read temperature as Celsius (the default)
float t = dht.readTemperature();
// Compute heat index in Celsius (isFahreheit = false)
float hic = dht.computeHeatIndex(t, h, false);
char temp = 0;
File dataFile = SD.open("datalog.txt", FILE_WRITE);
if(dataFile)
{
while(Serial.available())
{
temp = Serial.read();
if(gps.encode(temp))
{
displayInfo();
Serial.print(F(","));
Serial.print(h);
Serial.print(F(","));
Serial.print(t);
Serial.print(F(","));
Serial.print(hic);
Serial.println();
lcd.setCursor(0,0);
lcd.print(gps.location.lat(), 6);
lcd.setCursor(11,0);
lcd.print(t);
lcd.setCursor(0,1);
lcd.print(gps.location.lng(), 6);
lcd.setCursor(11,1);
lcd.print(h);
dataFile.print(gps.location.lat(), 6);
dataFile.print(F(","));
dataFile.print(gps.location.lng(), 6);
dataFile.print(F(","));
dataFile.print(gps.date.month());
dataFile.print(F("/"));
dataFile.print(gps.date.day());
dataFile.print(F("/"));
dataFile.print(gps.date.year());
dataFile.print(F(","));
if (gps.time.hour() < 10) dataFile.print(F("0"));
dataFile.print(gps.time.hour());
dataFile.print(F(":"));
if (gps.time.minute() < 10) dataFile.print(F("0"));
dataFile.print(gps.time.minute());
dataFile.print(F(":"));
if (gps.time.second() < 10) dataFile.print(F("0"));
dataFile.print(gps.time.second());
dataFile.print(F("."));
if (gps.time.centisecond() < 10) dataFile.print(F("0"));
dataFile.print(gps.time.centisecond());
dataFile.print(F(","));
dataFile.print(t);
dataFile.print(F(","));
dataFile.print(h);
dataFile.print(F(","));
dataFile.println(hic);
}
}
dataFile.close();
}
}
void displayInfo()
{
// Serial.print(F("Location: "));
if (gps.location.isValid())
{
Serial.print(gps.location.lat(), 6);
Serial.print(F(","));
Serial.print(gps.location.lng(), 6);
Serial.print(F(","));
}
else
{
Serial.print(F("INVALID"));
}
// Serial.print(F(" Date/Time: "));
if (gps.date.isValid())
{
Serial.print(gps.date.month());
Serial.print(F("/"));
Serial.print(gps.date.day());
Serial.print(F("/"));
Serial.print(gps.date.year());
}
else
{
Serial.print(F("INVALID"));
}
Serial.print(F(","));
if (gps.time.isValid())
{
if (gps.time.hour()< 10) Serial.print(F("0"));
Serial.print(gps.time.hour());
Serial.print(F(":"));
if (gps.time.minute() < 10) Serial.print(F("0"));
Serial.print(gps.time.minute());
Serial.print(F(":"));
if (gps.time.second() < 10) Serial.print(F("0"));
Serial.print(gps.time.second());
Serial.print(F("."));
if (gps.time.centisecond() < 10) Serial.print(F("0"));
Serial.print(gps.time.centisecond());
}
else
{
Serial.print(F("INVALID"));
}
}