Fish farming prototype

buoy_ghana

The goal of this prototype is giving a first look on how to develop an IoT device for fish farming. The device is capable of giving real time reading about relevant information for fish ponds such as acidity, dissolved oxygen and communicate this data via LoRa. Below you will find a get started tutorial : how to develop the electronic and software part of the solution.

Measure

The measures taken by the device are the temperature, the dissolved oxygen and the acidity of the water. The device is powered by a solar panel with a battery. The solution will be placed in a watertight box to protect the electronic, we also get temperature and humidity into the box. The last value controled is the voltage of the battery.

hardware

Electronic Build

Arduino

The Arduino board with his microcrontroller is the central part of the electronic assembly. It contains all the logic : it gathers sensors information, process it and send it via the LoRa module. In this first section we will just see how to connect the sensors to the Arduino, on a second hand we will see how to program it. Arduino

PH and DO sensors

The PH and DO sensors work with a small circuit to precise measures and calibrate the sensor : DO EZO circuit and PH EZO circuit.

EZO Circuits

Both circuits are connect to I2C wire of arduino (pin A4 and A5 on arduino pro mini). In order to works with I2C, the circuits need to be switch in I2C mode.

These are the instructions to switch an EZO circuit to I2C mode :

  • Connect the circuit to the arduino as following :
    • VCC of arduino to VCC of EZO circuit
    • GND of arduino to GND of EZO circuit
    • PGND of EZO circuit to TX of EZO circuit
  • Power the arduino
  • Wait for the circuit LED to change from Green to Blue
  • Remove the connection from PGND pin to TX pin
  • The device is now in I2C mode

The default I2C adress for the EZO DO is 97, for the EZO PH it’s 99.

Rather than power the sensors with the VCC output of the arduino we prefer power it with the digital output pins. This choice makes power saving management easier. In our prototype we choose the pin 6 to power the EZO DO circuit and pin 7 to power the EZO PH.

Wire table : EZO Circuits

Then we have to connect each sensor to their EZO circuit. Each sensor come with a DC barrel jack to connect to the circuit. Connect the GND pin of the barrel connector with the PGND pin of the EZO circuit and connect the pin next to the GND of the barrel connector to PRB bin of the EZO circuit as below.

Sensors Connection

Water temperature sensor

The sensor we use to control water temperature is the DS18B20.

DS18B20

As we did for PH and DO sensor, we power this sensor using digital output pin of the arduino. However you can use the VCC pin to your own preference. Here the digital pin 8 is used for power and the digital pin 2 is used for data. A 4k7 ohms resistor is required between VCC pin and Data pin. Below it’s the wire schema of the sensor.

DS18B20 Wire

LoRa module

The LoRa chip we use is the inAir9 module.

InAir9 and wires

Below the wire table of the InAir9 module with the arduino.

InAir9 and wires

Temperature and Humidity sensor (optional)

Our first prototype is test in Ghana which is a particulary hot country thus we decide to add a temperature and humidity sensor to check conditions into the box. You can choose to add it or not as your convenience. The sensor we use to this purpose is the DHT22 sensor.

DHT22

The VCC pin of the sensor is connected to the digital pin 8 of the arduino, but you can use the VCC pin also. The DATA pin of the sensor is connected to the digital pin 3 of the arduino. As we did with the DS18B20 sensor, a 4k7 ohms resistor is needed between VCC and DATA.

DHT22 table

Voltage measure

We want to know the voltage at battery outputs to estimate the charge level. We use a li-ion battery so the voltage ranges from 3V (empty) to 4.2V (fully charged) at 25°C. We reach 50% charge level at about 3.8V still at 25°C temperature. To get voltage at the arduino inputs we use a voltage divider. We choose the analog pin 1 of the arduino to calculate the real voltage. Below this is the schema of the voltage divider. We use high-value resistor to minimize the current waste.

Voltage divider

Charger, battery and solar panel

We take a charger board from sparkfun to connect the solar panel, the battery and the arduino together. This charger board provide us the ability to get the most possible power out of our solar panel and into a rechargable Li-ion battery. The set up is easy, just plug the solar panel into one side of the circuit, the battery and the arduino in parallel on the other side.

Charger board

General schematic

Here you can see the schematic of the entire circuit without the charger part.

Schematic

Implementation

figure

In our implementation we decide to create a PCB board of the circuit to make the component assembly easier.

figure

figure

Software

Program the Arduino

We can program the Arduino by sending a set of instructions to the microcrontroller on the board. To do so we use the IDE Arduino Software which uses a simplified version of C++. The Arduino software can be download here. Then to connect the Arduino Mini to the computer we use an USB-FTDI chip as below.

