Seven Segment Display flickering more when LCD used

Hello, currently I am working on a project that involves using an LCD display to print some values for the different pitch and yaw angles off of a gyroscope, and a Seven Segment Display to print values for the temperature reading from the gyroscope, both repeating in their own if statement loops within the greater void loop() part of the code. I have both working, but it seems that the temperature reading on the Seven Segment Display flickers much more clearly when running it in the same program as the code for the LCD displaying its own readings for the angle values. The Seven Segment Display flickers much less violently when the part of the code to print the angular values is removed, even if the much larger block of code that calculates those values is still left in, so i know its something to do with the LCD and the SevSeg display affecting each-other specifically, but I am not so sure as to what I can do to remedy the problem. Here's the relevant parts of the code:

MPU6050 mpu;
SevSeg sevseg;
//timers
const unsigned long mpuRepeatTime = 10;        //interval of when MPU reads and writes gyro and accel data
const unsigned long mpuTempRepeatTime = 1000;  //interval of when MPU reads and writes temperature data
unsigned long mpuPrevTime = 0;
unsigned long mpuTempPrevTime = 0;

//more code

void loop() {
  unsigned long currentTime = millis();

  //nested gyroscope-reading code within if statement
  if (currentTime - mpuPrevTime >= mpuRepeatTime) {
    //more code which is finding the values kalPitch and kalRoll
    lcd.print("Pitch = ");
    lcd.print(kalPitch, 2);
    lcd.print("                ");
    lcd.setCursor(0, 1);  //moves LCD cursor to second line first row
    lcd.print("Roll = ");
    lcd.print(kalRoll, 2);
    lcd.print("                ");
    lcd.setCursor(0, 0);  // moves LCD display cursor back to first position first line
    //more code which triggers certain LEDs to light up depending on pitching angle
    mpuPrevTime = currentTime;
  }

  //nested MPU temperature reading code within if statement
  if (currentTime - mpuTempPrevTime >= mpuTempRepeatTime) {
    //find temperature reading from gyroscope
    mpuTemp = mpu.readTemperature();
    //Output to Seven-Segment Display
    sevseg.setNumberF(mpuTemp, 2);
    mpuTempPrevTime = currentTime;
  }
  sevseg.refreshDisplay();
}

A snippet which omits which libraries you're using is not "the relevant parts of the code".

I'm got a pretty good guess at what your problem might be. But my daily quota of guessing at problems that are incompletely presented has already been used up.

Show your whole sketch.

its for a university project that isn't yet due in so id prefer to not write out the entire thing on a public forum and then by the time I submit it it gets marked as having copied someone else's without the appropriate credit if I don't actually need to do that. but here is the code with more parts added including the libraries I am using or may need to use later in the process:

//included libraries
#include <I2Cdev.h>
#include <math.h>
#include <QMI8658C.h>
#include <Stepper.h>
#include <SR04.h>
#include <Servo.h>
#include <pitches.h>
#include <MFRC522.h>
#include <MPU6050.h>
#include <Keypad.h>
#include <LiquidCrystal.h>
#include <DS3231.h>
#include <dht_nonblocking.h>
#include "SevSeg.h"
#include <KalmanFilter.h>  //library from Jarzebski's GitHub, same as with MPU library
#include <Wire.h>

MPU6050 mpu;
SevSeg sevseg;
// Timers
const unsigned long mpuRepeatTime = 10;        //interval of when MPU reads and writes gyro and accel data
const unsigned long mpuTempRepeatTime = 1000;  //interval of when MPU reads and writes temperature data
unsigned long mpuPrevTime = 0;
unsigned long mpuTempPrevTime = 0;
float timeStep = 0.01;  //needed to find yaw as doesn't use Kalman
//Kalman filter and MPU variables
KalmanFilter kalmanX(0.001, 0.003, 0.03);
KalmanFilter kalmanY(0.001, 0.003, 0.03);
float accPitch = 0;
float accRoll = 0;
float kalPitch = 0;
float kalRoll = 0;
float yaw = 0;
float mpuTemp = 0;

LiquidCrystal lcd(22, 23, 24, 25, 26, 27); //LCD Display Pin Assignment

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  // Initialize MPU6050
  while (!mpu.begin(MPU6050_SCALE_2000DPS, MPU6050_RANGE_2G)) {
    Serial.println("Could not find a valid MPU6050 sensor, check wiring!");
    delay(500);
  }

  mpu.calibrateGyro(); //calibrate the gyroscpoe while its at rest for a few seconds before beginning find and to read off data
  mpu.setThreshold(3); //mpu threshold sensitivity (3 is the default value)

  //setting up 7 segment display
  byte numDigits = 4;
  byte digitPins[] = { 36, 37, 38, 39 };
  byte segmentPins[] = { 35, 28, 29, 31, 32, 34, 33, 30 };
  bool resistorsOnSegments = true;
  byte hardwareConfig = COMMON_CATHODE;

  sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments);

