編集履歴一覧に戻る
maruaのアイコン画像

marua が 2022年02月13日16時55分41秒 に編集

初版

タイトルの変更

+

土壌の水分/温度センサ

タグの変更

+

Arduino

+

DS18B20

メイン画像の変更

メイン画像が設定されました

記事種類の変更

+

製作品

ライセンスの変更

+

(CC BY-SA 4+) Creative Commons Attribution-ShareAlike CC BY-SA version 4.0 or later

本文の変更

+

植木鉢の水分管理の目安になるよう、ATmega328Pに  ・水分センサ  ・温度センサ を接続して、  ・LCDに水分センサの値と温度を表示  ・水無しを検知したら LEDを点滅 する基板を作成しました。 温度センサは「One Wire」という1本の信号線でやり取りをする方式のため、「ライブラリの管理」から ライブラリのインストールが必要になります。 ログを記録できるよう SDカードとRTCも搭載しましたが、こちらは まだ手を掛けていません。 また、5V/3.3V どちらでも動作するよう 部品を選定してあります。 ●主な使用部品 | No. | 品名 | 品番 | 備考 | |:---:|:---:|:---:|:---:| | 1 | マイコン | ATmega328P | | | 2 | LCDモジュール | AE-AQM1602A(KIT) | 秋月 K-08896 | | 3 | LED | | | | 4 | 水分センサ | diystudio | アナログ出力 | | 5 | 温度センサ | diystudio | DS18B20使用 | | 6 | RTCモジュール | AE-RX8900 | 秋月 K-13009 | | 7 | SDカードモジュール | AE-microSD-LLCNV | 秋月 K-14015 | ●I/O一覧 | | ポート B | ポート C | ポート D | |:---:|:---:|:---:|:---:| | bit 7 | - | | - | | bit 6 | - | (RES) | - | | bit 5 | SD_CLK | SCL | - | | bit 4 | SD_MISO | SDA | - | | bit 3 | SD_MOSI | DS18B20 | - | | bit 2 | SD_SS | LED | - | | bit 1 | - | - | TxD | | bit 0 | - | 水分センサ | RxD | ```arduino:水分/温度センサ #include <Wire.h> #include "OneWire.h" #define LCD_ADRS 0x3E #define Moist_Sens 0 #define dLED 0x04 // ■ LCD_ctrl() // 111111 //0123456789012345 char vram1[] = "Moisture Sensor "; char vram2[] = " "; char LCD_task = 0; // LCD タスクNo. int LCD_col = 0; // LCD 書込み桁 unsigned long LCD_tim0 = 0; // LCD 基準時間 // ■ cmd1() char cmd1_task = 0; // cmd1 タスクNo. unsigned long cmd1_tim0 = 0; // コマンド1 基準時間 unsigned long tim_cnt = 0; // タイム・カウンタ int Moist_dat = 0; // 水分データ int Moist_th; // 水分量判定値 unsigned char Moist_lvl = 0; // 水分量 ("0"で閾値未満/ "1"で閾値以上(水無し)) unsigned char sec = 0; // 秒カウンタ // ■ LED_ctrl() char LED_task = 0; // LED制御 タスクNo. unsigned long LED_tim0 = 0; // LED制御 基準時間 // ■ DS18B20 byte i; byte present = 0; byte type_s; byte data[12]; byte addr[8]; float celsius, fahrenheit; char Thermo_task = 0; // 温度センサ タスクNo. unsigned long Thermo_tim0 = 0; // 温度センサ 基準時間 // ■ ログ制御 char log_task = 0; // ログ タスクNo. unsigned long log_tim0 = 0; // ログ 基準時間 OneWire ds(17); // DS18B20 on pin 17 (a 4.7K resistor is necessary) // ■■ void setup() { Serial.begin(9600); Wire.begin(); init_LCD(); LCD_tim0 = 0; // LCD 基準時間 Thermo_tim0 = 100; // 温度センサ 基準時間 LED_tim0 = 200; // LED制御 基準時間 cmd1_tim0 = 900; // コマンド1 基準時間 DDRC |= dLED; // PC2 = 出力 Moist_th = 350; // 水不足判定値 set_Moist(); // 水分量判定 Serial.println("Moisture Sensor v0.01"); } // ■ void loop() { LCD_ctrl(); // LCD 表示制御 LED_ctrl(); // LED 点滅制御 cmd1(); // コマンド1処理 Thermo_ctrl(); // DS18B20 デモ log_ctrl(); // ログ制御 } unsigned char sub(unsigned char a, unsigned char b) { a = a - b; return a ; } // ■■ void cmd1() { int val; unsigned long tim; switch (cmd1_task) { case 0: cmd1_tim0 = millis(); // 基準時間セット cmd1_task ++; break; case 1: tim = abs(millis() - cmd1_tim0); if ( tim > 1000 ) { Moist_dat = analogRead(Moist_Sens); set_VRAM2(1, Moist_dat); val = (int)(celsius*100) ; set_VRAM2(7, val); vram2[12] = vram2[11]; vram2[11] = vram2[10]; vram2[10] = '.'; vram2[13] = 0xdf; vram2[14] = 'C'; set_Moist(); // 水分量判定 cmd1_task ++; } break; default: cmd1_task = 0; break; } } void set_Moist(void) { if (Moist_dat < Moist_th) { // 水有り if (Moist_lvl != 1) { Moist_lvl = 1; LED_task = 99; // LED 消灯 } } else { // 水無し if (Moist_lvl != 0) { Moist_lvl = 0; LED_task = 20; // LED 点滅 } } } void log_ctrl() { switch (log_task) { case 0: log_tim0 = millis(); // 基準時間セット log_task ++; break; case 1: unsigned long tim; tim = abs(millis() - log_tim0); if ( tim > 600000 ) { Serial.print(millis()); Serial.print(' '); Serial.print(Moist_dat); Serial.print(' '); Serial.println(celsius); log_task ++; } break; default: log_task = 0; break; } } void Thermo_ctrl() { switch (Thermo_task) { case 0: case 2: Thermo_tim0 = millis(); // 基準時間セット Thermo_task ++; break; case 1: if ( (millis() - Thermo_tim0) > 1000 ) { Thermo_01(); // DS18B20 読取り-1 Thermo_task ++; } break; case 3: if ( (millis() - Thermo_tim0) > 1000 ) { Thermo_02(); // DS18B20 読取り-2 Thermo_task ++; } break; default: Thermo_task = 0; break; } } void LCD_ctrl() { switch (LCD_task) { case 0: writeCommand(0x80+0x00); // ADRESS SET (1行目の先頭) LCD_col = 0; LCD_task ++; break; case 1: LCD_tim0 = millis(); // 基準時間セット LCD_task ++; break; case 2: if ( (millis() - LCD_tim0) > 20 ) { writeData(vram1[LCD_col]); LCD_col ++; if (LCD_col >15) { LCD_task ++; } } break; case 3: writeCommand(0x80+0x40); // ADRESS SET (2行目の先頭) LCD_col = 0; LCD_task ++; break; case 4: LCD_tim0 = millis(); // 基準時間セット LCD_task ++; break; case 5: if ( (millis() - LCD_tim0) > 20 ) { writeData(vram2[LCD_col]); LCD_col ++; if (LCD_col >15) { LCD_task ++; } } break; default: LCD_task = 0; break; } } void set_VRAM2(int col, int dat) { for (int num=4; num>=0; num--) { vram2[col+num] = (dat % 10) + 0x30; dat /= 10; } } // void LED_ctrl(void) { switch(LED_task) { case 0: break; // 1秒周期の点滅 (Duty=50%) case 10: LED_tim0 = millis(); LED_on(); LED_task ++; break; case 11: if ( (millis() - LED_tim0) > 500) { LED_task ++; } break; case 12: LED_tim0 = millis(); LED_off(); LED_task ++; break; case 13: if ( (millis() - LED_tim0) > 500) { LED_task = 10; } break; // 1秒周期の点滅 (ON 50ms) case 20: LED_tim0 = millis(); LED_on(); LED_task ++; break; case 21: if ( (millis() - LED_tim0) > 10) { LED_task ++; } break; case 22: LED_tim0 = millis(); LED_off(); LED_task ++; break; case 23: if ( (millis() - LED_tim0) > 950) { LED_task = 20; } break; case 99: default: LED_off(); LED_task = 0; break; } } void LED_on(void) { PORTC |= dLED; } void LED_off(void) { PORTC &= ~dLED; } // ■■ DS18B20 Demo ■■ void Thermo_01(void) { if ( !ds.search(addr)) { ds.reset_search(); return; } if (OneWire::crc8(addr, 7) != addr[7]) { return; } // the first ROM byte indicates which chip switch (addr[0]) { case 0x10: type_s = 1; break; case 0x28: type_s = 0; break; case 0x22: type_s = 0; break; default: return; } ds.reset(); ds.select(addr); ds.write(0x44, 1); // start conversion, with parasite power on at the end } void Thermo_02(void) { present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for ( i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } // Convert the data to actual temperature // because the result is a 16 bit signed integer, it should // be stored to an "int16_t" type, which is always 16 bits // even when compiled on a 32 bit processor. int16_t raw = (data[1] << 8) | data[0]; if (type_s) { raw = raw << 3; // 9 bit resolution default if (data[7] == 0x10) { // "count remain" gives full 12 bit resolution raw = (raw & 0xFFF0) + 12 - data[6]; } } else { byte cfg = (data[4] & 0x60); // at lower res, the low bits are undefined, so let's zero them if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms //// default is 12 bit resolution, 750 ms conversion time } celsius = (float)raw / 16.0; fahrenheit = celsius * 1.8 + 32.0; } // ■■ AQM1602 Demo ■■ char moji[] ="AQM1602XA-RN-GBW"; //データ書き込み void writeData(byte t_data) { Wire.beginTransmission(LCD_ADRS); Wire.write(0x40); Wire.write(t_data); Wire.endTransmission(); delay(1); } //コマンド書き込み void writeCommand(byte t_command) { Wire.beginTransmission(LCD_ADRS); Wire.write(0x00); Wire.write(t_command); Wire.endTransmission(); delay(10); } //液晶初期化 void init_LCD() { delay(100); writeCommand(0x38); // (001 1 1000):FUNCTION SET(8bit, 2行, Normal Height, 0, IS=0) delay(20); writeCommand(0x39); // (001 1 1001):IS=1 delay(20); writeCommand(0x14); // (0001 0100):INT OSC FREQUENCY(1/5 bias, freq="100") delay(20); // writeCommand(0x7A); // (0111 1010):CONTRAST SET 0,1,2,3(contrast L="1010") ※DC5V用 writeCommand(0x7E); // (0111 1110):CONTRAST SET 0,1,2,3(contrast L="1110") ※DC4V用 delay(20); writeCommand(0x54); // (0101 0100):CONTRAST SET 4,5(icon OFF, booster ON, contrast H="00") delay(20); writeCommand(0x6C); // (0110 1100):F0LLOWER CONTROL delay(20); writeCommand(0x38); // (001 1 1000):IS=0 delay(20); writeCommand(0x0C); // (00001 100):Display ON, cursor OFF, position OFF delay(20); writeCommand(0x01); // (0000 0001 ):Clear Display delay(20); writeCommand(0x06); // (000001 10 ):Entry Mode(cursor INC, shift OFF) delay(20); } ``` ●水分センサについて 使用した水分センサは、NE555で作成したパルスを 検知電極を介したフィルタに通して積分したものを出力する方式のようです。 ![水分センサ](https://camo.elchika.com/a658e22155c2f4f84875fe24af0899fa3bf52631/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f33633934613036612d653465342d343838392d383133342d3434636466393330333066352f33366239663630632d643635632d343532392d383034342d623165383936623266653764/) DC5V印可時は、OPEN状態で 約500(水無し)/ 電極を手で包むと約250(水有り) でしたが、 単4電池×3本で駆動すると 約580/ 約300と値が変動しました。 とりあえず、setup()にて 判定値:「Moist_th」を350に設定してありますが、使用する環境により 変更する必要が有ります。