LCD flickering with ethernet

I have a Adafruit ESP32 Feather Huzzah board on which I run Arduino code. I have attached an LCD screen (16X2) and a HC-SR04 ultrasound distance meter. The aim is to display the amount of water sitting in my rain water tanks. The display shows percentage, total volume and a graph bar. This all works flawlessly with the following code:

#include <HCSR04.h>
#include <Wire.h>
#include "rgb_lcd.h"
#include <SPI.h>

// HC-SR04
UltraSonicDistanceSensor distanceSensor(15, 14);

// 16x2 LCD
rgb_lcd lcd;

// Variables
int MaxHeight = 200; //height of water tank
int MaxVol = 20000; //max vol in L of both water tanks
int CurVol = 0;
// Progress Bar characters
byte zero[] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000};
byte one[] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B10000};
byte two[] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000};
byte three[] = {B11100, B11100, B11100, B11100, B11100, B11100, B11100, B11100};
byte four[] = {B11110, B11110, B11110, B11110, B11110, B11110, B11110, B11110};
byte five[] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};

// Function for Progress Bar
void updateProgressBar(unsigned long count, unsigned long totalCount, int lineToPrintOn)
{
  double factor = totalCount / 80.0; // 16 characters x 5
  int percent = (count + 1) / factor;
  int number = percent / 5;
  int remainder = percent % 5;
  if (number > 0)
  {
    for (int j = 0; j < number; j++)
    {
      lcd.setCursor(j, lineToPrintOn);
      lcd.write(5);
    }
  }
  lcd.setCursor(number, lineToPrintOn);
  lcd.write(remainder);
  if (number < 16)
  {
    for (int j = number + 1; j <= 16; j++)
    {
      lcd.setCursor(j, lineToPrintOn);
      lcd.write((byte) 0x00);
    }
  }
}

// Function to setup LCD screen
void setupLcd () {
  lcd.begin(16, 2);
  lcd.createChar(0, zero);
  lcd.createChar(1, one);
  lcd.createChar(2, two);
  lcd.createChar(3, three);
  lcd.createChar(4, four);
  lcd.createChar(5, five);
  lcd.clear();
}

void setup () {

  Serial.begin(9600); // Start the serial output
  setupLcd (); // Start LCD
  lcd.print("(re)Starting...");
  lcd.clear();
}

void loop () {

  lcd.setCursor(6, 1);
  float inputReading = distanceSensor.measureDistanceCm();
  int CurPercentage = (100 - (inputReading / MaxHeight * 100));
  int CurVol = MaxVol / 100 * CurPercentage;
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print(CurVol);
  lcd.print(" L - ");
  lcd.print(CurPercentage);
  lcd.print(" %");

  // update Progress Bar
  updateProgressBar(CurPercentage, 100, 1);

  delay(3000);

}

As this display is going to be sitting in my garage outside, I also want the latest information to be available on a web page (json object, so I can display it on a monitor inside the house. I added an Adafruit Ethernet Featherwing to the setup. I added Ethernet and Json libraries, and adapted the code. However, when I do this, the LCD screen flickers annoyingly. I have no idea how this happens (I'm completely new to Arduino IDE coding. Is there something wrong in my loop ?

#include <HCSR04.h>
#include <Wire.h>
#include "rgb_lcd.h"
#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoJson.h>

// HC-SR04
UltraSonicDistanceSensor distanceSensor(15, 14);

// 16x2 LCD
rgb_lcd lcd;

// Wired Ethernet
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 30); // static IP not working due to changed ESP32/server.h
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
EthernetServer server(80);

// Variables
int MaxHeight = 200; //height of water tank
int MaxVol = 20000; //max vol in L of both water tanks
int CurVol = 0;
// Progress Bar characters
byte zero[] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000};
byte one[] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B10000};
byte two[] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000};
byte three[] = {B11100, B11100, B11100, B11100, B11100, B11100, B11100, B11100};
byte four[] = {B11110, B11110, B11110, B11110, B11110, B11110, B11110, B11110};
byte five[] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};

// Function for Progress Bar
void updateProgressBar(unsigned long count, unsigned long totalCount, int lineToPrintOn)
{
  double factor = totalCount / 80.0; // 16 characters x 5
  int percent = (count + 1) / factor;
  int number = percent / 5;
  int remainder = percent % 5;
  if (number > 0)
  {
    for (int j = 0; j < number; j++)
    {
      lcd.setCursor(j, lineToPrintOn);
      lcd.write(5);
    }
  }
  lcd.setCursor(number, lineToPrintOn);
  lcd.write(remainder);
  if (number < 16)
  {
    for (int j = number + 1; j <= 16; j++)
    {
      lcd.setCursor(j, lineToPrintOn);
      lcd.write((byte) 0x00);
    }
  }
}

