Soil moisture sensor: Difference between revisions
Jump to navigation
Jump to search
| (44 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
= Soil moisture sensor = | = Soil moisture sensor = | ||
= Authors = | |||
For more information contact Frederic Cherqui, Adam Simankowicz or Edmond Lascaris | |||
== Applications == | == Applications == | ||
Soil moisture of trees, garden beds and agriculture. Sensors originally deployed in Africa to educate farmers about irrigation of food crops | |||
== How the Soil moisture sensor works == | |||
* Soil moisture sensor circuit explained by Matthew Driver | |||
=== Components === | |||
* Arduino MKR Zero microcontroller using two analog pins with 10 bit analog to digital converters (ADC). | |||
* Two 10K 0.1% precision resistors | |||
* Chameleon soil moisture sensor | |||
=== Power supply voltage (Vcc) === | |||
* This design is independent of voltage. | |||
* In this circuit the power will be supplied via a Pololu 5V step-up regulated supply. | |||
=== Analog pins === | |||
* The analog pins on the Arduino microcontroller are dual purpose. | |||
** They can measure a voltage between 0 and Vcc (when their mode is set as an analog INPUT) | |||
** They can also but can also output Vcc (3.3 or 5V) or drain to ground (when their mode is set to digital OUTPUT) * Both of these states are set using the pinmode function in the Arduino code. | |||
=== States === | |||
* To measure the soil moisture the sensor circuit is placed into three different states: | |||
** Forward current | |||
** Reverse current (the flow of electrons is reversed from the perspective of the sensor) | |||
** No current | |||
=== Schematic === | |||
* A schematic of the soil moisture sensor circuit is shown below. | |||
* Pin X and Pin Y are the two analog input pins on the Arduino microcontroller. | |||
[[File:Screen Shot 2022-01-03 at 2.47.07 pm.png]] | |||
=== Excitation and and measuring (states applied) === | |||
==== Forward current flow ==== | |||
* To begin the measurement a '''Forward current''' is applied to the sensor | |||
* The '''Forward current''' state is applied for 250us (microseconds) by setting the pins to: | |||
** pinMode(Pin_Y,INPUT) | |||
** pinMode(Pin_X,OUTPUT) | |||
** digitalWrite(Pin_X, HIGH) | |||
* The voltage is then measured at pin Y and stored in a variable. | |||
[[File:Screen Shot 2022-01-03 at 2.50.35 pm.png]] | |||
== | ==== No current flow ==== | ||
* | * A '''No current''' state is then applied for 250us by setting the pins to: | ||
** '''digitalWrite(Pin X, LOW)''' | |||
* The result is all pins are at ground so no current flows. | |||
* Note that the INPUT pin acts as a drain, which is equivalent to ground. | |||
[[File:Screen Shot 2022-01-03 at 2.53.52 pm.png]] | |||
=== | ==== Reverse current flow ==== | ||
* Then '''Reverse current''' state is then applied for 250us (microseconds) by setting the pins to: | |||
** '''pinMode(Pin_X,INPUT)''' | |||
** '''pinMode(Pin_Y,OUTPUT)''' | |||
** '''digitalWrite(Pin_Y, HIGH)''' | |||
* The voltage is then measured at pin X and stored in variable. | |||
[[File:Screen Shot 2022-01-03 at 2.56.12 pm.png]] | |||
=== Sensor resistance calculation === | |||
* The Arduino convert the analog input to a digital value between 0 and 1023 where 1023 is the Vcc voltage (5V), * | |||
* Sensor Resistance = Rs = 10K * (1023 - Vs) / Vs | |||
** where Vs is the average of forward and reverse current values | |||
* Temperature will change the measured value | |||
* A simple formula allows for soil temperature changes to be corrected. | |||
** Formula: Ra = R0 * (1 + (Temp - 22) * 0 .018 ) | |||
* Lastly, the soil moisture resistance is converted to '''soil moisture tension''' and expressed in '''kPa pressure units'''. | |||
* This is a measure of how much pressure plants need to apply to extract moisture from soil. | |||
* The relationship is shown in the graph below. | |||
[[File:Screen Shot 2022-01-02 at 6.26.41 pm.png]] | |||
== Interpreting Soil Moisture sensor values == | |||
* The Soil Moisture Sensor measures how hard it is for plants to draw water from soil. | |||
* This soil moisture sensor measures resistance and then converts this value to soil moisture tension in kPa. | |||
* This higher the soil moisture tension (kPa), the harder it is for plants to extract moisture from the soil. | |||
* Soil moisture tension from '''20 - 30 kPa''' signifies '''wet''' soil - ideal for broccoli, celery, lettuce and onion | |||
* Soil moisture tensions from '''30 - 45 kPa''' is '''moist''' soil - ideal for beans, cabbage, carrot, capsicum, corn, cucumber, eggplant, melons, potato and tomato. | |||
* Soil moisture tenions from '''45 - 60 kPa''' is '''dry''' soil - ideal for beet, peas, sweet potato and pumpkin. | |||
== Software installation requirements == | == Software installation requirements == | ||
| Line 40: | Line 109: | ||
|Solder-able Breadboard | |Solder-able Breadboard | ||
| | |2 | ||
|Core Electronics | |Core Electronics | ||
|SKU: PRT-12070 | |SKU: PRT-12070 | ||
| Line 62: | Line 131: | ||
|$35.95 | |$35.95 | ||
|Used to program LoPy4 | |Used to program LoPy4 | ||
|- | |||
|'''Arduino MKR Zero''' | |||
|1 | |||
|Core Electronics | |||
|SKU: ABX00012 | |||
|https://core-electronics.com.au/arduino-mkr-zero-i2s-bus-sd-for-sound-music-digital-audio-data.html | |||
|$45.00 | |||
|Used to measure soil moisture | |||
|- | |- | ||
|DHT22 Temperature and Relative Humidity Sensor Module | |DHT22 Temperature and Relative Humidity Sensor Module | ||
| Line 78: | Line 155: | ||
|$18.80 | |$18.80 | ||
|1-Wire interface library | |1-Wire interface library | ||
|- | |||
|'''Soil moisture sensor (calibrated) - Chameleon card 3 sensor pack''' | |||
|1 | |||
|ViaShopCSIRO | |||
|3 sensor pack | |||
|https://viashop.csiro.au/chameleon-card-six-sensor-pack | |||
|$49.00 | |||
|Soil moisture sensor | |||
|- | |- | ||
|Electrolytic Decoupling Capacitors - 100uF/25V | |Electrolytic Decoupling Capacitors - 100uF/25V | ||
| Line 183: | Line 268: | ||
|Attached to LoPy4 | |Attached to LoPy4 | ||
|- | |- | ||
|Through Hole Resistor, 10 kohm, CMF, 500 mW, ± 0.1%, Axial Leaded, 250 V | |'''Through Hole Resistor, 10 kohm, CMF, 500 mW, ± 0.1%, Axial Leaded, 250 V''' | ||
| | |4 | ||
|Element-14 | |Element-14 | ||
|Order code 3596907 | |Order code 3596907 | ||
| Line 209: | Line 294: | ||
== Circuit schematic == | == Circuit schematic == | ||
* File - Fritzing - Soil moisture sensor Cham.fzz | |||
[[File:Screenshot 2023-06-19 at 7.42.11 am.png | 900px]] | |||
== Photos of completed circuit board == | == Photos of completed circuit board == | ||
| Line 231: | Line 319: | ||
[[File:Screen Shot 2022-01-02 at 5.18.58 pm.png]] | [[File:Screen Shot 2022-01-02 at 5.18.58 pm.png]] | ||
== Electronic circuit construction == | == Electronic circuit construction for LoPy4 == | ||
=== Pycom LoPy4 === | === Pycom LoPy4 === | ||
* LED on LoPy4 nearest top end of board. | * LED on LoPy4 nearest top end of board. | ||
* Cut female header pins to 14 pin length (x2) | * Cut female header pins to 14 pin length (x2) | ||
* Solder from pins '''C10 to C23''' and from pins '''H10 to H23''' | * Solder from pins '''C10 to C23''' and from pins '''H10 to H23''' | ||
* VIN (3.5-4.2V direct from battery rail) from positive rail nearest to J to pin ''' | * VIN (3.5-4.2V direct from battery rail) from positive rail nearest to J to pin '''J10''' | ||
* GND from negative rail nearest to J to pin '''J11''' | * GND from negative rail nearest to J to pin '''J11''' | ||
* 3.3V supply from pin '''I12''' | |||
=== Voltage divider circuit === | === Voltage divider circuit === | ||
| Line 256: | Line 345: | ||
* 10k ohm resistor (low precision) '''D30 to E29''' | * 10k ohm resistor (low precision) '''D30 to E29''' | ||
* GND connection from '''E27''' to negative rail nearest to J | * GND connection from '''E27''' to negative rail nearest to J | ||
* DATA connection from '''D29 to | * DATA connection from '''D29 to D21''' | ||
* VCC connection from '''E30 to | * VCC connection from '''E30 to G4''' | ||
=== Pololu 5V Step-Up Voltage Regulator U1V11F5 === | === Pololu 5V Step-Up Voltage Regulator U1V11F5 === | ||
* Cut female header pins to 4 pin length | * Cut female header pins to 4 pin length | ||
* Solder from pins ''' | * Solder from pins '''H1 to H4''' | ||
* Solder right angle male header pins to Regulator so board stands upright (supplied with regulator) | * Solder right angle male header pins to Regulator so board stands upright (supplied with regulator) | ||
** ''' | ** '''H1''' to shutdown (SHDN) | ||
** ''' | ** '''H2''' to input voltage (VIN) - from battery | ||
** ''' | ** '''H3''' to ground (GND) | ||
** ''' | ** '''H4''' to output voltage (VOUT) - 5V regulated | ||
* 10k ohm pull-down resistor (low precision) '''J1''' to negative rail nearest to J | * 10k ohm pull-down resistor (low precision) '''J1''' to negative rail nearest to J | ||
* GND connection from '''J3''' to negative rail nearest to J | * GND connection from '''J3''' to negative rail nearest to J | ||
* Electrolytic Decoupling Capacitor positive terminal ''' | * Electrolytic Decoupling Capacitor positive terminal '''J2''' | ||
* Electrolytic Decoupling Capacitor negative terminal ''' | * Electrolytic Decoupling Capacitor negative terminal '''J3''' | ||
* VIN (3.3V from LoPy4) from ''' | * VIN (3.3V from LoPy4) from '''I12 to I2''' | ||
* SHDN (shutdown) pin connected from '''E20 to F1''' | |||
=== DS18B20 external temperature sensor === | === DS18B20 external temperature sensor === | ||
* Cut female header pins to 3 pin length | * Cut female header pins to 3 pin length | ||
* Solder from pins ''' | * Solder from pins '''D1 to D3''' | ||
** ''' | ** '''D1''' to DATA | ||
** ''' | ** '''D2''' to VCC | ||
** ''' | ** '''D3''' to GND | ||
* Solder right angle male header pins to temperature sensor leads | * Solder right angle male header pins to temperature sensor leads | ||
* GND connection from pin ''' | * GND connection from pin '''A3''' to negative rail nearest to A | ||
* DATA connection from pin ''' | ** negative rail connected to '''D27''' | ||
* DATA connection from pin '''E1 to E23''' | |||
* VCC (5V regulated supply) connection from pin '''E2 to I4''' | * VCC (5V regulated supply) connection from pin '''E2 to I4''' | ||
* 10k ohm pull-up resistor (low precision) from pin '''B1 to | ** 5V rail connected to '''F4''' | ||
* 10k ohm pull-up resistor (low precision) from pin '''B1 to C2''' | |||
=== UART connector === | |||
* UART connector to receive soil moisture sensor data from Arduino MKR Zero | |||
* UART connection on LoPy4 from pin '''A15 to A5''' | |||
=== Underboard connector pins for LoPy4 board === | |||
* two-pin connector (+ and - rails) on rail nearest to A | |||
** one close to pin '''A1''' | |||
** second close to pin '''A19/A20''' | |||
* two-pin connector (both within - rail) on rail nearest to J | |||
** one close to pin '''J7''' | |||
** second close to pin '''J26''' | |||
* UART connector pin '''B5 and C5''' to receive soil moisture sensor data from Arduino | |||
== Electronic circuit construction for Arduino MKR Zero == | |||
=== Arduino MKR Zero === | |||
* LED on LoPy4 nearest top end of board. | |||
* Cut female header pins to 14 pin length (x2) | |||
* Solder from pins '''I8 to I21''' and from pins '''C8 to C21''' | |||
* VIN (3.5-4.2V direct from battery rail) from positive rail nearest to A to pin '''A9''' | |||
* GND from negative rail nearest to A to pin '''A11''' | |||
* UART transmission from pin '''B13 to A5''' | |||
=== Soil moisture sensor === | |||
* 10k ohm high precision resistor (0.1%) from negative rail nearest to J to pin '''J10''' | |||
* 10k ohm high precision resistor (0.1%) from negative rail nearest to J to pin '''J11''' | |||
* Soil moisture sensor connection lead '''F10''' to external plug | |||
* Soil moisture sensor connection lead '''F11''' to external plug | |||
* GND bridging lead from negative rail (close to pin J4) to opposite negative rail (close to pin A4) | |||
=== Underboard connector pins for Arduino board === | |||
* two-pin connector (+ and - rails) on rail nearest to A | |||
** one close to pin '''A1''' | |||
** second close to pin '''A19/A20''' | |||
* two-pin connector (both within - rail) on rail nearest to J | |||
** one close to pin '''J7''' | |||
** second close to pin '''J26''' | |||
* UART connector pin '''B5 and C5''' to receive soil moisture sensor data from Arduino | |||
== MicroPython code for Pycom LoPy4 microcontroller == | == MicroPython code for Pycom LoPy4 microcontroller == | ||
| Line 331: | Line 467: | ||
# main.py | # main.py | ||
#!/usr/bin/env python | |||
import binascii #module that makes conversion between binary and Ascii | import binascii #module that makes conversion between binary and Ascii | ||
import config | import config | ||
from dth import DTH | from dth import DTH # internal temp/humidity sensor library | ||
import gc # garbage collection | import gc # garbage collection | ||
from lora import LoraAU915 | from lora import LoraAU915 | ||
| Line 340: | Line 477: | ||
from machine import Pin | from machine import Pin | ||
from machine import ADC | from machine import ADC | ||
from onewire import DS18X20 | from machine import UART # UART serial communcation between LoPy and Arduino MKR | ||
from onewire import OneWire | from onewire import DS18X20 # external temp sensor library | ||
import pycom | from onewire import OneWire # library to communicate with external temp sensor | ||
import pycom # to control led lights on LoPy | |||
import network | import network | ||
import socket | import socket # for LoRa communcations | ||
import time | import time | ||
import ustruct | import ustruct | ||
| Line 354: | Line 492: | ||
# power up sensors | # power up sensors | ||
sensor_power = Pin(config.POWER_PIN, mode=Pin.OUT) # power for | sensor_power = Pin(config.POWER_PIN, mode=Pin.OUT) # power for DHT22 | ||
sensor_power.value(1) # power up | sensor_power.value(1) # power up | ||
time.sleep(2) # wait 2 seconds to stabilise | time.sleep(2) # wait 2 seconds to stabilise | ||
| Line 371: | Line 509: | ||
s = LoraAU915.setupLora() | s = LoraAU915.setupLora() | ||
# | #DS18B20 external temperature sensor | ||
ow = OneWire(Pin('P12')) | |||
temp = DS18X20(ow) | |||
while True: | while True: | ||
sensor_power.value(1) # power up | sensor_power.value(1) # power up all sensors | ||
time.sleep(1) | time.sleep(1) | ||
pycom.rgbled(config.ORANGE) | pycom.rgbled(config.ORANGE) | ||
| Line 384: | Line 523: | ||
# TEMPERATURE AND HUMIDITY OF AMBIENT AIR | # TEMPERATURE AND HUMIDITY OF AMBIENT AIR | ||
j=0 | j = 0 | ||
DHT_temperature = 0.0 | |||
DHT_humidity = 0.0 | |||
th = DTH(config.DHT22_DATA,1) | th = DTH(config.DHT22_DATA,1) | ||
time.sleep(1) | time.sleep(1) | ||
| Line 391: | Line 532: | ||
if result.temperature != 0: | if result.temperature != 0: | ||
print("temperature is not zero - breaking from while loop") | print("temperature is not zero - breaking from while loop") | ||
pycom.rgbled(config.GREEN) | |||
time.sleep(1) | |||
pycom.rgbled(config.OFF) | |||
DHT_temperature = result.temperature | |||
DHT_humidity = result.humidity | |||
break | break | ||
time.sleep(1) | time.sleep(1) | ||
| Line 396: | Line 542: | ||
print("Error code %d" % result.error_code) | print("Error code %d" % result.error_code) | ||
print(j) | print(j) | ||
print("Temperature = = %.2f C" % | print("Temperature = = %.2f C" % DHT_temperature) | ||
print("Humidity = %.2f %%" % | print("Humidity = %.2f %%" % DHT_humidity) | ||
# BATTERY | # BATTERY | ||
| Line 410: | Line 556: | ||
batt_volt = (meanbatt/j)*config.BATCOEFF #multiplcator coef to adjust the real value of the battery voltage | batt_volt = (meanbatt/j)*config.BATCOEFF #multiplcator coef to adjust the real value of the battery voltage | ||
print("Battery voltage = %.2f V" % batt_volt) | print("Battery voltage = %.2f V" % batt_volt) | ||
pycom.rgbled(config.ORANGE) | |||
time.sleep(1) | |||
pycom.rgbled(config.OFF) | |||
#DS18B20 TEMPERATURE SENSOR | #DS18B20 TEMPERATURE SENSOR | ||
| Line 419: | Line 569: | ||
#https://www.circuitbasics.com/raspberry-pi-ds18b20-temperature-sensor-tutorial/ | #https://www.circuitbasics.com/raspberry-pi-ds18b20-temperature-sensor-tutorial/ | ||
temp.start_conversion() | |||
time.sleep(1) | time.sleep(1) | ||
soil_temp = temp.read_temp_async() | soil_temp = temp.read_temp_async() | ||
time.sleep(1) | time.sleep(1) | ||
if soil_temp != None: | if soil_temp != None: | ||
| Line 437: | Line 585: | ||
soil_temp = 0.0 | soil_temp = 0.0 | ||
# SOIL MOISTURE SENSOR | |||
# Temperature compensation formula for temperature probe | |||
#Formula: Ra = R0 * (1 + (Temp - 22) * 0 .018 ) with: | |||
#Ra: adjusted resistance, | |||
#R0: measured resistance | |||
#Temp in degree celsius, soil temperature | |||
# this uses the UART_1 default pins for TXD and RXD (``P3`` and ``P4``) | |||
j = 0 | |||
buffer = "" | |||
soil = 0 | |||
#soil_temp = 0 | |||
uart = UART(1, baudrate=9600) | |||
time.sleep_ms(500) # helps with initialisation | |||
while j<15: | |||
print(j) | |||
if uart.any() > 0: | |||
print(uart.any()) | |||
time.sleep(1) | |||
buffer = uart.readline() | |||
if "\n" in buffer: | |||
print(buffer) | |||
# may need buffer.decode().strip() to get soil moisture | |||
str_buffer = buffer.decode() | |||
string_list = str_buffer.split(",") | |||
#soil_temp = int(string_list[0]) | |||
soil = int(string_list[1]) | |||
pycom.rgbled(config.BLUE) | |||
time.sleep(1) | |||
pycom.rgbled(config.OFF) | |||
break | |||
time.sleep_ms(1000) | |||
j += 1 | |||
#print("Soil temperature is {}".format(soil_temp)) | |||
print("Soil moisture is {}".format(soil)) | |||
soil_t = int(soil) | |||
soil_temp_t = int(soil_temp*100) | soil_temp_t = int(soil_temp*100) | ||
batt_volt_t = int(batt_volt*100) | batt_volt_t = int(batt_volt*100) | ||
sens_temp = int(DHT_temperature*100) | |||
sens_temp = int( | sens_humi = int(DHT_humidity*100) | ||
sens_humi = int( | |||
# converting 2 byte values into single bytes for transmission | # converting 2 byte values into single bytes for transmission | ||
soil1 = int(soil_temp_t//256) | soil1 = int(soil_t//256) | ||
soil2 = int(soil_t%256) | |||
soil_temp1 = int(soil_temp_t//256) | |||
soil_temp2 = int(soil_temp_t%256) | |||
batt1 = int(batt_volt_t//256) | batt1 = int(batt_volt_t//256) | ||
batt2 = int(batt_volt_t%256) | batt2 = int(batt_volt_t%256) | ||
| Line 455: | Line 641: | ||
humi1 = int(sens_humi//256) | humi1 = int(sens_humi//256) | ||
humi2 = int(sens_humi%256) | humi2 = int(sens_humi%256) | ||
print("sending to TTN") | print("sending to TTN") | ||
s.setblocking(True) | s.setblocking(True) | ||
print(soil1,soil2,batt1,batt2,temp1,temp2,humi1,humi2) | print(soil1, soil2, soil_temp1, soil_temp2, batt1, batt2, temp1, temp2, humi1, humi2) | ||
s.send(bytes([soil1,soil2,batt1,batt2,temp1,temp2,humi1,humi2])) | s.send(bytes([soil1, soil2, soil_temp1, soil_temp2, batt1, batt2, temp1, temp2, humi1, humi2])) | ||
s.setblocking(False) | s.setblocking(False) | ||
pycom.rgbled(config.BLUE) | |||
time.sleep(1) | |||
pycom.rgbled(config.OFF) | |||
# Save LoRa settings before deep sleep | # Save LoRa settings before deep sleep | ||
| Line 466: | Line 656: | ||
print("going to sleep") | print("going to sleep") | ||
sensor_power.value(0) # power down | sensor_power.value(0) # power down | ||
time.sleep(2) # wait 2 seconds to stabilise | |||
# note that batt_volt has been multiplied by 100 for transmission | # note that batt_volt has been multiplied by 100 for transmission | ||
if (batt_volt > | # note increased min voltage to force deep sleep if running on batteries | ||
# bat voltage normally above 6V when connected to Atom | |||
print("batt_volt {}".format(batt_volt)) | |||
if (batt_volt > 6.0): | |||
# to minimise writes to onboard memory and for teaching | # to minimise writes to onboard memory and for teaching | ||
sleep_duration = 600 # sleep for 10 minutes | sleep_duration = 600 # sleep for 10 minutes | ||
| Line 515: | Line 709: | ||
=== config.py === | === config.py === | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
######################## | ######################## | ||
# | # CONFIG FILE !!!! | ||
######################## | ######################## | ||
# PINs | # PINs | ||
POWER_PIN = 'P9' # pin that powers enable pin on 5V Pololu regulator | POWER_PIN = 'P9' # pin that powers enable pin on 5V Pololu regulator | ||
DHT22_DATA = 'P10' # pin that receives data from internal temp/humidity | DHT22_DATA = 'P10' # pin that receives data from internal temp/humidity sensor | ||
VOLTMETER='P20' # pin connected to voltage divider | VOLTMETER='P20' # pin connected to voltage divider | ||
| Line 673: | Line 865: | ||
# create an OTAA authentication parameters, change them to the provided credentials | # create an OTAA authentication parameters, change them to the provided credentials | ||
# whi- | |||
app_eui = binascii.unhexlify(' | # whi-soil-3 | ||
app_key = binascii.unhexlify(' | app_eui = binascii.unhexlify('your_device_app_eui') | ||
app_key = binascii.unhexlify('your_device_app_key') | |||
#Limit channels for AU915 | #Limit channels for AU915 | ||
| Line 968: | Line 1,161: | ||
else: | else: | ||
assert False | assert False | ||
</syntaxhighlight> | |||
== Arduino code for MKR Zero == | |||
* There are several different code segments used on the Pycom LoPy4 microcontroller to monitor temperature. | |||
** '''MKR_soil_1.ino''' - main Arduino program | |||
** '''chameleon.h''' - soil moisture sensor library (header file) | |||
** '''DS18B20.h''' - external temperature sensor library (header file) | |||
=== MKR_soil_1.ino === | |||
<syntaxhighlight lang="c"> | |||
/* | |||
* PROGRAM TO RECEIVE DATA FROM a chameleon soil moisture sensor (developed by CSIRO): | |||
* https://viashop.csiro.au/chameleon-card-six-sensor-pack | |||
* And sent it online through LoRaWAN using a MKR 1300 board | |||
* Mind4Stormwater project: http://mind4stormwater.org, , feel free to use, reuse, modify, share!! | |||
* University of Melbourne / INSA Lyon | |||
* Frédéric Cherqui - fcherqui@gmail.com | |||
----- last version 19/02/2020 ---- | |||
Arduino MKR boards Serial pins 13(RX), 14(TX) | |||
* | |||
*/ | |||
float temperature; //soil temperature from the DS18B20 | |||
int temp_int = 0; | |||
String temp_str = "0"; | |||
float resistance; // resistance measured (to be converted into soil moisture or water content) | |||
int resist_int = 0; | |||
String resist_str = ""; | |||
#include "chameleon.h" //all functions related to soil moisture measure | |||
#include "DS18B20.h" //all functions related to temperature measure | |||
void setup() { | |||
Serial.begin(9600); // initialize serial communications and wait for port to open | |||
Serial1.begin(9600); | |||
//Tempsensor.begin(); // Start up the Dallas temperature sensor | |||
} | |||
void loop() { | |||
resistance = medianValue(); | |||
resist_int = int(resistance); | |||
resist_str = String(resist_int); | |||
//temperature = readTemp(); | |||
//temp_int = int(temperature * 100); | |||
//temp_str = String(temp_int); | |||
// Send Serial message to Pycom LoPy | |||
Serial1.println(temp_str + "," + resist_str); | |||
} | |||
</syntaxhighlight> | |||
=== chameleon.h === | |||
<syntaxhighlight lang="c"> | |||
const int sensorXAnalogPin = A2; | |||
const int sensorYAnalogPin = A1; | |||
const float knownResistor = 10.0; // Constant value of known resistor in k Ohms | |||
int nsamples = 20; // take 20 samples to keep the median | |||
#define stepwavedelayMicro 250 // microsecond: pulse width of Vcc uC applied to sensor, reading voltage at end of pulse | |||
float ReadSensor() { | |||
int sensorVoltageX, sensorVoltageY; // Measured sensor voltage from 0 to 1023 | |||
float sensorVoltage; // Average of sensorVoltageX, sensorVoltageY | |||
//Forward current through sensor | |||
pinMode(sensorYAnalogPin, INPUT); //Set the pin as drain | |||
pinMode(sensorXAnalogPin, OUTPUT); | |||
digitalWrite(sensorXAnalogPin, HIGH); // set the voltage supply on | |||
sensorVoltageY = analogRead(sensorYAnalogPin); // read the sensor voltage takes 125us | |||
Serial.println("sensorY "+String(sensorVoltageY)); | |||
digitalWrite(sensorXAnalogPin, LOW); // set the voltage supply off | |||
delayMicroseconds(stepwavedelayMicro); //small delay before reversing polarity (mandatory according to Matt!) | |||
//Reverse current through sensor | |||
pinMode(sensorXAnalogPin, INPUT); //Set the pin as drain | |||
pinMode(sensorYAnalogPin, OUTPUT); | |||
digitalWrite(sensorYAnalogPin, HIGH); // set the voltage supply on | |||
sensorVoltageX = analogRead(sensorXAnalogPin); // read the sensor voltage takes 125us | |||
Serial.println("sensorX "+String(sensorVoltageX)); | |||
digitalWrite(sensorYAnalogPin, LOW); // set the voltage supply off | |||
sensorVoltage = (sensorVoltageY + sensorVoltageX) / 2; | |||
delayMicroseconds(stepwavedelayMicro); //small delay before reversing polarity (mandatory according to Matt!) | |||
//Calculate resistance | |||
float value= float(knownResistor) * ( 1023 - sensorVoltage ) / sensorVoltage; | |||
value = value * 1000; | |||
if (value>32000.0) { | |||
value = 32000.00; | |||
} | |||
Serial.print("---resistance: "+String(value)+" kOhms\n"); | |||
return value; | |||
} | |||
#include <RunningMedian.h> | |||
float medianValue() { //code to take the median value of n samples | |||
Serial.println("\n*** MEASURING ***"); | |||
float value; | |||
//Create an instance of the RunningMedian class with the number of samples to use | |||
RunningMedian samples = RunningMedian(nsamples); | |||
for(int i= 0; i<nsamples; i++){ | |||
value = ReadSensor(); //to measure the resistance | |||
samples.add(value); | |||
delay(500); | |||
} | |||
//Return the median value | |||
float finalValue=samples.getMedian(); | |||
Serial.println("*** MEDIAN VALUE ="+String(finalValue)); | |||
return finalValue; | |||
} | |||
</syntaxhighlight> | |||
=== DS18B20.h === | |||
<syntaxhighlight lang="c"> | |||
/* | |||
* Dallas Scientific temperature sensor | |||
* DS18B20 | |||
* code based on: https://create.arduino.cc/projecthub/TheGadgetBoy/ds18b20-digital-temperature-sensor-and-arduino-9cc806 | |||
* !! You will need a pull-up resistor of about 5 to 10 KOhm between the 1-Wire data line and your 5V power. | |||
*/ | |||
// First we include the libraries | |||
#include <OneWire.h> //https://www.pjrc.com/teensy/td_libs_OneWire.html (Arduino --> Manage Libraries --> OneWire by Jim Studt... version 2.3.5) | |||
#include <DallasTemperature.h> //https://github.com/milesburton/Arduino-Temperature-Control-Library (Arduino --> Manage Libraries --> DallasTemperature by Miles Burton... version 3.8.0 | |||
#define ONE_WIRE_BUS 3 // Data wire is plugged into pin 3 on the Arduino | |||
#define ONE_WIRE_POWER 1 // Power wire is plugged into pin 1 on the Arduino | |||
OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices, (not just Maxim/Dallas temperature ICs) | |||
DallasTemperature Tempsensor(&oneWire); // Pass our oneWire reference to Dallas Temperature. | |||
float readTemp() { | |||
Serial.println("\n*** MEASURING TEMPERATURE ***"); | |||
pinMode(ONE_WIRE_POWER, OUTPUT); | |||
digitalWrite(ONE_WIRE_POWER, HIGH); // set the voltage supply on | |||
delay(1000); | |||
Tempsensor.requestTemperatures(); // Send the command to get temperature readings | |||
Serial.print("---Temperature is: "); | |||
float temp=Tempsensor.getTempCByIndex(0); // Why "byIndex"? You can have more than one DS18B20 on the same bus. 0 refers to the first IC on the wire | |||
Serial.println(temp); | |||
if (temp==-127.00) { | |||
Serial.println("error!"); | |||
return 0; | |||
} else { | |||
Serial.println(temp); | |||
return temp; | |||
} | |||
digitalWrite(ONE_WIRE_POWER, LOW); // set the voltage supply off | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Latest revision as of 23:26, 23 June 2023
Soil moisture sensor
Authors
For more information contact Frederic Cherqui, Adam Simankowicz or Edmond Lascaris
Applications
Soil moisture of trees, garden beds and agriculture. Sensors originally deployed in Africa to educate farmers about irrigation of food crops
How the Soil moisture sensor works
- Soil moisture sensor circuit explained by Matthew Driver
Components
- Arduino MKR Zero microcontroller using two analog pins with 10 bit analog to digital converters (ADC).
- Two 10K 0.1% precision resistors
- Chameleon soil moisture sensor
Power supply voltage (Vcc)
- This design is independent of voltage.
- In this circuit the power will be supplied via a Pololu 5V step-up regulated supply.
Analog pins
- The analog pins on the Arduino microcontroller are dual purpose.
- They can measure a voltage between 0 and Vcc (when their mode is set as an analog INPUT)
- They can also but can also output Vcc (3.3 or 5V) or drain to ground (when their mode is set to digital OUTPUT) * Both of these states are set using the pinmode function in the Arduino code.
States
- To measure the soil moisture the sensor circuit is placed into three different states:
- Forward current
- Reverse current (the flow of electrons is reversed from the perspective of the sensor)
- No current
Schematic
- A schematic of the soil moisture sensor circuit is shown below.
- Pin X and Pin Y are the two analog input pins on the Arduino microcontroller.
Excitation and and measuring (states applied)
Forward current flow
- To begin the measurement a Forward current is applied to the sensor
- The Forward current state is applied for 250us (microseconds) by setting the pins to:
- pinMode(Pin_Y,INPUT)
- pinMode(Pin_X,OUTPUT)
- digitalWrite(Pin_X, HIGH)
- The voltage is then measured at pin Y and stored in a variable.
No current flow
- A No current state is then applied for 250us by setting the pins to:
- digitalWrite(Pin X, LOW)
- The result is all pins are at ground so no current flows.
- Note that the INPUT pin acts as a drain, which is equivalent to ground.
Reverse current flow
- Then Reverse current state is then applied for 250us (microseconds) by setting the pins to:
- pinMode(Pin_X,INPUT)
- pinMode(Pin_Y,OUTPUT)
- digitalWrite(Pin_Y, HIGH)
- The voltage is then measured at pin X and stored in variable.
Sensor resistance calculation
- The Arduino convert the analog input to a digital value between 0 and 1023 where 1023 is the Vcc voltage (5V), *
- Sensor Resistance = Rs = 10K * (1023 - Vs) / Vs
- where Vs is the average of forward and reverse current values
- Temperature will change the measured value
- A simple formula allows for soil temperature changes to be corrected.
- Formula: Ra = R0 * (1 + (Temp - 22) * 0 .018 )
- Lastly, the soil moisture resistance is converted to soil moisture tension and expressed in kPa pressure units.
- This is a measure of how much pressure plants need to apply to extract moisture from soil.
- The relationship is shown in the graph below.
Interpreting Soil Moisture sensor values
- The Soil Moisture Sensor measures how hard it is for plants to draw water from soil.
- This soil moisture sensor measures resistance and then converts this value to soil moisture tension in kPa.
- This higher the soil moisture tension (kPa), the harder it is for plants to extract moisture from the soil.
- Soil moisture tension from 20 - 30 kPa signifies wet soil - ideal for broccoli, celery, lettuce and onion
- Soil moisture tensions from 30 - 45 kPa is moist soil - ideal for beans, cabbage, carrot, capsicum, corn, cucumber, eggplant, melons, potato and tomato.
- Soil moisture tenions from 45 - 60 kPa is dry soil - ideal for beet, peas, sweet potato and pumpkin.
Software installation requirements
- Atom installed on PC or Mac with Pymakr package installed
- Arduino IDE installed
Parts list
| Item description | Number | Supplier | Part number | URL | Cost | Notes |
|---|---|---|---|---|---|---|
| Solder-able Breadboard | 2 | Core Electronics | SKU: PRT-12070 | https://core-electronics.com.au/solder-able-breadboard.html | $8.20 | 55mm x 80mm |
| Pycom LoPy4 (LoRaWAN) | 1 | Core Electronics | SKU: CE05399 | https://core-electronics.com.au/pycom-lopy4.html | $68.95 | Microcontroller with LoRa |
| Pycom Expansion Board 3.1 | 1 | Core Electronics | SKU: CE05545 | https://core-electronics.com.au/pycom-expansion-board-3-1.html | $35.95 | Used to program LoPy4 |
| Arduino MKR Zero | 1 | Core Electronics | SKU: ABX00012 | https://core-electronics.com.au/arduino-mkr-zero-i2s-bus-sd-for-sound-music-digital-audio-data.html | $45.00 | Used to measure soil moisture |
| DHT22 Temperature and Relative Humidity Sensor Module | 1 | Core Electronics | SKU: 018-DHT22 | https://core-electronics.com.au/dht22-temperature-and-relative-humidity-sensor-module.html | $13.75 | Temp precision +/-0.5degC |
| Temperature Sensor - Waterproof (DS18B20) | 1 | Core Electronics | SKU: SEN-11050 | https://core-electronics.com.au/temperature-sensor-waterproof-ds18b20.html | $18.80 | 1-Wire interface library |
| Soil moisture sensor (calibrated) - Chameleon card 3 sensor pack | 1 | ViaShopCSIRO | 3 sensor pack | https://viashop.csiro.au/chameleon-card-six-sensor-pack | $49.00 | Soil moisture sensor |
| Electrolytic Decoupling Capacitors - 100uF/25V | 1 | Core Electronics | SKU: COM-00096 | https://core-electronics.com.au/electrolytic-decoupling-capacitors-100uf-25v.html | $0.29 | Used to block high voltage spikes |
| Pololu 5V Step-Up Voltage Regulator U1V11F5 | 1 | Core Electronics | SKU: POLOLU-2562 | https://core-electronics.com.au/pololu-5v-step-up-voltage-regulator-u1v11f5.html | $11.65 | 5V supply for sensors |
| Resistor 10K Ohm 1/4 Watt PTH - 20 pack (Thick Leads) | 1 | Core Electronics | SKU: PRT-14491 | https://core-electronics.com.au/resistor-10k-ohm-1-4-watt-pth-20-pack-thick-leads.html | $2.05 | Pack of 20 resistors |
| Hook-Up Wire - Assortment (Solid Core, 22 AWG) | 1 | Core Electronics | SKU: PRT-11367 | https://core-electronics.com.au/hook-up-wire-assortment-solid-core-22-awg.html | $40.95 | Solid core for wiring circuit |
| 36-pin 0.1 Female header - pack of 5! | 1 | Core Electronics | SKU: ADA598 | https://core-electronics.com.au/20-pin-0-1-female-header-pack-of-5.html | $6.30 | Used for plugin components |
| 10 Pcs 40 Pin Headers - Straight | 1 | Core Electronics | SKU: FIT0084 | https://core-electronics.com.au/10-pcs-40-pin-headers-straight.html | $4.70 | Used to make sensors plugin |
| Holman 100mm PVC DWV Push On Cap | 1 | Bunnings | DWVF0198 | https://www.bunnings.com.au/holman-100mm-pvc-dwv-push-on-cap_p4770360 | $3.30 | End caps for sensor housing |
| Holman 100mm x 3m PVC DWV Pipe | 1 | Bunnings | DWV1003 | https://www.bunnings.com.au/holman-100mm-x-3m-pvc-dwv-pipe_p4770345 | $33.00 | Cut in store for easier transport |
| Selleys 310g Clear Glass Silicone Sealant | 1 | Bunnings | Model no 9300697100382 | https://www.bunnings.com.au/selleys-310g-clear-glass-silicone-sealant_p1234951 | $15.00 | Used with caulking gun |
| 18650 2600mAh Li-ion Protected Battery | 1 | Jaycar | CAT.NO: SB2299 | https://www.jaycar.com.au/18650-2600mah-li-ion-protected-battery/p/SB2299 | $21.95 | Inbuilt safety protection |
| Single 18650 Battery Holder | 1 | Jaycar | CAT.NO: PH9205 | https://www.jaycar.com.au/single-18650-battery-holder/p/PH9205 | $3.25 | Battery holder |
| Dual-Channel Li-ion / Ni-MH Battery Charger | 1 | Jaycar | CAT.NO: MB3635 | https://www.jaycar.com.au/dual-channel-li-ion-ni-mh-battery-charger/p/MB3635 | $39.95 | Only one required for all projects |
| MOLEX 211140-0100.. 868/915MHZ FLEXIBLE ANTENNA100MM CABLE | 1 | Element-14 | Order code 3225220 | https://au.element14.com/molex/211140-0100/868-915mhz-flexible-antenna100mm/dp/3225220?ost=molex++211140-0100..++868%2F915mhz+flexible+antenna100mm+cable | $2.84 | Attached to LoPy4 |
| Through Hole Resistor, 10 kohm, CMF, 500 mW, ± 0.1%, Axial Leaded, 250 V | 4 | Element-14 | Order code 3596907 | https://au.element14.com/vishay/cmf5510k000beea/res-10k-0-5w-axial-metal-film/dp/3596907?ost=vishay++cmf5510k000beea | $1.56 | High precision resistor for voltage divider |
| P9406 • 6 Pin 5A Screw-On Female Line IP66 Waterproof Socket | 1 | Altronics | Code: P9406 | https://www.altronics.com.au/p/p9406-amphenol-ltw-6-pin-5a-screw-on-female-line-ip66-waterproof-socket/ | $23.50 | Connected to sensor housing |
| P9416 • 6 Pin 5A Screw-On Male Chassis IP67 Waterproof Plug | 1 | Altronics | Code P9416 | https://www.altronics.com.au/p/p9416-amphenol-ltw-6-pin-5a-screw-on-male-chassis-ip67-waterproof-plug/ | $17.25 | Connected to sensor |
Circuit schematic
- File - Fritzing - Soil moisture sensor Cham.fzz
Photos of completed circuit board
- Aerial view of Pycom LoPy4 board showing battery supply, soil moisture sensor and external temperature sensor.
- Close up view of Pycom LoPy4 board.
- Close up view of sensor board without LoPy4 microcontroller.
- Close up view of Arduino MKR Zero microcontroller.
- Close up view of Arduino MKR Zero sensor board without Arduino.
- Back of Pycom LoPy4 board (top) and Arduino MKR Zero (bottom).
- Note the location and orientation of locator pins.
Electronic circuit construction for LoPy4
Pycom LoPy4
- LED on LoPy4 nearest top end of board.
- Cut female header pins to 14 pin length (x2)
- Solder from pins C10 to C23 and from pins H10 to H23
- VIN (3.5-4.2V direct from battery rail) from positive rail nearest to J to pin J10
- GND from negative rail nearest to J to pin J11
- 3.3V supply from pin I12
Voltage divider circuit
- 10k ohm high precision resistors x 2
- I16 to positive rail nearest to J
- J16 to negative rail nearest to J
Power supply
- Use either 3 x 1.2V Panasonic eneloop Ni-MH batteries
- Or use 1 x 3.7V Li-Ion battery
- Attach positive and negative rails nearest to J
DHT22 temperature and humidity sensor
- C30 to Pin 1 VCC (Power Supply)
- C29 to Pin 2 DATA (The data signal)
- C28 to Pin 3 NULL (Do not connect)
- C27 to Pin 4 GND (Ground)
- 10k ohm resistor (low precision) D30 to E29
- GND connection from E27 to negative rail nearest to J
- DATA connection from D29 to D21
- VCC connection from E30 to G4
Pololu 5V Step-Up Voltage Regulator U1V11F5
- Cut female header pins to 4 pin length
- Solder from pins H1 to H4
- Solder right angle male header pins to Regulator so board stands upright (supplied with regulator)
- H1 to shutdown (SHDN)
- H2 to input voltage (VIN) - from battery
- H3 to ground (GND)
- H4 to output voltage (VOUT) - 5V regulated
- 10k ohm pull-down resistor (low precision) J1 to negative rail nearest to J
- GND connection from J3 to negative rail nearest to J
- Electrolytic Decoupling Capacitor positive terminal J2
- Electrolytic Decoupling Capacitor negative terminal J3
- VIN (3.3V from LoPy4) from I12 to I2
- SHDN (shutdown) pin connected from E20 to F1
DS18B20 external temperature sensor
- Cut female header pins to 3 pin length
- Solder from pins D1 to D3
- D1 to DATA
- D2 to VCC
- D3 to GND
- Solder right angle male header pins to temperature sensor leads
- GND connection from pin A3 to negative rail nearest to A
- negative rail connected to D27
- DATA connection from pin E1 to E23
- VCC (5V regulated supply) connection from pin E2 to I4
- 5V rail connected to F4
- 10k ohm pull-up resistor (low precision) from pin B1 to C2
UART connector
- UART connector to receive soil moisture sensor data from Arduino MKR Zero
- UART connection on LoPy4 from pin A15 to A5
Underboard connector pins for LoPy4 board
- two-pin connector (+ and - rails) on rail nearest to A
- one close to pin A1
- second close to pin A19/A20
- two-pin connector (both within - rail) on rail nearest to J
- one close to pin J7
- second close to pin J26
- UART connector pin B5 and C5 to receive soil moisture sensor data from Arduino
Electronic circuit construction for Arduino MKR Zero
Arduino MKR Zero
- LED on LoPy4 nearest top end of board.
- Cut female header pins to 14 pin length (x2)
- Solder from pins I8 to I21 and from pins C8 to C21
- VIN (3.5-4.2V direct from battery rail) from positive rail nearest to A to pin A9
- GND from negative rail nearest to A to pin A11
- UART transmission from pin B13 to A5
Soil moisture sensor
- 10k ohm high precision resistor (0.1%) from negative rail nearest to J to pin J10
- 10k ohm high precision resistor (0.1%) from negative rail nearest to J to pin J11
- Soil moisture sensor connection lead F10 to external plug
- Soil moisture sensor connection lead F11 to external plug
- GND bridging lead from negative rail (close to pin J4) to opposite negative rail (close to pin A4)
Underboard connector pins for Arduino board
- two-pin connector (+ and - rails) on rail nearest to A
- one close to pin A1
- second close to pin A19/A20
- two-pin connector (both within - rail) on rail nearest to J
- one close to pin J7
- second close to pin J26
- UART connector pin B5 and C5 to receive soil moisture sensor data from Arduino
MicroPython code for Pycom LoPy4 microcontroller
- There are several different code segments used on the Pycom LoPy4 microcontroller to monitor temperature.
- boot.py - bootstrap program that runs first.
- main.py - main program where program code is contained
- pymakr.conf - configuration file for Atom and Pymakr package
- lib - directory that contains additional library files
- config.py - list of static variables
- dth.py - library for DHT22 internal temperature sensor
- lora.py - library for LoRa. Contains private keys
- onewire.py - onewire library to communicate with DS18B20 external temperature sensor
boot.py
# boot.py -- run on boot-up
import pycom
from machine import UART
import machine
import os
from network import Bluetooth
from network import WLAN
if pycom.heartbeat() == True:
pycom.heartbeat(False)
if pycom.wifi_on_boot() == True:
pycom.wifi_on_boot(False)
#wlan = WLAN()
#wlan.deinit()
bluetooth = Bluetooth()
bluetooth.deinit()
uart = UART(0, baudrate=115200)
os.dupterm(uart)
machine.main('main.py')
main.py
# main.py
#!/usr/bin/env python
import binascii #module that makes conversion between binary and Ascii
import config
from dth import DTH # internal temp/humidity sensor library
import gc # garbage collection
from lora import LoraAU915
import machine
from machine import Pin
from machine import ADC
from machine import UART # UART serial communcation between LoPy and Arduino MKR
from onewire import DS18X20 # external temp sensor library
from onewire import OneWire # library to communicate with external temp sensor
import pycom # to control led lights on LoPy
import network
import socket # for LoRa communcations
import time
import ustruct
import utime
#INIT EVERYTHING
sleep_duration = 600 # in seconds (10 minutes)
pycom.rgbled(config.GREEN)
# power up sensors
sensor_power = Pin(config.POWER_PIN, mode=Pin.OUT) # power for DHT22
sensor_power.value(1) # power up
time.sleep(2) # wait 2 seconds to stabilise
print("starting main")
pycom.rgbled(config.OFF)
# voltage divider setup to monitor battery voltage
adc = ADC()
time.sleep(2) # wait 2 seconds to stabilise
# garbage collection enabled
gc.enable()
# config LoRa communications
s = LoraAU915.setupLora()
#DS18B20 external temperature sensor
ow = OneWire(Pin('P12'))
temp = DS18X20(ow)
while True:
sensor_power.value(1) # power up all sensors
time.sleep(1)
pycom.rgbled(config.ORANGE)
time.sleep(1)
pycom.rgbled(config.OFF)
# TEMPERATURE AND HUMIDITY OF AMBIENT AIR
j = 0
DHT_temperature = 0.0
DHT_humidity = 0.0
th = DTH(config.DHT22_DATA,1)
time.sleep(1)
while j < 5:
result = th.read()
if result.temperature != 0:
print("temperature is not zero - breaking from while loop")
pycom.rgbled(config.GREEN)
time.sleep(1)
pycom.rgbled(config.OFF)
DHT_temperature = result.temperature
DHT_humidity = result.humidity
break
time.sleep(1)
j += 1
print("Error code %d" % result.error_code)
print(j)
print("Temperature = = %.2f C" % DHT_temperature)
print("Humidity = %.2f %%" % DHT_humidity)
# BATTERY
meanbatt=0
j=0
while j < 5:
utime.sleep(0.2)
batt = adc.channel(attn=3, pin=config.VOLTMETER)
meanbatt += batt.voltage()/1000
j += 1
batt_volt = (meanbatt/j)*config.BATCOEFF #multiplcator coef to adjust the real value of the battery voltage
print("Battery voltage = %.2f V" % batt_volt)
pycom.rgbled(config.ORANGE)
time.sleep(1)
pycom.rgbled(config.OFF)
#DS18B20 TEMPERATURE SENSOR
#https://docs.pycom.io/tutorials/hardware/owd/#app
#Also note - needed to add a 10k resistor pull up on the board
#Internal pull up resistor was not sufficient
#used this reference for Raspberry Pi circuit which includes 5k to 10k pull up resistor
#https://www.circuitbasics.com/raspberry-pi-ds18b20-temperature-sensor-tutorial/
temp.start_conversion()
time.sleep(1)
soil_temp = temp.read_temp_async()
time.sleep(1)
if soil_temp != None:
print("Soil temperature = %.2f C" % soil_temp)
else:
print("No data received from soil temp probe")
pycom.rgbled(config.RED)
time.sleep(1)
pycom.rgbled(config.OFF)
soil_temp = 0.0
# SOIL MOISTURE SENSOR
# Temperature compensation formula for temperature probe
#Formula: Ra = R0 * (1 + (Temp - 22) * 0 .018 ) with:
#Ra: adjusted resistance,
#R0: measured resistance
#Temp in degree celsius, soil temperature
# this uses the UART_1 default pins for TXD and RXD (``P3`` and ``P4``)
j = 0
buffer = ""
soil = 0
#soil_temp = 0
uart = UART(1, baudrate=9600)
time.sleep_ms(500) # helps with initialisation
while j<15:
print(j)
if uart.any() > 0:
print(uart.any())
time.sleep(1)
buffer = uart.readline()
if "\n" in buffer:
print(buffer)
# may need buffer.decode().strip() to get soil moisture
str_buffer = buffer.decode()
string_list = str_buffer.split(",")
#soil_temp = int(string_list[0])
soil = int(string_list[1])
pycom.rgbled(config.BLUE)
time.sleep(1)
pycom.rgbled(config.OFF)
break
time.sleep_ms(1000)
j += 1
#print("Soil temperature is {}".format(soil_temp))
print("Soil moisture is {}".format(soil))
soil_t = int(soil)
soil_temp_t = int(soil_temp*100)
batt_volt_t = int(batt_volt*100)
sens_temp = int(DHT_temperature*100)
sens_humi = int(DHT_humidity*100)
# converting 2 byte values into single bytes for transmission
soil1 = int(soil_t//256)
soil2 = int(soil_t%256)
soil_temp1 = int(soil_temp_t//256)
soil_temp2 = int(soil_temp_t%256)
batt1 = int(batt_volt_t//256)
batt2 = int(batt_volt_t%256)
temp1 = int(sens_temp//256)
temp2 = int(sens_temp%256)
humi1 = int(sens_humi//256)
humi2 = int(sens_humi%256)
print("sending to TTN")
s.setblocking(True)
print(soil1, soil2, soil_temp1, soil_temp2, batt1, batt2, temp1, temp2, humi1, humi2)
s.send(bytes([soil1, soil2, soil_temp1, soil_temp2, batt1, batt2, temp1, temp2, humi1, humi2]))
s.setblocking(False)
pycom.rgbled(config.BLUE)
time.sleep(1)
pycom.rgbled(config.OFF)
# Save LoRa settings before deep sleep
LoraAU915.saveLora()
print("going to sleep")
sensor_power.value(0) # power down
time.sleep(2) # wait 2 seconds to stabilise
# note that batt_volt has been multiplied by 100 for transmission
# note increased min voltage to force deep sleep if running on batteries
# bat voltage normally above 6V when connected to Atom
print("batt_volt {}".format(batt_volt))
if (batt_volt > 6.0):
# to minimise writes to onboard memory and for teaching
sleep_duration = 600 # sleep for 10 minutes
print("Time delay sleep only")
time.sleep(sleep_duration) # sleep in seconds
else:
sleep_duration = 3600 # sleep for 60 minutes
print("Sleep using deepsleep")
machine.deepsleep(1000 * sleep_duration) # deepsleep in ms
pymakr.conf
{
"address": "COM10",
"username": "micro",
"password": "python",
"sync_folder": "",
"sync_file_types": [
"py",
"txt",
"log",
"json",
"xml",
"html",
"js",
"css",
"mpy",
"pem",
"cet",
"crt",
"key"
],
"sync_all_file_types": false,
"open_on_start": true,
"safe_boot_on_upload": false,
"py_ignore": [],
"fast_upload": false
}
config.py
########################
# CONFIG FILE !!!!
########################
# PINs
POWER_PIN = 'P9' # pin that powers enable pin on 5V Pololu regulator
DHT22_DATA = 'P10' # pin that receives data from internal temp/humidity sensor
VOLTMETER='P20' # pin connected to voltage divider
BATCOEFF = 2.0 # to calculate the real voltage
# led colors
OFF = 0x000000
WHITE=0xFFFFCC
RED = 0xff0000
BLUE = 0x0000ff
GREEN = 0x00ff00
YELLOW = 0x7f7f00
PURPLE = 0x7f007f
ORANGE = 0xFF9900
dth.py
# dth.py
import time
import pycom
from machine import enable_irq, disable_irq, Pin
# DHT22 sensor pins - when sensor lying down
#1 - VCC - red wire Connect to 3.3 - 5V power. Sometime 3.3V power isn't enough in which case try 5V power.
#2 - Data out - white or yellow wire
#3 - Not connected
#4 - Ground - black wire
# Note - 10 Kohm resistor between VCC and the data pin
# current draw 1.5mA - pycom advise up to 6mA per pin
class DTHResult:
'DHT sensor result returned by DHT.read() method'
ERR_NO_ERROR = 0
ERR_MISSING_DATA = 1
ERR_CRC = 2
error_code = ERR_NO_ERROR
temperature = -1
humidity = -1
def __init__(self, error_code, temperature, humidity):
self.error_code = error_code
self.temperature = temperature
self.humidity = humidity
def is_valid(self):
return self.error_code == DTHResult.ERR_NO_ERROR
class DTH:
'DHT sensor (dht11, dht21,dht22) reader class for Pycom'
#__pin = Pin('P3', mode=Pin.OPEN_DRAIN)
__dhttype = 0
def __init__(self, pin, sensor=0):
self.__pin = Pin(pin, mode=Pin.OPEN_DRAIN)
self.__dhttype = sensor
self.__pin(1)
time.sleep(1.0)
def read(self):
# pull down to low
self.__send_and_sleep(0, 0.019)
data = pycom.pulses_get(self.__pin,100)
self.__pin.init(Pin.OPEN_DRAIN)
self.__pin(1)
#print(data)
bits = []
for a,b in data:
if a ==1 and 18 <= b <= 28:
bits.append(0)
if a ==1 and 65 <= b <= 75:
bits.append(1)
#print("longueur bits : %d " % len(bits))
if len(bits) != 40:
return DTHResult(DTHResult.ERR_MISSING_DATA, 0, 0)
#print(bits)
# we have the bits, calculate bytes
the_bytes = self.__bits_to_bytes(bits)
# calculate checksum and check
checksum = self.__calculate_checksum(the_bytes)
if the_bytes[4] != checksum:
return DTHResult(DTHResult.ERR_CRC, 0, 0)
# ok, we have valid data, return it
[int_rh, dec_rh, int_t, dec_t, csum] = the_bytes
if self.__dhttype==0: #dht11
rh = int_rh #dht11 20% ~ 90%
t = int_t #dht11 0..50°C
else: #dht21,dht22
rh = ((int_rh * 256) + dec_rh)/10
t = (((int_t & 0x7F) * 256) + dec_t)/10
if (int_t & 0x80) > 0:
t *= -1
return DTHResult(DTHResult.ERR_NO_ERROR, t, rh)
def __send_and_sleep(self, output, mysleep):
self.__pin(output)
time.sleep(mysleep)
def __bits_to_bytes(self, bits):
the_bytes = []
byte = 0
for i in range(0, len(bits)):
byte = byte << 1
if (bits[i]):
byte = byte | 1
else:
byte = byte | 0
if ((i + 1) % 8 == 0):
the_bytes.append(byte)
byte = 0
#print(the_bytes)
return the_bytes
def __calculate_checksum(self, the_bytes):
return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
lora.py
# lora.py
import config
import utime
import time
from network import LoRa
import socket
import binascii
import struct
import machine
class LoraAU915: #Australia AU915
# https://stackoverflow.com/questions/5690888/variable-scopes-in-python-classes
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915)
def setupLora():
#lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.AU915)
# Initialise LoRa in LORAWAN mode.
# create an OTAA authentication parameters, change them to the provided credentials
# whi-soil-3
app_eui = binascii.unhexlify('your_device_app_eui')
app_key = binascii.unhexlify('your_device_app_key')
#Limit channels for AU915
for i in range(0,8):
LoraAU915.lora.remove_channel(i)
for i in range(16,65):
LoraAU915.lora.remove_channel(i)
for i in range(66,72):
LoraAU915.lora.remove_channel(i)
# join a network using OTAA (Over the Air Activation)
#uncomment below to use LoRaWAN application provided dev_eui
#https://docs.pycom.io/tutorials/networks/lora/nvram/
new_connection = True
LoraAU915.lora.nvram_restore()
if(LoraAU915.lora.has_joined() == False):
print("LoRa memory restored but need to join LoRa")
LoraAU915.lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
else:
print("Memory restored and have rejoined LoRa with previous Dev Add")
new_connection = False
# wait until the module has joined the network
while not LoraAU915.lora.has_joined():
utime.sleep(1)
if utime.time() > 15:
print("possible timeout")
machine.reset()
pass
print('Joined')
# create a LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
if new_connection:
# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 0) # dr0 equiv to SF12
# make the socket non-blocking
s.setblocking(False)
print("LoRa connection set up")
utime.sleep_ms(5)
return s
def saveLora():
LoraAU915.lora.nvram_save()
print("LoRa setting saved to memory")
onewire.py
"""
OneWire library for MicroPython
"""
import time
import machine
class OneWire:
CMD_SEARCHROM = const(0xf0)
CMD_READROM = const(0x33)
CMD_MATCHROM = const(0x55)
CMD_SKIPROM = const(0xcc)
def __init__(self, pin):
self.pin = pin
self.pin.init(pin.OPEN_DRAIN, pin.PULL_UP)
def reset(self):
"""
Perform the onewire reset function.
Returns True if a device asserted a presence pulse, False otherwise.
"""
sleep_us = time.sleep_us
disable_irq = machine.disable_irq
enable_irq = machine.enable_irq
pin = self.pin
pin(0)
sleep_us(480)
i = disable_irq()
pin(1)
sleep_us(60)
status = not pin()
enable_irq(i)
sleep_us(420)
return status
def read_bit(self):
sleep_us = time.sleep_us
enable_irq = machine.enable_irq
pin = self.pin
pin(1) # half of the devices don't match CRC without this line
i = machine.disable_irq()
pin(0)
sleep_us(1)
pin(1)
sleep_us(1)
value = pin()
enable_irq(i)
sleep_us(40)
return value
def read_byte(self):
value = 0
for i in range(8):
value |= self.read_bit() << i
return value
def read_bytes(self, count):
buf = bytearray(count)
for i in range(count):
buf[i] = self.read_byte()
return buf
def write_bit(self, value):
sleep_us = time.sleep_us
pin = self.pin
i = machine.disable_irq()
pin(0)
sleep_us(1)
pin(value)
sleep_us(60)
pin(1)
sleep_us(1)
machine.enable_irq(i)
def write_byte(self, value):
for i in range(8):
self.write_bit(value & 1)
value >>= 1
def write_bytes(self, buf):
for b in buf:
self.write_byte(b)
def select_rom(self, rom):
"""
Select a specific device to talk to. Pass in rom as a bytearray (8 bytes).
"""
self.reset()
self.write_byte(CMD_MATCHROM)
self.write_bytes(rom)
def crc8(self, data):
"""
Compute CRC
"""
crc = 0
for i in range(len(data)):
byte = data[i]
for b in range(8):
fb_bit = (crc ^ byte) & 0x01
if fb_bit == 0x01:
crc = crc ^ 0x18
crc = (crc >> 1) & 0x7f
if fb_bit == 0x01:
crc = crc | 0x80
byte = byte >> 1
return crc
def scan(self):
"""
Return a list of ROMs for all attached devices.
Each ROM is returned as a bytes object of 8 bytes.
"""
devices = []
diff = 65
rom = False
for i in range(0xff):
rom, diff = self._search_rom(rom, diff)
if rom:
devices += [rom]
if diff == 0:
break
return devices
def _search_rom(self, l_rom, diff):
if not self.reset():
return None, 0
self.write_byte(CMD_SEARCHROM)
if not l_rom:
l_rom = bytearray(8)
rom = bytearray(8)
next_diff = 0
i = 64
for byte in range(8):
r_b = 0
for bit in range(8):
b = self.read_bit()
if self.read_bit():
if b: # there are no devices or there is an error on the bus
return None, 0
else:
if not b: # collision, two devices with different bit meaning
if diff > i or ((l_rom[byte] & (1 << bit)) and diff != i):
b = 1
next_diff = i
self.write_bit(b)
if b:
r_b |= 1 << bit
i -= 1
rom[byte] = r_b
return rom, next_diff
class DS18X20(object):
def __init__(self, onewire):
self.ow = onewire
self.roms = [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28]
self.fp = True
try:
1/1
except TypeError:
self.fp = False # floatingpoint not supported
def isbusy(self):
"""
Checks wether one of the DS18x20 devices on the bus is busy
performing a temperature convertion
"""
return not self.ow.read_bit()
def start_conversion(self, rom=None):
"""
Start the temp conversion on one DS18x20 device.
Pass the 8-byte bytes object with the ROM of the specific device you want to read.
If only one DS18x20 device is attached to the bus you may omit the rom parameter.
"""
if (rom==None) and (len(self.roms)>0):
rom=self.roms[0]
if rom!=None:
rom = rom or self.roms[0]
ow = self.ow
ow.reset()
ow.select_rom(rom)
ow.write_byte(0x44) # Convert Temp
def read_temp_async(self, rom=None):
"""
Read the temperature of one DS18x20 device if the convertion is complete,
otherwise return None.
"""
if self.isbusy():
return None
if (rom==None) and (len(self.roms)>0):
rom=self.roms[0]
if rom==None:
return None
else:
ow = self.ow
ow.reset()
ow.select_rom(rom)
ow.write_byte(0xbe) # Read scratch
data = ow.read_bytes(9)
return self.convert_temp(rom[0], data)
def convert_temp(self, rom0, data):
"""
Convert the raw temperature data into degrees celsius and return as a fixed point with 2 decimal places.
"""
temp_lsb = data[0]
temp_msb = data[1]
if rom0 == 0x10:
if temp_msb != 0:
# convert negative number
temp_read = temp_lsb >> 1 | 0x80 # truncate bit 0 by shifting, fill high bit with 1.
temp_read = -((~temp_read + 1) & 0xff) # now convert from two's complement
else:
temp_read = temp_lsb >> 1 # truncate bit 0 by shifting
count_remain = data[6]
count_per_c = data[7]
if self.fp:
return temp_read - 25 + (count_per_c - count_remain) / count_per_c
else:
return 100 * temp_read - 25 + (count_per_c - count_remain) // count_per_c
elif rom0 == 0x28:
temp = None
if self.fp:
temp = (temp_msb << 8 | temp_lsb) / 16
else:
temp = (temp_msb << 8 | temp_lsb) * 100 // 16
if (temp_msb & 0xf8) == 0xf8: # for negative temperature
temp -= 0x1000
return temp
else:
assert False
Arduino code for MKR Zero
- There are several different code segments used on the Pycom LoPy4 microcontroller to monitor temperature.
- MKR_soil_1.ino - main Arduino program
- chameleon.h - soil moisture sensor library (header file)
- DS18B20.h - external temperature sensor library (header file)
MKR_soil_1.ino
/*
* PROGRAM TO RECEIVE DATA FROM a chameleon soil moisture sensor (developed by CSIRO):
* https://viashop.csiro.au/chameleon-card-six-sensor-pack
* And sent it online through LoRaWAN using a MKR 1300 board
* Mind4Stormwater project: http://mind4stormwater.org, , feel free to use, reuse, modify, share!!
* University of Melbourne / INSA Lyon
* Frédéric Cherqui - fcherqui@gmail.com
----- last version 19/02/2020 ----
Arduino MKR boards Serial pins 13(RX), 14(TX)
*
*/
float temperature; //soil temperature from the DS18B20
int temp_int = 0;
String temp_str = "0";
float resistance; // resistance measured (to be converted into soil moisture or water content)
int resist_int = 0;
String resist_str = "";
#include "chameleon.h" //all functions related to soil moisture measure
#include "DS18B20.h" //all functions related to temperature measure
void setup() {
Serial.begin(9600); // initialize serial communications and wait for port to open
Serial1.begin(9600);
//Tempsensor.begin(); // Start up the Dallas temperature sensor
}
void loop() {
resistance = medianValue();
resist_int = int(resistance);
resist_str = String(resist_int);
//temperature = readTemp();
//temp_int = int(temperature * 100);
//temp_str = String(temp_int);
// Send Serial message to Pycom LoPy
Serial1.println(temp_str + "," + resist_str);
}
chameleon.h
const int sensorXAnalogPin = A2;
const int sensorYAnalogPin = A1;
const float knownResistor = 10.0; // Constant value of known resistor in k Ohms
int nsamples = 20; // take 20 samples to keep the median
#define stepwavedelayMicro 250 // microsecond: pulse width of Vcc uC applied to sensor, reading voltage at end of pulse
float ReadSensor() {
int sensorVoltageX, sensorVoltageY; // Measured sensor voltage from 0 to 1023
float sensorVoltage; // Average of sensorVoltageX, sensorVoltageY
//Forward current through sensor
pinMode(sensorYAnalogPin, INPUT); //Set the pin as drain
pinMode(sensorXAnalogPin, OUTPUT);
digitalWrite(sensorXAnalogPin, HIGH); // set the voltage supply on
sensorVoltageY = analogRead(sensorYAnalogPin); // read the sensor voltage takes 125us
Serial.println("sensorY "+String(sensorVoltageY));
digitalWrite(sensorXAnalogPin, LOW); // set the voltage supply off
delayMicroseconds(stepwavedelayMicro); //small delay before reversing polarity (mandatory according to Matt!)
//Reverse current through sensor
pinMode(sensorXAnalogPin, INPUT); //Set the pin as drain
pinMode(sensorYAnalogPin, OUTPUT);
digitalWrite(sensorYAnalogPin, HIGH); // set the voltage supply on
sensorVoltageX = analogRead(sensorXAnalogPin); // read the sensor voltage takes 125us
Serial.println("sensorX "+String(sensorVoltageX));
digitalWrite(sensorYAnalogPin, LOW); // set the voltage supply off
sensorVoltage = (sensorVoltageY + sensorVoltageX) / 2;
delayMicroseconds(stepwavedelayMicro); //small delay before reversing polarity (mandatory according to Matt!)
//Calculate resistance
float value= float(knownResistor) * ( 1023 - sensorVoltage ) / sensorVoltage;
value = value * 1000;
if (value>32000.0) {
value = 32000.00;
}
Serial.print("---resistance: "+String(value)+" kOhms\n");
return value;
}
#include <RunningMedian.h>
float medianValue() { //code to take the median value of n samples
Serial.println("\n*** MEASURING ***");
float value;
//Create an instance of the RunningMedian class with the number of samples to use
RunningMedian samples = RunningMedian(nsamples);
for(int i= 0; i<nsamples; i++){
value = ReadSensor(); //to measure the resistance
samples.add(value);
delay(500);
}
//Return the median value
float finalValue=samples.getMedian();
Serial.println("*** MEDIAN VALUE ="+String(finalValue));
return finalValue;
}
DS18B20.h
/*
* Dallas Scientific temperature sensor
* DS18B20
* code based on: https://create.arduino.cc/projecthub/TheGadgetBoy/ds18b20-digital-temperature-sensor-and-arduino-9cc806
* !! You will need a pull-up resistor of about 5 to 10 KOhm between the 1-Wire data line and your 5V power.
*/
// First we include the libraries
#include <OneWire.h> //https://www.pjrc.com/teensy/td_libs_OneWire.html (Arduino --> Manage Libraries --> OneWire by Jim Studt... version 2.3.5)
#include <DallasTemperature.h> //https://github.com/milesburton/Arduino-Temperature-Control-Library (Arduino --> Manage Libraries --> DallasTemperature by Miles Burton... version 3.8.0
#define ONE_WIRE_BUS 3 // Data wire is plugged into pin 3 on the Arduino
#define ONE_WIRE_POWER 1 // Power wire is plugged into pin 1 on the Arduino
OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices, (not just Maxim/Dallas temperature ICs)
DallasTemperature Tempsensor(&oneWire); // Pass our oneWire reference to Dallas Temperature.
float readTemp() {
Serial.println("\n*** MEASURING TEMPERATURE ***");
pinMode(ONE_WIRE_POWER, OUTPUT);
digitalWrite(ONE_WIRE_POWER, HIGH); // set the voltage supply on
delay(1000);
Tempsensor.requestTemperatures(); // Send the command to get temperature readings
Serial.print("---Temperature is: ");
float temp=Tempsensor.getTempCByIndex(0); // Why "byIndex"? You can have more than one DS18B20 on the same bus. 0 refers to the first IC on the wire
Serial.println(temp);
if (temp==-127.00) {
Serial.println("error!");
return 0;
} else {
Serial.println(temp);
return temp;
}
digitalWrite(ONE_WIRE_POWER, LOW); // set the voltage supply off
}
Trouble shooting
- Ensure that all sensors have pull-up resistors connected to data lines.
- Ensure that the Pololu 5V regulator has a pull-down resistor connected to the shutdown (SHDN) pin.
- Ensure that the Pololu 5V regulator is plugged in correctly.
- Ensure that batteries used to power the circuit are fully charged and are in good health. The LoPy will not function correctly if batteries cannot deliver sufficient current on start up.
- Ensure that the Electrolytic capacitor is installed with the correct polarity.
- Ensure that the Pycom LoPy4 is plugged in correctly (LED closest to 5V regulator)










