Arduino Tide Gauge

Updated 28 April 2022

I used an Arduino and a US-100 ultrasonic sensor to make an extremely low-cost tide gauge.

This project measures the tide by sensing the distance to water using a US-100 ultrasonic sensor. The device has a 3D printed base and uses a peanut butter jar to create a waterproof housing. The 3D printed base fits atop a PVC tube which acts as a stilling well to dampen wave effects. Timestamped data is logged to a microSD card while the instantaneous results are displayed on an optional screen or blinked on an LED. The aim of the design was to make an extremely cheap tide gauge so that multiple units could be made which would essentially be disposable.

To Create this project the following components were used:

Schematic

arduino-tide-gauge-schematic

How I built this project:

The build for this project mostly consisted of connecting existing modules, though there were a couple of modifications.

The 3.3v regulator on the Arduino Pro Mini is in not very efficient, so removing it will help reduce power consumption of the device. All of the modules are 3.3v compatible too, so a 3.3v step-up regulator is used to supply VCC directly.

The power LED on the DS1307 isn't necessary and was removed to reduce power consumption.

The US-100 ultrasonic sensor is being used in serial mode hence the jumper in the centre of the module is bridged with solder.

The modified components were orientated on the perf board and soldered using header pin. The ribbon cable was used to connect the components as per the circuit diagram. The 5110 LCD screen was connected via male and female header pins so the one screen could be used for checking multiple tide gauges. The AA holder was attached to the back of the perf board using some silicone.

arduino-tide-gauge-assembly-1

arduino-tide-gauge-assembly-2

A 3D printed base was designed using Tinkercad which held the perfboard, had a thread to fit the peanut butter jar and would slot over the PVC pipe. Additionally, a small filter piece was printed to fit the bottom of the pipe and further reduce waves action in the stilling tube. The filter piece has a coarse grill on the base, small funnel orifice and slot for foam filter. The STL files are attached below.

arduino-tide-gauge-assembly-3

To protect the LCD I printed the Nokia 5110 LCD case by villamany, published on November 21, 2013 www.thingiverse.com/thing:188205

arduino-tide-gauge-assembly-4

Code

Code for the project was written with the Arduino IDE and is linked at the bottom of this project write-up, so you can download and use it yourself. The code makes use of open source libraries available in the Arduino IDE, and I would like to thank the various library authors for their contributions.

// Ultrasonic Distance logger based on us100
//
//   Get dist
//   Get temp
//   Log to SD card
//   Wait until next sample
//   Minimise power usage where possible
//
//
//

// User setup variables
int scheduleFreq = 20;      // logging freq in seconds
#define avgSamples 70       // number of distance sample measured
#define edgeSamples 10      // number of samples discarded at upper and lower range
#define minRge 300          // minimum distance
#define maxRge 2000         // maximum range
#define debugPlot 0             // debug plot mode

String ver = "1.0.0";     // version number (5 char max)

///////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////
// Date and time functions (DS1307 I2C RTC)
#include <Wire.h>
#include <RTClib.h>
// SD Card Functions
#include <SdFat.h>
SdFat SD;
// Powersaving functions
#include <LowPower.h>
// us100 functions
#include <SoftwareSerial.h>
// lcd
#include <avr/pgmspace.h>
#include <SPI.h>


#define CLK  2
#define DIN  3
#define DC  4
#define CE  5
#define RST 6
#define US100_RX 7
#define US100_TX 8
#define powPin 9          // Power for us100
#define chipSelect 10     // SD chip select
#define mosiPin 11
#define misoPin 12
#define clkPin 14  // RTC Power
#define analogPin 1 // battery monitor pin (max 3.3v)

#define maxTrys 20  // attempts to get bytes from us100
#define maxWaits 10  // delays for bytes ready from us100

// New serial channel Instance
SoftwareSerial portUS100(US100_RX, US100_TX);

// Real Time Clock
RTC_DS3231 RTC;

char fname[13] = {'\0'}; //file length+1 (8.3 format)
unsigned long future = 0;
char buf[20];

