2016年3月30日水曜日

ESP-NOW 1:N通信




■ESP-NOWの実験■

手前左がスレーブ(受信)、奥の4つがサーバ(送信)です。スレーブには4台分のMACアドレスを登録し、サーバには受信側のMACアドレスを登録してあります。

送信側:
起動してからsetup()でdigitalWrite(13,LOW)が実行されるまでの間は左側LEDがぼんやり点灯しています(deep sleep中はoff)。右側のシリアルTx LEDの2回目の点灯が送信です(deep sleepからの解除時のシステムメッセージで1回光り、送信時にもう一回光ります)。

受信側:
受信時に光ります。ご覧のとおり、送信側の2回目の点灯と受信側のシリアルの点灯が同期しています。よかったよかった。と思ったら1回明らかに抜けているところがあるなぁ。

フレームのレイヤで誤り検出をしているので、よその電波とぶつかって信号が乱れたらいさぎよく破棄されるのが電波通信の習わしです。きっと他のWiFiなどとの輻輳でデータが壊れたのでパケットが破棄されたのでしょう。

以下ソースは受信側(スレーブ)です。複数のサーバを相手にするために「氾濫原」さんのものに少し変更しました。ありがとうございます。

// ESP_NOW_Sleve(Receiver)
//
// thanks:
// http://lowreal.net/2016/01/14/2
// ESP8266 の低消費電力の限界をさぐる (ESP-NOWを使ってみる)
//
// Arranged by koichi kurahsahi 2016-03-31
// 相手サーバを4つに増やしましたw
//
#include <Arduino.h>
#include <ESP8266WiFi.h>
extern "C" {
#include <espnow.h>
#include <user_interface.h>
}
#define WIFI_DEFAULT_CHANNEL 1
uint8_t mac[][6] = {
{0x18, 0xFE, 0x34, 0xEF, 0x5C, 0x76},
{0x18, 0xFE, 0x34, 0xEE, 0x6D, 0xFC},
{0x5C, 0xCF, 0x7F, 0x08, 0x36, 0x9A},
{0x5C, 0xCF, 0x7F, 0x08, 0x36, 0xA1}
};
void printMacAddress(uint8_t* macaddr) {
Serial.print("{");
for (int i = 0; i < 6; i++) {
Serial.print("0x");
Serial.print(macaddr[i], HEX);
if (i < 5) Serial.print(',');
}
Serial.println("}");
}
void setup() {
pinMode(13, OUTPUT);
Serial.begin(115200);
Serial.println("Initializing...");
WiFi.mode(WIFI_AP);
WiFi.softAP("foobar", "12345678", 1, 0);
uint8_t macaddr[6];
wifi_get_macaddr(STATION_IF, macaddr);
Serial.print("mac address (STATION_IF): ");
printMacAddress(macaddr);
wifi_get_macaddr(SOFTAP_IF, macaddr);
Serial.print("mac address (SOFTAP_IF): ");
printMacAddress(macaddr);
if (esp_now_init() == 0) {
Serial.println("init");
} else {
Serial.println("init failed");
ESP.restart();
return;
}
esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
esp_now_register_recv_cb([](uint8_t *macaddr, uint8_t *data, uint8_t len) {
Serial.println("recv_cb");
Serial.print("mac address: ");
printMacAddress(macaddr);
Serial.print("data: ");
for (int i = 0; i < len; i++) {
Serial.print(" 0x");
Serial.print(data[i], HEX);
}
Serial.println("");
});
esp_now_register_send_cb([](uint8_t* macaddr, uint8_t status) {
Serial.println("send_cb");
Serial.print("mac address: ");
printMacAddress(macaddr);
Serial.print("status = "); Serial.println(status);
});
int l = sizeof(mac) / sizeof(mac[0]);
for (int i = 0; i < l; i++) {
Serial.print("ch=");
Serial.print(i);
Serial.println(esp_now_add_peer(mac[i], (uint8_t)ESP_NOW_ROLE_CONTROLLER, (uint8_t)WIFI_DEFAULT_CHANNEL, NULL, 0));
}
}
void loop() {
delay(500);
}

■怪現象■