void loop() {
  unsigned long currentTime = millis();

  //nested gyroscope-reading code within if statement
  if (currentTime - mpuPrevTime >= mpuRepeatTime) {

    // Output to Serial Monitor
    Serial.print(" Pitch = ");
    Serial.print(kalPitch);
    Serial.print(" Roll = ");
    Serial.print(kalRoll);
    Serial.print(" Yaw = ");
    Serial.println(yaw);

    lcd.print("Pitch = ");
    lcd.print(kalPitch, 2);
    lcd.print("                ");
    lcd.setCursor(0, 1);  //moves LCD cursor to second line first row
    lcd.print("Roll = ");
    lcd.print(kalRoll, 2);
    lcd.print("                ");
    lcd.setCursor(0, 0);  // moves LCD display cursor back to first position first line
    }
    mpuPrevTime = currentTime;
  }
  //nested MPU temperature reading code within if statement
  if (currentTime - mpuTempPrevTime >= mpuTempRepeatTime) {
    //find temperature reading from gyroscope
    mpuTemp = mpu.readTemperature();
    //Output to Seven-Segment Display
    sevseg.setNumberF(mpuTemp, 2);
    mpuTempPrevTime = currentTime;
  }
  sevseg.refreshDisplay();
}

The multiplexing of the 7 segment display is affected by any blocking activities in the loop() which results in flicker. The libraries for the LCD display generally block during some activities, for example "clear screen" (a millisecond or so). There are alternative LCD libraries see: LiquidCrystal library no delay() - #16 by 6v6gt .
However, you have lots of other activities in the loop(), which could also contribute to display flicker, so the better solution would be to use a hardware timer (if there is still one free on your board with all those libraries you are using) to control the LED display multiplexing. This is because the activities in a timer interrupt service routine (ISR) are unaffected by the loop activities. For flicker free displays, refreshing at 4ms or less is about right.
If the SevSeg.h library is compatible with an ISR (no delay() statements etc.) you could try calling this from an ISR: sevseg.refreshDisplay() instead of in the loop(), otherwise write your own driver for the LED display.

make a stand alone code which shows the problem.

On long term, get a LED driver IC for your seven segment display (MAX7219, HT16K33, TM1934, ...) - it will keep the update of the seven segment display even if there are some blocking parts in your code.

Hi @ameliamax ,

It seems as a clear problem of not refreshing the seven segments modules at the minimum pace required to keep the cinematic effect as being all lit at all times, that's the usual effect of not having tight control over the time consumed between refreshings, by putting the refresh action in the loop() "GenPop" execution code.

There are several solutions for this:

  • Use a timer library that will keep refreshing the display independently of what's happening in the code (of course, using an interrupt, but it'll save yourself of the interrupt management details). There are many of them out there, I prefer the TimerOne library.
  • Using a driven 7 segments led display, either dynamically or statically. These will save you the refreshing task, MPU pins and in most cases processing time, specially when using displays with dedicated drivers, like TM1637, MAX7219, HT16K33 etc.
  • Other solution would be to buy cheap SIPO shift registers, 74HC595, one per display digit, and arrange them so they will keep the display modules permanently on, you'll just have to send them the data when it changes. You can see the cabling and working simulation at this wokwi simulation.

Of course, for most of the solutions you'll need an extra library, but you're already using some, so it seems not a limitation to your asignment.

Good Luck!

Gaby.//

Or add some

sevseg.refreshDisplay();

Calls in other parts of loop() e.g. between calls to lcd. I know it's not a nice solution, but it's a simple change to try.

On the topic of possibly getting shift registers, my seven segment display is a single 4-digit, 12 pin (six on top, six on bottom) display, and on some examples I'd looked at before people has just needed a single shift register, would this be the case in this scenario?

What Arduino board are you using?

No. With a single shift register the multiplexing, that is the cycling of the digits at a speed necessary to maintain a persistence of vision effect, is still controlled by the loop. If you are going to make hardware changes, then select a display of the type which handles the multiplexing as already suggested.

EDIT

A 12 pin 4 digit 7 segment display has to be multiplexed so ensuring the refresh interval is regular and often is critical to avoiding flicker. Shift registers alone will not cure that.

