Help me understand why my display doesn't work

I'm trying to build a timer for an rc racetrak using an ultrasonic sensor, a 4 digit 7 segment display (5461as), a 74HC595N IC and an UNO R3.

I have managed to make the counting part work (the variable "passage" that is going to be displayed only updates once every lap) but I cannot figure how to display it. Currently it only shows 8s and dots and I'm 99% sure the issue comes from my programming.

Here is the code :

Big thanks in advance :slight_smile:

//4 digit display using 74HC595 shift register 

#define clockPin 10 // Clock pin for 74HC595 

#define dataPin 8 // Data pin for 74HC595 

#define latchPin 9 // Latch pin for 74HC595 




// Common pins of the four digits definitions 

#define Dig1 2 

#define Dig2 3 

#define Dig3 4 

#define Dig4 5 




//Clock

#include <Chrono.h>

Chrono tours;

unsigned long passage=0;




//Echo_sensor

#include "SR04.h"

#define TRIG_PIN 11

#define ECHO_PIN 12

SR04 sr04 = SR04(ECHO_PIN,TRIG_PIN);

long a;

int memo_passage = LOW;




// Variable declarations 

byte current_digit = 1; 

int count = 0; 





// Digit to segment map for common cathode display (change as needed for common anode) 

const byte segmentMap[10] = {

0b00111111, // 0 

0b00000110, // 1 

0b01011011, // 2 

0b01001111, // 3 

0b01100110, // 4 

0b01101101, // 5 

0b01111101, // 6 

0b00000111, // 7 

0b01111111, // 8 

0b01101111 // 9 

}; 




void setup() { 

// Set pin modes 

Serial.begin(9600);

pinMode(Dig1, OUTPUT); 

pinMode(Dig2, OUTPUT); 

pinMode(Dig3, OUTPUT); 

pinMode(Dig4, OUTPUT); 

pinMode(clockPin, OUTPUT); 

pinMode(dataPin, OUTPUT); 

pinMode(latchPin, OUTPUT); 




// Function to turn off all digits 

}




void disp_off() { 

  digitalWrite(Dig1, LOW); 

  digitalWrite(Dig2, LOW); 

  digitalWrite(Dig3, LOW); 

  digitalWrite(Dig4, LOW); 

}

// Function to display a number on the 7-segment 

  void disp(byte number) { 

  digitalWrite(latchPin, LOW); // Prepare shift register for new data 

  shiftOut(dataPin, clockPin, MSBFIRST, segmentMap[number]); // Send digit data 

  digitalWrite(latchPin, HIGH); // Latch the data to update the display 

}




void loop() { 




a = sr04.Distance();

Serial.println(a);

Serial.println(passage);

Serial.println(current_digit);




if ((a < 30) && (memo_passage == LOW)) {

    passage=tours.elapsed();

    tours.restart();

    memo_passage = HIGH;

  }

if ((a > 30)) {

    memo_passage = LOW;

  }

disp_off(); // Turn off the display before updating 




  // Display the appropriate digit 

  switch (current_digit) { 

    case 1: 

      disp((passage / 10000) % 10); // Display thousands place 

      digitalWrite(Dig4, HIGH); // Turn on digit 4 

      break; 

    case 2: 

      disp((passage / 1000) % 10); // Display hundreds place 

      digitalWrite(Dig3, HIGH); // Turn on digit 3 

      break; 

    case 3: 

      disp((passage / 100) % 10); // Display tens place 

      digitalWrite(Dig2, HIGH); // Turn on digit 2 

      break; 

    case 4: 

      disp((passage / 10) % 10); // Display ones place 

      digitalWrite(Dig1, HIGH); // Turn on digit 1 

      break; 

  }

delay(500);

  current_digit = (current_digit % 4) + 1; // Cycle to the next digit 

}


We need to be as sure as you :wink:

  • Please read and follow the posting guidelines for the forum.

  • Suggest you ask AI before continuing here.
    This is its first comment:

Your biggest problem is that you're multiplexing the display at only one digit every 500 ms. A 4-digit multiplexed display normally refreshes every digit every 1–5 ms (roughly 50–250 Hz for the whole display). At your current rate each digit is only updated once every 2 seconds. Also, before debugging the software, verify the hardware: is the 5461AS common anode or common cathode, how are the digit pins driven, and can you successfully display a single digit first? Finally, your digit extraction is off—you never display passage % 10, so the units digit is missing. A schematic and a photo of the wiring would help determine whether the "all 8's" symptom is caused by software or by the display wiring.