FTDI Arduino

Libraries

Sensor coding samples

DO Sensor

The sample code to manage the DO sensor can be find here.

Attention, if you have used digital pin 6 for power supply, don’t forget to add this code in setup section to switch power on :

    void setup()
    {
     Serial.begin(9600);
     Wire.begin();
     pinMode(6,OUTPUT);
     digitalWrite(6,HIGH);
    }

PH Sensor

The sample code to manage the PH sensor can be find here.

Attention, if you have used digital pin 6 for power supply, don’t forget to add this code in setup section to switch power on:

    void setup()
    {
     Serial.begin(9600);
     Wire.begin();
     pinMode(7,OUTPUT);
     digitalWrite(7,HIGH);
    }

Water temperature sensor

To get temperature from DS18B20 sensor we use the OneWire library. Here is a sample code:

    #include <OneWire.h> 

    #define TemperatureSensorsPowerPin 8

    int DS18S20_Pin = 2; //DS18S20 Signal pin on digital 2

    //Temperature chip i/o
    OneWire ds(DS18S20_Pin);  // on digital pin 2

    void setup(void) {
      Serial.begin(19200);
        pinMode(TemperatureSensorsPowerPin,OUTPUT);    // Switch on power for temp and humdity sensor
        digitalWrite(TemperatureSensorsPowerPin,HIGH);
    }

    void loop(void) {
      float temperature = getTemp(); //will take about 750ms to run
      Serial.println(temperature);
      delay(2000);
    }


    float getTemp(){
      //returns the temperature from one DS18S20 in DEG Celsius

      byte data[12];
      byte addr[8];

      if ( !ds.search(addr)) {
          //no more sensors on chain, reset search
          ds.reset_search();
          return -1000;
      }

      if ( OneWire::crc8( addr, 7) != addr[7]) {
          Serial.println("CRC is not valid!");
          return -1000;
      }

      if ( addr[0] != 0x10 && addr[0] != 0x28) {
          Serial.print("Device is not recognized");
          return -1000;
      }

      ds.reset();
      ds.select(addr);
      ds.write(0x44,1); // start conversion, with parasite power on at the end

      delay(750); // Wait for temperature conversion to complete

      byte present = ds.reset();
      ds.select(addr);    
      ds.write(0xBE); // Read Scratchpad


      for (int i = 0; i < 9; i++) { // we need 9 bytes
        data[i] = ds.read();
      }

      ds.reset_search();

      byte MSB = data[1];
      byte LSB = data[0];

      float tempRead = ((MSB << 8) | LSB); //using two's compliment
      float TemperatureSum = tempRead / 16;

      return TemperatureSum;

    }

Temperature and Humidity Sensor

To get temperature and humidity measure from DHT22 sensor we use the DHT library. Here is a sample code:

    #include "DHT.h"

    #define DHT_PIN 3

    const byte DHT_SUCCESS = 0;
    const byte DHT_TIMEOUT_ERROR = 1;
    const byte DHT_CHECKSUM_ERROR = 2;
    DHT dht(DHT_PIN);


    void setup()
    {
      Serial.begin(19200);
      pinMode(8,OUTPUT);
      digitalWrite(8,HIGH);
    }

    void loop()
    {
      float temperature, humidity;


      switch (dht.readDHT22(&temperature, &humidity)) {
      case DHT_SUCCESS: 

        Serial.print(F("Humidity (%): "));
        Serial.println(humidity, 2);
        Serial.print(F("Temperature (C): "));
        Serial.println(temperature, 2);
        break;

      case DHT_TIMEOUT_ERROR: 
        Serial.println(F("No response!")); 
        break;

      case DHT_CHECKSUM_ERROR: 
        Serial.println(F("Communication pb")); 
        break;
      }

      delay(1000);
    }

LoRa module

To send data via LoRa we use the SX1272 library.

You will find all the samples to use this library here.

Battery Voltage

Below we have a sample to calculate the battery voltage output.

#define RESISTOR1 47000.0        // RESISTOR to calculate voltage
#define RESISTOR2 10000.0

#define VoltagePin  A2


float batteryVoltage;

void setup() {
  Serial.begin(38400);

}

void loop() {

  batteryVoltage = getBatteryVoltage();
  Serial.println(batteryVoltage);

  delay(2000);

}

float getBatteryVoltage()
{
   int rawVin;
   int sumRawVin = 0;
   analogReference(INTERNAL);

  for (byte y=0; y<30; y++){
    rawVin = analogRead(VoltagePin);
    sumRawVin=sumRawVin+rawVin;
    delay(5);
  }

  rawVin = sumRawVin/30;
  float real_v = (rawVin * 1.1 / 1024.0) / (RESISTOR2/(RESISTOR1+RESISTOR2));
  if (real_v < 0.1) {  real_v=0.0; }

  return real_v;
}