If, however, you had 4 individual single digit 7 segment displays, each controlled by a shift register, then these would not be subject to flicker because this configuration does not rely on a
persistence of vision effect. However, the complexity of the wiring would be substantially increased.

Hi @ameliamax,

No, I'm afraid you'll need two 74HC595, and it will still need refreshing. The circuit and simulation is HERE

If you decide to use the two 74HC595 configuration you'll need a library, you've got plenty of them including my own FourBitLedDigitalTube .

Good Luck!

Gaby.//

Hi @ameliamax ,

The display module surely have a code printed in one of it's sides, please add the information if you want to have a precise answer.

Good Luck!

Gaby.//

Hi @gabygold,
the code is on the bottom side, and is 57-0193

You didn't answer my question in post #9
My reason for asking is because, if you are using an ESP32, it has software timers. You could create one, and have the callback for it call
sevseg.refreshDisplay(). I've used this for refreshing LEDs on an ESP32, but I wasn't using a LED library. I just use the timer callback to switch off the current digit, output the values for the next digit, then switch on the next digit.

Data sheet

Hi @ameliamax ,

I was just about to say "It's already working, just flickering? Go to the software solution, a library as TimerOne if you're using a regular AVR Arduino (and many more), or a software timer if you're using an ESP32, as @Dave_Lowther correctly (as usual) stated.

But I found the same datasheet as he did, and this is a common cathode display module. This means the library/software timer is still the best solution, but you'll have to be cautious of not playing around lighting more than a single digit a time for your board safety.

Using shift registers or driving chips leaves that risk practically out, but is buying more hardware and learning to use it...

Good Luck!

Gaby.//

I'm using an Arduino Mega2560 R3

Hello,
I found and downloaded the library for TimerOne and looked at the example codes it had, but am still not sure how I could implement it into the existing code I have. Also, I have added to the code since earlier as it still required data from two other sources to be averaged with the value I had from the MPU, so I will post the latest version, all I have so far this time as there could be other parts affecting it:

//included libraries
#include <Arduino.h>
#include <Adafruit_SHT31.h>
#include <I2Cdev.h>
#include <math.h>
#include <QMI8658C.h>
#include <Stepper.h>
#include <SR04.h>
#include <Servo.h>
#include <pitches.h>
#include <MFRC522.h>
#include <MPU6050.h>
#include <Keypad.h>
#include <LiquidCrystal.h>
#include <DS3231.h>
#include <dht_nonblocking.h>
#include "SevSeg.h"
#include <KalmanFilter.h>  //library from Jarzebski's GitHub, same as with MPU library
#include <Wire.h>

// Thermistor parameters from TTC203 datasheet
#define RT0 20000 //thermistor resistance at 25°C, in Ω
#define B 4250 //beta coefficnent at 25°C

#define R 22000 //series resistor value in Ω

MPU6050 mpu;
SevSeg sevseg;
Adafruit_SHT31 sht31 = Adafruit_SHT31();
// Timers
const unsigned long mpuRepeatTime = 10;        //interval of when MPU reads and writes gyro and accel data
const unsigned long tempRepeatTime = 1000;  //interval of when MPU, Thermistor, and Sensiron SHT31-D read and write temperature data
unsigned long mpuPrevTime = 0;
unsigned long tempPrevTime = 0; //holds value of when temp was last read (in milliseconds)
float timeStep = 0.01;  //needed to find yaw as doesn't use Kalman

//Kalman filter and MPU variables
KalmanFilter kalmanX(0.001, 0.003, 0.03);
KalmanFilter kalmanY(0.001, 0.003, 0.03);
float accPitch = 0;
float accRoll = 0;
float kalPitch = 0;
float kalRoll = 0;
float yaw = 0;

float RT, VR, ln, TX, T0, VRT; // Variables for thermistor calculations
float mpuTemp; //holds value of temperature according to the mpu
float t; //holds value of temperature from Adafruit Sensiron
float avgTemp; //average temperature value from the 3 sensors

int buzzerPin = 40; //assigning a pin to the buzzer

//Declarations for ultrasonic sensor
const int trigPin = 2;
const int echoPin = 3;
float duration, distance;
unsigned long previousSonicMillis = 0;  // stores last time the ultrasonic was triggered
const long sonicInterval = 12;          // interval at which to trigger a reset of sonic (milliseconds)

LiquidCrystal lcd(22, 23, 24, 25, 26, 27); //LCD Display Pin Assignment

