2016年1月24日日曜日

BME280とOLEDをESP-WROOM-02で使ってみた


■構成■

BME280はボッシュ製の気温湿度気圧センサー。ワンチップでこの3つが計測できてなかなか便利、しかも秋月で600円1080円。SPIかI2Cで接続できますが、今回はI2Cとします。しかし秋月さん、どのぐらいの単位で仕入れているんでしょうね。Mouser.jpとかでこのチップ取り寄せても700円以上するし基板は特殊形状なので普通ならかなり高価になるはずです。考えるだけで恐ろしい。

OLEDは今や電子工作標準品ともいうべき0.96インチサイズのアレ。作例もライブラリも豊富、I2Cと電源/GNDの合計4本だけで動いてしまいフレームバッファ(画像メモリ)も持っているので何かとラク。

できあがったあとで、WiFiついてるのにセンサーと表示器を同居させてどうすんだ?と自分自身に突っ込みました。

■製作■

何かケースに入れて外に設置したいので、今回もユニバーサル基板で組みました。以前、「いつかきっと使う」という想いで買い集めたaitendoのFRISK大の変換基板を適当に折って使います。基板上にモジュールを挿したヘッダソケットを挿して見て、レイアウトに見当を付けます。ハンダ付けが終わったあとでおとなりと干渉して実装できない、なんてのは悲しすぎますので。

それから秋月の資料とOLEDの実物をみながら、簡単に配線メモを書きます。前回のLEDで懲りました。複数の資料をあたりながら裏側での配線を考えるのは私の脳ミソでは無理だ、と。ということで、裏側、配線面で各要素のピンがどうなっているかをメモにしておきます。



上の段からOLED、ESP基板、BME280基板I2Cでの接続(ユニバーサル基板上での位置、I2Cでの接続、BME280基板での名前)と並んでます。下の2つは作業過程のメモで使うのは上の3つです。上から4ピンヘッダソケット、9ピン足長ソケット(10ピンのを切断して使用)、6ピンヘッダソケットを使います。BME280のCSBは3.3v、SDOはGNDに接続しました。SDOはHIGHかLOWかでI2Cのアドレスを切り替えることができますが、GNDに接続するとデフォルトの0x76になり、今回使ったライブラリもそのままで動きます。



VDD、GND、SDA、SCLをそれぞれ接続します。ああ言葉で言うと簡単。朝っぱらから2時間かかったですよ、これ作るのにorz ヘッダソケットで3階建てになっているので、ヤケに高さがあります。

一番上で緑の背中を見せているのはBME280


■ソフトウェア■

BME280はこちらのライブラリを使い、サンプルを参考にさせていただきました(参考というかほぼ手直しが必要ないのですけども)。

OLEDはこちらです。今回、単純な文字表示にしか使わなかったのですが、uiクラスがあってそれを使うと複数画面を指定したアニメーションで入れ替えつつ任意の位置にミリ秒単位で別の画面をオーバーレイできる、という素晴らしい完成度です。また文字表示も素晴らしく、きれいなフォントはサイズが3種類あって右寄せ左寄せなども使えます。残念ながらクラスにラインを引っ張る命令がないので、そのうちDDAか何かで作って貢献したいですが…。

これら素晴らしいライブラリの作者に経緯を表し、感謝申し上げます。

さて、書いたプログラムはごく単純です。setupで各要素を初期化、loopでは1秒ごとに計測して表示を更新し、1分ごとにThingSpeak.comに送っています。

