製作モジュール一覧

ラベル BME280 の投稿を表示しています。 すべての投稿を表示
ラベル BME280 の投稿を表示しています。 すべての投稿を表示

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対応させたところで、猛暑のピークは過ぎたようです(@@;

2018年8月8日水曜日

温度・湿度・気圧センサーのBME280をArduino Pro Miniで使う。

AliExpressで発注した温度・湿度・気圧センサーのBME280が届いたので動作確認しました。ショップはRoarKitで7月21日発注、8月7日到着で到着まで17日でした。

動作確認


ブレッドボード配線図

まずはArduino Pro mini(3.3V/8MHz)版を使って単体で動作確認です。USB-UART変換の接続はこちら

3.3Vの電源は安定化電源から供給しています。

BME280のモジュール上でI2Cのプルアップが行われているので外付け抵抗は不要です。

プルアップが行われているだけではなく、MOS-FETを使ったI2Cのレベル変換回路も載っているようです。(中華製なので回路図は怪しいですが・・・)

BME280モジュールの回路図

ArduinoのライブラリはSparkFun BME280 Arduino Libraryを使いました。

スケッチ例のSparkfunBME280_Example1_BasicReadingsを一部書き換えれば動作しました。

  mySensor.setI2CAddress(0x76);
  if (mySensor.beginI2C() == false) //Begin communication over I2C
  {
    Serial.println("The sensor did not respond. Please check wiring.");
    while(1); //Freeze
  }

ライブラリのデフォルトのI2Cアドレスは0x77ですが、モジュールのI2Cアドレスが0x76になっているようなので、mySensor.beginI2C() の前に、mySensor.setI2CAddress(0x76); を追加してI2Cアドレスを指定します。

I2Cアドレスをデフォルト(0x77)のままで動作させた場合のI2C信号

ch1:SDA ch2:SCK

動作させるとシリアルモニタにエラーメッセージが表示されますが、I2C信号は出力されています。

I2Cアドレスを0x76にして動作させた場合のI2C信号

ch1:SDA ch2:SCK

比較すると、I2Cアドレスが違っている場合は、マスターがI2Cアドレスを送信後、9ビット目のSDAがLに引っ張られていません。

スレーブが、受信したI2Cアドレスが自身のI2Cアドレスと一致していたとき9ビット目をLに引っ張り(ACK)、その後マスター・スレーブ間でデータの送受信が行われます。

また、スケッチの気温、高度の測定も単位が華氏、フィートになっているので摂氏、メートルに変更しました。

  Serial.print(" Alt: ");
  Serial.print(mySensor.readFloatAltitudeMeters(), 1);
  //Serial.print(mySensor.readFloatAltitudeFeet(), 1);

  Serial.print(" Temp: ");
  Serial.print(mySensor.readTempC(), 2);
  //Serial.print(mySensor.readTempF(), 2);

SSD1306 128x64のOLEDに表示


SSD1306 128x64 SPIのOLEDも同時に購入したので測定値を表示させました。

ブレッドボード配線図

<追記:2018.08.26> ブレッドボード図のArduino Pro miniのSPI/SCKの位置がずれていたので修正しました。(D12→D13) </追記>


※ブレッドボード配線図と写真は180度回転しています。

Arduinoのスケッチ <BME280_TempMeter_u8g2.ino>

OLEDのライブラリはu8g2を使用しました。


#include <U8g2lib.h>
#include <stdio.h>
#include <Wire.h>
#include "SparkFunBME280.h"

#define TITLE_STR1  ("BME280 Temp Meter")
#define TITLE_STR2  ("20180808")

const int measureInterval = 500;

//U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);  // Adafruit ESP8266/32u4/ARM Boards + FeatherWing OLED
U8G2_SSD1306_128X64_NONAME_1_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);

BME280 bme280;

void setup()
{
  Serial.begin(9600);
  Serial.println();
  Serial.println(TITLE_STR1);
  Serial.println(TITLE_STR2);

  // BME280
  Wire.begin();

  bme280.setI2CAddress(0x76);
  if (bme280.beginI2C() == false) //Begin communication over I2C
  {
    Serial.println("The bme280 did not respond. Please check wiring.");
    while(1); //Freeze
  }
  
  // u8g2
  u8g2.begin();
  u8g2.clearBuffer();
  u8g2.firstPage();
  u8g2.setFont(u8g2_font_8x13_tf);
  do {
    u8g2.drawStr(0,10,TITLE_STR1);
    u8g2.drawStr(0,25,TITLE_STR2);
  } while (u8g2.nextPage());
  u8g2.sendBuffer();

  delay(1000);
}

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

void loop()
{
  char strBuffer[32];
  char strBuffer2[32];

  float humidity = bme280.readFloatHumidity();
  float pressure = bme280.readFloatPressure();
  float Altitude = bme280.readFloatAltitudeMeters();
  float temperature = bme280.readTempC();
  float discomfort = calcDiscomfort(temperature, humidity);
  
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_logisoso28_tf);
    sprintf(strBuffer2, "%s%cC", dtostrf(temperature, 2, 1, strBuffer), 0xb0);
    u8g2.drawStr(0,32, strBuffer2);
  
    u8g2.setFont(u8g2_font_logisoso20_tf);
    sprintf(strBuffer2, "%s%%", dtostrf(humidity, 2, 1, strBuffer));
    u8g2.drawStr(0,64, strBuffer2);
    sprintf(strBuffer2, "%s", dtostrf(discomfort, 5, 1, strBuffer));
    u8g2.drawStr(64,64, strBuffer2);
  } while (u8g2.nextPage());

  Serial.print(temperature);
  Serial.print("\t");
  Serial.print(humidity);
  Serial.print("\t");
  Serial.print(pressure);
  Serial.println("");
  
  delay(measureInterval);
}

温度、湿度と不快指数を計算してOLEDに表示しています。

Serialには温度、湿度、気圧を出力しています。

電源電流はほぼ一定していて16mA程度です。