Multiple I2C sensors and libraries using Arduino(s) on a Water Tank ( Wireless )

Hello!

I tried my best to get the title that explains my problems.

OK, I have project (another TOPIC for the complete information) that needs about 7 sensors and an I2C LCD with Mega 2560. I would glad to have all my questions and concerns answered.

  1. I’m using I2C UltraSonic sensor for water level and I want to control the water pump depending on the output.
    sounds easy, right? the problem is the pump will be connected to a second Arduino ( this will have only the pump ) and the goal is to have it connected to the main Arduino - has all the sensors connected - with wireless communication. So, whenever the level reachs a part ( next problem ) , the component responsible for wireless on the main Arduino will send a command to the second Arduino to start/close motor.
    My question is can I use this TYPE of component to achieve that? and what is the simple idea for writing its code?

  2. I wrote a code to achieve the controlling of the pump. ( not wireless, yet )

#define SCL_PIN 0            
#define SCL_PORT PORTD 
#define SDA_PIN 1                 
#define SDA_PORT PORTD 
#define I2C_TIMEOUT 100  

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>
#include <SoftI2CMaster.h>
int Relay = 7; // digital pin 7  

//LCD part Begin


#define BACKLIGHT_PIN 3 
//LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  //Set the LCD I2C address 

//LCD part End

void setup()
{
  // Initialize both the serial and I2C bus
  Serial.begin(9600);

  //LCD B

  lcd.begin(20,4);
  // LCD Backlight ON
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);

  //LCD E

 lcd.print("Smart Water Tank");

  pinMode(LED_BUILTIN, OUTPUT); // digital pin 13 built in LED
  
}

void loop()
{
read_the_sensor_example();
}


///////////////////////////////////////////////////
// Function: Start a range reading on the sensor //
///////////////////////////////////////////////////
//Uses the I2C library to start a sensor at the given address
//Collects and reports an error bit where: 1 = there was an error or 0 = there was no error.
//INPUTS: byte bit8address = the address of the sensor that we want to command a range reading
//OUPUTS: bit  errorlevel = reports if the function was successful in taking a range reading: 1 = the function
//  had an error, 0 = the function was successful
boolean start_sensor(byte bit8address){
  boolean errorlevel = 0;
  bit8address = bit8address & B11111110;               //Do a bitwise 'and' operation to force the last bit to be zero -- we are writing to the address.
  errorlevel = !i2c_start(bit8address) | errorlevel;   //Run i2c_start(address) while doing so, collect any errors where 1 = there was an error.
  errorlevel = !i2c_write(81) | errorlevel;            //Send the 'take range reading' command. (notice how the library has error = 0 so I had to use "!" (not) to invert the error)
  i2c_stop();
  return errorlevel;
}


///////////////////////////////////////////////////////////////////////
// Function: Read the range from the sensor at the specified address //
///////////////////////////////////////////////////////////////////////
//Uses the I2C library to read a sensor at the given address
//Collects errors and reports an invalid range of "0" if there was a problem.
//INPUTS: byte  bit8address = the address of the sensor to read from
//OUPUTS: int   range = the distance in cm that the sensor reported; if "0" there was a communication error
int read_sensor(byte bit8address){
  boolean errorlevel = 0;
  int range = 0;
  byte range_highbyte = 0;
  byte range_lowbyte = 0;
  bit8address = bit8address | B00000001;  //Do a bitwise 'or' operation to force the last bit to be 'one' -- we are reading from the address.
  errorlevel = !i2c_start(bit8address) | errorlevel;
  range_highbyte = i2c_read(0);           //Read a byte and send an ACK (acknowledge)
  range_lowbyte  = i2c_read(1);           //Read a byte and send a NACK to terminate the transmission
  i2c_stop();
  range = (range_highbyte * 256) + range_lowbyte;  //compile the range integer from the two bytes received.
  if(errorlevel){
    return 0;
  }
  else{
    return range;
  }
}