// ESP_BME280_SSD1306 by k.kurahashi 2016-01-24
//
// thanks:
// BME280 Library
// https://github.com/embeddedadventures/BME280
// OLED Library
// https://github.com/squix78/esp8266-oled-ssd1306
//
// ThingSpeak.com
// https://thingspeak.com/channels/81094
//
#include <Wire.h>
#include <Time.h>
#include <BME280_MOD-1022.h>
#include "SSD1306.h"
#include "SSD1306Ui.h"
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
extern "C" {
#include "user_interface.h"
}
//
// BME280
//
float averageHumidity; // 湿度の計測値はわりとフラフラするので簡単な移動平均を取る
//
// OLED / SSD1306
//
SSD1306 display(0x3c, 4, 5);
//
// Wifi
//
const char *ssid = "***********";
const char *password = "***********";
WiFiClient client;
//
// Setup BME280 ほぼサンプル, thanks for Embedded Adventures
//
void setup_BME280() {
uint8_t chipID;
Serial.println("Welcome to the BME280 MOD-1022 weather multi-sensor test sketch!");
Serial.println("Embedded Adventures (www.embeddedadventures.com)");
chipID = BME280.readChipId();
// find the chip ID out just for fun
Serial.print("ChipID = 0x");
Serial.print(chipID, HEX);
// need to read the NVM compensation parameters
BME280.readCompensationParams();
BME280.writeStandbyTime(tsb_0p5ms); // tsb = 0.5ms
BME280.writeFilterCoefficient(fc_16); // IIR Filter coefficient 16
BME280.writeOversamplingTemperature(os2x); // temperature x2
BME280.writeOversamplingHumidity(os1x); // humidity x1
BME280.writeOversamplingPressure(os16x); // pressure x16
BME280.writeMode(smNormal);
}
//
// setup
//
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
//
// Wifi
//
WiFi.begin ( ssid, password );
Serial.println("Started");
// Wait for connection
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
Serial.println("Wifi Connected");
// BME and OLED
Wire.begin();
setup_BME280();
display.init();
display.flipScreenVertically();
display.displayOn();
display.clear();
}
//
// loop
//
void loop() {
draw();
delay(1000);
}
int prevMinute = minute(); // 分ごとに送信するためのフラグ
//
// OLEDへの描画
//
void draw() {
float temp, humidity, pressure, pressureMoreAccurate;
double tempMostAccurate, humidityMostAccurate, pressureMostAccurate;
char buffer[80];
// BME280起動待ち
BME280.writeMode(smForced);
while (BME280.isMeasuring()) {
delay(50);
}
// BME280計測実行
BME280.readMeasurements();
// BME280各データ取り出し
temp = BME280.getTemperature();
humidity = BME280.getHumidity();
pressure = BME280.getPressure();
pressureMoreAccurate = BME280.getPressureMoreAccurate(); // t_fine already calculated from getTemperaure() above
tempMostAccurate = BME280.getTemperatureMostAccurate();
humidityMostAccurate = BME280.getHumidityMostAccurate();
pressureMostAccurate = BME280.getPressureMostAccurate();
// 湿度の値を平準化
averageHumidity = (averageHumidity * 9.0 + humidityMostAccurate) / 10.0;
// OLEDへ表示
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString( 0, 16, "Temperature");
display.drawString( 0, 32, "Humidity");
display.drawString( 0, 48, "Pressure");
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
dtostrf(tempMostAccurate, 7, 2, buffer);
display.drawString(127, 12, buffer);
dtostrf(averageHumidity, 7, 2, buffer);
display.drawString(127, 28, buffer);
dtostrf(pressureMostAccurate, 7, 2, buffer);
display.drawString(127, 44, buffer);
display.display();
// ThingSpeak.comへ送信
if (minute() != prevMinute) {
sendTHP(tempMostAccurate, averageHumidity, pressureMostAccurate);
prevMinute = minute();
}
}
//
// ThingSpeakへのパラメータを作って送信
//
void sendTHP(float inTemperature, float inHumidity, float inPressure) {
char tBuf[10], hBuf[10], pBuf[10];
dtostrf(inTemperature, 7, 2, tBuf);
dtostrf(inHumidity, 7, 2, hBuf);
dtostrf(inPressure, 7, 2, pBuf);
String postStr = "&field1=" + String(tBuf) + "&field2=" + String(hBuf) + "&field3=" + String(pBuf);
send(postStr);
Serial.println(postStr);
}
// ThingSpeakへ送信
//
void send(String inPostStr) {
String apiKey = "********************";
Serial.print("Connecting...");
if (client.connect("184.106.153.149", 80)) { // api.thingspeak.com
Serial.print("Connected....");
String postStr = apiKey + inPostStr + "\r\n\r\n";
client.print("POST /update HTTP/1.1\n");
client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");
client.print("X-THINGSPEAKAPIKEY: " + apiKey + "\n");
client.print("Content-Type: application/x-www-form-urlencoded\n");
client.print("Content-Length: ");
client.print(postStr.length());
client.print("\n\n");
client.print(postStr);
Serial.println("posted.");
}
client.stop();
}
ご質問などはコメント欄にご遠慮なく。

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。