なお、最初は4枚をミニブレッドボードに乗せたのですが、その時に奇妙な現象が起こりました。ごく接近した2枚が勝手に同期してしまうのです。最初電源の問題かと思ったのですが、ミニブレッドボードの端と端において離せば再現しません。別電源でも1cm程度に近づけると同期します。何が原因でしょうね。

Eagle自動配線の小ネタ

最初に書いておきますが、アマチュアの戯れ言です。

中学生の頃にも方眼紙とレタリングテープと塩化第二鉄でプリント基板を作っていました。その後長いブランクを経て、Eagleで設計+Elecrowに発注というタッグで再度基板を作るようになってからのお話。

2014年10月、最初の基板

初めての基板はスッカスカでした。それから約1年半が経過して、密度は大分向上しました。そうするとEagle自動配線一発では100%にはならず、混雑しているところをひっくり返したり優先したい配線を先に通したりして、何とかします。

で、以前はベタアースも全部設定してから自動配線していたんですが、ベタアースと同時に配線すると自動配線失敗する確率が高くなります。

なので、私は:

  1. 優先して扱うべき信号ラインがあれば再優先で最短距離を引く
  2. ここで一度gitに保存
  3. 電力系、スイッチング系があるときには1点に電源/アースを集中して手配線
  4. ここでまたgitに保存
  5. その他の配線が地下鉄大江戸線状態にならないようにレイアウトなどいじりながら様子を見る。どうにもならないときには2や1やその前に戻る
  6. いい感じになってからベタアースを施す

って感じで作業しています。gitにはsource treeというアプリを使ってます。Eagleはgitなど外部からのファイル操作に対応しているわけではないので、
  • 一度Eagleのwindowを閉じる
  • commitして保存するか、revertで過去に戻る。source treeだとどっちも直感的に操作できるので良です。
  • 再度Eagleで開く

という操作を入れてます。まぁコーヒーブレイクに丁度良いですね。

今は両面びっしり。吸着ピンセット欲しい

…冷静に考えてみると、基板設計技術が向上したわけではなくて、自動設計依存の設計フローが改善しただけですね…orz

プロの方は、どの程度自動設計を使っているんでしょう?

2016年3月28日月曜日

ESP-WROOM-02の直接通信モードESPNOWを試す


ESP NOWは生パケットをやりとりするモード。DHCPどころかチャンネル自動選択もないので基本的にはチャンネル指定して送りっぱなし。つまり、余計な処理がない分オーバーヘッドも消費電力が少ないということです。

いやー、知りませんでした。何か別のことを調べていたらこの記事がヒットして、そんなことがあったのかー、となった次第。ちゃんとリリースノートを読まないといかんですね。


■ビルドする■

ただし、esp-nowは普通のarduino sdkには含まれていないので、ビルド通りません。設定を変える必要があります。

Arduino IDE : 1.6.5
Board Manager : esp8266 2.1.0

一度Arduino IDEを終了し、
~/Library/Arduino15/packages/esp8266/hardware/esp8266/2.1.0/platform.txt
のcompiler.c.elf.libs=-lm -lgcc -lhal -lphy -lpp -lnet80211 -llwip -lwpa -lcrypto -lmain -lwps -laxtls -lsmartconfig -lmesh -lwpa2という行に -lespnowを追加します。