You can buy nice multiplexed led displays off eBay , complete with driver IC’s. Simple to use with libraries for the driver IC and lot less wires !

I forgot to take out the delay I added at the end it was meant for debugging/better understand what was happening.

I checked and the display is a common cathode.

I cannot display a single digit currently, I tested the display with another code where every digit was synced and there was no issue with each number so it leads me to believe that the program is the cause.

"you never display passage % 10, so the units digit is missing." I do not want to display the unit since I only have 4 digits so I chose to have (second-second,-milisecond-milisecond) if that makes sense ?

Here is a picture of the wiring

As mentioned already, delay of 500 milliseconds is far too long. This is why you are likely seeing a garbled mess. The delay should be very short, only a few milliseconds.

Your switch statement has a slight error in how it calculates and displays the digits. You're also displaying digits in reverse order. Dig1 should correspond to the first digit from the left (most significant digit), and Dig4 to the last digit from the right (least significant digit).

To achieve the SS.MM (Seconds . Hundredths of a second) format:

  • Digits 1 & 2 display the seconds (time / 10000 and time / 1000).

  • Digit 2 will activate its decimal point to separate the seconds from the fractions.

  • Digits 3 & 4 display the fractional seconds (tenths and hundredths, using time / 100 and time / 10).

Refactored Code

/* ==========================================================
 *  RC RACE TRACK TIMER
 *  Hardware: Arduino UNO, HC-SR04, 74HC595, 4-Digit 7-Segment
 *  Format: SS.MM (Seconds and Hundredths of a second)
 * ========================================================== */

// --- LIBRARIES ---
#include <Chrono.h>
#include "SR04.h"

// --- HARDWARE DEFINITIONS ---
// 74HC595 Shift Register Pins
#define clockPin 10 
#define dataPin  8  
#define latchPin 9  

// 4-Digit 7-Segment Common Pins
#define Dig1 2 // Tens of Seconds
#define Dig2 3 // Units of Seconds (with Decimal Point)
#define Dig3 4 // Tenths of a Second
#define Dig4 5 // Hundredths of a Second

// HC-SR04 Ultrasonic Sensor Pins
#define TRIG_PIN 11
#define ECHO_PIN 12

// --- GLOBAL OBJECTS & VARIABLES ---
SR04 sr04 = SR04(ECHO_PIN, TRIG_PIN);
Chrono tours;

unsigned long passage = 0; // Stores the lap time in milliseconds
long distance;             // Stores the distance reading from the sensor
int memo_passage = LOW;    // State variable to prevent multiple triggers per pass

// --- SEGMENT MAP ---
// Digit to segment map for common cathode display
// Bits map to segments: DP-G-F-E-D-C-B-A
const byte segmentMap[10] = {
  0b00111111, // 0
  0b00000110, // 1
  0b01011011, // 2
  0b01001111, // 3
  0b01100110, // 4
  0b01101101, // 5
  0b01111101, // 6
  0b00000111, // 7
  0b01111111, // 8
  0b01101111  // 9
};

const byte DP_BIT = 0b10000000; // Bit mask to activate the Decimal Point

/* ==========================================================
 *  SETUP ROUTINE
 * ========================================================== */
void setup() {
  Serial.begin(9600);

  // Initialize Shift Register Pins
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(latchPin, OUTPUT);

  // Initialize Digit Control Pins
  pinMode(Dig1, OUTPUT);
  pinMode(Dig2, OUTPUT);
  pinMode(Dig3, OUTPUT);
  pinMode(Dig4, OUTPUT);
}

/* ==========================================================
 *  DISPLAY HELPER FUNCTIONS
 * ========================================================== */

/**
 * Turns off all digit control pins to prevent ghosting during updates.
 */
void disp_off() {
  digitalWrite(Dig1, LOW);
  digitalWrite(Dig2, LOW);
  digitalWrite(Dig3, LOW);
  digitalWrite(Dig4, LOW);
}

/**
 * Sends the segment data to the shift register.
 * @param number The number (0-9) to display.
 * @param showDP Boolean indicating whether to light up the decimal point.
 */
void disp(byte number, bool showDP) {
  digitalWrite(latchPin, LOW); 
  
  byte segmentData = segmentMap[number];
  if (showDP) {
    segmentData = segmentData | DP_BIT; // Combine number with decimal point bit
  }
  
  shiftOut(dataPin, clockPin, MSBFIRST, segmentData); 
  digitalWrite(latchPin, HIGH); 
}

