[IoT] Aquarium monitor; hardware wiring

Hi there! This is post #2 in my Aquarium monitor series. In this post, I’ll explain how I wired the different sensors to my Raspberry Pi and Arduino devices. Now you probably do not have exactly the same set-up as I have, as there are all kinds of different devices out there which all require their own set-up / configuration. So this post is written as general guidance of how to connect sensors in general. 

The hardware

Small recap from the first post; I’ll be using a Raspberry Pi 2 running Windows 10 IoT. Linked to that will be an Arduino nano with some sensors attached to it. I recommend hackster.io for tons of good blogs detailing on how you can wire all kinds of sensors to all kinds of boards. Do some research first and order the parts that suit your situation best. In this case, I’ll be wiring:

  • 2 temperature sensors (DS18B20 onewire) to monitor water temperature
  • 1 pH sensor

Once you get the trick of reading a sensor, the rest becomes pretty trivial real fast. So feel free to slam in a couple of more if you need them.

 

Start small! Hello world!

Ok yes, this might be the lamest thing I could start with, but I really need to emphasize the need to start small. Once you start receiving the stuff you ordered, your hands will be itching to get it all working, like mine were too. But this set-up consists of several moving parts. And the more parts you have, the more places you create for failure to happen. So my advise would be the following:

  1. Find out whether your sensor is analog or digital. Analog sensors you will connect to the Arduino, digital ones you can connect to either the Raspberry or the Arduino. I would recommend to wire everything to the Arduino to begin with, just to have everything in one place.
  2. Find out how your sensor is wired to the device (see hackster link above) and connect it like that. Check multiple blogs when you can find ’em as I found out the information is not always 100% correct.
  3. With the sensor connected, write a little Arduino program to read out the value and print it to the serial monitor. Do this for all your sensors and be sure to save that code somewhere safe (like GitHub).
  4. Once you have all sensors reading values, now start working on getting those values to the Raspberry using I2C. That I will describe in more detail below.

 

Wiring the sensors

So I started hacking away. Analog and digital sensors require different ways of getting the values out of them. My temperature sensors require a 4k7 ohm “pull-up” resistor which apparently has something to do with how the signal hits the board. Again, I can’t stress enough that I am no expert in these things and you should google your ass off to try and find blogs covering your particular set-up. The good thing here is that you can use all of the Arduino blogs out there, your good as long as you can get the correct value to print out on the screen.

Neptune Sketch_bb

Above is (part of) my set-up made with a nifty little program called Fritzing. In the diagram you see the following:

  • The Raspberry is connected to a breadbord using whats called a ‘cobbler’, the blue T-shape component on the right.
  • The Arduino is the little device you see on the left.
  • Digital port 3 (D3) of the Arduino is linked to two DS18B20 sensors. Yes, two on the same port. This works beautifully without much additional effort, take a look at the code below.
  • Digital port 13 (D13) is linked to my pH sensor board (which in turn is connected with the actual sensor).
  • Ports 23 – 26 of the Raspberry are connected to the inputs of the relay board (4 channels).
  • The SDA and SCL ports of the Raspberry are the ones we can use for I2C communication. Those are linked to A4 and A5 respectively (which are SDA and SCL on the Arduino). More about I2C is found below.
  • I’ve linked the grounds of all components to create one common ground.
  • The Arduino is fed using power from the Raspberry via USB, the relay board is powered via the 5V output channel.

That’s pretty much all of the wiring, although I must mention this doesn’t feature the moisture sensor I have. As you can see, not that hard once you’ve got it figured out.

 

Writing the code

Okay. So now we have to write some code to get these actual values. As stated earlier; do this one sensor at a time so you know the individual parts work. Once you have all parts, combine them into one which will resemble something like you see below. In my opinion this isn’t much code so I’m just going to paste it in here. Explanation is inline and below.

#include <Wire.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 3
#define SensorPin 0            //pH meter Analog output to Arduino Analog Input 0
#define Offset 0.00            //deviation compensate

unsigned long int avgValue;     //Store the average value of the sensor feedback

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

// arrays to hold device address
DeviceAddress thermometer1, thermometer2;

float temp1, temp2, ph;

void setup()
{
  pinMode(13,OUTPUT);  
  Serial.begin(9600);  
  Serial.println("Ready");    //Test the serial monitor

  // locate devices on the bus
  Serial.print("Locating devices...");
  sensors.begin();
  Serial.print("Found ");
  Serial.print(sensors.getDeviceCount(), DEC);
  Serial.println(" devices.");

  // report parasite power requirements
  Serial.print("Parasite power is: "); 
  if (sensors.isParasitePowerMode()) Serial.println("ON");
  else Serial.println("OFF");
  
  if (!sensors.getAddress(thermometer1, 0)) Serial.println("Unable to find address for Device 0"); 
  if (!sensors.getAddress(thermometer2, 1)) Serial.println("Unable to find address for Device 1"); 

  // show the addresses we found on the bus
  Serial.print("Device 0 Address: ");
  printAddress(thermometer1);
  Serial.println();

  Serial.print("Device 1 Address: ");
  printAddress(thermometer2);
  Serial.println();

  // set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions)
  sensors.setResolution(thermometer1, 9);
  sensors.setResolution(thermometer2, 9);
 
  Serial.print("Device 0 Resolution: ");
  Serial.print(sensors.getResolution(thermometer1), DEC); 
  Serial.println();

  Serial.print("Device 1 Resolution: ");
  Serial.print(sensors.getResolution(thermometer2), DEC); 
  Serial.println();

  Serial.println("Beginning communication on slave #18");
  Wire.begin(18);
  Wire.onRequest(requestEvent); // data request to slave
}

