水田水管理

水管理の問題点

人が管理する時、水尻(排水口)の水位を調整する板(調整板)をセットした後、水口(給水口)を全開にして水を取り込みます。
どれぐらいの時間で水田に水が満たされるのかが分からないので、半日ぐらいそのままにしています。そして水が満たされていたら水口を閉じます。
不足している場合はまた数時間後に水の状態を確認に来ます。

水が必要な時期は重なることが多く、同時に水を取り入れると水路の末端部分では水が流れてこない事もあります。
一方では不足し、一方では水尻からどんどん溢れている水田もあります。
水資源を有効に使うには必要な時は給水して、満たされたら自動的に止める事が必要です。

水尻(排水口)から水が溢れるようになっても水田全体に水が行き渡っているとは限りません。全体に水が行き渡っている事を確認するには複数の水位計が必要になってきます。また、こうした水位の情報を全体でまとめて給水の開閉ができなければなりません。

水管理の仕組み

水口では水の供給量の調整(大・中・小)が信号でできること。
水田の複数の位置に水位計を配置し、水位を送れること。
水尻の水位の調整板の上下を信号でできること。

給水開始時の制御
1.全体の制御マイコンを起動し、水口の装置、水尻の装置、水位計の装置と通信を開始する。
2.水尻の調整板を最高位置にする。
3.水口の給水量を(大)にする。
4.各ポイントの水位計の測定を開始し、一定の時間(例えば10分)毎に各ポイントの水位を制御マイコンに送る。
5.全てのポイントの水位計に水を感知したら水尻の調整板を設定したい高さにする。
6.全てのポイントの水位計の水位が増加しなくなったら水口の水を閉じる。
7.全てのポイントの水位計の水位が一定量下がったら水口の水を少し開ける。
8.6~7の制御を繰り返す。

これらを実現するためには通信機能を持ったマイコンが必要になります。
現在の所、TWELITEが最適と考えています。
TWELITEも独自でプログラムができます。また、消費電力を減らすsleepモードがあります。

水口(給水口)制御機構

テスト用として水口制御機構を作ってみました。まだ未完成ですが…。
水の量を制御する部分の全体写真です。
ゲートを上下する部分の写真です。

「楽しい工作シリーズ」のパーツやホームセンターで買える物で作りました。
耐久性には大いに疑問があるし防水も必要ですが、テスト用なので今回は良しとします。
構造はリニアアクチュエータと同じで、長いネジを回転してナットの部分が上下します。
リミットスイッチ(マイクロスイッチ)を取り付けることができました。
全開、半開、全閉の位置で信号が取り出せます。

電子基板の写真です。

スルーホール基板にDIP等の部品をはんだ付けしています。
ギヤボックスがかなり振動するので、ブレッドボードは使えません。
そうなるとDIPのプログラム書き換えがTWELITE R2でできなくなります。
そこで、7Pインターフェースを使ってプログラムの書き換えができるようにしました。

TWELIT DIP 7Pインターフェース

DIPの基板に細ピンヘッダーをはんだ付けしています。
基板にもSET用とVCC用に細ピンヘッダーを付けています。
DIPとR2(R3)とのピンの関係は以下の通りです。



私はコネクタ付きケーブル オスメスしか持っていなかったので 途中でブレッドボードで中継しています。

DIPにピンソケット(メス)をはんだ付けすることも考えましたが、
SETとVCCがない事と、一斉に差し込んだら取れなくなるかもしれないと思いやめました。

SET(15)はモジュールの15番目のピンで、シルクは12、DI1、デジタル入力1のピンです。
※ DIPはSETを接続する必要は無いようです。「twelite r3 データシート」でググってください。

ここで、ピンの番号について説明しておきたいと思います。
TWELITR DIPにはそれぞれのピンについて「ピン(番号)」「シルク(印刷)」「信号名」「機能」があります。
※「シルク」とは基板にシルク印刷された数字や文字の事です。
「ピン(番号)」はモジュールの周囲を反時計回りに1,2,3 … 28と単純に付けられた番号になります。
「シルク(印刷)」はモジュールの半導体チップの機能の数字や文字になります。VCCやGNDもシルク印刷の情報です。
「信号名・機能」は「シルク」を説明する情報と考えて良いと思います。