//////////////////////////////////////////////////////////
// Code Example: Read the sensor at the default address //
//////////////////////////////////////////////////////////
void read_the_sensor_example(){
  boolean error = 0;  //Create a bit to check for catch errors as needed.
  int range;
  int percentage;

  error = start_sensor(112);    //Start the sensor and collect any error codes.
  if (!error){                  //If you had an error starting the sensor there is little point in reading it as you will get old data.
    delay(100);
      range = read_sensor(112);   //reading the sensor will return an integer value -- if this value is 0 there was an error
      range = constrain(range, 25, 200);
      percentage = map(range, 200, 25, 0, 100); // 2nd and 3rd are low and max for tank
  }
      Serial.print("R:"); Serial.println(range);
      Serial.print("%" );Serial.println(percentage);
      lcd.setCursor(3,0);
      lcd.print("R="); lcd.println(range);
      lcd.setCursor(4,0);
      lcd.print("Water level "); 
      lcd.print("%"); lcd.println(percentage);

   if (percentage <= 25) 
    {
     digitalWrite(LED_BUILTIN, HIGH); //start the pump
     lcd.clear(); // test or remove
     lcd.print("Water Tank low ");
     lcd.setCursor(1,0);
     lcd.println("Motor Turned On");
     
     delay(500); 

    }
    else if  ( percentage == 100 )
    {digitalWrite(LED_BUILTIN, LOW);  //STOP motor
    lcd.clear(); // test or remove
    lcd.print("Water Tank Low ");
    lcd.setCursor(2,10);
    lcd.println("Motor On");
     
     delay(500); 
}
    
}

Used the built-in led to simulate the pump. Tried to test it, but the led ( pump ) doesn’t switch off.
I want the led to be HIGH only when the level reaches 25 percent or less and LOW only when the level is 100 percent. I’m not good in programming, not used to do a lot, but I want led to only respond to these conditions. I’m not sure if it has to do with the values in between.

  1. THE MAIN PROBLEM is that I’m using I2C UltraSonic that uses the SoftI2CMaster library and I2C LCD with the wire library. I tried first to compile and I got errors about multiple files. Deleted SI2CIO and LiquidCrystal_SI2C files ( h and cpp types ) and got them to upload correctly. Then, the sensor stops working and the lcd.print parts under void loop is not showing ( is it wrong? ). I’ve read there are problems with these libraries. I’m sure how to solve this. Used LCD alone and it was working fine. same with Ultrasonic alone. I’m using 4.7k pull-up resistors.

Another weird problem is even with SCL & SDA defined 0 and 1, I can’t establish a connection until I connect it to 21 and 20. am I missing something ?

Sorry for the long post and thank you in advance.

My question is can I use this TYPE of component to achieve that?

Depending on the range, you might, or might not, be able to.

and what is the simple idea for writing its code?

Use the RadioHead library. It does all the work.

I wrote a code to

There is a lot of that code that is commented out. Delete commented out code before posting, unless it is the commented out code that you have questions about.

I’m not sure if it has to do with the values in between.

You can’t write code until you know what you want the code to do. It seems to me that you’d want to start the pump when the level dropped to 25% (that is when percentage <= 25) and stop the pump when the level got to 95% (that is when percentage >= 95). When percentage is between 25 and 95, there is nothing to do. Just enjoy the sound of the pump running (or the look of the pretty light that pretends to be the pump).

Why would you use Wire for one I2C device and software I2C for the other? Why not just use Wire for both? Do they have the same address or something?

PaulS:
Depending on the range, you might, or might not, be able to.
Use the RadioHead library. It does all the work.
There is a lot of that code that is commented out. Delete commented out code before posting, unless it is the commented out code that you have questions about.
You can’t write code until you know what you want the code to do. It seems to me that you’d want to start the pump when the level dropped to 25% (that is when percentage <= 25) and stop the pump when the level got to 95% (that is when percentage >= 95). When percentage is between 25 and 95, there is nothing to do. Just enjoy the sound of the pump running (or the look of the pretty light that pretends to be the pump).

