# Sap Flow and Arduinos

Most people interested in technology and especially open source hardware and software solutions already know the wonderful world of Arduino. Some innovative minds have already adopted Arduino’s to agriculture, examples include the Vinduino project and a project that inspired this work that can be found here. Since last October I have worked to design a system that measures homemade sap flow sensors with the Arduino-cloned Moteino micro controller, and then relays the data to a web server so the plant water status can be viewed in real-time. This post will detail the basics of sap flow and explain the components used for my sensor network.

#### Sap Flow Basics

Sap flow is the measurement of water movement in a plant’s xylem. There is a public domain method for constructing probes that can be found at UCLA’s Center for Embedded Network Sensing. Let’s start with a picture! The basics of Granier’s Thermal Dissipation Probes can be seen here:

diagram of sap flow probes inserted into plant

The probes are type T thermocouples inside a medical steel needle. Thermocouples are simply two dissimilar metals connected together.  As the temperature changes at the junction of these two metals, the voltage produced will change according to the temperature.  One of these probes is wrapped with heater wire and constantly heated. The probes’ constantan wires are soldered together so that we measure the voltage difference between the two copper wires of the probes. As the water movement in the xylem changes, so does the temperature (voltage) output of the probes. The higher the temperature difference, the less sap flow, and vice versa.

The theory behind sapflow is relatively straightforward, I recommend this academic paper for a very good analysis of the method.  To calculate the instantaneous rate of flow, we need to know the maximum differential temperature and the current differential temperature, which we then use to calculate our flow index (K):

#### K = (∆TMax – ∆T )/∆T

Granier, the founder of the technique, discovered that the flow index has a nonlinear relationship with the sap flow (F), explained by this equation:

#### F = 0.000119 * K1.231

This gives us sap flow (F) in cubic meters, per square meter, per second [m3·m-2·s].

OK so theoretically this is possible, but how are we going to do it on an Arduino?

#### Finding the Right Tool For the Job

My first concern was that the ADC of the standard Arduino simply cannot register voltages on the thermocouple scale. Type T thermocouples have a voltage output of .041mV per degree Celsius. Luckily stevemarple on GitHub had already written a library for the MCP342x family of ADC converters that operate via the I2C bus.

The next issue with the design is the requirement of a constant power supply for the heated sensor, specifically .2 Watts of heat.  My take on this is a little different from the tutorial from CENS I linked earlier, so I want to explain the differences.  The first and biggest change I made was to use a heater wire with much higher resistance per meter, greatly decreasing the amount of power required by the system.  Good old Ohm’s Law ( Voltage = Current * Resistance) always seems to come in handy.  The specific wire I used is called Kanthal D, and I actually found it on ebay.  The next step I did was to use a 5V fixed voltage source instead of the variable voltage regulator mentioned in CENS.  I used the 5v step up/step down regulator from Pololu because my system will be battery powered and also recharged with solar panels (more on this in a bit).  This means that we need to check the resistance of our heater wire to ensure that the fixed 5V will output .2W.  In my case I ended up needing ~84 cm (125 Ohms) of wire to produce our required heat field.  The power requirement of our heat field is 40mA, not accounting the current used by the voltage regulator.  With a multimeter I measured around 30mA of current to step up the voltage when the regulator was powered from 3 AA NiMH batteries.  So lets play it safe and say our power requirement is 80 mA.

With that 80mA per hour, we can assume we will use 80 * 24 = 1920mA of current per day, at most.  3 AA batteries in series will give us 2500mAH and output 3.6V that needs to be stepped up to 5V for our heated probe.  Rechargeable batteries are easy to come by, but I do recommend using a reputable brand if the quality is important, I used Tenergy.  To charge the batteries, I used a 1.5W solar panel from SeeedStudio that outputs around 4.8V under load and up to 300mA in good sun, average of 250mA.  So that gives us around 8 hours of sunlight needed to cover the maximum daily power requirements of the heated probe.

#### Going Wireless

To assess variability of an agricultural field, you’re going to need sensors in multiple locations.  This means some sort of wireless technology should be used.  The specific wireless enabled board I chose is known as the Moteino.  It comes with a built in 433MHz transceiver that has enough range to cover a small vineyard.  Each sap flow measurement system as described above will be paired with a Moteino, which will transmit it’s measurements to a base station.  All of the nodes will send to one gateway, so we should prepare for collisions just in case.  The base station is a Moteino attached to a Raspberry Pi, which is in turn connected to a WiFi Cellular Hotspot so that the data can be uploaded to a web server.  The Raspi and Moteino communicate via a USB to Serial cable and the pyserial  python library.  The Pi stores data locally and also uploads it to a Postgres database on a server.

Schematic and Sample Code

Here’s a simple schematic for the completed nodes:

And here’s the sample Arduino code, don’t forget to grab any needed libraries:

##### Node:
```/*

MOTEINO SKETCH TO READ 4 SAP FLOW VALUES ONCE PER MINUTE,
AVERAGE THESE 4 VALUES,
TAKE MEDIAN OF VALUES OVER 15 MINUTES,
THEN SEND MEDIAN VALUE TO GATEWAY

*/

///LIBRARIES
#include <RFM69.h>
#include <SPI.h>
#include <Wire.h>
#include <MCP342x.h>
#include <LowPower.h>
#include "FastRunningMedian.h"

//DEFINITIONS
#define NODEID 1 //change this for each node
#define NETWORKID 100
#define GATEWAYID 20
#define FREQUENCY RF69_433MHZ
#define ACK_TIME 100 // # of ms to wait for an ack
#define RETRY_NUMBER 5 // # of retries to attempt
#define SLEEP_PERIOD SLEEP_4S
#define ONE_MIN_ARRAY_LEN 4
#define FIFTEEN_MIN_ARRAY_LEN 15
#define SLEEP_IN_SECONDS 60 //how long to sleep

int sleepPeriod = 4; //take number from SLEEP_PERIOD
int oneMinLen = ONE_MIN_ARRAY_LEN;
int fifteenMinLen = FIFTEEN_MIN_ARRAY_LEN;
int sleepTime = SLEEP_IN_SECONDS;

//variables for mcp342x
uint8_t address = 0x68; //cannot change, set in hardware

//var for moteino
byte sendSize = 0;
boolean requestACK = true;
byte errorCount = 0;

typedef struct {
byte nodeId;
long sapFlow;
byte errorCount;
}

//sapflow arrays
long oneMinVals[ONE_MIN_ARRAY_LEN];
long fifteenMinVals[FIFTEEN_MIN_ARRAY_LEN];
FastRunningMedian<long, FIFTEEN_MIN_ARRAY_LEN, 0> fifteenMinMedian;

//setup
void setup() {
Serial.begin(115200);
data.nodeId = NODEID;
Wire.begin();
MCP342x::generalCallReset();
delay(1); // MC342x needs 300us to settle, wait 1ms
if (!Wire.available()) {
Serial.print("No device found at address ");
while (1)
;
}
}

//variables for loop:
long sapFlowValue, sapFlowMedian;
byte minuteCount;

//loop
void loop() {
for (minuteCount = 0; minuteCount < fifteenMinLen; minuteCount++) {
sleepInSeconds(sleepTime);
}
sapFlowMedian = fifteenMinMedian.getMedian();
data.sapFlow = sapFlowMedian;
data.errorCount = errorCount;
errorCount = 0;
for (byte i = 0; i < 5; i++) {
if (radio.sendWithRetry(GATEWAYID, (const void*)(&data), sizeof(data), RETRY_NUMBER, ACK_TIME)) {
break;
}
else {
delay(100);
delay(random(300));
}
}
}

/*
FUNCTION sleepInSeconds
sleeps a set number of seconds at lower power consumption, must be at least four seconds
INPUT : integer of seconds
OUTPUT: none
*/

void sleepInSeconds(int seconds) {
int sleepIntervals = seconds / sleepPeriod; //how many times are we going to sleep?
for (byte i = 0; i < sleepIntervals; i++) {
}
}

/*
FUNCTION arrayAverage
averages the values of an array based on the number of elements in the array
INPUT : long array of values, int of number of elements of the array
OUTPUT: long average of array
*/
long arrayAverage(long * data, int count)
{
int i;
long total;
long result;

total = 0;
for (i = 0; i < count; i++)
{
total = total + data[i];
}
result = total / count;
return result;
}

/*
INPUT : none
OUTPUT: unsigned int average of array
*/
long value;
for (int i = 0; i < oneMinLen; i++) {
MCP342x::Config status;
MCP342x::resolution18, MCP342x::gain8,
1000000, value, status);
if (err) {
MCP342x::generalCallReset();
delay(1);
errorCount++;
}
else {
oneMinVals[i] = value;
delay(10);
}
}
long arrayAvg = arrayAverage(oneMinVals, oneMinLen);
return arrayAvg;
}

/*
Blinks LED for certain length of time
INPUT : byte LED pin, int Delay in milliseconds
OUTPUT: None
*/
{
pinMode(PIN, OUTPUT);
digitalWrite(PIN, HIGH);
delay(DELAY_MS);
digitalWrite(PIN, LOW);
}
```
##### Gateway:
```
#include <RFM69.h>
#include <SPI.h>
#include <Wire.h>
#include <MCP342x.h>

#define NODEID 20
#define NETWORKID 100
#define FREQUENCY RF69_433MHZ
#define SERIAL_BAUD 115200
#define SAP_ARRAY_LEN 3 //how many sap flow values to read each time data is received
#define LED 9

//moteino variables
int arrayLen = SAP_ARRAY_LEN;
long sapVals[SAP_ARRAY_LEN];

//variables for mcp342x
uint8_t address = 0x68; //cannot change, set in hardware

typedef struct {
byte nodeId; //sender ID
long sapFlow; //sapFlow value
int decagon;

void setup() {
Serial.begin(SERIAL_BAUD);
delay(10);
Wire.begin();
MCP342x::generalCallReset();
delay(1); // MC342x needs 300us to settle, wait 1ms
data.decagon = 0;
}

long sapFlowValue;

void loop() {
{

{
}
Serial.println(data.nodeId);
Serial.println(data.sapFlow);
Serial.println(data.errorCount);
Serial.println(sapFlowValue);
Serial.println(data.decagon);
}
}

{
pinMode(PIN, OUTPUT);
digitalWrite(PIN, HIGH);
delay(DELAY_MS);
digitalWrite(PIN, LOW);
}
/*
FUNCTION arrayAverage
averages the values of an array based on the number of elements in the array
INPUT : long array of values, int of number of elements of the array
OUTPUT: long average of array
*/
long arrayAverage(long * data, int count)
{
int i;
long total;
long result;

total = 0;
for (i = 0; i < count; i++)
{
total = total + data[i];
}
result = total / count;
return result;
}

/*
INPUT : none
OUTPUT: unsigned int average of array
*/
long value;
for (int i = 0; i < arrayLen; i++) {
MCP342x::Config status;
MCP342x::resolution18, MCP342x::gain8,
1000000, value, status);
if (err) {
MCP342x::generalCallReset();
delay(1);

}
else {
sapVals[i] = value;
delay(10);
}
}
long arrayAvg = arrayAverage(sapVals, arrayLen);
return arrayAvg;
}
<pre>```