//Servo Control Pin Assignment
int servoPin1 = 6;
Servo servoPitch;
int servoPin2 = 5;
Servo servoRoll;
int servoPin3 = 4;
Servo servoYaw;
float pos = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  // Initialize MPU6050
  while (!mpu.begin(MPU6050_SCALE_2000DPS, MPU6050_RANGE_2G)) {
    Serial.println("Could not find a valid MPU6050 sensor, check wiring!");
    delay(500);
  }

  if (! sht31.begin(0x44)) {
    Serial.println("Couldn't find SHT31");
    while (1) delay(1);
  }

  T0 = 25 + 273.15; // Convert T0 for thermistor from Celsius to Kelvin

  pinMode(buzzerPin, OUTPUT);

  //for ultrasonic sensor
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

  mpu.calibrateGyro(); //calibrate the gyroscpoe while its at rest for a few seconds before beginning find and to read off data
  mpu.setThreshold(3); //mpu threshold sensitivity (3 is the default value)

  servoPitch.attach(servoPin1);  // Attaches Pitch control servo to its specified pin
  servoRoll.attach(servoPin2);    // Attaches Roll control servo to its specified pin
  servoYaw.attach(servoPin3);   // Attaches Yaw control servo to its specified pin

  //setting pins for LEDs
  pinMode(47, OUTPUT);
  pinMode(48, OUTPUT);
  pinMode(49, OUTPUT);
  pinMode(50, OUTPUT);
  pinMode(51, OUTPUT);
  pinMode(52, OUTPUT);
  pinMode(53, OUTPUT);

  //setting up 7 segment display
  byte numDigits = 4;
  byte digitPins[] = { 36, 37, 38, 39 };
  byte segmentPins[] = { 35, 28, 29, 31, 32, 34, 33, 30 };
  bool resistorsOnSegments = true;
  byte hardwareConfig = COMMON_CATHODE;

  sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments);

  //Printing SID on LCD display
  lcd.begin(16, 2);  // Can now interact with the LCD, e.g.:
  lcd.print("11162788");
  delay(5000);  // Waits 5 seconds
  lcd.clear();  // Clear screen
}

