前回作成した”ESP32で海面気圧付きエアモニター”で未着手だった”スイッチで切り替えてOLEDにGPSの受信データを表示させる予定”を実施しました。
スイッチを実装したついでに設定された測定値表示間隔(10秒)では無く毎秒更新するモードも付加しました。
追加した機能と目的
・GPSモニター モードスイッチが押される度に通常のエアモニターと切り替え、位置や精度情報などを観る。
・連続表示更新 モードスイッチをおしたまま起動した場合、表示を毎秒更新させてGPSやセンサーの挙動を観る。
・表示更新LED データ更新プロセスでESP32DevボードのLEDを点灯、表示値が変化しない場合でも動作中を確認。
末尾にコードを置きますが、前回のコードに機能追加しただけです。
ベースにしたスケッチはスイッチサイエンスのサイト”AmbientでIoTをはじめよう 空気品質を測定し、記録する”です。
/*
* This sketch is modified from "Ambient_GPS_BME280_CCS811".
* Change MCU to ESP32, Added Display SSD1305 OLED.
* And stopping send data to Ambient by WiFi.
*/
#include <SoftwareSerial.h> // No need this line when use Hardware Serial.
#include <WiFi.h>
#include "Ambient.h"
#include <TimeLib.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "bme280_i2c.h"
#include "SparkFunCCS811.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeMono9pt7b.h>
#define Rxpin 16 // No need this line when use Hardware Serial.
#define Txpin 17 // No need this line when use Hardware Serial.
// /HardwareSerial ss(2); //use HardwareSerial-2 (Pins are fixed: Rx=16 Tx=17)
SoftwareSerial ss; //use SoftwareSerial
TinyGPSPlus gps;
TinyGPSCustom pdop(gps, "GPGSA", 15); // $GPGSA sentence, 15th element
TinyGPSCustom vdop(gps, "GPGSA", 17); // $GPGSA sentence, 17th element
#define SDA 21
#define SCL 22
#define CCS811_ADDR 0x5A // CCS811 Address
#define CCS811_HW_RESET 5
#define CCS811_WAKE 4
CCS811 ccs811(CCS811_ADDR);
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define SCREEN_ADDRESS 0x3C // I2C:SSD1306
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
BME280 bme280(BME280_I2C_ADDR_PRIM);
#define ModePin 27
int NowMode = 0 ; // 0:Air測定モード 1:GPSモニターモード
int PERIOD = 10 ; // Set measuring interval in sec.
const int WFuse = 0; // 0:WiFi,Ambientを使わない 1:使う
WiFiClient client;
const char* ssid = "xxxx";
const char* password = "xxxxxxxx";
Ambient ambient;
unsigned int channelId = 1234; // AmbientチャンネルID
const char* writeKey = "xxxxxxxxx"; // Ambientライトキー
const int offset = 9; // 時刻差(ローカル時刻 - UTC)
int Year;
byte Month, Day, Hour, Minute, Second;
int Lastday = 0;
uint16_t CO2, TVOC;
void ccs811_hw_reset() { // CCS811のRESETピンをLOWにしてリセットする
digitalWrite(CCS811_HW_RESET, LOW);
delay(10);
digitalWrite(CCS811_HW_RESET, HIGH);
}
void ccs811_wake() { // CCS811のnWAKEピンをLOWにしてI2C通信できるようにする
digitalWrite(CCS811_WAKE, LOW);
delay(10);
}
void ccs811_sleep() { // CCS811のnWAKEピンをHIGHにして休ませる
digitalWrite(CCS811_WAKE, HIGH);
}
void setup()
{
pinMode (ModePin, INPUT_PULLUP); // 表示モード切替スイッチ入力
pinMode (2, OUTPUT); // 測定結果更新ランプ on board LED
Serial.begin(115200);
delay(100);
if(!digitalRead(ModePin)){ // 立ち上がり時にModeスイッチが押されていたら連続測定モード
PERIOD = 0;
}
Serial.println("\r\nAir quality with location");
// ss.begin(9600); //use HardwareSerial2
ss.begin(9600,SWSERIAL_8N1, Rxpin, Txpin, false); //use SoftwareSerial
if (!ss) { // If the object did not initialize, then its configuration is invalid
Serial.println("Invalid SoftwareSerial pin configuration, check config");
while (1) { // Don't continue with invalid configuration
smartDelay(1000);
}
}
if (WFuse == 1){
WiFi.begin(ssid, password); // Wi-Fi APに接続
while (WiFi.status() != WL_CONNECTED) { // Wi-Fi AP接続待ち
Serial.print(".");
smartDelay(1000);
}
Serial.print("WiFi connected\r\nIP address: ");
Serial.println(WiFi.localIP());
}
pinMode(CCS811_HW_RESET, OUTPUT);
pinMode(CCS811_WAKE, OUTPUT);
ccs811_hw_reset(); // CCS811のリセット
ccs811_wake(); // CCS811をI2C通信可能にする
Wire.begin(SDA, SCL);
bme280.begin(); // BME280の初期化
CCS811Core::status returnCode = ccs811.begin(); // CCS811を初期化
if (returnCode != CCS811Core::SENSOR_SUCCESS) { // 初期化に失敗したら
Serial.print(".begin() returned with an error: ");
Serial.println(returnCode, HEX);
while (1) {
Serial.print("CCS811 stopiing prog. in setup ");
display.clearDisplay(); display.setCursor(0, 30); display.print("Err CCS811");
display.display();
delay(0); // プログラムを停止する
}
}
ccs811_sleep(); // CCS811のI2C通信を止める
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.setTextColor(SSD1306_WHITE); // Draw white text
display.clearDisplay();
display.setFont(&FreeMono9pt7b);
display.setCursor(0, 30); display.print("in progress");
display.display();
smartDelay(1000); // Pause for 1 seconds
if (WFuse == 1){
ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
}
}
void readCCS811(float humid, float temp)
{
ccs811_wake(); // CCS811をI2C通信可能にする。
// BME280で読んだ温度、湿度の値を使って補正をおこなう
ccs811.setEnvironmentalData(humid, temp);
CO2 = 0;
while (CO2 < 400 || CO2 > 8192) { // CCS811の測定範囲は400〜8192ppmなので、範囲外の値は読み飛ばす
long t = millis();
while (!ccs811.dataAvailable()) { // CCS811のデータが読み出し可能かチェック
smartDelay(100);
long er = millis() - t;
Serial.print("waiting for CCS811 data (max:3000 msec) : ");
Serial.print("Elapsed waiting time is "); Serial.print( er ); Serial.println(" msec.");
if (er > 3000) {
// 3秒以上データーが読み出し可能にならなければCCS811をリセットして再起動する
t = millis();
Serial.println("data unavailable for too long.");
ccs811_hw_reset();
CCS811Core::status returnCode = ccs811.begin(); // CCS811を初期化
Serial.println("Begin CCS811");
if (returnCode != CCS811Core::SENSOR_SUCCESS) { // 初期化に失敗したら
Serial.print("CCS811.begin() returned with an error: ");
Serial.print(returnCode, HEX); Serial.print(":");
display.clearDisplay(); display.setCursor(0, 30); display.print("Err CCS811"); display.display();
while (1) {
delay(0); // プログラムを停止する
}
}
}
}
ccs811.readAlgorithmResults(); // CO2とTVOCの値をCSS811から読み出す。
CO2 = ccs811.getCO2(); // CO2の値を取得する。
TVOC = ccs811.getTVOC(); // TVOCの値を取得する。
}
ccs811_sleep(); // CCS811のI2C通信を停止する。
}
time_t lasttime = 0;
void loop()
{
while (ss.available() > 0) {
if (gps.encode(ss.read())) {
break;
}
// /delay(0);
}
NowMode = (digitalRead(ModePin) == NowMode) ? 1 : 0 ; //change display mode, if mode switch on
if ((millis() - lasttime) > PERIOD * 1000 && gps.location.isValid() && gps.altitude.meters()) {
if (gps.date.day()!= Lastday){
setTime((gps.time.hour()), (gps.time.minute()), (gps.time.second()), (gps.date.day()), (gps.date.month()), (gps.date.year()));
adjustTime(offset * SECS_PER_HOUR); // /UTCとJSTの時間差(hour)を秒数に変換してarduinoの時計を調整
Serial.println("MCU date and time adjusted by GPS ");
Lastday = gps.date.day();
}
digitalWrite(2, HIGH); // データ更新中LED点灯
Serial.print(year()); Serial.print("/"); Serial.print(month()); Serial.print("/"); Serial.print(day()); Serial.print(" ");
Serial.print(hour()); Serial.print(":"); Serial.print(minute()); Serial.print(":"); Serial.print(second()); Serial.print(" ");
Serial.print("Elapse: "); Serial.print((millis() - lasttime)); Serial.print("msec");
Serial.print(" SATs:"); Serial.println(gps.satellites.value());
lasttime = millis();
char buf[16];
// /緯度・経度はTinyGPS++ライブラリでGPS度分データの分を60で割った10進数の度単位に換算されている
dtostrf(gps.location.lat(), 10, 6, buf); Serial.print("lat: "); Serial.print(buf);
dtostrf(gps.location.lng(), 10, 6, buf); Serial.print(", lng: "); Serial.print(buf);
dtostrf(gps.altitude.meters(), 6, 1, buf); Serial.print(", altitude: "); Serial.print(buf);
dtostrf(gps.hdop.hdop(), 4, 2, buf); Serial.print(", hdop: "); Serial.print(buf);
Serial.print(", vdop: "); Serial.print(vdop.value());
Serial.print(", pdop: "); Serial.println(pdop.value());
struct bme280_data data;
bme280.get_sensor_data(&data);
dtostrf(data.temperature, 5, 2, buf); Serial.print(" temp: "); Serial.print(buf);
dtostrf(data.humidity, 5, 2, buf); Serial.print(", humid: "); Serial.print(buf);
dtostrf(data.pressure / 100, 7, 2, buf); Serial.print(", press: "); Serial.print(buf);
float press0 = (data.pressure/100) / (pow((1-((0.0065*gps.altitude.meters())/(data.temperature+0.0065*gps.altitude.meters()+273.15))),5.257));
Serial.print(", press(0m): "); Serial.println(press0);
// 温度、湿度を渡して、CO2、TVOCの値を測定する
readCCS811(data.humidity, data.temperature);
Serial.print("CO2: ");
if(CO2 != 400){
Serial.print(CO2); Serial.print(" ppm, TVOC: "); Serial.print(TVOC);Serial.println(" ppb");
}
else{
Serial.println("Lower limit");
}
Serial.print("\r\n");
//Display
display.clearDisplay();
if (NowMode == 0){ // Air測定結果表示
display.setFont(&FreeMono9pt7b);
display.setCursor(0, 10);
display.print("Temp ");
display.print(data.temperature, 1);
display.println("'C");
display.setCursor(0, 27);
display.print("RHum ");
display.print(data.humidity, 1);
display.println("%");
display.setCursor(0, 44);
display.print("Pres ");
display.print(press0,0);
display.println("hP");
display.setCursor(0, 61);
display.print("CO2 ");
if(CO2 != 400){
display.print(CO2);
display.println("ppm");
}
else{
display.print("Lower");
}
}
else{ // GPSモニター表示
display.setFont(NULL);
display.setTextSize(1);
display.setCursor(10, 0);
display.print("GPS DATA MONITOR");
display.setCursor(0, 9);
display.print(year()); display.print("/"); display.print(month()); display.print("/"); display.print(day()); display.print(" ");
display.print(hour()); display.print(":"); display.print(minute()); display.print(":"); display.print(second());
display.setCursor(0, 18);
dtostrf(gps.location.lat(), 10, 6, buf); display.print("Lat: "); display.print(buf);
display.setCursor(0, 27);
dtostrf(gps.location.lng(), 10, 6, buf); display.print("Lng: "); display.print(buf);
display.setCursor(0, 36);
dtostrf(gps.altitude.meters(), 6, 1, buf); display.print("Alt: "); display.print(buf); display.print("m");
display.setCursor(0, 45);
display.print("SATs:"); display.print(gps.satellites.value());display.print(", pdop: "); display.print(pdop.value());
display.setCursor(0, 54);
dtostrf(gps.hdop.hdop(), 4, 2, buf); display.print("hdop: "); display.print(buf);display.print(", vdop: "); display.print(vdop.value());
}
display.display();
if (WFuse == 1){ // 温度、湿度、気圧、CO2の値をAmbientに送信する
ambient.set(1, data.temperature);
ambient.set(2, data.humidity);
ambient.set(3, data.pressure / 100);
ambient.set(4, press0);
ambient.set(5, CO2);
dtostrf(gps.location.lat(), 12, 8, buf);
ambient.set(6, buf);
dtostrf(gps.location.lng(), 12, 8, buf);
ambient.set(7, buf);
ambient.send();
}
}
else{
if (! gps.location.isValid()){
Serial.print("\r\nElapse:"); Serial.print(millis()/1000);Serial.println("sec Waiting for GPS data to stable.");
}
}
digitalWrite(2, LOW); // データ更新中LED消灯
smartDelay(800); // 測定インターバル最小値
}
static void smartDelay(unsigned long ms){ // 通常のdelay()を使用してプロセスを止めないように、TinyGPS++がsmartDelayを推奨している
unsigned long start = millis();
do
{
while (ss.available())
gps.encode(ss.read());
} while (millis() - start < ms);
}