google-site-verification: google3bd66dd162ef54c7.html
FC2ブログ

水草水槽のCO2圧力変化をOLEDにグラフ表示

 ペン型オシロが完成したので、0.96インチOLEDを使った遊びは山場を越えました。ところで、最初にやってみたかったのは、水草水槽用の化学反応式CO2発生装置の圧力計のモニタです。そんなことで遅まきながら、CO2発生タンクの圧力変化を記録してみました。

▼水草水槽に取り付け
水草水槽とCO2発生装置
 左が水草水槽で、右側がCO2供給装置。右手前が今回作った圧力モニタ回路です。

▼圧力モニタ回路
OLEDとArduino
 仮設置しただけなので、既存の回路にミノムシクリップで接続しています。

▼圧力変化グラフ
CO2圧力変化グラフ
 縦軸は圧力で、mmAq単位。横軸の1目盛りは6時間なので、過去30時間分の圧力の変化を表示するようになっています。なお、右端が現在時刻で、この写真は午前10時頃のものです。

 昼間の圧力は710mmAq付近で、この圧力で水槽のストーンにCO2を供給しています。夜間はCO2の発生を止めて(少なくして)いるので圧力が下がっています。朝になるとCO2の発生を再開しますが、ストーンに水が入っているためだと思いますが、圧力が一旦1100mmAq以上まで上がった後、で定常状態になっています。

 この一連の圧力変化で一番気になっているのは、夜間に圧力がマイナスになってしまうことです。負圧になると重曹タンクに水槽の水が吸い込まれてしまう恐れがあります。

 この問題の対策として、水が逆流しないように逆止弁を入れています。また、夜間も少しずつCO2を発生させることでタンク圧力の低下を防いでいます。ただ、実際にどういう値になっているのかを見るために、一晩中見張っている訳にもいかないので、こんなふうに圧力変化を連続でモニターする仕掛けが欲しかった訳です。

 なお、夜間にCO2の圧力が低下する原因は、気温の変化(低下)と水へのCO2の溶解量の変化(増加)が原因だろうと推定しています。また、重曹を新たにチャージした後は負圧になる傾向が強くなるので、反応生成物の量の影響もあるようです。

▼回路図
ArduinoとOLEDによるロガー
 Arduino UNO にちょっと配線しただけの簡単な回路です。

 ソフトは以前の記事で公開したものとあまり変わりませんが、以下の通りです。

◆プログラム
/* 圧力トレンドロガー(_20190311DataTrendLogger.ino)
0.96インチOLEDにアナログ入力の変化グラフを書く
(表示フォーマットや換算式をある程度汎用化したバージョン)
2019/03/11 ラジオペンチ http://radiopench.blog96.fc2.com/
*/

#include <Wire.h>
#include <Adafruit_GFX.h> // adafruitのライブラリを使用
#include <Adafruit_SSD1306.h>
#include <MsTimer2.h> // 定周期割込みに使用

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define STRING_L 4 // 合計表示文字数
#define AFTER_DECIMAL_L 0 // 小数点以下の表示桁数

//#define LOG_INTERVAL 110 // ログ記録周期 110ms(最速)
//#define LOG_INTERVAL 2000 // ログ記録周期 2秒(200秒記録)
//#define LOG_INTERVAL 10000 // ログ記録周期 10秒(16.7分記録)
//#define LOG_INTERVAL 30000 // ログ記録周期 30秒(50分記録)
//#define LOG_INTERVAL 60000 // ログ記録周期 1分(100分記録)
//#define LOG_INTERVAL 864000 // ログ記録周期 864秒(24時間記録)
#define LOG_INTERVAL 1080000 // ログ記録周期 1080秒(30時間記録)

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

int latestData;
int dataBuff[110]; // データーバッファ
char chrBuff[10]; // 表示フォーマットバッファ
int dataMin;
int dataMax;
volatile boolean timeFlag = LOW;
float aaa = 4.5332; // 圧力換算係数 aaa y =aaa * x + bbb
float bbb = -448.0; //        bbb

void setup() {
pinMode(13, OUTPUT);
Serial.begin(115200);
// analogReference(INTERNAL); // フルスケール=1.1V

if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x64
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Don't proceed, loop forever
}
// pinMode(SCL,INPUT); // CPU側のプルアップを外す(場合による)
// pinMode(SDA,INPUT); // (OLED側で3.3Vにプルアップされているため)

