Seeeduino XIAOで土壌センサーの情報を取得

組み込みエンジニア

こんにちは、ENGかぴです。

Arduino環境ではアナログ入力をAD変換する標準ライブラリが実装されています。Seeeduino XIAOを使って土壌センサーの出力電圧を計測し土壌の水分量の判定した結果をOLEDに表示して動作確認を行いました。

土壌センサーはSEN0114(DFROBOT製:秋月電子で購入)を使用しています。土壌センサーの動作検証と動作履歴をSDカードに保存して動作確認を行いました。

Seeeduino XIAO用拡張ボード(秋月電子)を使用しています。Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

土壌センサーの情報を取得する

SEN0114は土壌の抵抗分で発生する電圧を出力するセンサーです。水分量が多いほど土壌の抵抗分が少なくなり電気を通しやすくなることを利用したものです。下記リンクにセンサーの情報とスケッチ例が説明されています。

SEN0114 Moisture Sensor-DFROBOT

Arduino用に出力調整されているためライブラリで読み取った値から土壌の状態を確認することができます。

センサー情報の使用例

if( timdataget == TIME_UP){
  timdataget = TIM_MEAS;
  sen0114.buf[sen0114.wp] = analogRead(A0);

  if(++sen0114.wp >= MEAS_MAX){
    sen0114.wp = 0;
  }
  sum = 0;
  for(uint8_t i =0; i < MEAS_MAX; i++){
    sum += sen0114.buf[i];
  }
  sen0114.humid = sum >> 3; //8で割る
}

アナログデータはノイズの影響などで実際の値から少し上下することがあるため数回読み込んだデータを平均化して使用します。

電圧値の取得はanalogRead()関数を使用します。引数にアナログピンの番号を指定します。AD変換値が戻り値にセットされるので変数に格納します。

例では100ms毎にデータを取得して最新のデータから8個分のデータを使用して平均値を算出しています。平均化する際に>>3で3回右にシフトしていますが8で割るのと同じ結果になります。

マイコンが計算する際にビットシフトで計算すると計算時間を減らすことができます。演算器を持たないマイコンを使用する場合においては効果的な方法です。

土壌センサーとしての用途を考えると短期間で土壌の水分がしないため計測の頻度を減らして平均化しない構成でも問題ないと思います。

動作検証

SEN0114の動作検証

Seeeduino XIAOは3.3V動作なので3.3V時の動作の目安について検証を行いました。Arduino(5V)から取得した値で土壌の状態の目安として0~300 dry soil、300~700 humid soil、700~950 in waterと記載されています。

電源5Vが3.3Vになった場合の計測値の相違について次のパターンで計測値の確認を行います。

  1. プローブ間を指で触れる
  2. プローブを水の中に入れる(上記の写真の通り)
  3. プローブ間をショートする

これらのパターンの結果をシリアルモニタで確認します。

SEN0114の動作確認(シリアルモニタ)
SEN0114の動作確認(シリアルモニタ)

1. プローブ間を指で触れた場合は190程度の値となっています。2. プローブを水の中に入れた場合は690付近(5V時は750)の値になっています。3. プローブ間をショートした場合は820付近(5V時は880)の値になっています。

3.3Vの結果と5Vの結果を比較すると指で触れた場合を除いて水に入れた場合とショートした場合で50~60程度値が低く出る傾向がありました。in waterの目安値を650程度で考えるとよさそうです。

3.3Vの場合直接水につけた場合でも690程度の値になることが分かりました。プローブ間をショートすると820付近の値になりますが土壌に使用する場合ショートする可能性は限りなく低いことから690付近が最大値と考えて問題なさそうです。

SDカードに履歴保存

void saveSd(void){

  if( timsave == TIME_UP ){
    timsave = TIM_SAVE;
    myfile = SD.open(filepath,FILE_WRITE);

    if( myfile ){ //ファイルが開けたら書き込む
      myfile.print("time:");
      myfile.print(timcnt);
      myfile.print(" humid:");
      myfile.println(sen0114.humid);
      myfile.close(); //ファイルを閉じる
    }
    ++timcnt;
  }
}

SDカードに土壌の水分量の履歴を保存するためタイマで管理しています。土壌は直射日光が当たらない限り乾燥しにくいため頻繁にタイムアップするような構成にする必要はありません。SDカードの操作については下記記事にまとめています。

ArduinoのライブラリでSDカードにデータを保存する

SDカードに履歴を保存することで観葉植物の土壌の様子が分かるため水やりの最適なタイミングを掴むことができます。面倒見が良すぎがあまり根腐れさせてしまうことに悩む可能性の軽減につながることが期待できます。

拡張基板のOLEDに文字を表示

OLEDに文字を表示するためにライブラリを追加して動作確認したことを下記記事にまとめています。

Seeeduino XIAOの拡張ボードのOLEDを使用する

拡張ボードはOLED用に4ピンと5ピンを使用するため使用することができません。使用するとOLEDの表示がバグるなど不安定な動作になります。

本記事では土壌の状態をOLEDで表示するために使用しています。

#define DRY_SOIL 300
#define HUMID_SOIL 650

if( sen0114.humid < DRY_SOIL){ //300未満
  u8x8.print("-->dry soil");
}
else if( sen0114.humid >= DRY_SOIL && sen0114.humid < HUMID_SOIL){
  u8x8.print("-->humid soil");
}
else{ //650以上
  u8x8.print("-->in water");
}

