2016年3月16日水曜日

ミニブレッドボード満載

今気付いたけど温度と湿度が逆だな。直しました。

ミニブレッドボードはかわいい。場所を取らないし安い。出来る子なんです。というわけで、てんこ盛りにしてみました。

BME280、秋月さんがずっと品切れ状態なので、部品沼に沈んでいたものを使います。同ブレイクアウト基板はBME280の機能すべてを使える形のものが秋月さん、Sparkfun、スイッチサイエンスさんから出てますが、このメーカー不詳の基板はI2Cでの利用だけに特化しているので、場所を取らず配線がラクです。

空気質センサーは電源にUSBシリアルからの電源を直接つなぎ、センサーからのアナログ出力を半固定抵抗で分圧してTOUTに入力しています。

OLEDは例によって0.96インチSSD3306搭載のアレですが、今回は白を使ってみました。なお、ロットによってGNDとVCCの位置が逆だったりしますので、くれぐれもご注意ください。

肝心のESP-WROOM-02は自前のボードです。近日発売なので、その節はよろしくお願いします。 本日発売されました(2016年4月11日、追記)。

<追記 2016-03-18> 空気質センサーは90mAも消費するだけあって、動作させたままだと結構暖かくなります。放射温度計で測ったところ、一番暖かい場所で51度でした(室温24度)。使用する時にはこのことをお忘れなく。

■設計■

123Dとか使えば良いんですが、必要なモジュールがないと面倒だし、今回は回路ってほどのものではないので表計算を使いました(笑)。

あ、背景塗り忘れがある。けど面倒なので直さない

雑ですが、色は「硬いジャンパーワイヤ」の色です。」と「は曲がり角。まず最初に部品同士がぶつからないか実際に挿してみてレイアウトを決めます。それから、最初にGND、次に電源、それから信号線を置いていきます。置いたら長さを数えて、それに合った色を塗っておきます。

なお、配線図でBME280のVccとGndをESP-WROOM-02のIO13, IO12につないでいます。BME280はごくわずかな電流しか消費しないので、ESP-WROOM-02のGPIO出力から電源を取ってしまおうという寸法です。ピンによってはプルアップ済だったりリセット時に異常な電圧が出てくるのですが、IO4/IO5/IO12/IO13であれば問題ありません。ただ、ESPにかぎらずマイコンのGPIOはリセット時にハイインピーダンス状態になるので、起動したらなるべく早くHIGHとLOWを出力するようにします。間違ってもHIGHとLOWを間違えないようにしてください。

ちなみに私のジャンパーワイヤー入れはこんなぐあいに長さと色を表?にして張ってあります。長さより穴数の方が良かったなーと毎回思うので次のバージョンでは併記したいと思います。まぁたまにオレンジ色みたいな赤とか、青か緑か区別できない色のワイヤが混ざってたりして悩むのは改善されませんが。


なお、このパッケージに入って売っているジャンパーワイヤーは短いのがすぐになくなって、長いのだけが残ることで有名ですが、サンハヤトで短いやつだけ売ってます。直角になってなかったり平行でなかったりして閉口しますが、私はたくさん買ってストックしてあります。


■ソフトウェア■

今回先に貼っておきます。今まで使っていたBME280用ライブラリがこのモジュールでは動作しなかったので、例によって彷徨った結果、Sparkfunさんのライブラリが動作しました。I2cモードを選択し、I2Cアドレスは0x76ですのでご注意を。

余談ですが、以前から使っていたライブラリが動かなかった時には「あ、またebay.comにやられたか?」と思いました。Ebayはたまに部品番号はあっていてもタイトルが間違っていることがあります。以前9960を探している時に「RGB Gesture Sensor」って書いていてある9930というチップがヒットしました。やけに安かったのでたくさん買ってみたら単に明るさと近接センサーだけで安物買いのなんとやら。なので今回、もしや高価なBME280(温度湿度気圧)ではなくBME180(温度湿度)なのか?と思ったりしましたが、無事でした。

なお、9930はお風呂時計用デジタル時計で「部屋が明るくなったらLEDの表示をonにするための機能」として健気に働いております。

ebayのご利用は慎重に、くれぐれもat your own riskとpaypalで。

まぁ今回は、ライブラリ変えたら動きました。空気質センサーは半固定抵抗で分圧した値をTOUTに入力したので、アナログ値を読み取ってそのまま表示しています。