Arduino sleep mode

In order to save battery, you will certainly need to put the Arduino and the sensors in sleep mode. It exists 5 different sleep modes in arduino from the least to the most power saving mode:

  • SLEEP_MODE_IDLE
  • SLEEP_MODE_ADC
  • SLEEP_MODE_PWR_SAVE
  • SLEEP_MODE_STANDBY
  • SLEEP_MODE_PWR_DOWN

When the arduino is in SLEEP_MODE_PWR_DOWN the only way to wake it is with either a watchdog timer interrupt, a level interrupt on pins 2 or 3, or a Pin Change interrupt. So in our cas we need to use the watchdog timer interrupt.

Enable Watchdog timer interrupt

You can skip this section if you are not using an Arduin Pro mini board.

Unfortunately watchdog timer interrupt is defective on Arduino Pro mini board. To fix the problem a new bootloader has to be installed on it. We will install the bootloader of the Arduino UNO which uses the same microprocessor chip(atmega328).

Here you will find how to install this bootloader :

  • First Open the bootloader file boards.txt wich is located

on windows :

C:\Users\{userName}\AppData\Local\Arduino\packages\arduino\hardware\avr\{version}\boards.txt

on linux :

/usr/share/arduino/hardware/arduino/avr/boards.txt

In this file, find the lines about the Arduino pro mini 3.3V

## Arduino Pro or Pro Mini (3.3V, 8 MHz) w/ ATmega328
## --------------------------------------------------
pro.menu.cpu.8MHzatmega328=ATmega328 (3.3V, 8 MHz)

pro.menu.cpu.8MHzatmega328.upload.maximum_size=30720
pro.menu.cpu.8MHzatmega328.upload.maximum_data_size=2048
pro.menu.cpu.8MHzatmega328.upload.speed=57600

pro.menu.cpu.8MHzatmega328.bootloader.low_fuses=0xFF
pro.menu.cpu.8MHzatmega328.bootloader.high_fuses=0xDA
pro.menu.cpu.8MHzatmega328.bootloader.extended_fuses=0xfd
pro.menu.cpu.8MHzatmega328.bootloader.file=atmega/ATmegaBOOT_168_atmega328.hex

pro.menu.cpu.8MHzatmega328.build.mcu=atmega328p
pro.menu.cpu.8MHzatmega328.build.f_cpu=8000000L

Replace the line: “pro.menu.cpu.8MHzatmega328.bootloader.file=atmega/ATmegaBOOT_168_atmega328.hex

By the line : “pro.menu.cpu.8MHzatmega328.bootloader.file=optiboot/optiboot_atmega328.hex

Save and close the file.

  • Then we need to flash the boot using an in system programmer.

In this tutorial we will use an Arduino UNO as ISP but it also works with other Arduino boards. We start by connecting the Arduino UNO to the computer. In Arduino IDE, we go to file -> examples and select ArduinoISP. It opens the sketch to use the Arduino as an in system programmer. We flash this sketch on the Arduino UNO.

Next, we connect the Arduino UNO and the Arduino Pro mini as following.

Arduino UNO Arduino Pro mini
3V3 VCC
GND GND
Digital 10 RST
Digital 11 Digital 11
Digital 12 Digital 12
Digital 13 Digital 13

Then we select “Arduino Pro or Pro mini” in tools->Board and “ATmega328 (3.3v, 8 MHz)” in tools->processor. Finally we click on tools->Burn Bootloader.

The Arduino Pro mini is now ready.

Sample code for sleep mode

The longest interval the Watchdog timer can be set is 8 sec.

Here you will find a sample on how to put the Arduino Pro mini in sleep mode for a longer period.

#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>


#define SLEEP_LOOP 4  // one loop = 8 sec. Example sleep_loop = 4 -> 4*8 = 32 sec

volatile int nbr_remaining; 

void setup()
{

  Serial.begin(38400);
  configure_wdt();
  nbr_remaining=0;

}


void loop(void)
{

  Serial.println("Loop Start");
  while (1) {
      Serial.println(F("System awake"));
      wdt_reset(); // Reset the watchdog timer to not trigger when not needed

      // Put your code here
      delay(2000);
      // if your code take more than 8 sec to process 
      //  dont forget to put some wdt_reset() or the watchdog will be triggered


      Serial.println(F("System sleep"));
      Serial.flush(); // always put a Serial.flush() before sleep
                      // to let the serial finished its task before starting sleep mode
      sleep(SLEEP_LOOP);
  }

}

