前回に続き、段階圧密試験用データロガーをつくります。今度は少し趣向を変え、Wi-Fiでデータをインターネットに送り、自宅や外出先からでも長期試験の様子を確認できるようにします。ご存じの通り、標準圧密試験は1日ごとに荷重を増やしていくので、家から途中経過を見られても特段のメリットはないですが(どっちみち重りを置きに行かないといけないし・・・)、クリープ試験など長期にわたる特殊な試験では、心の平安のためにも進捗を見ておきたいことがあります。ここでは第32回で説明したESP32とAmbientを利用したロガーをつくります。繰り返すと、ESP32はArduinoの高性能版のようなマイコンボードで、WiFiやBluetoothモジュールが内蔵されています。また、Ambientはインターネット上でデータの記録と図化をやってくれるサービスです。ここではディスプレイとして、第18回で紹介したLCDではなく、OLED(有機LED)ディスプレイSSD1306の128×32ピクセル版を使います。1個500円程度で買えます。LCDよりも解像度が高く製品が小さいので、コンパクトに収まります。このOLEDはArduinoでも使うことができるのですが、解像度が高いため、文字を表示するライブラリのサイズが大きく、これをArduino UnoやPro Miniなどに入れると、他のスケッチがほとんど入らなくなります。ESP32(第31回で触れた通り、メモリ・処理能力ともにArduinoより格段に上)の場合、SSD1306を制御するライブラリがESP32のボードマネージャとともにArduino IDEにインストールされるので、新たにライブラリをダウンロードせずに使うことができます。ESP32のボードマネージャのインストールについては第32回を読んで下さい。
段階圧密試験用データロガー(ESP32+Ambient利用版)
■ 基本性能
4ゲージのひずみゲージ型センサー(段階圧密試験では変位計)の読みを24-bit分解能のHX711で取得し、毎秒OLEDディスプレイに表示する。Micro SDカードへの記録は、電源ONから1秒ごと120回(2分)、10秒ごと108回(18分)、1分ごと40回(40分)、10分ごと300回(約2日)、以降1時間ごと、とします。Wi-Fiでのインターネットへのデータ転送は毎時00分とします。ただし、電源ONから初期化にかかる時間を考慮し、5秒カウントダウンしてから記録を始めます(その間に、載荷用の重りを持ち上げてスタンバイします)。
■ 必要部品*
*価格は執筆の時点でAmazonで見つけた最安値に基づいて単価として表しています。「\320」とは「5個セット\1,600」という意味の場合もあります。
コア部
・ESP32開発ボード
(ここではNodeMCU-32SではなくESP32 Dev Moduleのほうとします)・・・互換品 \1,000
・HX711ブレークアウトモジュール(ひずみゲージアンプ)・・・\143
・OLEDディスプレイ SSD1306(128×32ピクセル)・・・\500
・SD(フルサイズ)シールド・・・\121
・RTCモジュール(DS3231、コイン電池付き)・・・\170
・ユニバーサル基板5cm×7cm・・・\100
・SD 4GB・・・\500
・その他、ジャンパーワイヤーの切れ端少々
計 約\2,500
付属部
・(変位計のシールドケーブル先がバラなら)端子台5極
ここで、RTC DS3231のモジュールは第7回で記したものではなく、先の写真のようにより小型(その代わり、アラーム用のパルス出力端子などが引き出していない)のものを使っています(I2Cで接続するなど、使い方は同じです)。ピンヘッダ―用のソケットがついていますが、ペンチでひっぺがしたうえで残ったはんだを溶かすことでフラットにでき、ユニバーサル基板に実装しやすくなります。なお、このモジュールはSDA・SCLではなくD・Cと書いてあることが多いです。
また、あえてMicro SDではなくフルサイズのSDカードを使っています。第5回で触れたように、SDカードのシールドにはいくつかタイプがあります(以下の写真)。真ん中のものはコンパクトで3.3V対応なのでESP32(3.3V駆動)には良さそうなのですが、なぜかESP32ではうまく動きませんでした。他のウェブサイトによると、SDカードシールドにはMOSI・MISO・SCK・CSに10kΩのプルアップ抵抗が必要とのことでしたが、このボードには見たところプルアップ抵抗はすでについてそうですし、やってみたところやはり動きませんでした。右のモジュールを使うとすんなり動きました(左も動きましたが、5Vで印加する必要があります)。
■ 必要外部ライブラリ
HX711.h(第19回参照)
Ambient.h(第32回参照)
SSD1306.h(先に書いた通り、ESP32ボードマネージャとともにArduino IDEにインストールされているので、その意味では「外部ライブラリ」ではない)
DS3231は第7回で使った外部ライブラリではなくESP32用のRTClib.h(ESP32ボードマネージャとともにArduino IDEにインストール済み)を使います。
■ 接続図
下図のように接続します。ESP32では、AtMega328PをつかったArduino(Uno、Nano、Pro Miniなど)とI2CやSPIのピンが全く異なることに注意。ESP32では、
I2C: SDA – IO21, SCL – IO22
SPI: MISO – IO19, MISO – IO23, SCK – IO18, SS(CS) – IO5
です。
■ 出来姿
RTCとSDシールドは見える必要もないですし、裏側に隠してしまいました。以下は地盤工学会誌の2021年1月号論説に掲載予定の写真です(西村 聡 論説「室内土質試験の新たなオプション」)。
上の写真の後、次にあげるスケッチで微妙に表示フォーマットを修正しました。このように見えます。
■ スケッチ
留意点は以下の通りです。
・例によって、一度RTCの時間合わせをした後、その文をコメントアウトしてもうスケッチをESP32に送ってください(「// RTCの設定」の次の2行))。
・WiFiのSSIDとPasswordは使っているルーター等を参照してください。
・ESP32へのスケッチの送り方は第32回に書いたとおりです。Dev Moduleの場合、何も工夫は要らず、Arduinoと同じようにただ送り込めばよいです。けっこう時間がかかります(数分かかることも)。
・第32回に書いたように、Ambientのアカウントを持っている(登録無料)ことが前提です。
・ESP32で使うSD.hは、Arduinoで使うSD.hと名前は同じですが異なります(異なるフォルダにあります)。ですので、使い勝手も微妙に異なります。これまで、FILE_WRITEでもデータは追記されてきましたが、ここではFILE_APPENDにしないと追記されず上書きされます(データが常に1つしかSDカードに残りません)。また、スケッチにあるようにファイルパスに”/”が必要です。このように、Arduinoに慣れているといろいろとクセがあるのがESP32です。
・ループは第35回に書いたように約1秒周期です。これはHX711のサンプリング周波数が10Hzで、サンプリングを10回行い平均をとっているからでした。この理屈だと、loop()関数全体で周期は1秒とちょっとかかるはずなのですが、実際には0.9secほどで回るようです。ということは、同じ秒の間に2回ループが通ることがときどき起こるので、同じ秒のデータを2回とらないようにprev_secとprev_minという変数を使って条件判定をしています(今の秒や分が先のループと同じならSDカードに記録しない)。
#include <Wire.h>
#include "SSD1306.h"
#include <WiFi.h>
#include "Ambient.h"
#include <RTClib.h>
#include <SPI.h>
#include <SD.h>
#include "HX711.h"
RTC_DS3231 rtc;
WiFiClient client;
Ambient ambient;
SSD1306 display(0x3c, 21, 22); //SSD1306インスタンスの作成(I2Cアドレス,SDA,SCL)
HX711 channel1;
const char* ssid = "SPWN_N35_791949";
const char* password = "3abce32a55646";
unsigned int channelId = 22048; // AmbientのチャネルID
const char* writeKey = "d7e39511a66a2df7"; // ライトキー
// Calibration factors for the sensor
float CF0=0; // [mm]: Offset at 0 reading
float CF1=0.001; // [mm/reading]: Slope
// Logging interval setting
int log_mode = 0; // 0: Every second, 1: Every 10 seconds, 2: Every 1 minute, 3: Every 10 minutes, 4: Every 1 hour
int log_num[4]={120, 108, 40, 300}; // それぞれのlog_modeでのログ回数
int log_cnt=0; // それぞれのlog_modeでの現在のカウント
bool log_fg=false;
int prev_sec;
int prev_min;
int ref_min;
int ref_sec;
void setup()
{
// シリアル通信の設定
Serial.begin(115200);
delay(10);
Serial.println("Start");
// WiFiの設定
WiFi.begin(ssid, password); // Wi-Fi APに接続
while (WiFi.status() != WL_CONNECTED) { // Wi-Fi AP接続待ち
delay(100);
}
Serial.print("WiFi connected\r\nIP address: ");
Serial.println(WiFi.localIP());
// Ambientの設定
ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
// OLEDの設定
display.init(); //ディスプレイを初期化
display.setFont(ArialMT_Plain_24); //フォントを設定
// SDカードモジュールの設定
Serial.println("SD_conect...");
delay(10);
if (!SD.begin(5))
Serial.println("Card failed, or not present");
else
Serial.println("card initialized.");
// RTCの設定
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
rtc.adjust(DateTime(2019, 1, 12, 10, 32, 0)); // Year, Month, Day, Hour, Minute, Second
// HX711の設定
channel1.begin(13, 14); // HX711.DOUT - pin IO13, HX711.PD_SCK - pin IO14
// 5秒のカウントダウン(PCとOLEDに表示)
display.drawString(0, 0, "Starting");
display.display(); //指定された情報を描画
delay(1000);
int cnt1;
char cnt[2];
for(cnt1=0;cnt1<5;cnt1++)
{
Serial.println(5-cnt1);
snprintf(cnt, 2, "%d", 5-cnt1);
display.clear();
display.drawString(0, 20, cnt);
display.display(); //指定された情報を描画
delay(1000);
}
}
void loop()
{
DateTime now = rtc.now();
// Displacement sensor reading: Convert to [mm] by CF0 and CF1
float sensor1=CF0+channel1.read_average(10)*CF1;
char disp[10];
snprintf(disp, 10, "%6.3f", sensor1);
// Display to PC via serial
Serial.print(now.year()); Serial.print("/"); Serial.print(now.month()); Serial.print("/"); Serial.print(now.day());Serial.print(" ");
Serial.print(now.hour()); Serial.print(":"); Serial.print(now.minute()); Serial.print(":"); Serial.print(now.second());Serial.print(" ");
Serial.print(sensor1); Serial.print(" log_cnt="); Serial.print(log_cnt); Serial.print(" log_mode="); Serial.println(log_mode);
// log_modeごとのSDカードへの記録判定
switch(log_mode)
{
case 0: // Every second
if(now.second()!=prev_sec)
log_fg=true;
break;
case 1: // Every 10 seconds
if((abs(now.second()-ref_sec)%10==0)&&(now.second()!=prev_sec))
log_fg=true;
break;
case 2: // Every 1 minute
if((ref_sec==now.second())&&(now.minute()!=prev_min))
log_fg=true;
break;
case 3: // Every 10 minutes
if((abs(now.minute()-ref_min)%10==0)&&(now.second()==ref_sec)&&(now.minute()!=prev_min))
log_fg=true;
break;
case 4: // Every 1 hour
if((now.minute()==ref_min)&&(now.second()==ref_sec)&&(now.minute()!=prev_min))
log_fg=true;
break;
}
// log_mode更新の判定
if((log_mode<4)&&(log_cnt==log_num[log_mode]))
{
log_cnt=0;
log_mode++;
ref_min=now.minute();
ref_sec=now.second();
}
else
{
log_cnt=0; // log_mode=4の場合、(現実的ではないが理論上)永遠に続けるとlog_cntの値がオーバーフローするので、毎回0に戻す
}
File dataFile = SD.open("/datalog.csv", FILE_APPEND);
if(dataFile&&log_fg)
{
dataFile.print(now.year(), DEC); dataFile.print("/");
dataFile.print(now.month(), DEC); dataFile.print("/");
dataFile.print(now.day(), DEC); dataFile.print(" ");
dataFile.print(now.hour(), DEC); dataFile.print(":");
dataFile.print(now.minute(), DEC); dataFile.print(":");
dataFile.print(now.second(), DEC); dataFile.print(" ");
dataFile.println(disp);
dataFile.close();
prev_sec=now.second();
prev_min=now.minute();
log_fg=false;
log_cnt++;
}
// OLEDに表示
display.clear();
display.drawString(0, 0, disp);
display.drawString(80,20, "mm");
display.display();
// Ambientに送信
if((now.minute()==0)&&(now.second()==0))
{
ambient.set(1, sensor1); // センサー値をデータ1にセット
ambient.send(); // データをAmbientに送信
}
}
圧密試験に限らず、長期試験の経過を週末も家から見たいときに応用がきく装置です。