// Function to connect to Ethernet & start server
void setupEthernet () {
  Ethernet.init(33);
  Ethernet.begin(mac, ip, gateway, subnet);
  delay(1000);
  server.begin();
  Serial.println(F("Server is ready."));
  Serial.print(F("Please connect to http://"));
  Serial.println(Ethernet.localIP());
}

// Function to setup LCD screen
void setupLcd () {
  lcd.begin(16, 2);
  lcd.createChar(0, zero);
  lcd.createChar(1, one);
  lcd.createChar(2, two);
  lcd.createChar(3, three);
  lcd.createChar(4, four);
  lcd.createChar(5, five);
  lcd.clear();
}

// ========== MAIN FUNCTIONS: SETUP & LOOP ==========
void setup () {

  Serial.begin(9600); // Start the serial output
  setupLcd (); // Start LCD
  lcd.print("(re)Starting...");
  setupEthernet(); // Connect to Ethernet
  delay(10000);
  lcd.clear();
}

void loop () {

  lcd.setCursor(6, 1);
  float inputReading = distanceSensor.measureDistanceCm();
  int CurPercentage = (100 - (inputReading / MaxHeight * 100));
  int CurVol = MaxVol / 100 * CurPercentage;
  lcd.clear();
  lcd.setCursor(1, 0);
  lcd.print(CurVol);
  lcd.print(" L - ");
  lcd.print(CurPercentage);
  lcd.print(" %");

  // update Progress Bar
  updateProgressBar(CurPercentage, 100, 1);

  // Wait for an incoming connection
  EthernetClient client = server.available();
  if (!client)
    return;
  Serial.println(F("New client"));

  while (client.available()) client.read();
  StaticJsonDocument<100> doc;
  doc["data"] = CurPercentage;

  // Write response headers
  client.println(F("HTTP/1.0 200 OK"));
  client.println(F("Content-Type: application/json"));
  client.println(F("Connection: close"));
  client.print(F("Content-Length: "));
  client.println(measureJsonPretty(doc));
  client.println();
  serializeJsonPretty(doc, client);
  client.stop();

  delay(3000);

}

in the old code you have a blocking delay(3000) in each loop iteration.

in the new code you "return" if no client is connected. so the loop will start from new.
This will happen several times per seconds.
each time you do a lcd.clear().
So it is very obvious, that your LCD is flickering.

Write a LCD update routine and only call it for example each 3 seconds.
Blink Without delay shows you how to deal with millis().
Get rid of your delay in loop()

If you don't want flicker, stop calling lcd.clear() every time through loop(), You are clearing the display and redrawing everything.

using millis() for timing and doing several things at a time tutorials:
Several things at a time.
Beginner's guide to millis().
Blink without delay().

Example loop() to show separating the update of the LCD, not using clear() and millis() timing to get rid of delay and do "multitasking". Illustration, not tested.

void loop ()
{
   doEthernet();
   updateLCD();
}

void doEthernet()  // called very time through loop, but executes only every 3 seconds.
{
   static unsigned long timer = 0;
   unsigned long interval = 3000;
   if (millis() - timer >= interval)
   {
      timer = millis();
      float inputReading = distanceSensor.measureDistanceCm();
      int CurPercentage = (100 - (inputReading / MaxHeight * 100));
      int CurVol = MaxVol / 100 * CurPercentage;

      // update Progress Bar
      updateProgressBar(CurPercentage, 100, 1);

      // Wait for an incoming connection
      EthernetClient client = server.available();
      if (!client)
         return;
      Serial.println(F("New client"));

      while (client.available()) client.read();
      StaticJsonDocument<100> doc;
      doc["data"] = CurPercentage;

      // Write response headers
      client.println(F("HTTP/1.0 200 OK"));
      client.println(F("Content-Type: application/json"));
      client.println(F("Connection: close"));
      client.print(F("Content-Length: "));
      client.println(measureJsonPretty(doc));
      client.println();
      serializeJsonPretty(doc, client);
      client.stop();
   }
}

void updateLCD()  // called very time through loop, but executes only every 1 second.
{
   static unsigned long timer = 0;
   // I know that there is no use updating the LCD faster than the ethernet
   // just showing that you can
   unsigned long interval = 1000;
   if (millis() - timer >= interval)
   {
      timer = millis();
      lcd.setCursor(0, 0);
      lcd.print("                "); // overwrite old data with spaces
      lcd.setCursor(1, 0); // reset cursor
      lcd.print(CurVol);
      lcd.print(" L - ");
      lcd.print(CurPercentage);
      lcd.print(" %");
   }
}