PaulS:
Depending on the range, you might, or might not, be able to.
Use the RadioHead library. It does all the work.
There is a lot of that code that is commented out. Delete commented out code before posting, unless it is the commented out code that you have questions about.

so I can use the component for wireless in my project ? I’m only using it to proof control over wireless not on a long distance or something. So its OK then.

And Sorry, I was trying to link commented to the third problem. Just to show if I’m writing it correctly.

When percentage is between 25 and 95, there is nothing to do

So, the code is right ? tested but it’s not what I was intended for it to do. the LED doesn’t react to the measurement. And when the values between 20 and 95, what is the condition of the pump ? because as I said LED is HIGH from what I’ve tested it.

Delta_G:
Why would you use Wire for one I2C device and software I2C for the other? Why not just use Wire for both? Do they have the same address or something?

That is the provided code and this what they said about it Using an I2C‑MaxSonar with an Arduino | MaxBotix Inc.

So, using a different library is the problem ? I tried to delete Wire but the same thing.
Edit: ah. not using code means not using LCD. Is it common mistake to use two different library to to do the same thing ?

No, it's just odd that you would waste processor resources on software I2C when you already have the hardware I2C running.

Abdulaziz14:
That is the provided code and this what they said about it Using an I2C‑MaxSonar with an Arduino |

Wire has setClock. try setClock(50000) or setClock(400000) after Wire.begin().

So, the code is right ?

If it does what you want, yes. If not, no.

Break the problem down into pieces. Does the sensor detect a return pulse in a reasonable period of time? Does that time correlate in a meaningful way to distance? If the distance is reasonable, is your calculation of percent correct? If so, does the LED go on when the percent is less than 25? I'm not sure how percent can go over 100, so I can understand why the LED never goes off. You don't pour coffee until your cup is 100% full, do you? Why are you pumping the tank 100% full?

Delta_G:
No, it's just odd that you would waste processor resources on software I2C when you already have the hardware I2C running.

What is the problem then ?
Did I miss something up or the could should've worked ?
I don't have good experience and time so I had to go with this code. I want to be sure if I write it for wire it will work just to save time. thank you for the response.

Juraj:
Wire has setClock. try setClock(50000) or setClock(400000) after Wire.begin().

Thanks for the advice.

PaulS:
If it does what you want, yes. If not, no.

Break the problem down into pieces. Does the sensor detect a return pulse in a reasonable period of time? Does that time correlate in a meaningful way to distance? If the distance is reasonable, is your calculation of percent correct? If so, does the LED go on when the percent is less than 25? I'm not sure how percent can go over 100, so I can understand why the LED never goes off. You don't pour coffee until your cup is 100% full, do you? Why are you pumping the tank 100% full?

I Just want to make sure that I translated what I wanted correctly. Sometimes the reading goes above 100%. I don't know why. The values I keep changing them for testing. I want to stop the pump when it reaches the maximum which is 100%. I'm using constraint function so it's not really 1:1 scale to the tank. the sensor can't read below 20 cm.
I don't have a fixed level. My point for the project is showcasing it can start/stop the pump in practice. After that, we can set them as appropriate.

I have found another UltraSonic with I2C here
The addresses are similar for the commanding the sensor to read.

Here is the same code for mine :

#include <Wire.h>

void setup() {
  
  Serial.begin (9600);
  Wire.begin();
  Wire.setClock(400000L);
}

int Range=0;

void loop() {
  // Step1: instruct sensor to read
  Wire.beginTransmission(112);
  Wire.write(byte(0x00));      // sets register pointer to the command register (0x00)
  Wire.write(byte(0x51));      

  Wire.endTransmission();      // stop transmitting

  // step2: for reading to happen 
  
  delay(200); 

 // step 3: instruct sensor to return a particular echo reading
  Wire.beginTransmission(112); // transmit to device #112
  Wire.write(byte(0x02));      // sets register pointer to echo #1 register (0x02)
  Wire.endTransmission();      // stop transmitting
  

// step 4: request reading from sensor
  Wire.requestFrom(112, 2);    

  
// step 5: receive reading from sensor
  if (2 <= Wire.available()) { // if two bytes were received
    Range = Wire.read();  // receive high byte (overwrites previous reading)
    Range = Range << 8;    // shift high byte to be high 8 bits
    Range |= Wire.read(); // receive low byte as lower 8 bits
    Serial.println(Range);   // print the reading
  }

  delay(250);                
}
  
}