表示はOLED、以前と同じくこのライブラリを使っています。

各ライブラリの作者のみなさんに感謝致します。

// ESP_ALL_STARS.ino by Koichi Kurahashi 2016-03-15
// 2016-03-16 : ThingSpeakへの書き出しを追加。忘れてたわw
//
// thanks:
// OLED Library
// https://github.com/squix78/esp8266-oled-ssd1306
//
// BME280 Library
// https://learn.sparkfun.com/tutorials/sparkfun-bme280-breakout-hookup-guide
//
//
//
// ESP / Common
//
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <SPI.h>
#include <Time.h>
//
// Wifi
//
#include <WiFiClient.h>
#include "../../private_ssid.h"
//const char *ssid = "*************";
//const char *password = "*************";
WiFiClient client;
void SetupWiFi() {
WiFi.begin ( ssid, password );
Serial.println("Started");
}
//
// OLED / SSD1306
//
#include "SSD1306.h"
#include "SSD1306Ui.h"
//
// MBE280
//
#include "SparkFunBME280.h"
BME280 bme;
const int MBE_GND = 12;
const int MBE_Vcc = 13;
void SetupMBE() {
uint8_t chipID;
pinMode(MBE_GND, OUTPUT);
digitalWrite(MBE_GND, LOW);
pinMode(MBE_Vcc, OUTPUT);
digitalWrite(MBE_Vcc, HIGH);
bme.settings.commInterface = I2C_MODE;
bme.settings.I2CAddress = 0x76;
bme.settings.runMode = 3; //Forced mode
bme.settings.tStandby = 0; // 0.5mS
bme.settings.filter = 0;
bme.settings.tempOverSample = 2;
bme.settings.pressOverSample = 2;
bme.settings.humidOverSample = 2;
delay(10);
/// bme.begin();
}
//
// Air Quality
//
extern "C" {
#include "user_interface.h"
}
//
// OLED / SSD1306
//
SSD1306 display(0x3c, 4, 5);
void SetupSSD1306() {
display.init();
display.flipScreenVertically();
display.displayOn();
display.clear();
}
void setup() {
Serial.begin(115200);
Wire.begin();
SetupWiFi();
SetupMBE();
SetupSSD1306();
}
// ThingSpeakに1分ごとに送るためのフラグ
int prevMinute = minute();
//
// loop
//
void loop() {
float temp, humidity, pressure;
char buffer[80];
// BME280各データ取り出し
bme.begin();
delay(10);
temp = bme.readTempC();
humidity = bme.readFloatHumidity();
pressure = bme.readFloatPressure() / 100;
// Air Quality
int air = system_adc_read();
// OLEDへ表示
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString( 0, 0, "Temperature");
display.drawString( 0, 16, "Humidity");
display.drawString( 0, 32, "Pressure");
display.drawString( 0, 48, "Air Quality");
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
dtostrf(temp, 7, 2, buffer);
display.drawString(127, 0, buffer);
dtostrf(humidity, 7, 2, buffer);
display.drawString(127, 16, buffer);
dtostrf(pressure, 7, 2, buffer);
display.drawString(127, 32, buffer);
sprintf(buffer, "%5d ", air);
display.drawString(116, 48, buffer);
display.display();
// ThingSpeak.comへ送信
if ( WiFi.status() == WL_CONNECTED && minute() != prevMinute) {
sendTHPA(temp, humidity, pressure, air);
prevMinute = minute();
}
delay(80);
}
//
// ThingSpeakへのパラメータを作って送信
//
void sendTHPA(float inTemperature, float inHumidity, float inPressure, int inAirQuality) {
char tBuf[10], hBuf[10], pBuf[10], aBuf[10];
dtostrf(inTemperature, 7, 2, tBuf);
dtostrf(inHumidity, 7, 2, hBuf);
dtostrf(inPressure, 7, 2, pBuf);
sprintf(aBuf, "%d", inAirQuality);
String postStr = "&field1=" + String(tBuf) + "&field2=" + String(hBuf) + "&field3=" + String(pBuf) + "&field4=" + String(aBuf);
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();
}


■配線と動作確認■

設計通りにすべてのジャンパーワイヤーを刺していきます。直角に曲げるときにはラジオペンチやピンセットを使います。

刺し終わったものがこちら。


この状態でもう一度実物とあわせながら配線が間違っていないか確認します。


