はじめに
この記事は第62回下水道研究発表会でポスター発表した内容の補足記事になります。
私がI市S処理場に設置させていただいている装置は、大まかには次のような構成になっています。
発表ではこの装置に設置している以下の3点を紹介しました。ただし、実際に現地に設置しているものはかなり複雑なので、話がわかりやすくなるように、はしょれるところははしょりました。
- 濁度計
- 貯水槽に設置している水位計
- 実験棟に設置しているポンプ(の制御)
3.は時間で制御して逆回転を交えつつ原水を着水井から貯水槽に送ります。逆回転を交えるのは固形物による流路の閉塞を回避するためです。また、1.と2.の計測結果により自動制御しています。1.に関しては、流入水の濁度が高すぎる時には採水を止めるため、また、2.に関しては、貯水槽が溢れるのを避けるためです。
本記事を読み進めるにあたって、以下の記事も用意しましたので、参考にしていただければと思います。
装置の構成が簡単な水位計についてまずは説明させていただき、その後濁度計、ポンプの制御、ESP32、の順で紹介します。なお、Tweliteですが、上記記事に使い方を説明させていただいていますが、概ね、UARTによる通信(昔電話回線を通じてインターネットに接続する時にモデムで使って通信していましたが、その時と同じ通信方式で、シリアル通信ともいう)を無線化するために使っていると理解してくださると、詳細はわからなくとも概ねのところご理解いただけるかと思います。
また、取得したデータをGoogle Spread Sheetにアップロードしたり、あるいはGoogle Spread Sheet上で指定したパラメータをArduinoの動作に反映させたりすることもできますが、今回の発表ではその実演は省きました。本記事に後日書き加えたいと思います。
水位計
全体像
- Arduino Pro Mini (3.3V):pro miniは3.3V動作のものと5V動作のものが市販されていて、これは3.3Vで動作するものです。
- Twelite:より正確にはここではTwelite Blue UARTというものを使っています。
- 電極:ステンレス棒2本を使っています。が、用途によっては2本の電線(並行ビニルコード)でもよいです。
- 電池:単三電池2本を直列にしています。電池ケースからの配線はより線で、そのままではブレッドボードにさせないので、コネクタを使って0.65ミリの単線につなぎ直し、ブレッドボードにつないでいます。
ブレッドボード内の穴のつながり方については、インターネットで検索するとすぐに見つかるかと思います。写真の場合、一番上の赤色でマークしてあるラインの穴は全てつながっています。これを、3.3Vの電源の線として使っています。また、その下の青色でマークしてあるラインの穴も同様に全てつながっています。これらはGND(0V)を供給するラインとして使っています。写真の下の方にも同じように赤色のライン、青色のラインがありますが、上の方の赤色ライン(青色ライン)と下の方の赤色ライン(青色ライン)は直接はつながっていません。下の方の青色ラインは、Arduino Pro MiniのGNDピンと接続することで、GNDとして機能させています。また、これら赤色ライン・青色ラインの間には、5x30個の穴が、ブレッドボードのセンターラインを挟んで2対あります。写真縦方向の5個の穴は、互いに繋がっています。以下の写真のような感じです。
配線はほとんど0.65mmの単線で繋げています。また、赤色のLEDや固定抵抗も使っています。
主要な配線
主な配線は次の通りです。
- Arduinoへの給電
3Vの電池、プラス側--> ArduinoのVcc
3Vの電池、マイナス側--> ArduinoのGND - 水位センサー
10番ピン--> 水位センサ(2本の電極)-->(A0)-->固定抵抗(1MOhm) --> GND - 水位異常を知らせるLED
11番ピン--> LED --> 固定抵抗(500 Ohm) --> GND - Twelite
Arduino 2番ピン --> Twelite TX
Arduino 3番ピン --> Twelite RX
Arduino 9番ピン --> Twelite Vcc
Arduino GNDピン --> Twelite GND
水位センサーは、測定時にのみ10番ピンをHIGHにし、センサーの電極に電位をかけます。電極が水に使っていると、2本の電極の間に電流が流れますが、その電流は必ず1MOhmの固定抵抗を通ります。センサーと固定抵抗がつながる点の電位は、電流が流れた場合のみ、V = IRに従って、高くなります。その電位を、ArduinoのA0ピンから読み取ります。なお、電極が水に使っていない場合は電極間に電流は流れないので、固定抵抗にかかる電位は0Vになります。
また、電極が水没していることが見た目にわかるように、水没が検出されたら(A0ピンの値がある値より大きくなったら)11番ピンをHIGHにして、そこにつながるLEDを点灯させます。なお、LEDに過大な電流が流れることがないよう、500Ohmの固定抵抗を入れています。
Arduinoのスケッチ
Arduino pro mini(3.3V)に書き込まれているスケッチは、次のようなものです。
#include <SoftwareSerial.h> //普通のピンをシリアル通信んために使用できるようにするライブラリを読み込む。
#include <Sleep_n0m1.h> //スリープをするために必要なライブラリを読み込む。
Sleep sleep;
String myID = "SUI"; //水位計なので、名称SUI
SoftwareSerial mySerial(2, 3); //Tweliteで通信するためのシリアルポートを宣言する。通常のピンをシリアル通信のために代用するので、ソフトウェアシリアル通信という。
// 2がRx(受信)、3がTx(送信)
int WLReadPin = A0; // 水位計からの信号を取り込むピンの番号を宣言
int WLPWRPin = 10; //水位計に電圧をかけるためのピンの番号を宣言
int twelitePWRPin = 9; //Tweliteに電力を供給するためのピンの番号を宣言。なお、Arduino pro miniのデジタルピンはそれぞれ20mAまで給電できる。
int warningPin = 11; //水位が設定を上回った時にLEDを点灯させたい。そのためのピンの番号を宣言
int value; //水位計からの信号の読み値を入れる変数
String text; //シリアル通信に流す文字列を入れる変数を宣言
void setup() {
// setup()の中の処理は、Arduinoの電源が入ってから、あるいはリセットされてから、最初に一回だけ実行される。
// 二つのシリアルポート(パソコンとUSBで通信する時に用いる)を設定する。
Serial.begin(9600); //SerialポートはUSBを介してパソコンと通信する時に使う。
mySerial.begin(38400); //mySerialポートはTweliteを通じて信号を送受信する時に使う。
//出力ピンを設定する。
pinMode(WLPWRPin, OUTPUT);
pinMode(twelitePWRPin, OUTPUT);
pinMode(warningPin, OUTPUT);
digitalWrite(WLPWRPin, LOW);
digitalWrite(twelitePWRPin, LOW);
digitalWrite(warningPin, LOW);
delay(10); //電気の供給を始めたら、Tweliteが立ち上がるまでちょっとだけ待つ。
text += "";
text += myID;
text += " started"; //このような感じで文字列をつなげていく。
mySerial.print(text); //mySerialポートに出力する。mySerialポートはTweliteにつながっている。
Serial.println(text); //USBを介してパソコンと通信するSerialポートにも同じテキストを出力する。デバッグの時に便利。
}
void loop() {
// loop()の中身は永遠に繰り返される。
// まずは水位計に電圧をかけて、固定抵抗にかかる電位を測定する。電流が流れなければ0、そうでなければ何らかの正の数字が返ってくる。
digitalWrite(WLPWRPin, HIGH); // 電位をかける。
delay(5);
value = analogRead(WLReadPin); // 測定する。
digitalWrite(WLPWRPin, LOW); //測定が終わったら電位をかけるのをやめる。
//測定結果について判断する。ここでは測定値が500以上だったら高水位を超えているとみなし、floodedの値をtrueにする。そうでなければfalse。
if (value > 500) {
digitalWrite(warningPin, HIGH);
} else {
digitalWrite(warningPin, LOW);
}
//結果を伝える文字列を作成する。
text = "";
text += "SUI"; //はじめは送信者のID
text += printFourDigits(value); //測定値。諸般の事情で、4文字になるように修正する。例えば測定値が100なら、0100とする。
text += "DA"; // ここでは私が決めたルールとして、通信の終わりに"DA(だ)"をつけることにする。
digitalWrite(twelitePWRPin, HIGH);// Tweliteへの給電を始める。
delay(10); // Tweliteの起動を待つ
Serial.println(text); //パソコンに結果を送信する。
mySerial.println(text); //Tweliteに結果を送信する。
delay(10); // Tweliteがデータを送信完了するのを待つ。データ量が多ければdelayを長くする必要がある。
digitalWrite(twelitePWRPin, LOW); //Tweliteへの給電を止める。
//スリープモードにして、次の測定まで待機する。
sleep.pwrDownMode(); //スリープのモードを設定する。
sleep.sleepDelay(3000); //この場合、3000ミリ秒(つまり3秒)スリープする。
}
sleepをするために使っているライブラリ(Sleep_n0m1.h)は、Arduinoのスケッチを編集するソフトウェアArduino IDEにあらかじめ読み込んでおく必要があります。ライブラリは下の図に示す手順でインストールします。
sleepをさせると、待機時間の消費電力をかなり削減することができます。特に、USB-シリアル変換器を装着していないPro Miniでは、その効果は顕著で、通常20mA程度の消費電流を、1mA程度にまで削減することができます。一方、Arduino Unoですと、sleepさせても電力消費の削減は通常電力消費の半分くらいに止まります。
動作としては、大まかには次のようになっています。
- setup()の前
- 二つのライブラリを読み込みます。
- SoftwareSerial.h
- これは、Arduino IDEにデフォルトで入っています。使い方は、Arduino IDEの"ファイル > スケッチ例 > SoftwareSerial"で、例を見ることができます。
- sleep_n0m1.h
- 使いやすいスリープのライブラリです。上に述べたように、Arduino IDEのライブラリに登録する必要があります。
- SoftwareSerial.h
- さまざまな変数を定義します。
- 二つのライブラリを読み込みます。
- setup():以下を一度だけ実行
- シリアルポート(SerialおよびmySerial)の動作を開始する。
- 出力ピンを設定する。
- 動作確認のためにSerialおよびmySerialに文字列を出力する。
- loop():以下を繰り返し実行
- 水位電極に瞬間的に電圧をかけ、固定抵抗にかかった電圧を計測する。
- 電極が水没しているかどうか、判定し、水没していたらLEDを点灯する。
- 測定の結果をmySerialを通じてTweliteに送る。Tweliteはその文字列をそのまま発信する。
- 3秒間スリープモードに入って待機
Arduino pro miniへのスケッチの書き込み方法
配線
Arduino UNOは、USBケーブルを使ってパソコンと繋ぐことができます。また、その状態でスケッチを書き込むことができます。でも、Arduino Pro MiniにはUSBケーブルをさすことができるような場所はありません。Pro Miniは、UNOなどが持っているUSBシリアル変換の装置を持っていないのです。そのために、小型にできる上に、電力消費を極めて小さくすることができます。インターネットで"Arduino Pro Mini, USBシリアル変換"といったキーワードで検索すると、Pro Miniにスケッチを書き込むために必要なUSBシリアル変換ケーブルを見つけることができます。
スケッチの書き込み
ボードとポートを指定する必要があります。ボードはArduino Pro Miniを選びます。また、以下のようにATmega328P(3.3V, 8MHz)を選ぶ必要があります。ここで5Vが選ばれていると、スケッチを書き込むことはできるのですが、正しく動作しません。
濁度計
全体像
使っている主なパーツは次のとおりです。
- ソーラー電池(ガーデンライトから魔改造)
- TPL5110省エネタイマー
- Arduino Pro Mini(3.3V)
- Twelite Blue UART
- 濁度センサー
ソーラー電池については、ホームセンターで購入したガーデンライトを改造してつくりました。はじめからリチウムイオン電池は入っているし、また、リチウムイオン電池を安全に充電し、かつ、過放電を避ける仕組みも備わっているので、なかなかお買い得です。ただし、私の魔改造ではリチウムイオン電池のソケットから直接負荷につないでいるので、過放電を避けることはできない配線となってしまっています。作例としてはあまりよくありません。また、炎天下に置いておくと、かなり温度が高くなるので、リチウムイオン電池に良くないことが起きてしまいそうな気がします。現在、ソーラーパネル(出力5V 500mA程度)とTC4056リチウム充電器充電モジュールを組み合わせて、電池を日陰に置くことができるように改造を進めているところです。
TPL5110は、高度な省エネを実現するデバイスです。Pro Miniが省エネ性能が高く、特にスリープさせることで待機中の電力消費を1mA程度にできることは説明しました。しかし、TPL5110を用いると、待機電流を10 micro-A程度にまで低減させることができます。
濁度センサー詳細
濁度センサーは、光を照射するLED、試料が流れるチューブ、LEDから発せられチューブ中の試料を通過した光の強度を検出するCdSセルからなっています。CdSセルは光が当たると抵抗が小さくなって電流が流れやすくなり、逆に暗くなると抵抗が大きくなる性質を持っています。チューブを流れる水の濁りが強くなると、CdSセルに届く光が弱くなるのでCdSセルの抵抗が大きくなり、それによって濁度を検知する仕組みです。
LEDは常時光らせておく必要はもちろんありません。測定の時だけ一瞬光らせます。また、光を検知するためにはCdSセルに電圧をかける必要がありますが、これも常時電圧をかけておく必要はありません。LEDを光らせた瞬間だけ、電圧をかけます。
LED
電子工作用の小さなものを使っています。Amazonで買ったのですが、当時の商品はもう見当たりません。同じようなものをあげておきます。色々な色のLEDがセットになっていますが、私が試した限りは赤色や白色のLEDが一番感度よく検出できました。
なお、LEDを使う時、普通のLEDには極性があり、長い方の足をプラス側、短い方の足をマイナス側にさす必要があります。また、かならず直列に200〜500Ohm程度の固定抵抗を直列に入れる必要があります。そうしないとLEDに過大な電流が流れるだけでなく、Arduinoのピンにも過大な電流が流れ、壊れてしまう危険があります。
チューブ
PFAのチューブ(内径8mm、外径10mm)を15cmくらいに切って使っています。両端は異径チューブコネクタを介して内径12mmの耐圧ホースにつないでいます。また、PFAチューブは週一くらいで交換・洗浄しなければならないので、取り外しが簡単なようにクリップ式のホースバンドで固定しています。ペンチ一つでチューブを簡単につけ外しできるので、極めて便利です。
使用後のPFAチューブは内壁にバイオフィルムができています。1週間くらいバケツの水の中に入れておき、その後、中を小さくちぎったティッシュを通過させると、新品同様の綺麗さになり、再利用できます。
CdSセル
Amazonで買ったGL5528を使っています。明抵抗10kOhmくらい、暗抵抗1MOhmくらいということです。固定抵抗と直列につないで使いますが、ここでは200kOhmの固定抵抗と組み合わせています。
ホルダーというか、チューブを通す管
当初はホームセンターで購入した3cm角くらいの立方体にチューブ、LED、CdSセルをつける穴を開け、組み立てていました。初期型はPFAではなくトヨロンホースを使っていました。
また、3Dモデルはこちらに置いておきます。
ソーラー電池
先に述べたようにガーデンライトの電池ホルダーの両極に電線をはんだ付けして電気を取り出すようにしています。リチウムイオン電池が過放電になっても電流が負荷に流れてしまうので、よい改造ではありません。が、ソーラーパネルで発電された電力は確実にチャージコントローラーを経てリチウムイオン電池に供給されるので、安全面では問題ないかと思います。とはいえ、炎天下でソーラーパネルとリチウムイオン電池の双方が熱くなり、特にリチウムイオン電池の方はちょっと不安な気がします。
なお、ガーデンライトを改造するほか、市販の2.5Wソーラーパネルとリチウム電池充電モジュール(18650リチウムイオン電池ホルダーとくみになったものや、充電モジュールだけのものを組み合わせて自作できます。これを使えばパネルは太陽光に晒しつつ、リチウムイオン電池を日陰の涼しいところに置くことが可能になります。リチウム電池充電モジュールTP4056の使い方はインターネット上で見つかるかと思います。
TPL5110省エネタイマー
TPL5110の使い方はAdafruit社のページに詳しく記載されています。TPL5110にはタイマーの動作間隔を調節するのにネジで回す可変抵抗が付属してはいるのですが、微調整が大変そうだということで使ったことがありません。私は、裏側のTrim Enableの銅箔の細い部分をマイナスのネジで削って、そして、delayとGDを固定抵抗で接続することで、タイマーの動作時間を決めています。
配線ができているとします。TPL5110に給電を開始すると、同時にArduinoへの給電も始まります。Arduinoが所定の作業を終えると、作業完了した旨の信号をTPL5110のdoneに伝えます。そうするとTPL5110からArduinoへの給電が切断され、TPL5110はごくごく僅かな電流を消費しながら次のONになるタイミング(抵抗で指定された時間)を待ちます。途中で強制的にArduinoへの給電を始めたいという時は、TPL5110のボタンを押します。
ところで、使い方によってはタイマーを無視してArduinoを連続的に動作させたい場面もあると思います。例えばある条件ではタイマーを活用して省エネ運転するけれども、別の条件の時には連続運転させたい、ということは少なくないでしょう。TPL5110をそのような用途に使う時には注意が必要です。TPL5110は、抵抗で設定した時間が経過すると、一瞬だけ給電を止めてしまうのです。連続運転させるには、その一瞬の停電に備えた対応を講じておく必要があります。容量の大きなコンデンサで対応できると良いのですが、私が手持ちのコンデンサで試した限りではだめでした。
配線
主な配線は次の通り
- Arduinoへの給電まわり
- リチウムイオン電池出力 3.7V -> TPL5110 Vdd
- リチウムイオン電池出力 GND -> TPL5110 GND
- TPL5110 GND -> Arduino pro mini GND
- TPL5110 Delay -> 固定抵抗(10kOhm) -> Arduino pro mini GND
- TPL5110 Drive -> Arduino pro mini RAW
- TPL5110 done <- Arduino pro mini 10番ピン
- Arduino pro mini側で10番ピンをHighにすると、TPL5110driveからの給電が停止されます。
- 濁度計LED照明
- Arduinoの9番ピン -> LED -> 固定抵抗(500 Ohm) --> GND
- 濁度計光センサー(CdSセル)
- Arduinoの13番ピン--> 固定抵抗(200 kOhm)--> 光センサー(CdSセル, GL5528)--> GND
- Twelite
特にLEDとCdSセルへの配線に注目すると、次のようになっています。
LEDについては9番からCに行き、LEDに給電し、そしてDに帰ってきます。DはGNDと500Ohmの抵抗を介してつながっています。一方CdSセルについては13番から出て200kOhmの抵抗を経由しAにつながりCdSに給電し、そしてBに帰り、電線でGNDにつながります。
スケッチ
#include <SoftwareSerial.h>
String myID = "DAK"; // 濁度計なので、名称はDAK
int valuePin = A0; //光センサーにかかる電圧を読み取るピン。明るいとセンサーの抵抗は小さいので小さな値に、逆に暗いと抵抗が大きくなりそのため電圧も大きくなる。
int TwelitePWRPin = 5; //Tweliteに電力を供給するためのピンの番号を宣言。なお、Arduino pro miniのデジタルピンはそれぞれ20mAまで給電できる。
int LEDPWR = 9; //濁度計のLEDを点灯させるためのピン
int lightSensorPWR = 13; //光センサーとそれにつながる固定抵抗に電位をかけるためのピン
int donePin = 10; //タイマーに作業の完了を伝えてArduinoへの給電を止めるためのピン
int value; // 光センサーにかかる電圧の読み値を入れる変数
String text; //送信するテキストを入れる文字列変数
SoftwareSerial mySerial(2, 3); //<-----Tweliteを通じての通信で用いるシリアルポートを設定。2がRx(受信)、3がTx(送信)
void setup() {
// setup()の中の処理は、Arduinoの電源が入ってから、あるいはリセットされてから、最初に一回だけ実行される。
// まずは出力ピンを宣言し、また、それらの値を設定する。
pinMode(TwelitePWRPin, OUTPUT);
pinMode(LEDPWR, OUTPUT);
pinMode(lightSensorPWR, OUTPUT);
pinMode(donePin, OUTPUT);
digitalWrite(donePin, LOW); //donePin(タイマーに作業の終了を知らせるピン)の値は、はじめはLOWにしておく。
digitalWrite(TwelitePWRPin, HIGH);
digitalWrite(LEDPWR, LOW);
digitalWrite(lightSensorPWR, LOW);
Serial.begin(9600); //SerialポートはUSBを介してパソコンと通信する時に使う。
mySerial.begin(38400); //mySerialポートはTweliteを通じて信号を送受信する時に使う。
delay(10); //電気の供給を始めたら、Tweliteが立ち上がるまでちょっとだけ待つ。
//濁度を測定する。まずはLEDを点灯させ、そしてすぐに光センサーの両端にかかる電圧を読む。光センサーの両端にかかる電圧が、濁度を反映している。
digitalWrite(LEDPWR, HIGH);
digitalWrite(lightSensorPWR, HIGH);
delay(5);
value = analogRead(valuePin); // 濁度センサーの電位を測定
digitalWrite(lightSensorPWR, LOW);
digitalWrite(LEDPWR, LOW);
//測定結果を送信する。
text = "";
text += myID;
text += printFourDigits(value); //printFourDigitsは、必要に応じて0を付加して整数を4桁文字列にする関数。例えば730は0730となる。
text += "DA"; // 送信元名称、データ(4桁の数字)、そして最後にDA、という形式で一連のデータを送信
Serial.println(text);
mySerial.println(text);
delay(20); // シリアルポートからデータが送信されるまで少し時間がかかる。20ミリ秒くらいかかると見積もり、その間待つ。
digitalWrite(donePin, HIGH); //タイマーに作業が終わったことを伝える。次の瞬間、デバイスへの給電は途絶える。(一定時間の後、タイマーによって給電が再開される)
}
void loop() {
// setup()の内容が終わったところでArduinoの動作は終了するので、loop()の中身は何が書いてあったとしても実行されない。
}
スケッチの内容は大まかには次のようになっています。
- setup()の前
- SoftwareSerial.hを読み込みます。
- さまざまなパラメータを定義します。
- setup()
- 出力ピンを設定します。
- ピン名にPWRが入っているのは周辺機器に電気を供給するピンです。
- シリアル(SerialおよびmySerial)を開始します。
- 濁度を測定する。
- LEDを点灯し、その直後光センサーに電圧を印加し、光センサーの両端の電位差をValuePin(A0ピン)から読み取ります。
- 測定が完了したら、LEDおよび光センサーにかけている電圧を0(LOW)にします。
- 測定結果を送信する。
- TPL5110のdoneにつながっているdonePinをHIGHにする。この瞬間、Arduinoへの給電は停止する。
- 出力ピンを設定します。
- loop()
- setup()の一番最後にdigitalWrite(donePin, HIGH)があり、そこでArduinoへの給電が停止されてしまうので、loopの中身が実行されることはありません。
ポンプの制御
ポンプの制御は、20分ごとに30秒逆流させた後4分30秒貯水槽に送水します。逆行させるのは、下水に含まれるゴミによって流路が詰まるのを避けるためです。もちろん貯水槽がいっぱいの時にさらに送水を続けると、貯水槽が溢れてしまいます。そこで、貯水槽の水位計が満水を検知していたら、送水を停止します。またさらに、下水に汚泥処理返流水由来の高濃度の汚泥が含まれる時間帯があり、その時間帯は採水を止めたいです。そのために濁度計を設置し、高濃度の汚泥が含まれる場合には採水を停止します。いったんポンプを逆流させ、一定時間休止し、送水を再開します。
また、時々手動でポンプを動かす必要もあるでしょう。
ポンプは、ブラシ付き直流モーターで駆動するチューブポンプを使っています。具体的にはこの製品(WP1200, Welco社)です。駆動電圧は12Vです。Arduinoでは、モーターを駆動させることはできませんが、制御することは得意です。モータードライブという周辺機器を使って、モーターの回転方向および回転速度を制御できます。
モーターの運転の時間制御、および、水位計や濁度計からTweliteを通じて送信されてきたデータの処理(テキストデータの処理)、計測値による制御、そして、時々手動で運転もしたくなるでしょうから手動制御、という、さまざまな制御を含む作例となっています。
なお、ポンプを動かすということはたくさん電気を使うということですので、今までのように電池だけで動かすということは考えにくく、常時電源から電気を取ることができる環境だと考えた方が自然です。そうすると、省エネルギーについてあまり考慮する必要はなく、汎用で使いやすいArduino UNO R3互換機を使うことにしました。
全体像
写真にこの作品の全体像を示します。なお、ポンプは別の写真で示しています。ポスター発表では、ポンプのところは小型のチューブポンプ(WPM, Welco社、12V駆動)で代用しました。
使っている主なパーツは次のとおりです。
- 9V電池
- Arduino UNO R3互換機
- モータードライブ(MD13S, Cytron社)
- Twelite Blue UART
- 表示器(OLED)
- 白・青・赤のタクトスイッチ
タクトスイッチは次のような使い方を想定しています。
- 白ボタン:ポンプの運転モードを手動・自動で切り替える。
- 青ボタン:手動モードの時、ポンプの順方向への回転速度を増加させる。
- 赤ボタン:手動モードの時、ポンプの逆方向への回転速度を増加させる。
OLEDは、動作状況を確認するために便利です。手動運転か自動運転か、見てとることができます。また、水位計や濁度計からの測定値を表示させることもできます。
配線
主な配線は次の通りです。
- Arduinoへの給電
- モバイルバッテリ -> ArduinoのUSBポート
- モータードライブ(MD13S)への給電
- 9V電池 プラス -> MD13S プラス
- 9V電池 マイナス-> MD13S マイナス
- モータードライブへの制御信号
- ArduinoのGNDまたは2番ピン -> MD13S GND
- Arduinoの3番ピン -> MD13S PWM
- Arduinoの4番ピン -> MD13S DIR
- Twelite
Arduinoの3.3Vピン -> TweliteのVcc
ArduinoのGNDピン -> TweliteのGND
Arduino 4番ピン --> Twelite TX
Arduino 5番ピン --> 固定抵抗(50kOhm)--> (X) --> 固定抵抗(100kOhm)、(X)をTweliteのRXにつなげる。 - OLED
- Arduino SCLピン --> OLED SCL
- Arduino SDAピン --> OLED SDA
- Arduino 5Vピン --> OLED Vcc
- Arduino GNDピン --> OLED GND
- タクトスイッチ
なお、以下の点、追加しておきます。
- ArduinoのGNDを一旦ブレッドボードの青いピンに入れ、そこから各周辺機器のGNDにつないでいます。
- Tweliteは3.3V電源で動作し、UARTの通信も3.3Vです。
- Arduino UNO R3のデジタルピンは5V出力なので、Arduino側のデジタルピンからTweliteに給電することはできません。PWMのピンを使ってもダメです。Tweliteへは、Arduinoの3.3Vから給電します。また、ArduinoからUARTの信号は5番ピンから発信されTweliteに送られます。しかし、Twelite側では5Vの信号を受け取ることはできないので、固定抵抗を使って信号の電圧を三分の二程度に落としてやる必要があります。一方Twelite側からのUARTの信号は3.3Vの強度でArduino(UNO R3)の4番ピンに入ります。Arduinoの4番ピンは2.5Vを基準にHIGHとLOWを判断するので、TweliteからArduinoへの信号は直接入れて良いことになります。
- 水位計や濁度計の作品では、Arduino Pro Miniの3.3Vバージョンを使っています。この場合はArduino側は3.3Vを出力し、また、UART通信も3.3Vで行うので、電圧に関する特段の配慮は不要です。
タクトスイッチの使い方
タクトスイッチはボタンを押すと通電するようになるスイッチです。足が2本しかない時には極めてわかりやすいのですが、足が4本あるタクトスイッチは、ちょっと注意が必要です。
4本足のタクトスイッチですが、胴体から上に2本の手、下に2本の足、がでているように見ましょう(右に2本の足、左に2本の足、という見方はしないようにしましょう。)。そうすると、右手と右足は常時つながっています。また、左手と左足も常時繋がっています。そしてボタンを押すことで、2本の手、2本の足全てが通電するようになります。
タクトスイッチを使うにあたっては、Arduino側でのピンの設定も重要です。ArduinoのデジタルピンやアナログピンはpinMode(??, INPUT_PULLUP)という設定をすることができます。??はピンの番号です。INPUT_PULLUPというのは、そのピンが浮いている時(どこにもつながっていない時)に、常に検出値としてHIGHを出力するような設定です。例えば7番ピンはタクトスイッチAを押すと、GNDにつながります。しかし、タクトスイッチAが押されていない時は、どこにもつながっていません。いや、タクトスイッチAのGNDにつながっていない足につながってはいるのですが、では、そのピンの電位は、果たしてどうなっているのでしょうか?どこにもつながっていないのですから、不安定なふわふわした値となってしまいます。そういう時にINPUT_PULLUPにしておくと、タクトスイッチAが押されていない時には7番ピンは間違いなく5Vを検出し、一方、タクトスイッチAが押されると0Vを検出することになります。
スケッチ
主要部
#include <TimeLib.h> //Arduinoの内部時計を利用して定期的に作業するために、時計のライブラリを読み込む。
#include <Adafruit_SSD1306.h> //OLEDを使うために必要なライブラリを読み込む。
#include <SoftwareSerial.h> //普通のピンをシリアル通信んために使用できるようにするライブラリを読み込む。
SoftwareSerial mySerial(5, 6); //Tweliteで通信するためのシリアルポートを宣言する。通常のピンをシリアル通信のために代用するので、ソフトウェアシリアル通信という。
// 2がRx(受信)、3がTx(送信)
Adafruit_SSD1306 oled(128, 32, &Wire, -1); //OLEDに名前をつける。ここではoledという名前をつけている。
//カッコの中だが、最初の二つはサイズで、128ピクセル×32ピクセル。その後ろの二つのパラメータについては私は知らないので説明できない。
String oledMessage = ""; //OLEDに表示する文字列を入れるパラメータ
String myID = "OYA"; //主たる装置なので、親
int BAPin = 7; //白色のボタンの状態を読み取るピン。マニュアル・自動切り替え用
int BBPin = 8; //青色のボタンの状態を読み取るピン。手動運転時ポンプの出力増加
int BCPin = 9; //青色のボタンの状態を読み取るピン。手動運転時ポンプの出力減少
int pumpGNDPin = 2; //ポンプを動作させるモータードライブのGNDに0Vを出力するピン
int pumpSpeedPin = 3; //ポンプを動作させるモータードライブのPWMピンに送る電圧を出力するピン。
// PWM = pulse width modulationは、0Vと5Vを高速で切り替えて擬似的に中間的な電位を出力できる。
int pumpDirectionPin = 4; //ポンプの回転方向を決めるHIGH(正回転)/LOW(逆回転)を出力するピン
String mode = "A"; // 運転モードを入れる文字列変数。Aだとautomatic(自動)、Mだとmanual(手動)
String opMode = "OK"; // 自動運転の時の運転状態についての文字列変数。
//OKなら通常の自動運転。濁度を検出した時、逆回転(REV)、一定時間の休止(STOP)、運転再開(OK)、となる。
int pumpSpeed = 0; //ポンプの出力の設定値。順方向運転の時は正の値、逆行運転の時は負の値。
int pumpCurrentSpeed; //実際にポンプに伝える出力。設定値(pumpSpeed)に近づくように、徐々に変化させる。
time_t currentTime; //現在時刻
time_t manualStartTime; //manualモードになった時刻
time_t opModeStartTime; //現在のopModeになった時刻
int turbidity; //DAKから受信した濁度の値
int SUIValue; //SUIから受信した測定値(満水位なら大きな値)
bool tankFull = false; //SUIの水槽が満水位の時、true
int turbidityTHR = 50; //一定の時間内における濁度の上昇の上限。上昇がこの値を上回ったら採水を停止しポンプを逆回転させ、一定時間休止する。
int turbidityAverage = 0; //濁度の移動平均値
int turbidityAvgPoints = 30; //濁度の移動平均を取る時のパラメータ(過去30回分の測定値の平均)
int SUIValue_THR = 500; // SUIから受信した測定値が満水位を超えているかどうか判定する閾値
bool report_flag = false; //運転状況の報告が完了した時にたてるフラグ(目標)。
String receivedText; //受信した文字列を入れる入れ物
String text; //送信する文字列
int manualPumpStatus = 0; //手動運転時のポンプの設定値
void setup() {
Serial.begin(9600);
mySerial.begin(38400);
//OLEDの初期設定を実行する。
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C); // 一つ目のパラメータの意味は不明。0x3CはOLEDのI2Cアドレス。
// I2Cは周辺機器との通信方法の規格の一つ。さまざまなVcc, GND, SDA, SCLを複数の機器につなぎ同時に使うことができるが、その時に機器を区別するためにI2Cアドレスが用いられる。
// OLEDのI2Cアドレスが0x3Cでない場合は、OLEDは動作しない。その場合は、I2C Scannerというスケッチを使って機器のI2Cアドレスを調べることができる。
oled.clearDisplay();
oled.setTextSize(1); // 文字の大きさを設定する。1が一番小さい。2や3にして大きな文字を表示することもできる。
oled.setTextColor(WHITE); // 単色のOLEDの場合は多分WHITEで表示できる。
// Arduinoの内部時計を設定する。
setTime(0, 1, 2, 3, 4, 2022); // この例だと、時計は2022年4月3日0時1分3秒に設定される。用途にもよるが、日付は任意にして0時0分0秒からスタートさせるのがわかりやすいと思う。
// 出力ピンの設定をする。
pinMode(pumpSpeedPin, OUTPUT);
pinMode(pumpDirectionPin, OUTPUT);
pinMode(pumpGNDPin, OUTPUT);
pinMode(BAPin, INPUT_PULLUP); //ボタンの状態を読み取るピンは、INPUT_PULLUPにしておく。そうすると、通常はピンの値はHIGH、ボタンが押されてGNDと接続されるとLOWになる。
pinMode(BBPin, INPUT_PULLUP);
pinMode(BCPin, INPUT_PULLUP);
digitalWrite(pumpDirectionPin, LOW);
analogWrite(pumpSpeedPin, 0); // ポンプの出力の設定は、最初は0(無回転)
digitalWrite(pumpGNDPin, LOW);
// 動作確認を兼ねて起動した旨をOLEDに出力する。
oled.setCursor(0, 0);
oled.print("READY");
oled.display(); // OLEDへの出力は、出力する場所(横方向の位置、縦方向の位置の順で指定)、表示する文字列、を指示した後、oled.display()で反映させる。
// 起動した旨をシリアル(パソコン向けとTwelite向け)に送信
text += "";
text += myID;
text += " started"; //このような感じで文字列をつなげていく。
mySerial.print(text); //mySerialポートに出力する。mySerialポートはTweliteにつながっている。
Serial.println(text); //USBを介してパソコンと通信するSerialポートにも同じテキストを出力する。デバッグの時に便利。
}
/*
loop()では、次の作業を繰り返す。
1) 現在時刻の確認
2) 動作モードボタン(白いボタン)の状態の確認。白ボタンが押されると、mode(動作モード)が自動または手動に切り替わる。
buttonA()がこの処理にあたる。
3) mySerialに届いている情報の確認と処理。Twelite経由でDAKやSUIから送られてきた情報はmySerialポートに届いている。それを処理する。
readmySerial()がこの処理にあたる。
4) mode(動作モード)の値に応じて自動運転時の処理、手動運転時の処理を実行する。
if(mode=="A")で始まるifブロックがこれにあたる。このifブロックの中にはautomatic()およびmanual()があり、それぞれ自動運転、手動運転の動作が記述されている。
それぞれの処理の結果、現時点でのポンプの出力の設定値が決定される。なお、この処理ではまだ実際の運転には反映しない。
5) 警報等による設定値のオーバーライドの処理。
異常があった時にポンプの動作を停止する処理。4)で決めたポンプの設定値を0に書き換えてしまい、ポンプが動かないようにする。
safetyStop()がこの処理にあたる。
6) ポンプの動作に設定値を反映させる処理。
4)および5)で設定したポンプの動作設定の値を実際のポンプへの出力に反映させる。
7) 動作状況のOLEDへの表示
8) 10秒ごとに動作状況を報告
動作状況の報告そのものはreport()にて実行。
10秒ごとに一回report()を動作させるために、if文で時刻を確認するとともに、送信済みかどうかをreport_flagの値により確認。
*/
void loop() {
currentTime = now(); // 1) currentTimeに現在の時刻を入れる。
buttonA(); //2) buttonAの状態に応じて動作モードを変更する操作を行う。
readmySerial(); //3) mySerialに届いているデータを読み取り処理する。
if (mode == "A") { // 4) 動作モードに応じて自動または手動の処理を行う。いずれの処理でもポンプの出力の設定値が算出される。
automatic();
} else if (mode == "M") {
manual();
} else {
automatic();
}
safetyStop(); // 5) もし何らかの警告があればポンプの動作設定値を停止するように書き換える。
pumpOperate(); //6) ポンプの動作に設定値を反映させる。
// 7) OLEDに動作状況を表示する。
if (tankFull == true) {
oled.clearDisplay();
oled.setCursor(0, 0);
oled.println("HIGH WATER");
} else {
oled.clearDisplay();
oledFirstLine();
oledSecondLine();
oledThirdLine();
}
oled.display();
// 8) 動作状況を発信する。
if (second() % 10 < 5 && report_flag == false) {
// Serial.print("reporting");
report();
report_flag = true;
} else if (second() % 10 > 5) {
report_flag = false;
}
delay(1000);
}
大まかには、
- setup()より前の部分
- 以下のライブラリを読み込み
- TimeLib.h
- Michael Margolis氏によるライブラリです。Arduino本体を時計として利用したり、周辺機器としてreal time clock(水晶時計)を使用するためのライブラリです。Arduino IDEにインストールしておく必要があります。
- Adafruit_SSD1306.h
- Adafruit社によるOLEDを使うためのライブラリです。これもArduino IDEにインストールしておく必要があります。
- SoftwareSerial.h
- これはもともとArduino IDEに備わっているので、インストールする必要はありません。
- TimeLib.h
- 様々なパラメータの定義
- 以下のライブラリを読み込み
- setup()
- シリアル通信の開始(SerialおよびmySerial)
- OLEDの起動、設定
- 時計の設定
- 出力ピンの設定
- OLEDの試運転
- SerialおよびmySerialへの起動メッセージの送信
- loop()
- 現在時刻の確認
- 動作モードボタン(白いボタン)の状態の確認。白ボタンが押されると、mode(動作モード)が自動または手動に切り替わる。buttonA()がこの処理にあたる。
- mySerialに届いている情報の確認と処理。Twelite経由でDAKやSUIから送られてきた情報はmySerialポートに届いている。それを処理する。readmySerial()がこの処理にあたる。
- mode(動作モード)の値に応じて自動運転時の処理、手動運転時の処理を実行する。if(mode=="A")で始まるifブロックがこれにあたる。このifブロックの中にはautomatic()およびmanual()があり、それぞれ自動運転、手動運転の動作が記述されている。それぞれの処理の結果、現時点でのポンプの出力の設定値が決定される。なお、この処理ではまだ実際の運転には反映しない。
- 警報等による設定値のオーバーライドの処理。異常があった時にポンプの動作を停止する処理。4)で決めたポンプの設定値を0に書き換えてしまい、ポンプが動かないようにする。safetyStop()がこの処理にあたる。
- ポンプの動作に設定値を反映させる処理。4)および5)で設定したポンプの動作設定の値を実際のポンプへの出力に反映させる。
- 動作状況のOLEDへの表示
- 10秒ごとに動作状況を報告。動作状況の報告そのものはreport()にて実行。10秒ごとに一回report()を動作させるために、if文で時刻を確認するとともに、送信済みかどうかをreport_flagの値により確認。
スケッチで工夫したところ
- それぞれの処理の詳細は、関数として記述し、主たるスケッチの見通しがよくなるようにした。
- ボタンの状態の把握、情報の収集、ポンプの設定の算出、警報によるオーバーライド、と進めていき、ようやくポンプの実際の動作に反映させるようにした。
buttonA()の内容
void buttonA() {
int N = 0;
if (digitalRead(BAPin) == HIGH) { //ボタンが押されていなければ何もしない。
return;
} else { //ボタンが押されてBAPinの値がLOWになっている時は、以下の処理を実行
while (digitalRead(BAPin) == LOW) { //まず、ボタンが押下されている間、10ミリ秒ごとにカウントしながら待つ。
N = N + 1;
delay(10);
}
if (N > 3) { //ボタンから手が離されてwhileループから抜け出たら、ボタンが押下されていた時間をNの値で判断する。
//もしN>3だったら(30ミリ秒以上だったら)動作モードを変更する。
if (mode == "A") {
mode = "M";
manualPumpStatus = 0;
manualStartTime = currentTime;
} else {
mode = "A";
opMode = "OK";
}
oled.clearDisplay();
pumpSpeed = 0;
}
}
}
readmySerial()の内容
void readmySerial() {
if (mySerial.available()) { //mySerial.available()の値はTwiliteにつながっているmySerialポートにデータが届いている時にtrueとなる。
text = mySerial.readStringUntil('\n'); //ひとまとまりのデータの終わりには、必ず改行コード(\n)が入っている。そこまでを読み出してtextに収納する。
text.trim(); //これで、textの前後の空白や改行コード等が除去される。
/*
今回の場合はtextの内容は"DAK0123DA"あるいは"SUI0001DA"のようになっているはず。
最初の3文字を取り出し、送信者を確認する。また、8文字目、9文字目がDAになっていることを確認する。
それで問題がなければ、4文字目〜7文字目を取り出し、さらに整数化して、測定値の変数(濁度計の方はturbidity、水位計の方はSUIValue)に収納する。
*/
Serial.print("received:"); //パソコンを使って通信状況をモニターする時に、
Serial.println(text); //受信したメッセージを表示してくれると便利
if (text.substring(0, 3) == "DAK") {
if (text.substring(7, 9) == "DA") {
String tempString = text.substring(3, 7);
turbidity = tempString.toInt();
tempString = text.substring(7, 10);
updateAverage(); // turbidityAverageの値を更新する。濁度計は時間とともにセルが汚れてくるので、ベースラインの値が変化する。それに対応するために、時間的な移動平均を算出する。
}
}
if (text.substring(0, 3) == "SUI") {
if (text.substring(7, 9) == "DA") {
SUIValue = text.substring(3, 7).toInt();
}
}
}
}
void updateAverage() {
// ここでは濁度計から送られてくるデータの移動平均を算出する。
if (turbidityAverage == 0) {
turbidityAverage = turbidity;
} else {
if (turbidity - turbidityAverage < turbidityTHR) {
turbidityAverage = (turbidityAverage * (turbidityAvgPoints - 1) + turbidity) / turbidityAvgPoints;
}
}
}
mySerialポート(Tweliteとつながっている)に到着しているデータの有無を調べ、もしデータが到着している場合は、その内容を解析し、運転に反映させます。水位のデータは単にSUIValueという変数に代入するだけですが、濁度のデータは濁度計のセルが日にちの経過に伴って汚れてくるので、移動平均を計算しています。そのため、updataAverage()という処理を用意しています。
また、テキストデータの扱いに関して、以下のような処理を行っています。
- text.substring(a, b): textからa+1番目からb番目の文字を取り出す。
- value = text.toInt(): textという文字列を整数に変換した値をvalueに代入する
automatic()の内容
void automatic() {
if (opMode == "OK") {
OK();
} else if (opMode == "REV") {
REV();
} else if (opMode == "WAIT") {
WAIT();
} else {
}
}
void OK() {
// センサーの状態を確認、結果により、TRに以降
if (minute(currentTime) % 20 == 0 && second(currentTime) < 30) { //ポンプの動作は20分に一回5分間。ただし、最初の30秒はポンプを逆回転させる。
pumpSpeed = -120;
}
else if(minute(currentTime) % 20 < 5){ //最初の30秒の逆回転の後、ポンプを順方向に回転させる。
pumpSpeed = 180;
if ( turbidity - turbidityAverage > turbidityTHR) { //濁度が異常に上昇した場合、このif文の判定がtrueになり、opModeがREVとなる。
opMode = "REV";
opModeStartTime = currentTime; //opModeStartTimeはopModeが変更された時刻
pumpSpeed = -120;
}
} else {
pumpSpeed = 0;
}
}
void REV() { //opModeがREVのときは、2分間逆回転。
pumpSpeed = -120;
if (currentTime - opModeStartTime > 120) { //2分間の逆回転の後、opModeはWAITに変わる。
opMode = "WAIT";
opModeStartTime = currentTime;
pumpSpeed = 0;
}
}
void WAIT() {
pumpSpeed = 0;
if (currentTime - opModeStartTime > 600) { //10分間のWAITののち、opModeはOKに変わる。
opMode = "OK";
opModeStartTime = currentTime;
}
}
自動運転では運転のモードopModeがOK(通常)、REV(高濃度の濁度を検出し、ポンプを逆流させる運転)、WAIT(REVの運転の後しばらく待機する)、の、三つの動作モードが設定されています。
manual()の内容
void manual() {
delay(100);
buttonMB();
buttonMC();
if (manualPumpStatus >= 0) {
pumpSpeed = manualPumpStatus * 64 - 1;
if (pumpSpeed > 255) {
pumpSpeed = 255;
}
} else if (manualPumpStatus < 0) {
pumpSpeed = manualPumpStatus * 64 + 1;
if (pumpSpeed < -255) {
pumpSpeed = -255;
}
}
if (now() - currentTime > 1800) {
mode = "A";
opMode = "OK";
}
}
void buttonMB() { // 順方向に回転させる。
int N = 0;
if (digitalRead(BBPin) == HIGH) return;
else {
while (digitalRead(BBPin) == LOW) {
N = N + 1;
delay(10);
}
if (N > 3) {
manualPumpStatus++;
if (manualPumpStatus > 4) {
manualPumpStatus = 4;
}
}
}
}
void buttonMC() { // 逆方向に回転させる
int N = 0;
if (digitalRead(BCPin) == HIGH) return;
else {
while (digitalRead(BCPin) == LOW) {
N = N + 1;
delay(10);
}
if (N > 3) {
manualPumpStatus = manualPumpStatus - 1;
if (manualPumpStatus < -4) {
manualPumpStatus = -4;
}
}
}
}
manual()ではbuttonBおよびbuttonCを使ってポンプを手動で制御します。buttonBの制御はbuttonMB()で、buttonCでの制御はbuttonMC()で行います。
safetyStop()
満水位を超えた時にポンプを動かさないようにする処理です。
void safetyStop() {
// 水位計の読み値が閾値より高ければ、ポンプの出力の設定値を0にしてポンプを停止する。
// また、tankFullの値をtrueとして、7)でOLEDに動作状況を表示する時に、水位警報が出ていることをしめす。
if (SUIValue > SUIValue_THR) {
tankFull = true;
pumpSpeed = 0;
} else {
tankFull = false;
}
}
pumpOperate()
void pumpOperate() {
// pumpCurrentSpeedの調節。
if (pumpSpeed > pumpCurrentSpeed) {
pumpCurrentSpeed = pumpCurrentSpeed + 32;
if (pumpCurrentSpeed > pumpSpeed) {
pumpCurrentSpeed = pumpSpeed;
}
} else if (pumpSpeed < pumpCurrentSpeed) {
pumpCurrentSpeed = pumpCurrentSpeed - 32;
if (pumpCurrentSpeed < pumpSpeed) {
pumpCurrentSpeed = pumpSpeed;
}
}
// pumpCurrentSpeedの値に基づく値のピンへの出力
if (pumpCurrentSpeed > 0) { // pumpCurrentSpeedの値が正、負に基づき、pumpDirectionPinの出力値を変える。
digitalWrite(pumpDirectionPin, LOW);
} else {
digitalWrite(pumpDirectionPin, HIGH);
}
analogWrite(pumpSpeedPin, abs(pumpCurrentSpeed)); //pumpSpeedPinにはpumpCurrentSpeedの絶対値を出力する。
}
ポンプの運転設定や警報によるポンプの停止を反映させ、ポンプへの出力をする処理です。なお、ポンプの動作を急に変更するのはポンプの寿命(特にブラシの寿命)を短くするので、少し時間差を持って反映させるようにしています。そのため、pumpCurrentSpeedに直接pumpSpeedを代入するのではなく、pumpCurrentSpeedを徐々にpumpSpeedに近づけるような処理になっています。
OLEDへの出力
// OLEDへの出力の1行目
void oledFirstLine() {
// oled.init();
oled.clearDisplay();
oled.setCursor(0, 0);
text = "";
text += "DAK ";
text += printFourDigits(turbidity);
oled.print(text);
}
// OLEDへの出力の2行目
void oledSecondLine() {
text = "";
oled.setCursor(0, 12);
text += "SUI ";
text += printFourDigits(SUIValue);
oled.print(text);
}
// OLEDへの出力の3行目
void oledThirdLine() {
text = "";
oled.setCursor(0, 24);
text += printDigits(minute(currentTime));
text += ":";
text += printDigits(second(currentTime));
text += " ";
text += "mode ";
text += mode;
if (mode == "A") {
text += " ";
text += opMode;
}
oled.print(text);
}
String printDigits(int digits) {
String text = "";
if (digits < 10) {
text += ("0");
text += digits;
} else {
text += digits;
}
return text;
}
String printFourDigits(int digits) {
String text = "";
if (digits == "") {
text = "xxxx";
} else {
if (digits < 10) {
text += ("000");
text += digits;
} else if (digits < 100) {
text += ("00");
text += digits;
} else if (digits < 1000) {
text += ("0");
text += digits;
} else {
text += digits;
}
}
return text;
}
report()
void report() {
String text = myID;
if (mode == "A") text += "AUTO";
else text += "MANU";
if (opMode == "OK") {
text += "OKOK";
} else if (opMode == "REV") {
text += "REVS";
} else if (opMode == "WAIT") {
text += "WAIT";
} else if (opMode == "SLOW") {
text += "SLOW";
} else if (opMode == "WARN") {
text += "WARN";
} else {
text += "????";
}
text += "DA";
mySerial.println(text);
}
通信についての補足
ArduinoがTweliteと交信するのにソフトウェアシリアルを使うということはお話ししましたが、そもそもシリアル通信とはなんで、ソフトウェアシリアルとどういう関係になっているのか、説明しておりませんでした。
ArduinoがUSBケーブルを通じてパソコンと通信する時、Arduino自身の持つシリアルポートを使っています。ピンの番号としては、Arduino UNOの場合は0番ピンと1番ピンです。0番ピン、1番ピンに電線を挿すと、パソコンとArduinoの通信を盗聴することができます。これらのピンは、シリアル通信をするための専用のデバイスに接続されており、そのためハードウェアシリアルとも呼ばれます。Arduino Pro MiniのTXD、RXDピンも同様です。Arduino UNOやArduino Pro Miniは一つしかハードウェアシリアルを持っていませんが、複数のハードウェアシリアルをもつボードもあります。例えばArduino Megaは三つのハードウェアシリアルポートを持っています。
さて、UNOやPro Miniのように一つしかハードウェアシリアルを持たないArduinoとTweliteを交信させるには、どうするとよいでしょうか。たった一つのハードウェアシリアルを使うのも、実は一つの手です。UNOの場合は0番ピンをTweliteのTXDに、1番ピンを(2つの抵抗で分圧して電圧を下げてから)TweliteのRXDに入れればよいだけです。ただ、二つ問題があって、UNOをさらにパソコンに接続してシリアル通信することはできませんし、また、パソコンからUNOにスケッチを書き込む時には、Tweliteをシリアルポートから外す必要があります。とくに試運転の時、これらの問題はなかなか痛いです。デバッグするときにはArduinoの動作状況をSerial.print()で書き出させて確認したいことがしばしばですし、スケッチを修正して書き込むことも頻繁です。
ということで、今回の作例ではソフトウェアシリアルを使ってArduino UNOやPro Miniに擬似的に二つ目のシリアルポートを持たせて対策しました。ソフトウェアシリアルはシリアル通信専用のデバイスを持たずプログラムで制御するので、ハードウェアシリアルより遅くなりがちです。私の経験上、送信は115200bps出せるのですが、受信は38400程度にしないとうまく受信できません。
ソフトウェアシリアルには速度以外にもう一つ欠点があります。意外と電力を食うのです。しかも、水位センサーはスリープで省エネにして待機させましたが、その時の待機電流がかなり大きくなってしまいます。
ということで、開発がほぼほぼ終わった段階では、ハードウェアシリアルで通信が完結するようにするのがよいと考えています。そして、その時にはTweliteとの通信は115200bpsにするのがよいでしょう。
それから、Tweliteの方の通信速度の設定も、ちょっと面倒なところがあります。できれば常時115200で使った方が楽です。38400bpsに設定すると(こちらに書きましたが、少し無理やり設定することになります)、Tweliteの設定をするソフトウェアの設定も38400bpsに書き換えなければなりません。その作業をしつつ、万一意図せぬ通信速度を書き込んでしまうと、困ったことになります。