/**
 * Multiplexes the 4 digits to show the lap time in SS.MM format.
 * @param time_to_display Elapsed time in milliseconds.
 */
void refreshDisplay(unsigned long time_to_display) {
  // Digit 1: Tens of Seconds (e.g., the '4' in 45.67)
  disp_off();
  digitalWrite(Dig1, HIGH);
  disp((time_to_display / 10000) % 10, false); 
  delay(3);

  // Digit 2: Seconds + Decimal Point (e.g., the '5.' in 45.67)
  disp_off();
  digitalWrite(Dig2, HIGH);
  disp((time_to_display / 1000) % 10, true); 
  delay(3);

  // Digit 3: Tenths of a Second (e.g., the '6' in 45.67)
  disp_off();
  digitalWrite(Dig3, HIGH);
  disp((time_to_display / 100) % 10, false);
  delay(3);

  // Digit 4: Hundredths of a Second (e.g., the '7' in 45.67)
  disp_off();
  digitalWrite(Dig4, HIGH);
  disp((time_to_display / 10) % 10, false); 
  delay(3);
}

/* ==========================================================
 *  MAIN LOOP
 * ========================================================== */
void loop() {
  // 1. Read Distance Sensor
  distance = sr04.Distance();

  // 2. Lap Timer Logic
  // If an object is detected closer than 30cm and it's a new pass
  if ((distance < 30) && (memo_passage == LOW)) {
    passage = tours.elapsed(); // Save lap time
    tours.restart();           // Reset timer for the next lap
    memo_passage = HIGH;       // Block further readings until the car passes completely
    
    // Optional: Print to serial monitor for debugging
    Serial.print("Lap Time: ");
    Serial.println(passage);
  } 
  
  // Reset the trigger once the car has moved away
  if (distance > 30) {
    memo_passage = LOW;
  }

  // 3. Keep refreshing the display
  // Note: The display needs continuous refreshing to stay visible.
  refreshDisplay(passage);
}

Multiplexing relies on continuously looping therefreshDisplay() function very quickly.
Because your sr04.Distance() waits for sound waves to bounce back, it can take up to ~20-30 milliseconds to execute if nothing is in front of the sensor. That slight delay might cause your display to flicker slightly. If the flicker is too intense, you will need to change the sensor reading method from a "blocking" method to an "interrupt-driven" or "non-blocking" method.

Try these changes to your code first. if the flicker is acceptable, you are good to go!

The flicker doesn't seem to be an issue, the ultrasound sensor doesn't slow down the program too much.
It appears that your program hasn't fixed my issue the display seems unable to display any digits and just flashes randomly every segment. I have not been able to make it display a single number :frowning: .

A typical beginner mess. We will not be able to check this fully for you.

But I did notice some errors.

There should be 8 resistors, not 4. You must use one between each segment and the shift register output that is connected to it, not one resistor per digit. With one resistor per digit, you will get the problem of uneven brightness, with a "1" appearing much brighter than "8" for example. Using 8 resistors will ensure every value is evenly bright.

You are using 220R resistors I think. These are too low and will result in damage to the Arduino pins. With 8 220R resistors for the segments, up to 116mA could flow into the Arduino pin, which has an absolute maximum of 40mA. You need resistors around 800R.

I can see no bypass capacitor for the shift register. This should be 0.1uF ceramic type, closely connected to the Vcc and ground pins of the shift register. Such a capacitor is needed for every bare chip in a circuit.

Hi, @Niokay

At this point we would need a schematic of your project.
Can you please post a copy of your circuit, a picture of a hand drawn circuit in jpg, png?
Hand drawn and photographed is perfectly acceptable.
Please include ALL hardware, power supplies, component names and pin labels.

Also did you write your code in stages, do you have some code that JUST makes your display count up or down, to prove you are communicating with it properly without any timing or SR04 code in it?

Thanks.. Tom.... :smiley: :+1: :coffee: :australia:

Some useful suggestions here.
It's always best, in my opinion, to split projects up and get each part working properly.
Then add them one at a time and see what the result is.
That will often reveal any faults. Fault finding in the code and wiring when you start with the complete project can be tricky.
Also, by doing it in stages, you will have a collection of sub-projects you can use in the future.

I don't see any transistors.

When you connect the common pins directly to the Arduino this

would activate the digit.

I think you need to invert the logic Level for Dig1 ... Dig4.

look this over

i had MyHW version working on a multi-function shield

the multifunction shifts 16 bits, the additional bits select which digit. I left the code in to set the digit pin.

here's the important part. with multiplexed displays, you need to display one digit at a time and refresh them quickly. loop() uses a timer to call dispUpdt() which determines the digit for the desired value in dispVal. It then turns off the previous digit and calls disp() to display the specified digit. This means the 7 segments for that digit as well as turning on the appropriate digit on the display.

the timer running at 20 msec demonstrates the process but there's a lot of flicker. I used an interrupt to do the updates.

//4 digit display using 74HC595 shift register

const char *Version = "Niokay Display 260704c";

// Common pins of the four digits definitions
#undef MyHW
#ifdef MyHW
// #define Latch   4
// #define Clock   7
// #define Data    8
const byte PinClk  = 7;
const byte PinData = 8;
const byte PinLtch = 4;

const int  Ndigit = 4;
const byte PinDigit [Ndigit] = { 13, 12, 11, 10 };

const byte segmentMap [] = {
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0X80, 0X90
};

// ----------------------
#else
const byte PinClk  = 10; // Clock pin for 74HC595
const byte PinData = 8;  // Data pin for 74HC595
const byte PinLtch = 9;  // Latch pin for 74HC595

const int  Ndigit = 4;
const byte PinDigit [Ndigit] = { 5, 4, 3, 2 };

// PinDigit to segment map for common cathode display (change as needed for common anode)
const byte segmentMap[10] = {
    0b00111111, // 0
    0b00000110, // 1
    0b01011011, // 2
    0b01001111, // 3
    0b01100110, // 4
    0b01101101, // 5
    0b01111101, // 6
    0b00000111, // 7
    0b01111111, // 8
    0b01101111  // 9
};
#endif

// -------------------------------------------------------------------------
int  dispDigit [Ndigit];
enum { DigitOff = LOW, DigitOn = LOW };

unsigned long msecPeriod = 20;
unsigned long msec0;

char s [90];

// -----------------------------------------------------------------------------
void disp (int digit)
{
    digitalWrite (PinLtch, LOW);
    shiftOut     (PinData, PinClk, MSBFIRST, segmentMap [dispDigit [digit]]);
#ifdef MyHW
    shiftOut     (PinData, PinClk, MSBFIRST, 1 << digit);
#endif
    digitalWrite (PinLtch, HIGH);

    digitalWrite (PinDigit [digit], DigitOn);
}

// -----------------------------------------------------------------------------
int       dispVal  = 1234;
int       digit;

// --------------------------------------------
void dispUpdt ()
{
    dispDigit [0] = (dispVal / 1000) % 10;
    dispDigit [1] = (dispVal / 100 ) % 10;
    dispDigit [2] = (dispVal / 10  ) % 10;
    dispDigit [3] = (dispVal / 1   ) % 10;

    // turn previous digit off
    digitalWrite (PinDigit [digit], DigitOff);

    if (Ndigit <= ++digit)
        digit = 0;

    disp (digit);

#if (0)
    sprintf (s, "dispUpdt: [%d] %6d - %d %d %d %d - %d", digit, dispVal,
            dispDigit [0], dispDigit [1], dispDigit [2], dispDigit [3],
            dispDigit [digit]);
    Serial.println (s);
#endif
}

// -----------------------------------------------------------------------------
int val;

void cmd ()
{

    if (Serial.available ()) {
        char c = Serial.read ();

        switch (c)  {
        case '0'...'9':
            val = 10*val +c-'0';
            return;

        case 'm':
            msecPeriod = val;
            break;
            
        case 'v':
            dispVal = val;
            break;

        case '\n':
            break;

        default:
            Serial.println ("  invalid");
            break;
        }

        val = 0;
    }
}

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    if (msec - msec0 >= msecPeriod)  {
        msec0 += msecPeriod;
 
        dispUpdt ();
    }

    // -------------------------------------
    cmd ();
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin   (9600);
    Serial.println (Version);

    for (int n = 0; n < Ndigit; n++)  {
        pinMode      (PinDigit [n], OUTPUT);
        digitalWrite (PinDigit [n], DigitOff);
    }

    pinMode (PinClk,  OUTPUT);
    pinMode (PinData, OUTPUT);
    pinMode (PinLtch, OUTPUT);
}