static const byte ASCII[][5] PROGMEM =
{
  {0x00, 0x00, 0x00, 0x00, 0x00} // 20
  , {0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
  , {0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
  , {0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
  , {0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
  , {0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
  , {0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
  , {0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
  , {0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
  , {0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
  , {0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
  , {0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
  , {0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
  , {0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
  , {0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
  , {0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
  , {0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
  , {0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
  , {0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
  , {0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
  , {0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
  , {0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
  , {0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
  , {0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
  , {0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
  , {0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
  , {0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
  , {0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
  , {0x08, 0x14, 0x22, 0x41, 0x00} // 3c <
  , {0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
  , {0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
  , {0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
  , {0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
  , {0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
  , {0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
  , {0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
  , {0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
  , {0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
  , {0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
  , {0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
  , {0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
  , {0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
  , {0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
  , {0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
  , {0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
  , {0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
  , {0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
  , {0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
  , {0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
  , {0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
  , {0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
  , {0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
  , {0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
  , {0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
  , {0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
  , {0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
  , {0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
  , {0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
  , {0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
  , {0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
  , {0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥
  , {0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
  , {0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
  , {0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
  , {0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
  , {0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
  , {0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
  , {0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
  , {0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
  , {0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
  , {0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
  , {0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
  , {0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
  , {0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
  , {0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j
  , {0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
  , {0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
  , {0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
  , {0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
  , {0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
  , {0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
  , {0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
  , {0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
  , {0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
  , {0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
  , {0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
  , {0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
  , {0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
  , {0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
  , {0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
  , {0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
  , {0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
  , {0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
  , {0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
  , {0x10, 0x08, 0x08, 0x10, 0x08} // 7e ←
  , {0x78, 0x46, 0x41, 0x46, 0x78} // 7f →
};
//////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////
void setup() {
  // initialise serial for PC
  Serial.begin(9600);
  Serial.println();
  Serial.println(F("'------ Restarted - serial up ------"));
  Serial.print(F("'Dist.logr 'v"));
  Serial.println(ver);
  Serial.print(F("'Data logging frequency (seconds): '"));
  Serial.println(scheduleFreq);
  Serial.print(F("'Distance samples being averaged: '"));
  Serial.println(avgSamples);
  Serial.println();

  if (debugPlot) scheduleFreq = 0;

  // initialise us100
  pinMode(powPin, OUTPUT);
  digitalWrite(powPin, HIGH);
  pinMode(US100_RX, INPUT);
  pinMode(US100_TX, OUTPUT);
  portUS100.begin(9600);

  // Check RTC is running
  pinMode(clkPin, OUTPUT);
  digitalWrite(clkPin, HIGH);
  RTC.begin();
  if (RTC.lostPower()) startupError(1);

  // Create dataFile name
  DateTime now = RTC.now();
  sprintf(fname, "%02d%02d%02d%02d.csv", now.month(), now.day(), now.hour(), now.minute());

  // pulling up the SPI lines at the start of Setup with 328p’s internal resistors
  pinMode(chipSelect, OUTPUT); digitalWrite(chipSelect, HIGH); //pullup SD CS pin
  pinMode(mosiPin, OUTPUT); digitalWrite(mosiPin, HIGH);//pullup the MOSI pin
  pinMode(misoPin, INPUT); digitalWrite(misoPin, HIGH); //pullup the MISO pin
  delay(1);

  // set date time callback function
  SdFile::dateTimeCallback(dateTime);

  // see if the card is present and can be initialized:
  Serial.println(F("'Initializing SD card..."));
  if (!SD.begin(chipSelect)) startupError(2);
  Serial.println(F("'Card initialized"));


  // open the file. If the file is available, write headers.
  File dataFile = SD.open(fname, FILE_WRITE);
  if (dataFile) {
    dataFile.print(F("Dist.logr v"));
    dataFile.println(ver);
    dataFile.print("File name: ");
    dataFile.println(fname);
    dataFile.print(F("Data logging frequency (seconds): "));
    dataFile.println(scheduleFreq);
    dataFile.print(F("Distance samples: "));
    dataFile.println(avgSamples);
    dataFile.print(F("Discard upper and lower samples: "));
    dataFile.println(edgeSamples);
    dataFile.print(F("Minimum range (mm): "));
    dataFile.println(minRge);
    dataFile.print(F("Maximum range (mm): "));
    dataFile.println(maxRge);
    dataFile.println();
    dataFile.println(F("AEST Date Time (dd/mm/yyyy hh:mm:ss),Temperature (deg C),Distance (mm),Dist stddev (mm),Voltage (V)"));
    dataFile.close();
  }

 
  // setup lcd
  pinMode(RST, OUTPUT);
  pinMode(CE, OUTPUT);
  pinMode(DC, OUTPUT);
  pinMode(DIN, OUTPUT);
  pinMode(CLK, OUTPUT);
  digitalWrite(RST, LOW);
  digitalWrite(RST, HIGH);

  LcdWriteCmd(0x21);  // LCD extended commands
  LcdWriteCmd(0xb2);  // set LCD Vop (contrast)
  LcdWriteCmd(0x04);  // set temp coefficent
  LcdWriteCmd(0x15);  // LCD bias mode 1:40
  LcdWriteCmd(0x20);  // LCD basic commands
  LcdWriteCmd(0x0C);  // LCD normal video

  for (int i = 0; i < 504; i++) LcdWriteData(0x00); // clear LCD
  LcdXY(0, 1);
  LcdWriteString("Dist:");
  LcdXY(0, 2);
  LcdWriteString("StdDev:");
  LcdXY(0, 3);
  LcdWriteString("Temp:");
  LcdXY(0, 4);
  LcdWriteString("Voltage:");

  // set schedule frequency and determine the time for first data point
  now = RTC.now();
  future = (now.unixtime() - (now.unixtime() % scheduleFreq) + scheduleFreq);

  // turn off RTC
  digitalWrite(clkPin, LOW);
}
/////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////
void loop() {
  digitalWrite(clkPin, HIGH);
  delay(1);
  DateTime now = RTC.now();

  if (now.unixtime() >= future) { // waited long enough??
    Serial.println(F("'------------"));

    digitalWrite(powPin, HIGH);
    pinMode(US100_RX, INPUT);
    pinMode(US100_TX, OUTPUT);
    portUS100.begin(9600);

    String dataString = "";
    sprintf(buf, "%02d/%02d/%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
    dataString += (buf);

    getDist();  // first echo always crap - dump it
    // get distances
    int sampleIndex = 0;
    int samples[avgSamples];
    Serial.println(F("'myDist (mm): "));
    while (sampleIndex < avgSamples) {
      int myDist = getDist();
      if ((myDist > minRge) && (myDist < maxRge)) {
        if (debugPlot) Serial.print("0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 ");
        Serial.println(myDist, DEC);
        samples[sampleIndex] = myDist;
        sampleIndex++;
      }
    }

    if (!debugPlot) {
      // sort values
      printArray(samples, avgSamples);
      isort(samples, avgSamples);
      printArray(samples, avgSamples);

      // average distances
      float myAverage = average(samples, avgSamples);
      float myStddev = stddev(samples, avgSamples, myAverage);

      // get temperature
      int myTemp = getTemp();
      Serial.print(F("myTemp '")); Serial.print(myTemp, DEC); Serial.println(F(" degC"));
      // turn off ultrasonic
      digitalWrite(powPin, LOW);
      pinMode(US100_TX, INPUT);

      dtostrf(myTemp, 2, 0, buf);
      dataString += (",");
      dataString += (buf);
      LcdXY (50, 3);
      LcdWriteString(buf);

      dtostrf(myAverage, 1, 0, buf);
      dataString += (",");
      dataString += (buf);
      LcdXY (50, 1);
      strcat(buf, " "); //add a space
      LcdWriteString(buf);

      dtostrf(myStddev, 1, 0, buf);
      dataString += (",");
      dataString += (buf);
      LcdXY (50, 2);
      strcat(buf, "   "); //add a space
      LcdWriteString(buf);

      int sensorValue = analogRead(analogPin);    // read the voltage input pin
      float voltage = sensorValue * (3.3 / 1023.0);
      dtostrf(voltage, 1, 2, buf);
      dataString += (",");
      dataString += (buf);
      LcdXY (50, 4);
      strcat(buf, " "); //add a space
      LcdWriteString(buf);

      // open the file. If the file is available, write to it.
      File dataFile = SD.open(fname, FILE_WRITE);
      if (dataFile) {
        dataFile.println(dataString);
        dataFile.sync();
        Serial.println("done writing");
      }

      // replace commas so serial plotter still works
      while (dataString.indexOf(',') > -1) {
        dataString[dataString.indexOf(',')] = ';';
      }
      Serial.print("'");
      Serial.println(dataString);
      Serial.flush();


      //blink dist in cm
      SPI.end();
      pinMode(13, OUTPUT);
      LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF);
      blinks(((int)myAverage % 10000) / 1000);
      LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF);
      blinks(((int)myAverage % 1000) / 100);
      LowPower.powerDown(SLEEP_500MS, ADC_OFF, BOD_OFF);
      blinks((((int)myAverage % 100) + 5) / 10);
    }

    // find next time needed to log
    future = (now.unixtime() - (now.unixtime() % scheduleFreq) + scheduleFreq);

  }
  // wait until next sample
  if (!debugPlot) {
    digitalWrite(clkPin, LOW); // turn off RTC
    LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);
  }
}
/////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////
void portUS100Flush() {
  // function to flush us100 software serial port
  while (portUS100.available() > 0) {
    char t = portUS100.read();
  }
}
/////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////
int getDist() {
  // function to get distance from US100
  int trys = 0;
  int waits = 0;
  int mmDist = 0;
  unsigned int MSByteDist = 0;
  unsigned int LSByteDist = 0;
  while ((mmDist == 0) && (trys++ < maxTrys)) {
    portUS100Flush(); // clears the buffer serial port
    portUS100.write(0x55); // distance measuring order
    waits = 0;
    while ((portUS100.available() < 2) && (waits++ < maxWaits)) delay(10);
    if (waits < maxWaits) { // must be 2 bytes available
      MSByteDist = portUS100.read(); // reading both bytes
      LSByteDist  = portUS100.read();
      mmDist  = MSByteDist * 256 + LSByteDist; // distance
      if ((mmDist < minRge) || (mmDist > maxRge)) {
        mmDist = 0; // checking the distance within range
      }
    }
  }
  // debuging
  //Serial.print("Dist trys: "); Serial.print(trys, DEC);
  //Serial.print(", Dist waits: "); Serial.println(waits, DEC);
  //Serial.print("Dist: "); Serial.print(mmDist, DEC); Serial.println(" mm");

  return mmDist;
}
/////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////
int getTemp() {
  // function to get temp from US100
  int trys = 0;
  int waits = 0;
  int temp = -99;
  while ((temp == -99) && (trys++ < maxTrys)) {
    portUS100Flush(); // clears the buffer serial port
    portUS100.write(0x50); // distance measuring order
    waits = 0;
    while ((portUS100.available() < 1) && (waits++ < maxWaits)) delay(10);
    if (waits < maxWaits) { // must be 1 bytes available
      temp = portUS100.read(); // reading byte
      if ((temp < 1) || (temp > 130)) {
        temp = -99; // temp error - try again
      }
      else temp -= 45; // correct offset 45ºC
    }
  }
  // debuging
  //Serial.print("Temp trys: ");   Serial.print(trys, DEC);
  //Serial.print(", Temp waits: ");  Serial.println(waits, DEC);
  //Serial.print("Temp: "); Serial.print(temp, DEC); Serial.println(" degC.");

  return temp;
}
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
void startupError(int err) {
  digitalWrite(clkPin, LOW);
  // turn off ultrasonic
  digitalWrite(powPin, LOW);
  pinMode(US100_TX, INPUT);
  switch (err) {
    case 1:
      Serial.println(F("RTC Error!"));
      break;
    case 2:
      Serial.println(F("SD Error!"));
      break;
    default:
      Serial.println(F("Error!"));
      break;
  }
  Serial.flush();  // get ready for sleep
  LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
void blinks(int blinkx) {
  int count = 0;
  while (count++ < blinkx) {
    digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
    LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);              // wait for 250 ms
    digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
    LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);              // wait for 250 ms
  }
}
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// call back for file timestamps
void dateTime(uint16_t* date, uint16_t* time) {
  DateTime now = RTC.now();

  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(now.year(), now.month(), now.day());

  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(now.hour(), now.minute(), now.second());
}
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
float average (int * array, int len) { // assuming array is int.
  long sum = 0L ;  // sum will be larger than an item, long for safety.
  for (int i = edgeSamples; i < (len - edgeSamples) ; i++)
    sum += array [i] ;
  return  ((float) sum) / (len - (2 * edgeSamples)) ; // average will be fractional, so float may be appropriate.
}
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
float stddev (int * array, int len, float avg) { // assuming array is int.
  float sqDevSum = 0.0;
  for (int i = edgeSamples; i < (len - edgeSamples); i++) {
    sqDevSum += pow((avg - float(array[i])), 2);
  }
  return  sqrt(sqDevSum / float(len - 2 * edgeSamples));
}
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
void LcdWriteString(char *characters) {
  while (*characters) LcdWriteCharacter(*characters++);
}
/////////////////////////////////////////////////

/////////////////////////////////////////////////
void LcdWriteCharacter(char character) {
  for (int i = 0; i < 5; i++) LcdWriteData(pgm_read_byte (&(ASCII[character - 0x20][i])));
  LcdWriteData(0x00);
}
/////////////////////////////////////////////////

/////////////////////////////////////////////////
void LcdWriteData(byte dat) {
  digitalWrite(DC, HIGH); //DC pin is low for commands
  digitalWrite(CE, LOW);
  shiftOut(DIN, CLK, MSBFIRST, dat); //transmit serial data
  digitalWrite(CE, HIGH);
}
/////////////////////////////////////////////////

/////////////////////////////////////////////////
void LcdXY(int x, int y) {
  LcdWriteCmd(0x80 | x);  // Column.
  LcdWriteCmd(0x40 | y);  // Row.
}
//////////////////////////////////////////////////

//////////////////////////////////////////////////
void LcdWriteCmd(byte cmd) {
  digitalWrite(DC, LOW); //DC pin is low for commands
  digitalWrite(CE, LOW);
  shiftOut(DIN, CLK, MSBFIRST, cmd); //transmit serial data
  digitalWrite(CE, HIGH);
}
//////////////////////////////////////////////////

//////////////////////////////////////////////////
// sort
void isort(int *a, int n) {
  for (int i = 1; i < n; ++i)  {
    int j = a[i];
    int k;
    for (k = i - 1; (k >= 0) && (j < a[k]); k--) {
      a[k + 1] = a[k];
    }
    a[k + 1] = j;
  }
}
//////////////////////////////////////////////////

//////////////////////////////////////////////////
// print int array
void printArray(int *a, int n) {
  for (int i = 0; i < n; i++)  {
    Serial.print("'");
    Serial.print(a[i], DEC);
    Serial.print(" ");
  }
  Serial.println();
}
/////////////////////////////////////////////////////

The code performs some initialisation routines, checks for SD card insertion, RTC functionality etc and then begins logging distance to water. The user is able to set the frequency of samples [default 20sec] and the minimum and maximum distances [default 300mm and 2000mm]. To produce good results each sample consists of 70 measurements, the highest 10 and lowest 10 of which are discarded and the remaining 50 points are averaged. The values are logged to the SD card, displayed on the LCD and blinked via the Arduino LED. Also, provision is made for a debug mode where when enabled values are sent to the Arduino serial port for plotting in real time using the Arduino IDE Serial Plotter.

The Arduino Set RTC time example was modified to allow the user to set the time on the first use. This was necessary as it also needed to switch on the RTC via Arduino GPIO14 first so that no hardware changes were needed. Additionally, as I made several of these devices I wanted them to blink at exactly the same time (like fairway beacon markers) so that at night they all lit up like fairway beacons. To achieve this, the user entered times wouldn't cut it so I wrote some python code which would send the values from a users PC to the Arduino programmatically meaning all the devices had exactly the same time. Code for both scenarios is linked below.

arduino-tide-gauge-assembly-5

Troubleshooting

It was noted that the US-100 ultrasonic sensor was not able to measure the water level in the pipe correctly in a test tank and was giving erroneous results as a consequence of echoes being returned from the side walls of the pipe. Essentially what was required was a sensor with a narrow beam that would go straight down the centre of the tube (think laser) not with a wide beam (like a torch). Rather than change the sensor, the problem was rectified by lining the first 12cm of the inside of the pipe with a thin sheet of stick-on foam sheet cut to size. This was able to absorb any ultrasonic energy which was not going down the centre of the pipe and reflecting off of the sides.

Testing

The PVC tube was attached to a star picket on the beach at low tide and the device was left to log data for a few hours. A plot of the logged data is shown below, it can be seen that as the tide came up the distance to water went from 2000mm to 1100mm over 5 hours.

arduino-tide-gauge-completed-project

arduino-tide-gauge-graphed-sample-data

Attachment - Project Files

Have a question? Ask the Author of this guide today!

Please enter minimum 20 characters

Your comment will be posted (automatically) on our Support Forum which is publicly accessible. Don't enter private information, such as your phone number.

Expect a quick reply during business hours, many of us check-in over the weekend as well.

Comments


Loading...
Feedback

Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.

Makers love reviews as much as you do, please follow this link to review the products you have purchased.