2018年8月16日木曜日

ESP WROOM 02(ESP8266)でBME280とSSD1306 OLED(I2C)を使う。

ESP WROOM 02(ESP8266)で、気温、湿度、気圧センサーのBME280とSSD1306 OLEDを同時に使用しました。

SSD1306はI2C接続のものとSPI接続のものがありますが、ESP8266のSPIは使ったことがないので、とりあえずI2C接続のものを使用しました。

I2Cは信号線をプルアップして使うので、I2CとSPIで信号線を共有するのは難しそうです。ESP8266はIOで使えるピンが少ないので、SPIとI2Cを同時使用するにはちゃんと検証する必要がありそうです。

ESP8266のI2Cの使い方もよくわかっていないので、ライブラリ等ありものを使わせていただいてとりあえず動作させるのを優先しました。

ブレッドボード配線図


運用時(USB-UART変換モジュールなし)

I2Cの信号線はBME280モジュールでもSSD1306モジュールでもオンボードでプルアップされているので、理屈としてはそれぞれの抵抗を並列した値になります。
10kΩ // 4.7kΩ ≒ 3.2kΩ
Lの時に (3.3V / 3.2kΩ) ≒ 1mA 程度流れます。SCLとSDAがあるので無視できない消費量ですね。

Arduinoのスケッチ


BME280のライブラリは「SparkFun BME280 Arduino Library」を使用していましたが、問題がありました。

I2CのピンがデフォルトでハードウェアI2Cのものを使うようになっていて、自由にピンを割り当てられるソフトウェアI2Cを使うのが少しめんどうです。

スケッチ例に「Example9_SoftwareI2C.ino」があって、Arduino IDEのライブラリマネージャから「SoftwareWire by Testato」をインストールして使うようになってます。

他にI2CのピンをアサインできるBME280のライブラリを探してみると「Arduino-ESP8266-BME280」がありました。(Ambientさんでした)

exampleもAmbientに送るようになっているのでそのまま使わせていただくことにしました。

SSD1306のライブラリはu8g2を使いました。

参考
HiLetogo 0.9" I2C OLED 128X32をu8g2で使ってみる。
u8g2のFull BufferとPage Buffer

BME280、SSD1306のどちらのライブラリもArduinoのI2C制御クラスのWireオブジェクトをクラス内で初期化(begin())していて同時に使用するのは怖いんですが、ArduinoのI2C制御クラスのWireはSingletonになっているとおもうので(←確証なしです)、SCLとSDAに使うPINを合わせておけばだいじょうぶかな~と半分やけくそで使いました。

結果としては動作しているように見えます。

WiFiでAmbientにデータを送信するようにして、DeepSleepをかけました。

<Ambient_ESP8266_BME280_u8g2.ino>

#include <ESP8266WiFi.h>
#include <Wire.h>
#include "BME280.h"
#include "Ambient.h"
#include "U8g2lib.h"
#include "etc/config.h"

extern "C" {
#include "user_interface.h"
}

#define UART_TRACE (1)
#define PIN_CHECK  (1)
#define USE_OLED   (1)

#define TITLE_STR1  ("ESP8266 BME280 Thermo")
#define TITLE_STR2  ("2018.08.13")

#define LED 4
#define SDA 14
#define SCL 13

#define PERIOD 60

/*
  const char* ssid = "ssid";
  const char* password = "password";
  unsigned int channelId = 100;
  const char* writeKey = "...writeKey...";
*/

WiFiClient client;
Ambient ambient;
BME280 bme280;
#if (USE_OLED)
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);
#endif

float calcDiscomfort(float temperature, float humidity) {
  return 0.81 * temperature + 0.01 * humidity * (0.99 * temperature - 14.3) + 46.3;
}

void setup()
{
  int t = millis();
  //wifi_set_sleep_type(LIGHT_SLEEP_T);

  struct rst_info* p_reset_info = ESP.getResetInfoPtr();

#if (UART_TRACE)
  Serial.begin(115200);
  delay(10);
  Serial.println("");
  Serial.println("");
  Serial.println(TITLE_STR1);
  Serial.println(TITLE_STR2);
  Serial.print(ESP.getResetReason());
  Serial.print("\t");
  Serial.println(p_reset_info->reason);
  Serial.print("Start");
#endif
#if (PIN_CHECK)
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);
#endif

  // BME280
  bme280.begin(SDA, SCL);
  // 電源投入直後の値は不安定なので、読み捨てる
  bme280.readTemperature();
  bme280.readHumidity();
  bme280.readPressure();

  double temperature = 0.0;
  double humidity = 0.0;
  double pressure = 0.0;
  float discomfort = 0.0;

  temperature = bme280.readTemperature();
  humidity = bme280.readHumidity();
  pressure = bme280.readPressure();
  discomfort = calcDiscomfort(temperature, humidity);