ACTプログラムでデジタル入出力をするピンを定義する時は「シルク」の数字を使います。

水位センサー

水位センサーのテストをしました。
原理は「雨降りセンサー」と同じです。センサー端子が水につかるとDIPのプルアップされたデジタル入力がLOWに変わります。

※ 回路図は「水魚堂さんのBSch3V」を使用しました。ありがとうございます。

次に、TWELITE DIPのACTプログラムです。
センサーの信号を入力するDIOはシルク18です。 5秒間(感知、1秒待ち、感知、1秒待ち…を5回)ずっとシルク18がLOなら水に浸かっていると判定する。
水に浸かっていると判定したら親機に対して状態を送信します。
親機にデータを送信後、60秒間のスリープに入ります。(実際は30分)
以下のコードは送信とスリープのサンプルコードにもなります。


WaterLevelSensorのソース
-----
// 水位センサー
// 30分毎に水位をチェックする。18ピンが5秒間LOになったら水位のデータを0x00へ送信する
#include <TWELITE>
#include <NWK_SIMPLE>
#include <SM_SIMPLE>
// DIOの定義
const uint8_t DIO_INPUT = 18;  // 入力
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
uint8_t u8devid = 0x01; // 自分のID
// 変数の定義
uint8_t mizu;		// 水に浸かった回数
uint8_t datax;		// 送信するデータ
uint8_t jobNo = 0;	// 各ステップの仕事を切換える

bool wait_ms(uint32_t duration);	// 任意のms数待つ

void setup() {
	// DIOの設定
	pinMode(DIO_INPUT, INPUT_PULLUP); // プルアップ入力設定
	mizu = 0;
	jobNo = 0;
	datax = 0;   // 0:浸かっていない 1:浸かった

	the_twelite
		<< TWENET::appid(APP_ID)
		<< TWENET::channel(CHANNEL);

	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
		nwk
			<< NWK_SIMPLE::logical_id(u8devid);	// 自分のID

	the_twelite.begin();
}

void wakeup() {
	Serial << "起床した" << mwx::crlf;
	mizu = 0;
	jobNo = 0;
	datax = 0;
}

void loop() {
//
	if (jobNo == 0) {
		for (int i = 0; i < 5; i++) {   // 5回ループ
			bool PinState = digitalRead(DIO_INPUT);
				// 水につからないと H
				// 水につかると L
			if (PinState == LOW) {
				mizu++;
				Serial << "水に浸かった" << int(mizu) << mwx::crlf;
			}
			delay(1000);  // 1秒待つ
		}
		jobNo = 1;			
	}
//
	if (jobNo == 1) {
		if (mizu == 5) {  // 水に5回浸かった
			Serial << "5回水に浸かった" << mwx::crlf;
			datax = 1;    // 1:浸かった
		} else {
			Serial << "5回水に浸からない" << mwx::crlf;
			datax = 0;    // 0:浸かっていない
		}			
		jobNo = 2;
	}
//
	if (jobNo == 2) {
		if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
			// 送信先、リトライ回数等をセット
			pkt
				<< tx_addr(0x00)	// 親機00へ送信
				<< tx_retry(0x1)
				<< tx_packet_delay(0,0,2);

			// 送信するデータをセット
			pack_bytes(
				pkt.get_payload(),	// 格納先
				uint8_t(datax)		// 格納物
			);

			Serial << "送信した " << int(datax) << mwx::crlf;
			// 送信実行だが、実際の送信はこの後。
			pkt.transmit();
		}
		jobNo = 3;
	}
//
	if (jobNo == 3) {
		if (wait_ms(100)) {
// 送信完了を待つ delay()は使えない。使うと送信が失敗する。
// 送信の完了はvoid on_tx_comp()のイベントで感知
		}
	}
//
	if (jobNo == 4) {
		Serial << "1分間スリープに入る" << mwx::crlf;
		Serial.flush();
		the_twelite.sleep(60000);	// 本番は30分:30×60×1000=1800000
		jobNo = 0;			// 不要かも
	}
}

// 送信の完了
void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) {
	if (ev.bStatus) {
		Serial << "送信成功" << crlf;
	} else {
		Serial << "送信失敗" << crlf;
	}
// 送信が済むとスリープへ
	jobNo = 4;
}

// 任意のmsを待つ関数
bool wait_ms(uint32_t duration) {
	static uint32_t t_start = 0;
	static bool b_waiting = false;

	if (!b_waiting) {
		t_start = millis();
		b_waiting = true;
		return false;
	}

	if (millis() - t_start >= duration) {
		b_waiting = false;
		return true;
	}

	return false;
}

水位をチェックする回路が1つしかないですが、水に浸かる端子を上下に動かす事で、任意の位置での水位を判定できると考えました。 単純に、水位に達したら水を止める。水位が減ったら水を入れる。常に水センサーの端子の位置の水位を保てれば良いと思います。
水位を細かく感知することもできますが、データを受け取った給水側の制御に水位の判定が必要になります。
ディップスイッチを付けて、それを読み取って…と処理が増えるので今回は止めます。
実際の水田の水はどれだけ対策しても水漏れはあるし蒸発もします。単純に給水をコントロールできれば良いでしょう。

それと今回初めて使ったのですが、7Pインターフェースを使うとDIOを使いながらでもインタラクティブモードで画面へのメッセージ出力を確認しながらデバッグできるので便利ですね。

Actプログラムに
// 送信の完了
void on_tx_comp(mwx::packet_ev_tx& ev, bool_t &b_handled) 部分を追加しました。
ここで送信の完了を待ってからsleepに入る必要があります。
そうしないと、実際に送信する前にsleepに入ってしまい、データーは送信されないことになります。

モータードライバについて
写真のモータードライバでは負荷をかけた状態でモーターが結構な頻度で動かない事が分かりました。
原因は、モーター出力端子の内部抵抗が0.22オームと大きい事と過電流検出機能がすぐに働いてしまうようです。
3Vの電池を直接つなぐと問題なく動くのですが、ドライバの内部抵抗で少し電圧が下がりモーター起動時に動きが遅れると 停止状態が長くなることで過電流が検出されて電源をOFFにされてしまうようです。
4.5Vの電圧をドライバに供給するとモーターは動くようになりますが、モーターの定格電圧を超える電圧がかかってしまいます。
また、それでも時々過電流検出機能が働いて電源がOFFになってしまいます。

別の方法でモーターを制御する必要があります。

誤解の無いように補足しますが、模型用3VのDCモーターを使う上では写真のモータードライバは不向きと言う事です。 DCギアードモーター 6Vの場合は、定格負荷電流が100~400mA以下だし、停止電流も200~800mA以下になっています。 パワー的にも6V位で動かす方が良いのかもしれません。

写真のモータードライバの資料では標準のモーター電圧が3Vとなっていました。maxが7Vなので6Vは使えるかもしれませんが微妙です。

(保護回路が無い)モータードライバを自作する事にしました。

図はDCモーターを正逆回転制御するための回路です。
SW1とSW4を同時にONにすると、モーターに → 方向に電流が流れます。
全てのSWをOFFにして、SW2とSW3をONにすると、モーターに ← 方向に電流が流れます。
注!それ以外の組み合わせでスイッチをONにするとショート状態になります。

SW1からSW4をフォトリレーにします。用途にメカリレーの置き換え用とあったので使ってみました。
使用するフォトリレーは双方向MOSFET出力でトリガLEDの電流が3mA程度の物です。
フォトリレーを使う事で、MOSFETのゲート電圧のよく理解できていない問題を考えずにすみました。

TEWLITEのDIOは1ピン当り4mA程度の電流を流す事ができるので、4つのDIOをそれぞれに接続します。
SW1とSW4、SW2とSW3をセットにしてトランジスタでドライブする事もできますが、回路を簡単にする事を優先しました。
なお、過電流に対する保護回路がないのでモーターを動かす電源は一般的な乾電池にしてください。
乾電池であればショートしてもフォトリレーのピーク電流の6Aも流れる事はないと思います。


作成したモータドライバはフォトリレーが4個並んでいます。それぞれにTWELITEからDIOが4本接続しています。
黄色と桃色の線がギアボックスのモーターに接続されています。
ゲートの開閉のリミットスイッチは黄色と水色の線です。リミット状態になると H → L になります。
モーター用電源とTWELITE用電源を分ける事もできます。今回は共有しています。


WaterSupplyGateのソース
-----
// 無線で水位情報を受信する。datax 0:水足りない 1:水浸った
// 水位情報と今のゲート位置の情報でゲートを「開く・閉じる・今のまま」を決める
// ゲートを開く時はモータを正回転で、閉じる時は逆回転で1秒運転を続ける。
// 今のままの時は何もしない。
// ゲートが動き出したらゲートの状態を監視する。jobNo == 5 の間はゲートが動いている。
// 閉じるのリミットに達したら、モーターを止める。監視を止める。
// 開くのリミットに達したら、モーターを止める。監視を止める。
// 途中の時は、(止めずに)そのまま監視を続ける。
// ゲートが全開・全閉になったら、水位データの受信を待つ。

#include <TWELITE>
#include <NWK_SIMPLE>

const uint8_t DI_SW1 = 11;  // ゲート閉じの位置のSW
const uint8_t DI_SW2 = 16;  // ゲート開きの位置のSW
const uint8_t MSW4 = 18;		// モーター制御
const uint8_t MSW3 = 19;		// モーター制御
const uint8_t MSW2 = 4;			// モーター制御
const uint8_t MSW1 = 9;			// モーター制御

const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
uint8_t u8devid = 0x00;  		// 自身のID
uint8_t datax = 0;					// 送られてきたデータ用
uint8_t jobNo = 9;					// 処理切り替え用

uint8_t Gstate = 0;					// ゲートの状態 0:閉じている、1:開いている 9:不明
uint8_t Gcommand = 0;				// 命令 0:ゲートを閉じろ、1:ゲートを開けろ、2:そのまま
uint8_t OldGcommand = 0;		// 1つ前のGcommandを記憶
static uint32_t t_last = 0;

/*** function prototype */
uint8_t GetGateState();
uint8_t Judge();
void OpenGate();
void CloseGate();
void StopGate();

void setup()	{
	pinMode(DI_SW1,INPUT_PULLUP);	// 閉のリミットスイッチ H→Lでリミット
	pinMode(DI_SW2,INPUT_PULLUP);	// 開のリミットスイッチ H→Lでリミット

	pinMode(MSW1,OUTPUT);		// モーター制御 HでスイッチON
	digitalWrite(MSW1,LOW);	// 停止

	pinMode(MSW2,OUTPUT);		// モーター制御
	digitalWrite(MSW2,LOW);	// 停止

	pinMode(MSW3,OUTPUT);		// モーター制御
	digitalWrite(MSW3,LOW);	// 停止

	pinMode(MSW4,OUTPUT);		// モーター制御
	digitalWrite(MSW4,LOW);	// 停止

//	StopGate();

	jobNo = 9;			// 最初にゲートを閉じろ
	Gcommand = 0;		// ゲートを閉じろ

	the_twelite
		<< TWENET::appid(APP_ID)
		<< TWENET::channel(CHANNEL)
		<< TWENET::rx_when_idle();	// 受信回路稼働

	auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();

	nwk 
		<< NWK_SIMPLE::logical_id(u8devid);	// 自身のID

	the_twelite.begin();
}

void loop() {
	if (jobNo == 9) {		// jobNo=9はゲートの初期化(閉じる)
// 最初にゲートの状態を得る
		Gstate = GetGateState();
		if (Gstate == 9) {	// ゲートが中途半端
// -------
			CloseGate();	// 閉じる命令を出す
			jobNo = 5;		// 閉じるまで待つ処理へ
// -------
		}
		if (Gstate != 9) {	// ゲートが全開か全閉なら(中途半端では無いなら)
			StopGate();
			jobNo = 0;	// 	水位情報受信待ち処理へ
		}
	}

//
	if (jobNo == 0) {
// 水位受信処理
		if (the_twelite.receiver.available()) {
			auto&& rx = the_twelite.receiver.read();

			expand_bytes(
				rx.get_payload().begin(),	// コンテナの先頭のポインタ
				rx.get_payload().end(),	// コンテナの末尾のポインタ
				datax			// 格納する変数 0:水不足、1:水に浸った
			);
			Serial << "受信した " << int(datax) << crlf;
			jobNo = 1;
		}
	}
// ゲートへの指示を出すために、現在のゲートの状態(全開か全閉)を得る。
	if (jobNo == 1) {
		Serial << "ゲートの状態を得る " << int(jobNo) << crlf;
		Gstate = GetGateState();
		if (Gstate == 9) {		// もし、(全開か全閉)でなければ
			jobNo = 5;					// ゲートを動かす
		} else {							// ゲートは既に全開か閉じた状態である。
			jobNo = 2;					// ゲートへの指示へ
		}
		Serial << "ゲートの状態 " << int(Gstate) << crlf;
	}
// ゲートへの指示 ← 水の状態と現在のゲートの状態から判定する
	if (jobNo == 2) {
		Serial << "ゲートへの指示 " << crlf;
// Judge()ではゲートの状態と水位の状態でゲートをどう動かすべきかを判断する。
// 水が満ちていてゲートが開いていたらゲートを閉じる。
// 水が足りなくて、ゲートが閉じていたらゲートを開ける。
		Gcommand = Judge();
		if (Gcommand == 0) {	// ゲートを閉じろ
			CloseGate();	// モーターが動き始める
			delay(1000);	// リミットスイッチから外れるまで1秒間強制的に動かす
			Serial << "ゲートを閉じろ " << crlf;
			jobNo = 5;	// ゲートが動いている間の処理へ
		}
		if (Gcommand == 1) {	// ゲートを開けろ
			OpenGate();
			delay(1000);
			Serial << "ゲートを開けろ " << crlf;
			jobNo = 5;
		}
		if (Gcommand == 9) {	// ゲートはそのまま動くな
			StopGate();
			Serial << "ゲートはそのまま " << crlf;
			jobNo = 0;	// 受信に戻る
		}
	}
// ゲートが動いている間の処理
	if (jobNo == 5) {
		delay(100);		// 100ms待つ
// ---------------
// 100ms毎にゲートがリミットスイッチに達したかを監視
		Gstate = GetGateState();
		if (Gstate == 0) {	// ゲートは閉じられた
			StopGate();
			Serial << "ゲートは閉じた " << crlf;
			jobNo = 0;
		}
		if (Gstate == 1) {	// ゲートは開けられた
			StopGate();
			Serial << "ゲートは開いた " << crlf;
			jobNo = 0;
		}
		if (Gstate == 9) {	// ゲートはまだ動いている
			jobNo = 5;
		}
// ---------------
	}		// end of jobNo()
}		// end of loop()

// 今のゲートの状態を返す関数
uint8_t GetGateState() {
	bool state1 = digitalRead(DI_SW1);	// 閉のリミットスイッチ
	bool state2 = digitalRead(DI_SW2);	// 開のリミットスイッチ
	if (!state1 && state2) {    // LO HI
		return 0;            // 0 ゲートは閉じている
	}
	else if (state1 && !state2) {  // HI LO
		return 1;            // 1 ゲートは開いている    
	}
	else {
        return 9;            // ゲートは途中
	}
}

// 水位情報とゲートの状態で開閉を決める関数 120
uint8_t Judge() {
	if (datax == 0 && Gstate == 0) {	// 0:水足りなくてゲートが0:閉じている
		return 1;			// 開けろ
	}
	else if (datax == 1 && Gstate == 1) {		// 1:水は浸ったゲートは1:開いている
		return 0;			// 閉じろ
	}
	else {
		return 9;			// そのまま
	}
}
// ゲートを開く
void OpenGate() {	// モータを正回転
	StopGate();
	digitalWrite(MSW1,HIGH);
	digitalWrite(MSW3,HIGH);
}

// ゲートを閉じる
void CloseGate(){	// モータを逆回転
	StopGate();
	digitalWrite(MSW2,HIGH);
	digitalWrite(MSW4,HIGH);
}

void StopGate(){	// モータを止める
	digitalWrite(MSW1,LOW);
	digitalWrite(MSW2,LOW);
	digitalWrite(MSW3,LOW);
	digitalWrite(MSW4,LOW);
}