Thanks, I'll adjust the code accordingly. There were multiple issues with my code.

I think I'm getting there (almost). The ultimate goal is that the LCD is "only" updated every 5 minutes or so (no need to have the water level updated too regularly), and that the web page should always have that last update. In the code below I put the update interval to 10 seconds.

Two more questions:

  1. Actually reading the sensor should be a separate function too ? With the result (CurVol and CurPercentage) passed on to update the LCD and update the web page ? I tried that but doesn't seem to work properly if I try to return the values.
  2. The client connection to the webserver can happen anytime during those 5 minutes. Is it OK then to have that run every second, so it can pick up the incoming client request?
#include <HCSR04.h>
#include <Wire.h>
#include "rgb_lcd.h"
#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoJson.h>

// HC-SR04
UltraSonicDistanceSensor distanceSensor(15, 14);

// 16x2 LCD
rgb_lcd lcd;

// Wired Ethernet
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 30); // static IP not working due to changed ESP32/server.h
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
EthernetServer server(80);

// Variables
int MaxHeight = 200; //height of water tank
int MaxVol = 20000; //max vol in L of both water tanks
int CurVol = 0;
int CurPercentage = 0;
// Progress Bar characters
byte zero[] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000};
byte one[] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B10000};
byte two[] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000};
byte three[] = {B11100, B11100, B11100, B11100, B11100, B11100, B11100, B11100};
byte four[] = {B11110, B11110, B11110, B11110, B11110, B11110, B11110, B11110};
byte five[] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};

// Function to connect to Ethernet & start server
void setupEthernet () {
  Ethernet.init(33);
  Ethernet.begin(mac, ip, gateway, subnet);
  delay(1000);
  server.begin();
  Serial.println(F("Server is ready."));
  Serial.print(F("Please connect to http://"));
  Serial.println(Ethernet.localIP());
}

// Function to setup LCD screen
void setupLcd () {
  lcd.begin(16, 2);
  lcd.createChar(0, zero);
  lcd.createChar(1, one);
  lcd.createChar(2, two);
  lcd.createChar(3, three);
  lcd.createChar(4, four);
  lcd.createChar(5, five);
  lcd.clear();
}

void updateLCD(int CurPercentage, unsigned long totalCount, int lineToPrintOn, int CurVol) {
  static unsigned long timer = 0;
  unsigned long interval = 10000;
  if (millis() - timer >= interval) {
    timer = millis();
    // Show progress bar on LCD
    double factor = totalCount / 80.0;  // 16 characters x 5
    int percent = (CurPercentage + 1) / factor;
    int number = percent / 5;
    int remainder = percent % 5;
    if (number > 0) {
      for (int j = 0; j < number; j++) {
        lcd.setCursor(j, lineToPrintOn);
        lcd.write(5);
      }
    }
    lcd.setCursor(number, lineToPrintOn);
    lcd.write(remainder);
    if (number < 16) {
      for (int j = number + 1; j <= 16; j++) {
        lcd.setCursor(j, lineToPrintOn);
        lcd.write((byte)0x00);
      }
    }
    // show current volume and percentage on LCD
    lcd.setCursor(0, 0);
    lcd.print("                ");
    lcd.setCursor(1, 0);
    lcd.print(CurVol);
    lcd.print(" L - ");
    lcd.print(CurPercentage);
    lcd.print(" %");
  }
}

void doEthernet(int CurPercentage) {
  static unsigned long timer = 0;
  unsigned long interval = 1000;
  if (millis() - timer >= interval) {
// Wait for an incoming connection
  EthernetClient client = server.available();
  if (!client)
  return;
  //Serial.println(F("New client"));
  while (client.available()) client.read();
  StaticJsonDocument<100> doc;
  doc["data"] = CurPercentage;
  // Write response headers
  client.println(F("HTTP/1.0 200 OK"));
  client.println(F("Content-Type: application/json"));
  client.println(F("Connection: close"));
  client.print(F("Content-Length: "));
  client.println(measureJsonPretty(doc));
  client.println();
  serializeJsonPretty(doc, client);
  client.stop();
  }
}
// ========== MAIN FUNCTIONS: SETUP & LOOP ==========
void setup () {

  Serial.begin(9600);
  setupLcd (); 
  lcd.print("(re)Starting...");
  setupEthernet(); // Connect to Ethernet
  delay(5000);
  lcd.clear();
}