動作検証でSEN0114の計測値を確認しました。結果を踏まえてOLEDの表示を切り替えます。SEN0114の計測値が0~299の間であれば–>dry soilを表示します。計測値が300~649の間であれば–>humid soilを表示します。計測値が650以上で–>in waterを表示します。

頻繁に文字を切り替えるとOLEDの表示がチカチカするので注意が必要です。計測タイミングと表示タイミングを区別することで見やすくなります。

本記事ではOLEDの表示をSDカードに履歴を保存するタイミングにして表示が頻繁に切り替わらないようにしています。

あなたのキャリアのお供に「生涯学習のユーキャン」

動作確認

動作確認用の回路図
動作確認用の回路図

Seeeduino XIAOをSeeeduino拡張ボードに挿入しSEN0114を実装します。SEN0114の電圧のAD変換値と土壌の水分量を判定した結果をOLEDに表示します。

アイキャッチのようにSEN0114を計測対象に挿入した状態で電源をONすると土壌の水分量を1分毎にSDカードに履歴保存しながらOLEDの表示を更新します。

動作結果(OLEDの表示)
動作結果(OLEDの表示)

計測を開始する前にSDカードの状態を確認します。SDカードが挿入されていない場合は–>SD-ERRを表示します。SDカード挿入されている場合は計測を開始します。1分毎にSEN0114のAD変換値と土壌の状態を判定した結果が表示されることを確認しました。

動作確認の結果(SDカードに保存した履歴)
動作確認の結果(SDカードに保存した履歴)

電源ONから30分程度放置して動作履歴を確認しました。SDカードに動作開始してからの時間と計測データが保存されていることが確認できました。

スポンサーリンク

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include <SPI.h>
#include <SD.h>
#include <TimerTC3.h>
#include <U8x8lib.h>
#include <Wire.h>

#define SD_CS 2
#define MEAS_MAX 8
#define TIM_MEAS 10
#define TIM_SAVE 6000
#define DRY_SOIL 300
#define HUMID_SOIL 650

#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値

struct DIS_MEAN{
  uint8_t wp;
  uint32_t buf[MEAS_MAX];
  uint32_t humid;
};

int8_t cnt10ms;
int16_t  timdataget;
int32_t  timsave;
String filepath = "/sample.txt";
File myfile;
DIS_MEAN sen0114;
uint32_t timcnt;
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8( SCL, SDA, U8X8_PIN_NONE);

void mainTimer(void);
void mainApp(void);
void saveSd(void);
void TimerCnt(void);

void setup() {
  Serial.begin(115200);

  u8x8.begin();
  u8x8.setFlipMode(1);   // set number from 1 to 3
  u8x8.setFont(u8x8_font_5x8_r);
  u8x8.setCursor(0, 0);
  u8x8.print("Seeeduino soil");
  u8x8.setCursor(0, 1);
  u8x8.print("       Ver1.00");
  u8x8.setCursor(0, 3);
  u8x8.print("SOIL HUMID:");
  u8x8.setCursor(0, 5);
  u8x8.print("STATUS:");

  if(!SD.begin(SD_CS)){
    Serial.println("initialization failed!");
    u8x8.setCursor(0, 6);
    u8x8.clearLine(6);
    u8x8.print("-->SD-ERR");
    while (1);
  }

  TimerTc3.initialize(1000);
  TimerTc3.attachInterrupt(TimerCnt);

  for( uint8_t i=0; i < 10; i++ ){
    timdataget = TIME_UP;
    mainApp();
    delay(100);
  }
}

void loop() {

  mainTimer();
  mainApp();
  saveSd();

}
/* メイン処理 */
void mainApp(void){
  uint16_t sum;

  if( timdataget == TIME_UP){
    timdataget = TIM_MEAS;
    sen0114.buf[sen0114.wp] = analogRead(A0);

    if(++sen0114.wp >= MEAS_MAX){
      sen0114.wp = 0;
    }

    sum = 0;
    for(uint8_t i =0; i < MEAS_MAX; i++){
      sum += sen0114.buf[i];
    }

    sen0114.humid = sum >> 3;
  }
}
/* SDカード処理 */
void saveSd(void){

  if( timsave == TIME_UP ){
    timsave = TIM_SAVE;
    u8x8.setCursor(0, 4);
    u8x8.clearLine(4);
    u8x8.print("-->");
    u8x8.print(sen0114.humid);
    u8x8.setCursor(0, 6);
    u8x8.clearLine(6);

    if( sen0114.humid < DRY_SOIL){
      u8x8.print("-->dry soil");
    }
    else if( sen0114.humid >= DRY_SOIL && sen0114.humid < HUMID_SOIL){
      u8x8.print("-->humid soil");
    }
    else{
      u8x8.print("-->in water");
    }

    myfile = SD.open(filepath,FILE_WRITE);

    if( myfile ){ //ファイルが開けたら書き込む
      myfile.print("time:");
      myfile.print(timcnt);
      myfile.print(" humid:");
      myfile.println(sen0114.humid);
      myfile.close(); //ファイルを閉じる
      //Serial.print("time:");
      //Serial.print(timcnt);
      //Serial.print(" humid:");
      //Serial.println(sen0114.humid);
    }
    ++timcnt;
  }
}
/* callback function add */
void TimerCnt(){
  ++cnt10ms;
}
/* タイマ管理 */
void mainTimer(void){

  if( cnt10ms >= BASE_CNT ){
    cnt10ms -=BASE_CNT;

    if( timdataget > TIME_UP ){
      --timdataget;
    }

    if( timsave > TIME_UP ){
      --timsave;
    }
  }
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Code Camp完全オンラインのプログラミング個人レッスン【無料体験】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました