A capacitive soil humidity sensor: Part 2

In a previous article I presented a simple way to read a capacitive moisture sensor with a simple RC generator.
In this post I will present a sensor with some added functionality that can be read through I2C. The circuit (figure) doesnt need much explanation: the RC generator we saw in the previous article and the two variable resistors in a voltage divide read by analogue inputs. The Attiny 45  is the heart, or rather the brains of the sensor. As the Attiny  functions as an I2C slave we will need the TinyWireS library. The library comes with some examples and one example was quite easy to rework to what I needed. The code is as follows.

#define I2C_SLAVE_ADDRES 0x4
#include <TinyWireS.h>//https://github.com/rambo/TinyWire
#ifndef TWI_RX_BUFFER_SIZE
#define TWI_RX_BUFFER_SIZE ( 16 )
#endif

volatile uint8_t i2c_regs[] =
{
	0x00,
	0x00,
	0x00,
	0x00,
};
// Tracks the current register pointer position
volatile byte reg_position;
const byte reg_size = sizeof(i2c_regs);

byte LDRvalue;
byte NTCvalue;
int MoistPulse;

/**
 * This is called for each read request we receive, never put more than one byte of data (with TinyWireS.send) to the
 * send-buffer when using this callback
 */
void requestEvent()
{
	TinyWireS.send(i2c_regs[reg_position]);
	// Increment the reg position on each read, and loop back to zero
	reg_position++;
	if (reg_position >= reg_size)
	{
		reg_position = 0;
	}
}

/**
 * The I2C data received -handler
 *
 * This needs to complete before the next incoming transaction (start, data, restart/stop) on the bus does
 * so be quick, set flags for long running tasks to be called from the mainloop instead of running them directly,
 */
void receiveEvent(uint8_t howMany)
{
	if (howMany < 1) { // Sanity-check return; } if (howMany > TWI_RX_BUFFER_SIZE)
	{
		// Also insane number
		return;
	}

	reg_position = TinyWireS.receive();
	howMany--;
	if (!howMany)
	{
		// This write was only to set the buffer for next read
		return;
	}
	while(howMany--)
	{
		i2c_regs[reg_position] = TinyWireS.receive();
		reg_position++;
		if (reg_position >= reg_size)
		{
			reg_position = 0;
		}
	}
}


void setup()
{
	pinMode(1, INPUT);
	TinyWireS.begin(I2C_SLAVE_ADDRESS);
	TinyWireS.onReceive(receiveEvent);
	TinyWireS.onRequest(requestEvent);
}

void loop()
{
	readSensors();
	/**
	 * This is the only way we can detect stop condition (http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=984716&sid=82e9dc7299a8243b86cf7969dd41b5b5#984716)
	 * it needs to be called in a very tight loop in order not to miss any (REMINDER: Do *not* use delay() anywhere, use tws_delay() instead).
	 * It will call the function registered via TinyWireS.onReceive(); if there is data in the buffer on stop.
	 */
	TinyWireS_stop_check();
}

void readSensors()
{
	LDRvalue = (analogRead(A2)) >>2; // max value is 1023/4=255 is 1 byte (FF) physical pin3=PB4=A2
        i2c_regs[0] = LDRvalue;
	NTCvalue = (analogRead(A3)) >>2; // max value is 1023/4=255 is 1 byte (FF) pin2=PB3=A3
	i2c_regs[1] = NTCvalue;
	//pulsepin= pin6=PB1
	MoistPulse = pulseIn(1, HIGH);
	i2c_regs[2] = highByte(MoistPulse); // or use = MoistPulse >> 8;
	i2c_regs[3] = lowByte(MoistPulse); // or use = MoistPulse & 0xFF;
}

I have called the pulseIn function with an integer rather than with a long. This means that you will have to choose a value for R3 that gives a reasonable range for the type of soil that you use. A range of say 0-200 uS is very reasonable. Once you have done that you can also add a timeout to the pulseIn function. This should be abt 2 times the pulselength you expect (but depends on the duty cycle). With regard to the two variable resistors, they are in pull up so their value is Rntc = Rseries/((1023/ADC) – 1));  For the NTC this could be substituted in a Steinhart Hart approximation

The code to call the values (and that is loaded onto the master arduino) is even simpler:

#include <Wire.h>
void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

void loop() {
  for (byte i=0;i<4;++i){
  Serial.print("0x");
  Serial.println(readRegister(i),HEX); // print the character
}
Serial.println(" ");
    delay(1000);
  }

uint8_t readRegister(uint8_t regaddress)
{
	Wire.beginTransmission(4);
	Wire.write((byte)regaddress);
	Wire.endTransmission();
	Wire.requestFrom(4, 1);
	return Wire.read();


 delay(500);
}

This code only prints out the values, you still need to combine the LSB and MSB from the cycle time and e.g. switch a pump based on the value. You could do that with this function:

int combine (byte lsbv, byte msbv)
{
	int value = msbv << 8;
	value = value | lsbv;
	//value= msbv<<8 | lsvb;//if you want to do it in one go
	return value;
}

Don’ t forget that the I2C lines need pull-up resistors of 4.7-10k.

In a next article I will present the construction of the probe itself.

Advertisements