Water Tank Level Display Help

First of all, all credit goes to Boris Landoni for coming up with this project. It can be found Here: http://www.open-electronics.org/water-tank-level-display-with-arduino/#.

Ok, the ultrasonic sensor "measures" the water level, and if it does not meet a certain threshold, a buzzer goes off. If it does not meet another threshold, the buzzing gets louder. Also, it will display the level on a 16X2 LCD screen. However, this error that I am getting may be unrelated to this, but I would still like an anwser.

Here's the code:

/* -*- mode: c -*- */
/**
 * pozzo.pde
 * version: 1.2
 */
 
#include <LiquidCrystal.h>
 
#define PING_PIN 13
#define BUZZER_PIN 8
#define SWITCH_INT 0 /* 0 => pin 2 */
#define PI 3.1415926535898
#define SUPERFICE_BASE (R_POZZO * R_POZZO * PI)
#define SIZE_BAR (16 * 5)
#define ALARM_ICON 0 /* code */
#define SOUND_ICON 6 /* code */
#define SOUND_ICON_ON 7 /* code */
 
#define R_POZZO 0.5 /* raggio pozzo (m) */
#define H_POZZO 146.0 /* cm */
#define SOGLIA_ALLARME_1 100 /* cm */
#define SOGLIA_ALLARME_2 120 /* cm */
#define DELAY_0 60000 /* ms; 1000 * 60 * 1 = 1 min */
#define DELAY_1 600 /* ms */
#define DELAY_2 200 /* ms */
 
/* initialize the library with the numbers of the interface pins */
LiquidCrystal lcd(12, 11, 5, 4, 3, 6);
 
int mute = 0;
 
byte *getChar(int n, byte newChar[]) {
  int i;
  byte code[5] = {
    B10000,
    B11000,
    B11100,
    B11110,
    B11111};
 
  for (i = 0; i < 8; i++)
    newChar[i] = code[n - 1];
 
  return newChar;
}
 
void setup() {
  int i;
  float h;
  byte newChar[8];
 
  /* set up the LCD's number of rows and columns: */
  lcd.begin(16, 2);
 
  for (i = 1; i < 6; i++)
    lcd.createChar(i, getChar(i, newChar));
 
  newChar = {
    B00000,
    B00100,
    B01010,
    B01010,
    B11111,
    B00100,
    B00000,
  };
 
  lcd.createChar(ALARM_ICON, newChar);
 
  newChar = {
    B00011,
    B00101,
    B11001,
    B11001,
    B11001,
    B00101,
    B00011,
  };
 
  lcd.createChar(SOUND_ICON, newChar);
 
  newChar = {
    B00100,
    B10010,
    B01001,
    B01001,
    B01001,
    B10010,
    B00100,
  };
 
  lcd.createChar(SOUND_ICON_ON, newChar);  
 
  pinMode(BUZZER_PIN, OUTPUT);
 
  /**
   * LOW to trigger the interrupt whenever the pin is low,
   * CHANGE to trigger the interrupt whenever the pin changes value
   * RISING to trigger when the pin goes from low to high,
   * FALLING for when the pin goes from high to low.
   */
  attachInterrupt(SWITCH_INT, button, RISING);
 
  /* initialize serial communication */
  Serial.begin(9600);
}
 
void loop() {
  long hWatherCm;
  int litres;
 
  hWatherCm = read_height();
  if (check_alarm(hWatherCm) != 0) /* read again wather height */
    hWatherCm = read_height();
 
  lcd.clear();
 
  print_histogram(hWatherCm);
 
  lcd.setCursor(0, 1);
 
  lcd.print(hWatherCm);
  lcd.print(" cm - ");
 
  // litres = SUPERFICE_BASE * (hWather / 100.0) * 1000
  litres = floor(SUPERFICE_BASE * hWatherCm * 10);
  lcd.print(litres);
  lcd.print(" l ");
 
  lcd.setCursor(14, 1);
  lcd.write(SOUND_ICON);
  lcd.setCursor(15, 1);
  if (!mute)
    lcd.write(SOUND_ICON_ON);
  else
    lcd.write('X');
 
/*
  Serial.print("cm = ");
  Serial.println(hWatherCm);
*/
 
  switch (check_alarm(hWatherCm)) {
  case 1:
    lcd.setCursor(0, 0);
    lcd.write(ALARM_ICON);
 
    buzz(200);
    delay(DELAY_1);
    break;
 
  case 2:
    lcd.setCursor(0, 0);
    lcd.write(ALARM_ICON);
 
    buzz(200);
    delay(200);
    buzz(200);
    delay(DELAY_2);
    break;
 
  case 0: // no alarm
    delay(DELAY_0);
  }
}
 