void loop() {
  unsigned long currentTime = millis();

  //nested gyroscope-reading code within if statement
  if (currentTime - mpuPrevTime >= mpuRepeatTime) {
    // calculate and print Pitch, Roll, & Yaw Data data every 10ms
    Vector acc = mpu.readNormalizeAccel();  //reading normalised values from the accelerometer
    Vector gyr = mpu.readNormalizeGyro();   //reading normalised values from the gyroscope
    // Calculate Pitch & Roll from accelerometer (in deg)
    accPitch = (atan2(acc.XAxis, sqrt(acc.YAxis * acc.YAxis + acc.ZAxis * acc.ZAxis)) * 180.0) / M_PI;
    accRoll = (atan2(acc.YAxis, acc.ZAxis) * 180.0) / M_PI;
    // Apply Kalman filter
    kalPitch = kalmanY.update(accPitch, gyr.YAxis);
    kalRoll = kalmanX.update(accRoll, gyr.XAxis);
    yaw = yaw + gyr.ZAxis * timeStep;  //Calculate Yaw [cant be done with a Kalman Filter, would require a magnetometer]
    // Output prints
    Serial.print(" Pitch = ");
    Serial.print(kalPitch);
    Serial.print(" Roll = ");
    Serial.print(kalRoll);
    Serial.print(" Yaw = ");
    Serial.println(yaw);
    lcd.print("Pitch = ");
    lcd.print(kalPitch, 2);
    lcd.print("                ");
    lcd.setCursor(0, 1);  //moves LCD cursor to second line first row
    lcd.print("Roll = ");
    lcd.print(kalRoll, 2);
    lcd.print("                ");
    lcd.setCursor(0, 0);  // moves LCD display cursor back to first position first line
    //making different LEDs turn on or off indicate pitchhing level
    if (kalPitch == 0) {
      digitalWrite(47, LOW);
      digitalWrite(48, LOW);
      digitalWrite(49, LOW);
      digitalWrite(50, HIGH);
      digitalWrite(51, LOW);
      digitalWrite(52, LOW);
      digitalWrite(53, LOW);
    } else if (kalPitch > 0 && kalPitch <= 8) {
      digitalWrite(47, LOW);
      digitalWrite(48, LOW);
      digitalWrite(49, HIGH);
      digitalWrite(50, LOW);
      digitalWrite(51, LOW);
      digitalWrite(52, LOW);
      digitalWrite(53, LOW);
    } else if (kalPitch < 0 && kalPitch >= -8) {
      digitalWrite(47, LOW);
      digitalWrite(48, LOW);
      digitalWrite(49, LOW);
      digitalWrite(50, LOW);
      digitalWrite(51, HIGH);
      digitalWrite(52, LOW);
      digitalWrite(53, LOW);
    } else if (kalPitch > 8 && kalPitch <= 16) {
      digitalWrite(47, LOW);
      digitalWrite(48, HIGH);
      digitalWrite(49, LOW);
      digitalWrite(50, LOW);
      digitalWrite(51, LOW);
      digitalWrite(52, LOW);
      digitalWrite(53, LOW);
    } else if (kalPitch > 16 && kalPitch <= 90) {
      digitalWrite(47, HIGH);
      digitalWrite(48, LOW);
      digitalWrite(49, LOW);
      digitalWrite(50, LOW);
      digitalWrite(51, LOW);
      digitalWrite(52, LOW);
      digitalWrite(53, LOW);
    } else if (kalPitch < -8 && kalPitch >= -16) {
      digitalWrite(47, LOW);
      digitalWrite(48, LOW);
      digitalWrite(49, LOW);
      digitalWrite(50, LOW);
      digitalWrite(51, LOW);
      digitalWrite(52, HIGH);
      digitalWrite(53, LOW);
    } else if (kalPitch < -16 && kalPitch >= -90) {
      digitalWrite(47, LOW);
      digitalWrite(48, LOW);
      digitalWrite(49, LOW);
      digitalWrite(50, LOW);
      digitalWrite(51, LOW);
      digitalWrite(52, LOW);
      digitalWrite(53, HIGH);
    }
    mpuPrevTime = currentTime;
  }
  //nested temperature reading code within if statement
  if (currentTime - tempPrevTime >= tempRepeatTime) {
    //find temperature reading from gyroscope
    mpuTemp = mpu.readTemperature();
    Serial.print(" Temp = ");
    Serial.print(mpuTemp);
    Serial.print(" *C (from MPU)");

    //find temp reading and displaying from thermistor
    VRT = (5.00 / 1023.00) * analogRead(A0); // Read the voltage across the thermistor
    VR = 5.00 - VRT; // Calculate the voltage across the resistor
    RT = VRT / (VR / R); // Calculate resistance of the thermistor
    ln = log(RT / RT0); // Calculate temperature from thermistor resistance
    TX = (1 / ((ln / B) + (1 / T0))); //same description as line above
    TX = TX - 273.15;  // Convert to Celsius
    Serial.print("Temperature: ");
    // Display in Celsius
    Serial.print(TX);                  
    Serial.print(" *C (from Thermistor)");
    
    // find temperature from sht31
    t = sht31.readTemperature();
    if (! isnan(t)) {  // check if 'is not a number'
      Serial.print("Temp = "); Serial.print(t); Serial.print(" *C (from Adafruit Sensiron)");
    } else { 
      Serial.println("Failed to read temperature");
    }
    //gathering an average value for temperature
    avgTemp = ((mpuTemp + t + TX)/ 3 );
    Serial.print("Avg Temp = "); Serial.print(avgTemp); Serial.println(" *C");
    //Output to Seven-Segment Display
    sevseg.setNumberF(avgTemp, 2);
    //Buzzer trigger if difference between any sensor for temperature is more than 5ºC
    if ((t - TX >= 5) || (t - TX <= -5) || (t - mpuTemp >= 5) || (t - mpuTemp <= -5) || (TX - mpuTemp >= 5) || (TX - mpuTemp <= -5)) {
      tone(buzzerPin, 1000, 2000);
    } else {
      noTone(buzzerPin);
    }
    tempPrevTime = currentTime;
  }
  sevseg.refreshDisplay();
}

no, a single shift register will not do the multiplexing without processing time in your program.
Use a dedicated LED driver IC like MAX7219, HT16K33, TM1637 (what ever fits better to your display... common anode or common cathode).

Hi @ameliamax ,

Try adding the following code in the corresponding sections. I left the void setup() and the void loop() just as references so you figure out what comes before, inside and after each one of them, hope is clear enough.

This is all code to add, the only line you should remove (while testing just comment it out) is your sevseg.refreshDisplay();.

Don't forget to comment how it resulted.

#include <TimerOne.h>

static void myDispRefresh();  //! Function prototype! 

const unsigned long refreshDelay{10000};   //! Value in MICROSECONDS! The larger the value wihtout flilckering, less unneeded interrupts!

void setup()
{
   Timer1.attachInterrupt(myDispRefresh);
   Timer1.initialize(refreshDelay);
   Timer1.start();

}

void loop()
{

   //! Delete (or comment out) sevseg.refreshDisplay();

}

static void myDispRefresh(){  //! This function definition must go after the last loop }
   sevseg.refreshDisplay();

   return;
}

Good Luck!

Gaby.//