…って書くと一瞬なんですけどね、ここまでたどり着くのに2日かかりました。makeEspArduinoを試したり、PlatformIOをインストールしたり。まぁその過程でSublime TextからESPをビルドする方法が見つかったりしたので、まったくムダになったというわけではありませんが(空元気

■試す■

まず上記ページに掲載されているソースを試してみます。いやー、シンプルで美しい。なお蛇足ですがESP-WROOM-02が2枚必要です。

スレーブは受信側、常時電源入れっぱなしにしておいてデータが届くのを待つイメージ。コントローラは送信側です。このサンプルでは2.5秒ごとにdeep sleepから復帰して適当な文字データを送っています。Deep Sleepを使うので、RESETとIO16を接続する必要があります

なお、実際に送受信する前に、2枚のESPそれぞれのMACアドレスが必要です。今回のアプリを起動すると自分のStation interfaceとSoft AP interfaceのMACアドレスがシリアルに出力されますので、

Slave : 相手側(Controller)STATION_IFのMACアドレス
Controller:相手側(Slave)SOFTAP_IFのMACアドレス

これをそれぞれのソースの最初の方にあるuint8_t macにセットします。相手側のアドレスを設定して登録するというのがミソ、まぁMACアドレスなんて変更できちゃうんですが、一応世界の一つだけのアドレスということになっているので、これで相手をフィルタリングするわけです。そうでないとご近所のWiFiがたまたま同じチャンネルに送信している時のデータをすべて受けてしまうので使いにくくて死にます。

実行結果はこんな感じ。画面下がController、2.5秒ごとにdeep sleepから復帰してデータを送信してます。画面上はSlaveでIO13のLEDは1秒ごとにLチカ、Controllerからデータが送られるたびにシリアルのLEDが点滅しているのがご覧いただけるかと思います。

データを送信する側は起きてすぐ寝るという感じで、通常TCP/IPなどで送信する場合に接続するだけで2-5秒かかってしまうのと比べるとかなりの消費電力削減が期待できます。




今回はこれで力尽きました(結果としてはよそのWebに紹介されてたプログラムを走らせて終わりなんだけどさ、ビルド通すまでが大変でした)。次回はこれでちゃんとセンサーのデータを送ってみます。

■追記■

1度に送れるデータは200バイトまでです。

…センサーからのデータをまとめて送ろうとしてハマりました。送っても送っても届かなくて、散々調べたらこれが原因でした。ちゃんとエラーステータスを見ましょうね、というお話ですね。とほほ。

2016年3月27日日曜日

Electron届いた



あがー。操作ミスで記事が消えてしまった。

もう一度書くほどの内容ではないので要約:
  • Electronは2G/3G回線そのまま使えるマイコンモジュール、$69でとても安い
  • パッケージも製品もとてもきれいな仕上がり
  • アクティベートは簡単だけど、モジュールが起動するまでちょっと時間がかかる
  • プログラムのアップロードはairでもできるけど通信料かかるのでUSB経由がお勧め。だけどWeb IDEはair専用なので、CLIでビルドする必要がある。
  • CLIでコンパイルするにはファイルを列挙してクラウド上にアップロード&ビルドする必要がある(後述)
  • ビルドしたバイナリをアップロードするには particle flash --usb Blink.bin
  • ただArduinoのソースそのままビルド通すのはかなり大変。
    • #include <Wire.h>などは削除して #include "application.h"に変更
    • Wire.begin(sda, scl)形式のコンストラクタは使えないのでWire.begin()へ
  • 最終的には_yield() / yield()が見つからないって言われてgive up。

CLIでのコンパイル例:
particle compile electron SSD1306Demo.ino  SSD1306.cpp SSD1306.h SSD1306Fonts.h SSD1306Ui.h images.h --saveTo firm.bin


こんな感じです。苦労して最終的に成功したのであれば頑張ってもう一度記事を書くけど、最終的に動かなかったのだからボツ。

大人気で売れたわりにインターネットに上がっている作例が少ないのは、Arduinoのソース / ライブラリがそのまま使えないことに原因があるんじゃないかと思います。

製品の仕上がりもいいしドキュメント類は使いやすいとはいえないけど充実させようと頑張っているのが伝わってくる。良いプラットフォームに育って欲しいとは思うんだけど、どうも私とは相性が悪いです。

苦労した挙句動かなかったのでどうも使う気力がわかないんですが…せっかくお金払ったことですし…とりあえずGPIOなどはArduinoと同様に使えるので、手持ちのアナログ温度センサー、PIR、空気質センサー、ダストカウンター、GPSなどをつないでみようかな。

「実家のおかんの生存確認センサー」ぐらいには使えますね。

輸送中にぐちゃぐちゃにw

バッテリも附属

うーむ、ムダになってしまった

Sublime TextからESP-WROOM-02を扱う


ESP-WROOM-02のプログラムをビルド&アップロードする一番手っ取り早い方法はArduino IDEにESP-WROOM-02ボードを登録することで、皆さんもお使いかと思います。簡単に使えるので私も愛用していますが…ちょっと長いプログラムを書くのには向いていないかな?という気がしています(※個人の感想です)。

とりあえずArduino IDEを「外部のエディタを使う」設定にして(preferenceで)、Sublime Textでコード書いてました。Sublimeでテキストを保存するとちゃんとArduino IDEにも反映されるので、これで十分快適ですが、やっぱりショートカット一発でビルドしたいのが人情です。

Sublime textにStino packageを入れてみたのですが、ESPには対応していない様子。

その後、別のことを調べていたら、PlatformIOを入れて、Sublime TextにDeviotプラグインを入れれば、可能でした。

ただ、「どうしてもPlatformIOを使いたい」「どうしてもSublime Textから使いたい」ということでないのなら、Arduino IDEから全面移行するメリットは小さいかもしれません。

PlatformIOのインストールもあれこれdependencies地獄をさまようことになって大変です。それにディレクトリの構造がArduino標準とは違うので、Sketchディレクトリを開いて.inoファイルをエディットしてそのままビルド、とはいきません。まずPlatformIO形式のプロジェクトを作り、その中のソースをエディットし、必要なライブラリをインポートしてからビルド…という手順が必要です。

あとSublimeの場合、DeviotとArduinoがぶつかってしまうのか cmd+/ でコメントを切り替えることができなくなってしまいます。言語をC++に切り替えれば使えるようになりますが。

そんなこんなで、気楽に使うのであれば、Sublime TextにはDeviotは入れずにエディタとして使い、ビルドはこれまで通りArduino IDEにまかせておくのが良いかもしれません。

2016年3月23日水曜日

ESP-WROOM-02 + BME280、省エネ編

ベランダに置いてたけど、結構汚れるもんだ。

■Deep Sleep■

以前作ったESP-WROOM-02 + BME280の瓶入り気象?センサーを少し改良して、省エネルギー仕様にし、エネループ単3黒3本で駆動してみたところ、17日間動作しました。

ESP-WROOM-02はいつものTareObjects製Board1のプロトタイプ、deep sleepが働くようにIO16とRESETをつないであります。

プログラムは以下の通りです。

// 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"
}
//
// OLED / SSD1306
//
SSD1306 display(0x3c, 4, 5);
//
// Wifi
//
#include "../../private_ssid.h"
//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();
drawAndSend();
ESP.deepSleep((60-second())*1000*1000, WAKE_RF_DEFAULT);
delay(1000);
}
//
// loop
//
void loop() {
delay(1000);
}
//
// OLEDへの描画と送信
//
void drawAndSend() {
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();
// 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(humidityMostAccurate, 7, 2, buffer);
display.drawString(127, 28, buffer);
dtostrf(pressureMostAccurate, 7, 2, buffer);
display.drawString(127, 44, buffer);
display.display();
// ThingSpeak.comへ送信
sendTHP(tempMostAccurate, humidityMostAccurate, pressureMostAccurate);
}
//
// 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();
}


約1分ごとのデータ収集ですが、気象データならもっと低頻度で十分かもしれないですね。外に置きっぱなしなので、電池交換頻度は1ヶ月に一度として40日ぐらいは動いて欲しいです。となると3分に1回か…。

■追記6/11日

間隔を5分にしました。104行目を60から300に変えるだけです。あ、黒エネループで17日だったのか…白エネループ入れたので約2ヶ月ってところかしら。

2016年3月21日月曜日

ElecrowのPremier PCB Service

PCBAで出来てきた基板のシルク印刷やカットが今ひとつ汚かったので、もうちょっと何とかならんかね?と言ったら、「これはあくまでも安くて早いプロトタイプなので、きれいな仕上がりを求めるならPremierにしてくれ」との返事。

いつもはこんなにひどくないのよ。過去最悪。

そんなに超高品質の仕上がりを求めいているわけではないのですが…まぁそういうことならしょうがない、ってことで、注文。

28x50mm、両面、1.00mm、銅箔35μ、白レジスト黒文字を100枚…という条件で$130でした。

通常のPCB Serviceだと10枚10ドルです。もちろん枚数違いますけども、そんなに高くなるというわけではないですね。

ご参考まで。

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();
}