// Init the watchdog timer interrupt
void configure_wdt(void)
{

  cli();                           // disable interrupts for changing the registers

  MCUSR = 0;                       // reset status register flags

                                   // Put timer in interrupt-only mode:                                       
  WDTCSR |= 0b00011000;            // Set WDCE (5th from left) and WDE (4th from left) to enter config mode,
                                   // using bitwise OR assignment (leaves other bits unchanged).
  WDTCSR =  0b01000000 | 0b100001; // set WDIE: interrupt enabled
                                   // clr WDE: reset disabled
                                   // and set delay interval (right side of bar) to 8 seconds

  sei();                           // re-enable interrupts 
}

// interrupt raised by the watchdog firing
ISR(WDT_vect)
{
    if(nbr_remaining > 0)
    {
        nbr_remaining = nbr_remaining - 1;
        wdt_reset();
    }
    else
    {
        MCUSR = 0;                          // reset flags

        WDTCSR |= 0b00011000;               // Enter config mode.
        WDTCSR =  0b00001000 | 0b000000;    // clr WDIE (interrupt enable...7th from left)
                                              // set WDE (reset enable...4th from left), and set delay interval
                                              // reset system in 16 ms...
                                              // unless wdt_disable() in loop() is reached first
       while(1);          

    }
}


void sleep(int ncycles)
{ 
  nbr_remaining = ncycles;
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);

  power_adc_disable();

  while (nbr_remaining > 0){ 
    sleep_mode();
    sleep_disable();
  }
  power_all_enable();

}

Implementation

The Arduino sketch used into the MVP water can be find here. Into this sketch we use a library that we created to facilitate the implementation of the sensors. The library can be find here.

Below you have a sample on how to use the library:

#include <WaterSensor.h>

#define address_PH 99         // default PH sensor adress on I2C port
#define address_DO 97         // default DO sensor adress on I2C port
#define DS18S20_Pin 2         // digital pin of the temperature sensor (outside the box)
#define DHT_PIN 3             // digital pin of the humidity and temperature sensor (inside the box)

#define TemperatureSensorsPowerPin 8
#define EzoDOSensorPowerPin 6
#define EzoPHSensorPowerPin 7

float temperatureBox, humidityBox, temperature;
char dataPH[10] , dataDO[10];

WaterSensor waterSensor(DS18S20_Pin,DHT_PIN, address_PH,address_DO);

void setup()
{
  // Open serial communications and wait for port to open:
  Serial.begin(38400);

  Wire.begin();           //enable I2C port.

  pinMode(TemperatureSensorsPowerPin,OUTPUT);    // Switch on power for temp and humdity sensor
  pinMode(EzoDOSensorPowerPin,OUTPUT);          // Switch on power for DO sensor
  pinMode(EzoPHSensorPowerPin,OUTPUT);          // Switch on power for PH sensor

  digitalWrite(TemperatureSensorsPowerPin,HIGH);
  digitalWrite(EzoPHSensorPowerPin,HIGH);
  digitalWrite(EzoDOSensorPowerPin,HIGH);

}

void loop(void)
{
  dataPH[0] = '\0';
  dataDO[0] = '\0';

  delay(200);

  // Wake up DO and PH sensors 
  waterSensor.wakeUpPHSensor();
  waterSensor.wakeUpDOSensor();

  temperature = waterSensor.getTemperatureValue();
  Serial.print(F("Temp in water : "));
  Serial.println(temperature);

  delay(1000);

  waterSensor.getInboxTemperatureHumidityValue(&temperatureBox,&humidityBox);
  Serial.print(F("Temp in box : "));
  Serial.println(temperatureBox);
  Serial.print(F("Humidity : "));
  Serial.println(humidityBox);

  delay(1000);

  waterSensor.sendTempToDOSensor(temperature);   // Send the temperature to DO sensor to precise the reading value
  waterSensor.getDOSensorValue(dataDO);          // Receive the DO value
  Serial.print(F("DO : "));
  Serial.println(dataDO);

  delay(1000);

  waterSensor.sendTempToPHSensor(temperature);     // Send the temperature to PH sensor to precise the reading of the value
  waterSensor.getPHSensorValue(dataPH);            // Receive the PH value
  Serial.print(F("PH : "));
  Serial.println(dataPH);

  delay(1000);

  waterSensor.sleepPHSensor();             // Put the PH sensor in sleep mode to save battery power
  waterSensor.sleepDOSensor();             // Put the DO sensor in sleep mode to save battery power
  delay(4000);
}