void print_histogram(int hWatherCm) {
  int i;
  int bloks;
  float histogram;
 
  // hWatherCm : HPOZZO = histogram : SIZE_BAR
  histogram = (SIZE_BAR * hWatherCm) / H_POZZO;
  histogram = histogram + 0.5;
 
  bloks = (int)histogram / 5;
 
  for (i = 0; i < bloks; i++)
    lcd.write(5);
 
  if ((int)(histogram) % 5 > 0)
    lcd.write((int)(histogram) % 5);
}
 
long read_height() {
  /**
   * establish variables for duration of the ping,
   * and the distance result in centimeters:
   */
  long duration, hWatherCm;
 
  /**
   * The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
   * Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
   */
  pinMode(PING_PIN, OUTPUT);
  digitalWrite(PING_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(PING_PIN, HIGH);
  delayMicroseconds(5);
  digitalWrite(PING_PIN, LOW);
 
  /**
   * The same pin is used to read the signal from the PING))): a HIGH
   * pulse whose duration is the time (in microseconds) from the sending
   * of the ping to the reception of its echo off of an object.
   */
  pinMode(PING_PIN, INPUT);
  duration = pulseIn(PING_PIN, HIGH);
 
  /* convert the time into a distance */
  hWatherCm = H_POZZO - microseconds_to_centimeters(duration);
 
  if (hWatherCm < 0)
    return 0;
 
  if (hWatherCm > H_POZZO)
    return H_POZZO;
 
  return hWatherCm;
}
 
void buzz(int msec) {
  if (!mute)
    digitalWrite(BUZZER_PIN, HIGH);
  delay(msec);
  digitalWrite(BUZZER_PIN, LOW);
}
 
int check_alarm(int hWatherCm) {
  if (hWatherCm > SOGLIA_ALLARME_1) {
     if (hWatherCm < SOGLIA_ALLARME_2)
       return 1;
     else
       return 2;
  }
  return 0;
}
 
long microseconds_to_centimeters(long microseconds) {
  /**
   * The speed of sound is 340.29 m/s or 29.4 microseconds per centimeter.
   * The ping travels out and back, so to find the distance of the
   * object we take half of the distance travelled.
   */
  return microseconds / 29.387 / 2;
}
 
void button() {
  //  Serial.println("Pulsante premuto");
  mute = !mute;
 
  lcd.setCursor(15, 1);
  if (!mute)
    lcd.write(SOUND_ICON_ON);
  else
    lcd.write('X');
}

Here is the error message:

sketch_mar13a.ino: In function 'void setup()':
sketch_mar13a:58: error: expected primary-expression before '{' token
sketch_mar13a:58: error: expected `;' before '{' token
sketch_mar13a:70: error: expected primary-expression before '{' token
sketch_mar13a:70: error: expected `;' before '{' token
sketch_mar13a:82: error: expected primary-expression before '{' token
sketch_mar13a:82: error: expected `;' before '{' token
sketch_mar13a.ino: In function 'void loop()':
sketch_mar13a:146: error: call of overloaded 'write(int)' is ambiguous
C:\arduino-1.0.3-windows\arduino-1.0.3\libraries\LiquidCrystal/LiquidCrystal.h:82: note: candidates are: virtual size_t LiquidCrystal::write(uint8_t)
C:\arduino-1.0.3-windows\arduino-1.0.3\hardware\arduino\cores\arduino/Print.h:49: note:                 size_t Print::write(const char*)
sketch_mar13a:154: error: call of overloaded 'write(int)' is ambiguous
C:\arduino-1.0.3-windows\arduino-1.0.3\libraries\LiquidCrystal/LiquidCrystal.h:82: note: candidates are: virtual size_t LiquidCrystal::write(uint8_t)
C:\arduino-1.0.3-windows\arduino-1.0.3\hardware\arduino\cores\arduino/Print.h:49: note:                 size_t Print::write(const char*)

I want to find out what these errors are requiring me to do in order to make this work. I have tried the following:

  1. Change newChar to byte
  2. Change newChar to createChar
  3. Adding "tokens" where the error report reports an error (such as ; or () or {})

Any help would be very appreciated, and thanks in advance.
LinktheInventor

Hello :slight_smile:

The problem, is that you can't set an array like you did, you need to do it manually for each array index, exemple:

newChar[0] = B00000;
newChar[1] = B00100;
...

PS: good idea for getting water level, I will do the same for a next project :slight_smile: Can you tell me if it's accurate?

Hi, in addition to what already suggested by guix, the lcd.write() function seems to require an argument of a specific type: either a uint8_t (unsigned 8 bit integer) or a string (in the C sense of array of chars). Passing 0 is ambiguous since it may be interpreted as an address. You may change:

#define ALARM_ICON 0

into

const uint8_t ALARM_ICON = 0;

(the same for the other occurrences of the error).

OK, I changed the code to this:

byte *getChar(int n, byte newChar[]) {
  int i;
  byte code[5] = {
   newChar[0] = B10000,
   newChar[1] =  B11000,
   newChar[2] = B11100,
   newChar[3] = B11110,
   newChar[4] = B11111};
 
  for (i = 0; i < 8; i++)
    newChar[i] = code[n - 1];
 
  return newChar;
}
 
void setup() {
  int i;
  float h;
  byte newChar[8];
 
  /* set up the LCD's number of rows and columns: */
  lcd.begin(16, 2);
 
  for (i = 1; i < 6; i++)
    lcd.createChar(i, getChar(i, newChar));
 
  newChar = {
    newChar[0] = B00000,
    newChar[1] = B00100,
    newChar[2] = B01010,
    newChar[3] = B01010,
    newChar[4] = B11111,
    newChar[5] = B00100,
    newChar[6] = B00000,
  };
 
  lcd.createChar(ALARM_ICON, newChar);
 
  newChar = {
    newChar[0] = B00011,
    newChar[1] = B00101,
    newChar[2] = B11001,
    newChar[3] = B11001,
    newChar[4] = B11001,
    newChar[5] = B00101,
    newChar[6] = B00011,
  };
 
  lcd.createChar(SOUND_ICON, newChar);
 
  newChar = {
    newChar[0] = B00100,
    newChar[1] = B10010,
    newChar[2] = B01001,
    newChar[3] = B01001,
    newChar[4] = B01001,
    newChar[5] = B10010,
    newChar[6] = B00100,
  };
 
  lcd.createChar(SOUND_ICON_ON, newChar);

And I get these errors:

sketch_mar16a.ino: In function 'void setup()':
sketch_mar16a:58: error: expected primary-expression before '{' token
sketch_mar16a:58: error: expected `;' before '{' token
sketch_mar16a:70: error: expected primary-expression before '{' token
sketch_mar16a:70: error: expected `;' before '{' token
sketch_mar16a:82: error: expected primary-expression before '{' token
sketch_mar16a:82: error: expected `;' before '{' token

Did I simply misunderstand you?

LinktheInventor:
OK, I changed the code to this:

  newChar = {

newChar[0] = B00100,
    newChar[1] = B10010,
    newChar[2] = B01001,
    newChar[3] = B01001,
    newChar[4] = B01001,
    newChar[5] = B10010,
    newChar[6] = B00100,
  };



... 
Did I simply misunderstand you?

I'm afraid you did. After the declaration -- where you are allowed to assign values to the whole array -- further assignments must be limited to individual elements, you cannot assign a whole array. As guix suggested:

newChar[0] = B00100;
newChar[1] = B10010;
newChar[2] = B01001;
newChar[3] = B01001;
newChar[4] = B01001;
newChar[5] = B10010;
newChar[6] = B00100;

These assignments are not enclosed in {} (and note the closing ';' instead of ',').
Finally, the change you made within getChar(), though syntactically correct, is probably not what you want.

byte code[5] = {
  newChar[0] = B10000, // this assigns B10000 to both newChar[0] and code[0]; is this what you want? 
...
}

So it would be this:

 newChar = {
    newChar[0] = B00000;
    newChar[1] = B00100;
    newChar[2] = B01010;
    newChar[3] = B01010;
    newChar[4] = B11111;
    newChar[5] = B00100;
    newChar[6] = B00000;
 }

?
Or this:

newChar[0] = B00000;
    newChar[1] = B00100;
    newChar[2] = B01010;
    newChar[3] = B01010;
    newChar[4] = B11111;
    newChar[5] = B00100;
    newChar[6] = B00000;

?
I am sorry that I am not good at this, but I am here to learn, and to make mistakes. I hope that you wil lunderstand that.

LinktheInventor:
So it would be this:

 newChar = {

newChar[0] = B00000;
    newChar[1] = B00100;
    newChar[2] = B01010;
    newChar[3] = B01010;
    newChar[4] = B11111;
    newChar[5] = B00100;
    newChar[6] = B00000;
}


?
Or this:


newChar[0] = B00000;
    newChar[1] = B00100;
    newChar[2] = B01010;
    newChar[3] = B01010;
    newChar[4] = B11111;
    newChar[5] = B00100;
    newChar[6] = B00000;


?
I am sorry that I am not good at this, but I am here to learn, and to make mistakes. I hope that you wil lunderstand that.

The second one (also get rid of the extra spaces, for readability). It's ok to make mistakes, but don't be afraid to try yourself and see what happens, you'll also learn to get used to error messages and their meaning.

Would it work the same for:

byte *getChar(int n, byte newChar[]) {
  int i;
  byte code[5] = {
   newChar[0] = B10000;
   newChar[1] = B11000;
   newChar[2] = B11100;
   newChar[3] = B11110;
   newChar[4] = B11111;
  }

?
Also, I get an error message for these lines stating that it expected unqualified-id and expected declaration: (I would try to fix them first, but I have no idea what they mean)

    for (i = 0; i < 8; i++) // error
    newChar[i] = code[n - 1];
 
  return newChar; // error on this line
} // and this one

Also, Error on this with the 'expected token' error message:

newChar[0] = B10000;

Would it work the same for:

You've been told NO enough, haven't you?

I'd like to utilize this for my home brewery, to detect water level in my HLT and BK. The plan is to utilize a 20x4 display and a series of blue LEDs (in a graphical array), with two waterproof ultrasonic sensors mounted in the ceiling above the pots.

I'd also like to convert this code to inches and US Gallons from their metric counterparts.

Future use will be integrating this with my electric brewery, which utilizes pumps and solenoids, so I can say "fill the hot liquor tank to 11 gallons", and the fill solenoid opens, then stops when the tank reaches the desired level.

In summary, here are the changes I'd like to make:

  • Two measurements instead of one
  • Remove Italian, replace with English (easy)
  • US units instead of metric. (easy with a little basic math)
  • The gauges will measure gallons in the pot, not inches of water (or cm) in the tank. Essentially all that's required is some basic calculations (which I have done already).
  • 20x4 display instead of 16x2
  • Array of LEDs, one for each 1/4 gallon level (that light when the level is reached, similar to how the histogram bar moves horizontally on the LCD in the current design). With a 15.5 gallon pot, this would mean 62 LEDs per pot. I may revise this to 1/2 gallon increments. Not sure yet, but I imagine that's a simple code change later.
  • New character for filling (water spigot maybe?)
  • No icon for sound or alarm - no need for that.
  • Manual input for desired fill level. Numeric keypad required with: 0-9, clear, decimal. Any suggestions on where I can custom-order such a keypad? My thought is a 4x3 keypad with the numbers, decimal, and clear, and a momentary switch to match my brew panel (round industrial pilots)
  • HLT gauge will be to fill and control solenoid, BK gauge will be just to monitor level, and volume drop through evaporation during boil.

I'm a complete newb to Arduino. I'm no stranger to electronics and to coding, but I lack the prerequisite knowledge of the Arduino itself, and I'll likely need some help here. I very much appreciate and value everyone's input here.

Here's what I have:

/* -*- mode: c -*- */
/**
 * WATER.pde
 * version: 1.2
 */
 
#include <LiquidCrystal.h>
 
#define HLT_PING_PIN 13   /* HLT Ping Sensor */
#define BK_PING_PIN 14    /* BK Ping Sensor */
#define HLT_FILL_PIN 8   /* HLT Fill Solenoid Output */
#define SIZE_BAR (16 * 5)
#define D_POT 16.0 /* diameter of pot (in) */
#define H_POT 18.0 /* height of pot (in) */
#define R_POT (D_POT / 2) /* radius of pot (in) = half of diameter */
#define PI 3.1415926535898
#define S_POT (R_POT * R_POT * PI)
 
/* initialize the library with the numbers of the interface pins */
LiquidCrystal lcd(12, 11, 5, 4, 3, 6);
 
int mute = 0;
 
byte *getChar(int n, byte newChar[]) {
  int i;
  byte code[5] = {
    B10000,
    B11000,
    B11100,
    B11110,
    B11111};
 
  for (i = 0; i < 8; i++)
    newChar[i] = code[n - 1];
 
  return newChar;
}
 
void setup() {
  int i;
  float h;
  byte newChar[8];
 
  /* set up the LCD's number of rows and columns: */
  lcd.begin(20, 4);
  Serial.begin(9600);
}
 
void loop() {
  long WaterLevelDistance;
  int gallons;
 
  WaterLevelDistance = read_height();
  if (check_alarm(WaterLevelDistance) != 0) /* read again water height */
    WaterLevelDistance = read_height();
 
  lcd.clear();
 
  print_histogram(WaterLevelDistance);
 
  lcd.setCursor(0, 1);
 
  lcd.print(WaterLevelDistance);
  lcd.print(" in - ");

 /* Gallons = surface area (R * R * PI) * height * .004329 */
  // gallons = S_POT * hWater * .004329
  gallons = floor(S_POT * WaterLevelDistance * .004329);
  lcd.print(gallons);
  lcd.print(" l ");
}
 
void print_histogram(int WaterLevelDistance) {
  int i;
  int bloks;
  float histogram;
 
  // WaterLevelDistance : HWATER = histogram : SIZE_BAR
  histogram = (SIZE_BAR * WaterLevelDistance) / H_POT;
  histogram = histogram + 0.5;
 
  bloks = (int)histogram / 5;
 
  for (i = 0; i < bloks; i++)
    lcd.write(5);
 
  if ((int)(histogram) % 5 > 0)
    lcd.write((int)(histogram) % 5);
}
 
long read_height() {
  /**
   * establish variables for duration of the ping,
   * and the distance result in centimeters:
   */
  long duration, WaterLevelDistance;
 
  /**
   * The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
   * Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
   * ALSO, ADDED THE SECOND SET OF INSTRUCTIONS, SINCE I HAVE 2 METERS...
   */
  pinMode(HLT_PING_PIN, OUTPUT);
  digitalWrite(HLT_PING_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(HLT_PING_PIN, HIGH);
  delayMicroseconds(5);
  digitalWrite(HLT_PING_PIN, LOW);

  pinMode(BK_PING_PIN, OUTPUT);
  digitalWrite(BK_PING_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(BK_PING_PIN, HIGH);
  delayMicroseconds(5);
  digitalWrite(BK_PING_PIN, LOW);
 
  /**
   * The same pin is used to read the signal from the PING))): a HIGH
   * pulse whose duration is the time (in microseconds) from the sending
   * of the ping to the reception of its echo off of an object.
   */
  pinMode(HLT_PING_PIN, INPUT);
  duration = pulseIn(HLT_PING_PIN, HIGH);
  
  pinMode(BK_PING_PIN, INPUT);
  duration = pulseIn(BKPING_PIN, HIGH);
 
  /* convert the time into a distance */
  WaterLevelDistance = H_POT - microseconds_to_inches(duration);
 
  if (WaterLevelDistance < 0)
    return 0;
 
  if (WaterLevelDistance > H_POT)
    return H_POT;
 
  return WaterLevelDistance;
}
 
void buzz(int msec) {
  if (!mute)
    digitalWrite(ALERT_PIN, HIGH);
  delay(msec);
  digitalWrite(ALERT_PIN, LOW);
}
 
int check_alarm(int WaterLevelDistance) {
  if (WaterLevelDistance > THRESHOLD_ALARM_1) {
     if (WaterLevelDistance < THRESHOLD_ALARM_2)
       return 1;
     else
       return 2;
  }
  return 0;
}
  /**
   * The speed of sound is 74.676 microseconds per inch
   * With a 16" diameter pot, this is 85.834 microseconds per gallon (round trip)
   */
long microseconds_to_inches(long microseconds) {
  return microseconds / 74.676 / 2;
}
long microseconds_to_gallons(long microseconds) {
  return microseconds / 85.834 / 2;
}

I know it needs a lot of help, but so far this is all I can figure out. Anyone want to help?

CraigMurphy:
In summary, here are the changes I'd like to make:

  • US units instead of metric. (easy with a little basic math)
    ...
  • Manual input for desired fill level. Numeric keypad required with: 0-9, clear, decimal. Any suggestions on where I can custom-order such a keypad? My thought is a 4x3 keypad with the numbers, decimal, and clear, and a momentary switch to match my brew panel (round industrial pilots)

The thing with using US units is, there are so many of them to choose from. If you find yourself using decimals with gallons, then try using quarts, or pints, instead.

Hi,

  for (i = 0; i < 8; i++)
    newChar[i] = code[n - 1];

  return newChar;

I think your for statement is missing some { and } brackets.

Tom.... :slight_smile:

TomGeorge:
Hi,

  for (i = 0; i < 8; i++)

newChar[i] = code[n - 1];

return newChar;



I think your for statement is missing some { and } brackets.

Tom.... :)

In this case, I think not.