for (int i = 0; i <= 99; i++) {
dataBuff[i] = -1; // バッファを未定義フラグ(-1)で埋める
}
latestData = pressMes(); // 読み捨て
latestData = pressMes();

display.clearDisplay(); // 画面全消去(0.4ms)
writeCommonImage(); // 共通部分を描画
dispNewData(); // 最新値を表示(1.42ms)
display.display();

MsTimer2::set(LOG_INTERVAL, timeUp); // 指定インターバルで割り込み
MsTimer2::start(); // タイマー割り込み開始
}

void loop() {
digitalWrite(13, HIGH);
latestData = pressMes(); // 圧力測定(26ms)
saveBuff(); // 表示バッファに書き込み(180us)
writeCommonImage(); // 共通イメージ作画(5.9ms)
dispNewData(); // 最新の値を表示(1.42ms)
plotData(); // グラフプロット(約20ms波形によって変わる)
dispVscale(); // 縦軸目盛り表示(6.6ms)
display.display(); // バッファの値を転送して表示(37ms)
digitalWrite(13, LOW); // 処理時間合計=約95ms

while (timeFlag == LOW) { // MsTimer2割込みが入るまで待つ
}
timeFlag = LOW; // フラグをクリアして次の割込みに備える
}

int pressMes() { // 圧力測定ルーチン
int pD;
long x = 0;
int count = 200; // 平均回数
for (int i = 0; i < count; i++) { // 指定回数積分
x = x + analogRead(0); // 圧力センサーを読む
}
pD = x / (count / 20); // 平均値計算(但し20倍)
// Serial.println(mmAq(pD));
return pD; // 圧力センサーの値を返す
}

void dispVscale() { // 縦軸目盛りの値を表示
float p1, p2, delta;
p1 = mmAq(dataMin); // 圧力値に変換
p2 = mmAq(dataMax); //
delta = p2 - p1;

dtostrf(p2, STRING_L, AFTER_DECIMAL_L, chrBuff); // 書式設定
display.setCursor(0, 9); display.print(chrBuff); // Max値表示
dtostrf(p1 + 2.0 * delta / 3.0, STRING_L, AFTER_DECIMAL_L, chrBuff); // 書式設定
display.setCursor(0, 24); display.print(chrBuff); // 2/3値表示
dtostrf(p1 + delta / 3.0, STRING_L, AFTER_DECIMAL_L, chrBuff); // 書式設定
display.setCursor(0, 41); display.print(chrBuff); // 1/3値表示
dtostrf(p1, STRING_L, AFTER_DECIMAL_L, chrBuff); // 書式設定
display.setCursor(0, 57); display.print(chrBuff); // Min値表示
}

void saveBuff() { // データバッファの更新と最大・最小値の修正
int d;
dataMin = 30000; // 最小値
dataMax = 0; // 最大値

for (int i = 98; i >= 0; i--) { // 配列に値を保存しながら最大と最小値を求める
d = dataBuff[i];
dataBuff[i + 1] = d; // 配列のデーターを一つ後ろにずらし
if (d != -1) { // ずらしたデータが有効値だったら、
if (d < dataMin) { // 最小と
dataMin = d;
}
if (d > dataMax) { // 最大値を記録
dataMax = d;
}
}
}
dataBuff[0] = latestData; // 配列の先頭には最新データーを記録し、
if (latestData < dataMin) { // 最小と
dataMin = latestData;
}
if (latestData > dataMax) { // 最大値を再確認
dataMax = latestData;
}
}

void dispNewData() { // 最新データーの値を画面の右上に表示
float p;
p = mmAq(latestData);
dtostrf(p, STRING_L, AFTER_DECIMAL_L, chrBuff); // 指定フォーマットで
display.setCursor(80, 0);
display.print(chrBuff); // 最新測定値を表示
}

void plotData() { // 配列の値に基づきデーターをプロット
long y1, y2;
for (int i = 1; i <= 99; i++) { // 線で接続するために、先頭の次データから開始
if (dataBuff[i] == -1) { // データーが未定(-1)なら
break; // プロット中止
}
y1 = map(dataBuff[i - 1], dataMin, dataMax, 63, 9); // y1をプロット座標へ変換(i番目のデーターが有効なのでその前は必ず有効)
y2 = map(dataBuff[i], dataMin, dataMax, 63, 9); // y2もプロット座標へ変換
display.drawLine(127 - i, y1, 126 - i, y2, WHITE); // 点間を線で結ぶ
}
}