テスターがあれば、最低限GND-Vcc、Vcc-Vin、Vin-GNDがショートしていないかを確認しておきます。次に、ESP-WROOM-02だけを挿して、USBシリアルとの間の5vとGndだけを接続してみます。この状態で、もう一度テスターでGnd-Vin(5v)、Gnd-3v3の電圧を確認します。USBシリアルからの出力電圧やUSBからの電源電圧はわりといい加減ですが、どちらも誤差5%以内なら問題ないです。


さて、電源に問題がなければ、一度USBシリアルからUSBを抜いて、5v-Vin間の配線を外します。この状態で残りの部品を挿して行きます。

今までもわりと慎重に進めてきましたが、ここで間違うと元も子もないので、配線表と確認しながら実施します。なお、私は過去、そもそもの配線表が間違っていた、という過ちにより何度かチップを殺してしまったことがあります。くれぐれも各段階での確認は怠りなく。


そして、最後に電源を接続してUSBシリアルをUSBと接続、今回上記BME280ライブラリが動かなかった件を除けば、一発で動作しました。まぁ、今まで使ったものを組み合わせただけですからねぇ。

なお、まだGPIOピンが3本(電源に流用したものを含めれば5本)余ってますから、ダストカウンターを接続することもできますし、I2Cにはアドレスがぶつからなければ、まだ5-6個は搭載できます。TxとRxも空いているのでGPSも使えます。

…そんなのを何に使うんだ?という疑問をもってはいけません。

以上、ミニブレッドボードてんこ盛りでした。

■追記■

ダストセンサーPPD42も追加してみました。

…いかん、ミニブレッドボードからはみ出してしまった。


// ESP_ALL_STARS.ino by Koichi Kurahashi 2016-03-15
// 2016-03-18 Add Dust Counter
//
// thanks:
// OLED Library
// https://github.com/squix78/esp8266-oled-ssd1306
//
// BME280 Library
// https://learn.sparkfun.com/tutorials/sparkfun-bme280-breakout-hookup-guide
//
//
//
// ESP / Common
//
#define USE_US_TIMER 1
extern "C" {
#include "user_interface.h"
#include "osapi.h"
}
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <SPI.h>
#include <Time.h>
//
// Air Quality and Dust Counter
//
const int DustCounterP1 = 15;
const int DustCounterP2 = 16;
ETSTimer Timer;
int prevSecond = second(); // 秒ごとに表示更新するためのフラグ
static long sum1, current1;
static long sum2, current2;
static long store1[60], store2[60]; // data ring buffer
static float p1, p2; // 直近の計測値(1分移動平均)
static long c1, c2; // 直近の計測値(直近1秒)
void SetupDustCounter() {
sum1 = sum2 = current1 = current2 = -1;
for (int i = 0; i < 60; i++) {
store1[i] = store2[i] = -1;
}
pinMode(DustCounterP1, INPUT);
pinMode(DustCounterP2, INPUT);
os_timer_setfn(&Timer, fetchFunction, NULL);
os_timer_arm_us(&Timer, 1000, true);
}
//
// Wifi
//
#include <WiFiClient.h>
#include "../../private_ssid.h"
//const char *ssid = "*************";
//const char *password = "*************";
WiFiClient client;
void SetupWiFi() {
WiFi.begin ( ssid, password );
Serial.println("Started");
}
//
// OLED / SSD1306
//
#include "SSD1306.h"
#include "SSD1306Ui.h"
//
// MBE280
//
#include "SparkFunBME280.h"
BME280 bme;
const int MBE_GND = 12;
const int MBE_Vcc = 13;
void SetupMBE() {
uint8_t chipID;
pinMode(MBE_GND, OUTPUT);
digitalWrite(MBE_GND, LOW);
pinMode(MBE_Vcc, OUTPUT);
digitalWrite(MBE_Vcc, HIGH);
bme.settings.commInterface = I2C_MODE;
bme.settings.I2CAddress = 0x76;
bme.settings.runMode = 3; //Forced mode
bme.settings.tStandby = 0; // 0.5mS
bme.settings.filter = 0;
bme.settings.tempOverSample = 2;
bme.settings.pressOverSample = 2;
bme.settings.humidOverSample = 2;
delay(10);
}
//
// OLED / SSD1306
//
SSD1306 display(0x3c, 4, 5);
void SetupSSD1306() {
display.init();
display.flipScreenVertically();
display.displayOn();
display.clear();
}
void setup() {
system_timer_reinit(); // to use os_timer_arm_us
Serial.begin(115200);
Wire.begin();
SetupWiFi();
SetupMBE();
SetupDustCounter();
SetupSSD1306();
}
// ThingSpeakに1分ごとに送るためのフラグ
int prevMinute = minute();
//
// loop
//
void loop() {
float temp, humidity, pressure;
char buffer[80];
// BME280各データ取り出し
bme.begin();
delay(10);
temp = bme.readTempC();
humidity = bme.readFloatHumidity();
pressure = bme.readFloatPressure() / 100;
// Air Quality
int air = system_adc_read();
// dust counter : p1 and p2 is global
// OLEDへ表示
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString( 0, 0, "Temperature");
display.drawString( 0, 16, "Humidity");
display.drawString( 0, 32, "Pressure");
display.drawString( 0, 48, "Air");
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
dtostrf(temp, 7, 2, buffer);
display.drawString(127, 0, buffer);
dtostrf(humidity, 7, 2, buffer);
display.drawString(127, 16, buffer);
dtostrf(pressure, 7, 2, buffer);
display.drawString(127, 32, buffer);
sprintf(buffer, "%4d %4d %4d", air, (int)p1, (int)p2);
display.drawString(127, 48, buffer);
display.display();
// ThingSpeak.comへ送信
if ( WiFi.status() == WL_CONNECTED && minute() != prevMinute) {
sendTHPA(temp, humidity, pressure, air);
prevMinute = minute();
}
delay(80);
}
//
// Dust Counter
//
//
// Lowレベルの比率をダストカウント量に変換
// y = 1.1x^3 - 3.8x^2 + 520x + 0.62
// この式は以下のサイトの近似式を流用させていただきました。ありがとうございます。
// thank you for this site > http://www.howmuchsnow.com/arduino/airquality/grovedust/
//
float conversion(float inRate) {
return 1.1*pow(inRate, 3.0) - 3.8*pow(inRate, 2.0) + 520.0*inRate + 0.62;
}
//
// 100uSごとにP1とP2を監視し、Lowレベルの時間を計測する
// DustCounter check both ports every 100uSec to detect LOW level duration
//
void fetchFunction(void *temp) {
// 1秒ごとにリングバッファの計測スロットをずらしていく
int tempSecond = second();
if (tempSecond != prevSecond) {
Serial.println(tempSecond);
int n = 0;
long sum1 = sum2 = 0;
for (int i = 0; i < 60; i++) {
if (store1[i] != -1) {
sum1 += store1[i];
sum2 += store2[i];
n++;
}
}
p1 = conversion((float)sum1 / (float)n / 1000.0); // Lowレベル比率をダスト量に換算
p2 = conversion((float)sum2 / (float)n / 1000.0);
c1 = store1[prevSecond]; // 直近のカウント率を取得
c2 = store2[prevSecond];
store1[tempSecond] = 0; // 次のカウント記録場所をクリア
store2[tempSecond] = 0;
prevSecond = tempSecond; // 秒監視フラグをクリア
}
// P1がlowなら、P1のカウンタをインクリメント
if (store1[tempSecond] != -1 && digitalRead(DustCounterP1) == LOW) {
store1[tempSecond]++;
}
// P2がlowなら、P2のカウンタをインクリメント
if (store2[tempSecond] != -1 && digitalRead(DustCounterP2) == LOW) {
store2[tempSecond]++;
}
}
//
// ThingSpeakへのパラメータを作って送信
//
void sendTHPA(float inTemperature, float inHumidity, float inPressure, int inAirQuality) {
char tBuf[10], hBuf[10], pBuf[10], aBuf[10];
dtostrf(inTemperature, 7, 2, tBuf);
dtostrf(inHumidity, 7, 2, hBuf);
dtostrf(inPressure, 7, 2, pBuf);
sprintf(aBuf, "%d", inAirQuality);
String postStr = "&field1=" + String(tBuf) + "&field2=" + String(hBuf) + "&field3=" + String(pBuf)
+ "&field4=" + String(aBuf);
send(postStr);
Serial.println(postStr);
}
// ThingSpeakへ送信
//
void send(String inPostStr) {
String apiKey = "SA52EN1M2KLY0OXO";
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 件のコメント:

コメントを投稿

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