#if (UART_TRACE)
  Serial.print("temp: ");
  Serial.print(temperature);
  Serial.print(" DegC,  humid: ");
  Serial.print(humidity);
  Serial.print(" %, pressure: ");
  Serial.print(pressure);
  Serial.print(" hPa ");
  Serial.print(discomfort);
  Serial.println("");
#endif

#if (USE_OLED)
  u8g2.begin();
  if (p_reset_info->reason != REASON_DEEP_SLEEP_AWAKE) {
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_7x14_tf);
    u8g2.drawStr(0, 16, TITLE_STR1);
    u8g2.drawStr(0, 32, TITLE_STR2);
    u8g2.sendBuffer();
    delay(3000);
  }

  char strBuffer1[16];
  char strBuffer2[16];
  
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_logisoso20_tf);
  sprintf(strBuffer2, "%s%cC", dtostrf(temperature, 2, 1, strBuffer1), 0xb0);
  u8g2.drawStr(0, 32, strBuffer2);
  u8g2.setFont( u8g2_font_helvR14_tr);
  sprintf(strBuffer2, "%s%%", dtostrf(humidity, 2, 1, strBuffer1));
  u8g2.drawStr(78, 16, strBuffer2);
  u8g2.drawStr(78, 32, dtostrf(discomfort, 2, 1, strBuffer1));
  u8g2.sendBuffer();
#endif

  // WiFi
  WiFi.begin(ssid, password);

  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);

#if (PIN_CHECK)
    digitalWrite(LED, i++ % 2);
#endif
#if (UART_TRACE)
    Serial.print(".");
#endif
  }
#if (PIN_CHECK)
  digitalWrite(LED, LOW);
#endif

#if (UART_TRACE)
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.print(WiFi.localIP());
  Serial.println("");
#endif

  ambient.begin(channelId, writeKey, &client);
  ambient.set(1, temperature);
  ambient.set(2, humidity);
  ambient.set(3, pressure);
  ambient.set(4, discomfort);
  ambient.send();

  t = millis() - t;
  t = (t < PERIOD * 1000) ? (PERIOD * 1000 - t) : 1;

  ESP.deepSleep(t * 1000, RF_DEFAULT);
  delay(1000);
}

void loop()
{
}

スケッチ冒頭の

struct rst_info* p_reset_info = ESP.getResetInfoPtr();

でResetの理由を保持するreset_infoを取得して、起動時にタイトル画面をOLEDに表示する処理を、Sleep復帰時には

if (p_reset_info->reason != REASON_DEEP_SLEEP_AWAKE) {
...
}

としてバイパスするようにしています。

ESP.getResetReason()

で、整数値ではなくString型でResetの理由も取得できます。

Sleep中もOLEDに電源を供給しておけば表示され続けますが、Sleepからの復帰時にOLEDが初期化されるので一瞬OLEDの表示が消えます。

電池ももったいないし、OLEDは動作確認用として、長期運用する場合は外しておくつもりです。

もっと細かい周期で環境を測定するためには、例えばArduino Nanoを使ってUSB経由でロギングするものを作ればいい?

Ambientのグラフ


電源電流


電源に0.1ΩのRを直列に入れOWON B35(DC/mV)で測定(1mV→10mA換算)。1秒周期

OLED、LEDあり

Avg 1.2414 mV
Min 0.59 mV
Max 12.83 mV

WakeUp時に80mA~130mA程度、Sleep時に6mA程度

OLED、LEDをブレッドボードから抜いた

Avg 0.614133333 mV
Min 0 mV
Max 12.35 mV

WakeUp時に70mA~125mA程度、Sleep時に0mA程度

OLED、LEDがあると常時6mA程度消費します。

I2C信号


ch1:SDA ch2:SCL

IC2クロックは多少揺れがあり、100kHzより少し遅いぐらいです。←Standard Modeぐらい。

と、なんとか不快指数計をWiFi対応させたところで、猛暑のピークは過ぎたようです(@@;

0 件のコメント:

コメントを投稿