Didn’t change it, but my question is why he wrote the register pointers parts ?

These are my sensor commands ( try to open them on a new window for clearer image )


So, do I need to do the register parts ?

I haven’t got the chance to test it. Can I improve the code? and how to write for it to measure real-time as it suggest above ?

you have one zero less in 400000L.

Juraj:
you have one zero less in 400000L.

oh, missed. Thanks.

Is everything inline with the image of the commands ? I'm only doing the first two commands.

in the tables there is no mention about a register address. device address is 112 decimal and in the code it is used in beginTransmission and the command "take a "range reading" 81 decimal (51 hexa) is written.

Juraj:
in the tables there is no mention about a register address. device address is 112 decimal and in the code it is used in beginTransmission and the command "take a "range reading" 81 decimal (51 hexa) is written.

So, the code is good ?

Could you explain me the step 5 ? is this a systematic way to read values ?

Thank you.

Abdulaziz14:
So, the code is good ?

it looks good.

Abdulaziz14:
Could you explain me the step 5 ? is this a systematic way to read values ?

I use

    byte hi = x.read();
    byte lo = x.read();
    int res = hi * 256 + lo;

but shift operator is faster

but shift operator is faster

Which a good compiler would know, and would substitute for you.

PaulS:
Which a good compiler would know, and would substitute for you.

and does it? there are so many small questions/doubts like this when I write a sketch, that it would be never ready if I would dig deeper every time

Juraj:
it looks good.

I use

    byte hi = x.read();
byte lo = x.read();
int res = hi * 256 + lo;



but shift operator is faster

Thank you. I got it to work finally!

At first, it didn’t work. But, I did run I2C search sketch to make sure ( this problem happened before and took me months to figure it out )
I found the address is 56 Decimal… I don’t know why the address has changes. Edited the code and worked.
Also, implemented your suggestion.

I have problem with LCD screen.


and code

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

//LCD
#define BACKLIGHT_PIN 3
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup() {
  
  Serial.begin(9600);
  lcd.begin(20,4);
  Wire.begin();
  Wire.setClock(400000L);
}

int Range=0;
int Percentage;
void loop() {
  lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
  lcd.setBacklight(HIGH);
 // Step1: instruct sensor to read
  Wire.beginTransmission(56);
  Wire.write(byte(0x00));      // sets register pointer to the command register (0x00)
  Wire.write(byte(0x51));      //81 decimal

  Wire.endTransmission();      // stop transmitting

  // step2: for reading to happen 
 
  delay(250); 

 // step 3: instruct sensor to return a particular echo reading
  Wire.beginTransmission(56); // transmit to device #112
  Wire.write(byte(0x02));      // sets register pointer to echo #1 register (0x02)
  Wire.endTransmission();      // stop transmitting
 

// step 4: request reading from sensor
  Wire.requestFrom(56,2);    

  
// step 5: receive reading from sensor
  if (2 <= Wire.available()) { // if two bytes were received
    byte hi = Wire.read();
    byte lo = Wire.read();
    int Range = hi * 256 + lo;

    Range = constrain(Range, 25, 150);
    Percentage = map(Range, 150, 25, 0, 100);

  Serial.print("Range= ");Serial.println(Range);   // print the reading
  Serial.print("% ");Serial.println(Percentage);

  lcd.setCursor(0, 0); // top left
  lcd.print("Range= "); lcd.println(Range);
  lcd.setCursor(0, 1); // bottom left
  lcd.println("Water level"); 
  lcd.setCursor(15, 1); // bottom right
  lcd.print("% "); lcd.println(Percentage);
  }

  
  delay(300);              
}

And thank you and PaulS