void loop () {

  float inputReading = distanceSensor.measureDistanceCm();
  int CurPercentage = (100 - (inputReading / MaxHeight * 100));
  int CurVol = MaxVol / 100 * CurPercentage;
  updateLCD(CurPercentage, 100, 1, CurVol);
  doEthernet(CurPercentage);
  
}

If you are updating the display every 5 minutes (or whatever interval) it would be good to update the readings at the same time so the "current" values on the display will match what is returned on the web page. You don't really need any timer in the doEthernet() function since it will either connect or not, on demand.

#include <HCSR04.h>
#include <Wire.h>
#include "rgb_lcd.h"
#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoJson.h>

// HC-SR04
UltraSonicDistanceSensor distanceSensor(15, 14);

// 16x2 LCD
rgb_lcd lcd;

// Wired Ethernet
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 0, 30); // static IP not working due to changed ESP32/server.h
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);
EthernetServer server(80);

// Variables
const int MaxHeight = 200; //height of water tank
const int MaxVol = 20000; //max vol in L of both water tanks
int CurVol = 0;
int CurPercentage = 0;
// Progress Bar characters
byte zero[] = {B00000, B00000, B00000, B00000, B00000, B00000, B00000, B00000};
byte one[] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B10000};
byte two[] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000};
byte three[] = {B11100, B11100, B11100, B11100, B11100, B11100, B11100, B11100};
byte four[] = {B11110, B11110, B11110, B11110, B11110, B11110, B11110, B11110};
byte five[] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};

// Function to connect to Ethernet & start server
void setupEthernet () {
  Ethernet.init(33);
  Ethernet.begin(mac, ip, gateway, subnet);
  delay(1000);
  server.begin();
  Serial.println(F("Server is ready."));
  Serial.print(F("Please connect to http://"));
  Serial.println(Ethernet.localIP());
}

// Function to setup LCD screen
void setupLcd () {
  lcd.begin(16, 2);
  lcd.createChar(0, zero);
  lcd.createChar(1, one);
  lcd.createChar(2, two);
  lcd.createChar(3, three);
  lcd.createChar(4, four);
  lcd.createChar(5, five);
  lcd.clear();
}

void updateLCD() {
  static unsigned long timer = 0;
  //const unsigned long interval = 1000UL * 60 * 5;
  const unsigned long interval = 1000UL * 10;   // testing

  if (millis() - timer >= interval) {
    timer = millis();

    float inputReading = distanceSensor.measureDistanceCm();
    CurPercentage = (100 - (inputReading / MaxHeight * 100));
    CurVol = MaxVol / 100 * CurPercentage;


    // Show progress bar on LCD
    float factor = 100.0 / 80.0;  // 16 characters x 5
    int percent = (CurPercentage + 1) / factor;
    int number = percent / 5;
    int remainder = percent % 5;
    if (number > 0) {
      for (int j = 0; j < number; j++) {
        lcd.setCursor(j, lineToPrintOn);
        lcd.write(5);
      }
    }
    lcd.setCursor(number, 1);
    lcd.write(remainder);
    if (number < 16) {
      for (int j = number + 1; j <= 16; j++) {
        lcd.setCursor(j, lineToPrintOn);
        lcd.write((byte)0x00);
      }
    }
    // show current volume and percentage on LCD
    lcd.setCursor(0, 0);
    lcd.print("                ");
    lcd.setCursor(1, 0);
    lcd.print(CurVol);
    lcd.print(" L - ");
    lcd.print(CurPercentage);
    lcd.print(" %");
  }
}

void doEthernet() {
  //static unsigned long timer = 0;
  //const unsigned long interval = 1000;
  
  //if (millis() - timer >= interval) {
    // Wait for an incoming connection
    EthernetClient client = server.available();
    if (!client)
      return;
    //Serial.println(F("New client"));
    while (client.available()) client.read();
    StaticJsonDocument<100> doc;
    doc["data"] = CurPercentage;
    // Write response headers
    client.println(F("HTTP/1.0 200 OK"));
    client.println(F("Content-Type: application/json"));
    client.println(F("Connection: close"));
    client.print(F("Content-Length: "));
    client.println(measureJsonPretty(doc));
    client.println();
    serializeJsonPretty(doc, client);
    client.stop();
  //}
}
// ========== MAIN FUNCTIONS: SETUP & LOOP ==========
void setup () {

  Serial.begin(9600);
  setupLcd();
  lcd.print("(re)Starting...");
  setupEthernet(); // Connect to Ethernet
  delay(5000);
  lcd.clear();
}

void loop () {

  updateLCD();
  doEthernet();
}

Thanks, this makes much more sense now. Learned a lot from this forum post.