// function to print a device address
void printAddress(DeviceAddress deviceAddress)
{
  for (uint8_t i = 0; i < 8; i++)
  {
    // zero pad the address if necessary
    if (deviceAddress[i] < 16) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
  }
}

void requestEvent() {
  Serial.println("request event");

  byte message[12];
  // write temp1 to the wire
  float2Bytes(temp1, &message[0]);
  float2Bytes(temp2, &message[4]);
  float2Bytes(ph, &message[8]);
  Wire.write(message, 12);

  Serial.println("Sent data to host");
}

void float2Bytes(float val, byte* bytes_array){
  // Create union of shared memory space
  union {
    float float_variable;
    byte temp_array[4];
  } u;
  // Overite bytes of union with float variable
  u.float_variable = val;
  // Assign bytes to input array
  memcpy(bytes_array, u.temp_array, 4);
}

float getPh()
{
  int rawValue = analogRead(SensorPin);
  int buf[10];                //buffer for read analog
  
  for(int i=0;i<10;i++)       //Get 10 sample value from the sensor for smooth the value
  { 
    buf[i]=1024 - analogRead(SensorPin);
    delay(10);
  }
  for(int i=0;i<9;i++)        //sort the analog from small to large
  {
    for(int j=i+1;j<10;j++)
    {
      if(buf[i]>buf[j])
      {
        int temp=buf[i];
        buf[i]=buf[j];
        buf[j]=temp;
      }
    }
  }
  
  avgValue=0;
  for(int i=2;i<8;i++)                      //take the average value of 6 center sample
    avgValue+=buf[i];
    
  float phValue=(float)avgValue*5.0/1024/6; //convert the analog into millivolt
  phValue=3.5*phValue+Offset;                      //convert the millivolt into pH value\

  float adjustedPh = (-0.01796 * phValue + 1.8056) * phValue - 2.94;
  return adjustedPh;
}

void loop()
{
  // get the pH from the sensor board
  ph = getPh();

  // call sensors.requestTemperatures() to issue a global temperature 
  // request to all devices on the bus
  sensors.requestTemperatures(); // Send the command to get temperatures

  temp1 = sensors.getTempC(thermometer1);
  temp2 = sensors.getTempC(thermometer2);
  
  Serial.print("pH: ");  
  Serial.print(ph,2);
  Serial.print("; temp1: ");  
  Serial.print(temp1,2);
  Serial.print("; temp2: ");  
  Serial.print(temp2,2);
  Serial.println(" ");

  digitalWrite(13, HIGH);       
  delay(1900);
  digitalWrite(13, LOW); 
}

I hope most of the code speaks for itself; but here are some extra pointers:

  • The one wire temperature sensors are really easy to read when you use the Dallas OneWire.h library.
  • The pH sensor board took me quite some time to get working and it’s quite possible that the same code might not work for yours. This is a cheap-ass China made one which I’ll be swapping for a better one real soon. Check out this post on stackexchange for more information on that one.

 

Sending information to the Raspberry via I2C

Ok, so if you’ve followed the steps, you now have Arduino code capable of reading and writing sensor values. Next step is to get these values to the Raspberry. As you’ve seen in the wiring, two ports are used for that which in turn use the I2C protocol for communicating. This is where the Wire.h library is used for so you don’t have to start writing communication protocols yourself.

The code itself consists of these parts:

  Serial.println("Beginning communication on slave #18");
  Wire.begin(18);
  Wire.onRequest(requestEvent); // data request to slave

This code runs as part of the setup method and registers the requestEvent method. This method will be called each time the device receives a request from the host. Inside the method, it should write it’s message payload to the wire, which you see below:

void requestEvent() {
  Serial.println("request event");

  byte message[12];
  // write temp1 to the wire
  float2Bytes(temp1, &message[0]);
  float2Bytes(temp2, &message[4]);
  float2Bytes(ph, &message[8]);
  Wire.write(message, 12);

  Serial.println("Sent data to host");
}

Not that complicated, right? It isn’t!

Of course this is only one side of master-slave communication. The Raspberry will run the master code and request data from the Arduino. It will now get a 12-byte array which consists of the registered temperatures (2x 4 bytes) and pH value (4 bytes).

The real cracks will most likely find all kinds of problems with this code, but at least it works! In my next blog, I’ll detail the Raspberry side of things and we’ll look at how to get some information up to the cloud. W00t!

 

Nice to knows

Whenever I think of things that are helpful to know, I’ll add them below. Hope it helps!

  • With wires going to the Raspberry, my laptop sometimes has trouble identifying the Arduino via USB. Temporarily disconnecting the two seems to fix this, probably some kind of current issue.

, ,

Related posts

Latest posts

2 comments

Leave a Comment

Leave a Reply to [IoT] My steps to an aquarium monitor | Jaspers' Weblog Cancel reply

Your email address will not be published. Required fields are marked *