float mmAq(int x) { // データーをmmAq単位の気圧に換算
float y;
y = x * aaa / 20.0 + bbb; // 圧力計算式(xは20倍で保存されているので、ここで補正)
return y;
}

void writeCommonImage() { // 共通画面の作画
display.clearDisplay(); // 画面全消去(0.4ms)
display.setTextColor(WHITE); // 白文字で描く
display.setCursor(0, 0); // 画面左上から、
display.println(F("Press 30Hr")); // グラフタイトル表示
display.setCursor(104, 0); // 右上に、
display.println(F("mmAq")); // 単位記号

display.drawFastVLine(26, 9, 55, WHITE); // 左縦線
display.drawFastVLine(127, 9, 55, WHITE); // 右縦線

display.drawFastHLine(25, 9, 3, WHITE); // 左端、Max値の補助マーク
display.drawFastHLine(25, 63, 3, WHITE);

display.drawFastHLine(46, 9, 3, WHITE); // 中間、Max値の補助マーク
display.drawFastHLine(46, 63, 3, WHITE);
display.drawFastHLine(66, 9, 3, WHITE); // 中間、Max値の補助マーク
display.drawFastHLine(66, 63, 3, WHITE);
display.drawFastHLine(86, 9, 3, WHITE); // 中間、Max値の補助マーク
display.drawFastHLine(86, 63, 3, WHITE);
display.drawFastHLine(106, 9, 3, WHITE); // 中間、Max値の補助マーク
display.drawFastHLine(106, 63, 3, WHITE);

display.drawFastHLine(126, 9, 2, WHITE); // 右端、Max値の補助マーク
display.drawFastHLine(126, 63, 2, WHITE);

for (int y = 26; y < 60; y += 19) { // 水平目盛り線を点線で2本書く
for (int x = 25; x <= 128; x += 4) {
display.drawFastHLine(x, y, 2, WHITE);
}
}

for (int x = (127 - 20); x > 30; x -= 20) { // 縦目盛り線を点線で4本描く
for (int y = 10; y < 63; y += 5) {
display.drawFastVLine(x, y, 2, WHITE);
}
}
}

void timeUp() { // MsTimer2割込み処理
timeFlag = HIGH;
}

 データー数は100固定で、記録したデーターの最小/最大の範囲に追従してグラフを作図するようになっています。ある程度汎用的に使えるように作ったので、カストマイズの方法について以下に説明します。

1.ログ収集間隔
 17-23行目で選択(必要な項目のコメントアウトを外す)。なお、タイマー割り込み周期をms単位で指定しています。

2.換算係数
 表示する値を y, ADコンバーターの値を x とした時、y = a*x + b の式で計算しています。a と b の値は35, 36行目で指定します。なお、x には0-1023までのADコンバーターの値が入ります。また、分解能改善のため、x の値は内部的には20倍した値で扱っています。
 換算式は一次方程式の一般式になっているので、Arduinoのアナログポートで読める電圧を、任意のスケールの値に線形に変換出来ます。つまり、温度や気圧などの値への変換が簡単に出来るので、このプログラムの応用範囲は広いはずです。

3.表示フォーマット
 表示文字数と、小数点以下の桁数を、14,15行目の STRING_L と AFTER_DECIMAL_L で指定しています。表示桁数などを変えたい時は、ここのパラメーターを変更します。但し表示スペースは4文字分しかありません。

 画面に表示している文字やグラフの目盛りなどは、コードを見れば判るでしょうから、適当に修正してください。

▼表示例
OLED表示例
 もう一つ表示例です。開始後約18時間後なので、グラフの左側がまだ空白になっています。

◆まとめ
 これを使うとCO2の供給圧の変化が判り易くなりました。いっそのこと、CO2供給装置を改造して、このOLEDを組み込んでしまえばいいのですが、それはちょっと面倒なのでどうしようかと、悩ましいところです。

 ちなみに、圧力変化のログをシリアルに出して、タブレットに保存し、後でエクセルでグラフにしたことがあります。でも、それより今回作った仕掛けの方が圧倒的に便利です。ログを出力する時に欲しいのはトレンドが判るグラフであって、細かいデーターではないということですね。

 あと、これまでいろいろいじった経験から、このOLEDの使い方のノウハウみたいなことがいくつか判ってきました。ということで、次の記事ではそういう話をまとめたいと思います。
関連記事

コメントの投稿

管理者にだけ表示を許可する

カレンダー
08 | 2019/09 | 10
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 - - - - -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード