2〜3週間の長雨でもへこたれない田んぼ監視デバイスを作る


IoTLT 2025/09/21
資料
林伸夫





2〜3週間の長雨でも止まらない田んぼ監視デバイスを作る






IoT Module (田んぼカメラ)成果 Demo

ここで実際の検証動画をご覧ください。

YouTubeに置いてありますので、どうぞ
2025年5月14日の田植えからから8月25日の稲刈りまで


今年も天候不順で3種間、お日さまが出ない期間があったが乗り切った


https://www.youtube.com/watch?v=i9vwlI59f2w

写真ブラウザ+天候データブラウザ+登熟温度計算のデータ







うまく動いているが、問題点も


バッテリーの用意はなかなか大変

設置状況はこうですが、機材が大きくて設置や撤収が大変



ソーラーパネル出力は10V〜30Vくらいになるので、大きなバッテリーチャージャーが必要


さらに12Vのバッテリーが必要


そこでリチウムイオン電池で使えるコンパクトなバッテリーチャージャーを探した

  1. 10から40Vくらいの電源を扱える
  2. バッテリーは18650を3〜4本程度
  3. 出力はUSB-Cで3Aくらい流せるもの
    1. Raspberry Pi4,5は3A必要



各種製品を購入して試してみた
1.
https://github.com/rcdrones/UPSPACK_V3/blob/master/README_en.md
5V 3A 流せるがソーラーパネルはつなげられない



2.
Type-C 15W 3A 18650 Lithium Battery Charger Module DC-DC Step Up Booster Fast Charge UPS Power Supply 5,9,12V
Raspberry Piをノンストップで動かせるが、ソーラーパネルはつなげられない


3.
WaveShare, Solar Power Manager Module (D), Supports 6V~24V Solar Panel and Type-C Power Adapter, 5V/3A Regulated Output | Solar Power Manager (D)、基板モジュール,コネクタはしっかりしたターミナルブロック、USB-PDのUSB-Cコネクタ装備

ようやく見つけたが
入力部分が差し込み式で野外で使うには心もとない


しばらくしたらターミナルブロックでがっちり固定する基板タイプのものが登場した
$12.39



Raspberry Piにつなげる電源としてこんなにコンパクトなのはなかなかない

開発元、購入先は
https://www.waveshare.com/wiki/Solar_Power_Manager_(D)

これで、ソーラーパネル→バッテリーチャージャー→Raspberry Pi の連携は劇的に改善することになったが、カメラなども含めて24時間常時稼働させるにはパワーが足りない。

間歇動作させ、休止中は消費電力0にできる基板を開発


リモートからの指示で5分から任意の時間間隔で起動、シャットダウンを繰り返すパワーコントロールボードを設計した。

ATTyny85を使ったパワーラッチコントロール基板

消費電力の目安


これで、写真撮影、環境データの取得後は完全に電力を遮断する。コントロール基板はATtiny85。


この電源モジュールはRaspberry PiからI2C経由で「何秒後にシャットダウン」、「何分後に起動」というコマンドを受け取って動作する
#include <Arduino.h>
#include <TinyWireS.h>
#include <EEPROM.h>
#include <TimeLib.h>

// ==== ピン定義 ====
const int SDA_PIN = 0; // PB0 (固定)
const int SCL_PIN = 2; // PB2 (固定)
const int kPower = 1; // PB1 (pin 6)
const int pilotLamp = 4; // PB4 (pin 3)
const int keepAlivePin = 3; // PB3 (pin 2)

#define I2C_SLAVE_ADDRESS 0x40

// ==== 状態管理 ====
const int kModeWaiting = 1;
const int kModeShouldOff = 2;
const int kModeShouldOn = 3;

int mode = kModeWaiting;
time_t eventArrivalTime = 0;
int nextTurnOnMinutes = 59;
int nextTurnOffSeconds = 30;

unsigned long lastKeepAliveTime = 0;
int lastKeepAliveState = LOW;
const unsigned long keepAliveTimeoutMs = 30000;

volatile byte errorCode = 0;
volatile bool i2cCommandReceived = false;
volatile byte receivedData[2];
volatile byte receivedIndex = 0;

// ==== プロトタイプ宣言 ====
void receiveEvent(uint8_t howMany);
void requestEvent();
void blink(int durationMs);

void setup() {
pinMode(pilotLamp, OUTPUT);
digitalWrite(pilotLamp, LOW);

pinMode(kPower, OUTPUT);
digitalWrite(kPower, HIGH); // 電源ONでスタート

pinMode(keepAlivePin, INPUT);

setTime(0, 0, 0, 1, 1, 2025); // 仮の時刻

TinyWireS.begin(I2C_SLAVE_ADDRESS);
TinyWireS.onReceive(receiveEvent);
TinyWireS.onRequest(requestEvent);

// EEPROMから復旧
byte storedOffSec = EEPROM.read(0);
byte storedOnMin = EEPROM.read(1);

if (storedOffSec == 0xFF || storedOffSec == 0 || storedOffSec > 120 ||
storedOnMin == 0xFF || storedOnMin == 0 || storedOnMin > 180) {
nextTurnOffSeconds = 30;
nextTurnOnMinutes = 59;
EEPROM.update(0, nextTurnOffSeconds);
EEPROM.update(1, nextTurnOnMinutes);
} else {
nextTurnOffSeconds = storedOffSec;
nextTurnOnMinutes = storedOnMin;
}
}

time_t prevTime = 0;

void loop() {
TinyWireS_stop_check();

time_t nowTime = now();

// LED表示
if (prevTime != nowTime) {
if ((nowTime % (errorCode ? 1 : 5)) == 0) {
blink(errorCode ? 100 : 50);
}
prevTime = nowTime;
}

// KeepAlive監視
int currentKeepAliveState = digitalRead(keepAlivePin);
if (currentKeepAliveState != lastKeepAliveState) {
lastKeepAliveState = currentKeepAliveState;
lastKeepAliveTime = millis();
}
if ((millis() - lastKeepAliveTime) > keepAliveTimeoutMs) {
mode = kModeShouldOff;
eventArrivalTime = nowTime;
errorCode = 1;
}

// モード制御
switch (mode) {
case kModeWaiting:
break;
case kModeShouldOff:
if (nowTime >= eventArrivalTime) {
mode = kModeShouldOn;
eventArrivalTime = nowTime + nextTurnOnMinutes * 60;
digitalWrite(kPower, LOW); // 電源OFF
}
break;
case kModeShouldOn:
if (nowTime >= eventArrivalTime) {
mode = kModeWaiting;
eventArrivalTime = 0;
digitalWrite(kPower, HIGH); // 電源ON
}
break;
}

// I2Cコマンド処理
if (i2cCommandReceived) {
i2cCommandReceived = false;
nextTurnOffSeconds = receivedData[0];
nextTurnOnMinutes = receivedData[1];

EEPROM.update(0, nextTurnOffSeconds);
EEPROM.update(1, nextTurnOnMinutes);

eventArrivalTime = nowTime + nextTurnOffSeconds;
mode = kModeShouldOff;
}

delay(50);
}

// ==== I2C受信イベント ====
void receiveEvent(uint8_t howMany) {
receivedIndex = 0;
while (TinyWireS.available() && receivedIndex < 2) {
receivedData[receivedIndex++] = TinyWireS.receive();
}
if (receivedIndex == 2) {
i2cCommandReceived = true;
}
}

// ==== I2Cリクエスト(マスターから読まれたとき) ====
void requestEvent() {
TinyWireS.send(errorCode);
}

// ==== LED点滅 ====
void blink(int durationMs) {
digitalWrite(pilotLamp, HIGH);
delay(durationMs);
digitalWrite(pilotLamp, LOW);
delay(200);
}

消費電力の目安


JLCPCBに基板発注する際の注意点
発注方法は大きく2つある
Fusion 360やKiCADでガーバーデータを作ってJLCPCBの入稿サイトで発注する
・EasyEDAのサイトで設計して面付けして発注

EasyEDAには面付けの機能が内蔵されているので、小さな基板を最低価格の100mmx100mmに詰め込んで1枚300円ほどで作ってもらうことができる



最近の発注例。こういう極小基板も面付けして100mmx100mmに納めると安く作れる。ただし、製品製作時のチェックで各モジュールが異なる機能部品であると判断されれば追加料金がかかってしまう。これは、検査エンジニアの判断によることが多い。

Thank You!

Any Questions?