Coding a Timer, with 4 digit 7 segement display AND 74HC595

Using an arduino Uno with shift register and common cathode 4 digit display; I am trying to make a timer that will count down in minutes and seconds using a colon.
I have a complete working prototype; along with code.
but it isnt the code i need, and i thought i could teach myself how to revise the code to achieve my desired results.
hours of countless research has led down many wrong paths, and i remain perplexed.
I attached the working schemetic and code;
could somebody please help me learn to rewrite the code so that it no longer uses hexidecimal digits and only numbers?
also to have the clock start at 15:00 and then count down by one second every second?
14:59, 14:58,14:57... etc...
I appreciate any advise or help as i am very much new to the coding world and am learning as i go, along with the help here, the internet (not always the best source) and some books from the library :slight_smile:

/* ********************************************************************** 
 * Four Digit Hex Counter
 *   Uses: one 74HC595 shift register
 *         one four digit seven segment display - common cathode (5641AS)
 *         four PN2222 transistors
 *   
 * Inspired by Elegoo Lesson 28 
 *   www.elegoo.com     2016.12.12
 *   modified by Ricardo Moreno
 *
 * Description:
 *   This sketch illustrates controlling a four digit 7-segment 
 *   display with a single 74HC595 shift register. Elegoo kit only
 *   has one 74HC595, so the Arduino will control 7-segment pins D1-D4. 
 *   
 *  History:
 *  6/06/2019 v1.0 - Initial release
 *  
 *  Verify circuit connections:
 *           74HC595 pin     Q7,Q6,Q5,Q4,Q3,Q2,Q1,Q0 
 *           Mapping to       g, c,DP, d, e, b, f, a (7-Segment LED)
 *           for array purposes D4 = digit0, etc.
 ********************************************************************* */

/* ***************************************************
 *                Global Constants                   *
 *************************************************** */
 
const int dataPin  = 9;  // 74HC595 pin 8 DS
const int latchPin = 10;  // 74HC595 pin 9 STCP
const int clockPin = 11;   // 74HC595 pin 10 SHCP
const int digit0   = 7;   // 7-Segment pin D4
const int digit1   = 6;   // 7-Segment pin D3
const int digit2   = 5;   // 7-Segment pin D2
const int digit3   = 4;   // 7-Segment pin D1 

/* ***************************************************
 *                Global Variables                   *
 *************************************************** */
// Hex values reference which LED segments are turned on
// and may vary from circuit to circuit.  Note the mapping above.
byte table[]= 
    {   0x5F,  // = 0
        0x44,  // = 1
        0x9D,  // = 2
        0xD5,  // = 3
        0xC6,  // = 4
        0xD3,  // = 5
        0xDB,  // = 6
        0x45,  // = 7
        0xDF,  // = 8
        0xC7,  // = 9
        0xCF,  // = A
        0xDA,  // = b
        0x1B,  // = C
        0xDC,  // = d
        0x9B,  // = E
        0x8B,  // = F
        0x00   // blank

};  //Hex shown
byte digitDP = 32;  // 0x20 - adds this to digit to show decimal point
byte controlDigits[] = { digit0, digit1, digit2, digit3 };  // pins to turn off & on digits
byte displayDigits[] = { 0,0,0,0,0 }; // ie: { 1, 0, 7, 13, 0} == d701 (all values from table array)
    /* Each array value holds digit values as table array index, or raw byte
     *  parameters: digit0, digit1, digit2, digit3, digitSwitch
     *  
     * digitSwitch: the four least significant bits controls data handling, 
     *              each bit controls associated digit
     *              starting with least-significant bit 0, 
     *              i.e. B1010, digit1 & digit3 are raw, 
     *                          digit0 & digit2 use table array 
     *       1 = raw byte
     *       0 = table array index                                         */  
unsigned long onTime = 0;             // tracks time
bool switchView = false;              // switch between HexCounter (table array) and RawDisplay (raw bytes)
                                      //    false = HexCounter
                                      //     true = RawDisplay
unsigned int counter = 0;             // RawDisplay counter

/* ***************************************************
 *           Global Adjustable Variables             *
 *************************************************** */
int digitDelay = 50;                  // delay between incrementing digits (ms)
int brightness = 90;                  // valid range of 0-100, 100=brightest
unsigned int ShowSegCount = 250;      // number of RawDisplay loops before switching again 
bool commonCathode = true;

/* ***************************************************
 *                   Void Setup                      *
 *************************************************** */
void setup() {
    //DDRD=0xff;                      // make pins 0-7 outputs
    //DDRB=0xff;                      // make pins 8-13 outputs
    //PORTD=0xf0;                     // make pins 4-7 HIGH
    
    pinMode(latchPin,OUTPUT);
    pinMode(clockPin,OUTPUT);
    pinMode(dataPin,OUTPUT);
    for (int x=0; x<4; x++){
        pinMode(controlDigits[x],OUTPUT);
        digitalWrite(controlDigits[x],LOW);  // Turns off the digit  
    }
}

/* ***************************************************
 *                   Functions                       *
 *************************************************** */    
void DisplaySegments(){
    /* Display will send out all four digits
     * one at a time.  Elegoo kit only has 1 74HC595, so
     * the Arduino will control the digits
     *   displayDigits[4] = the right nibble controls output type
     *                      1 = raw, 0 = table array
     *                  upper (left) nibble ignored
     *                  starting with 0, the least-significant (rightmost) bit
     */
    
    for (int x=0; x<4; x++){
        for (int j=0; j<4; j++){
            digitalWrite(controlDigits[j],LOW);    // turn off digits
        }
        digitalWrite(latchPin,LOW);
        if (bitRead(displayDigits[4],x)==1){
            // raw byte value is sent to shift register
            shiftOut(dataPin,clockPin,MSBFIRST,displayDigits[x]);
        } else {
            // table array value is sent to the shift register
            shiftOut(dataPin,clockPin,MSBFIRST,table[displayDigits[x]]);
        }
        
        digitalWrite(latchPin,HIGH);
        digitalWrite(controlDigits[x],HIGH);   // turn on one digit
        delay(1);                              // 1 or 2 is ok
    }
    for (int j=0; j<4; j++){
        digitalWrite(controlDigits[j],LOW);    // turn off digits
    }
}

void HexCounter(){
    /* Increments values stored in displayDigits array to
     * creates a Hex counter from the table array.
     * Uses mixed display types:
     *    Digit3 | Digit2 | Digit1 | Digit0 
     *    ---------------------------------
     *       C   |   0    |   0    |    0
     */
    byte Letter = B00011011;  // C
     
    //increment values for digits 0-2
    bool incrementValue = true;
    for (int d = 0; d < 3; d++){
        int x = int(displayDigits[d]);
        if (incrementValue == true) {
            x++;
            incrementValue = false;
            if (x > 15) {
                displayDigits[d] = 0;
                incrementValue = true;
            } else {
                displayDigits[d] = byte(x);
            }
        }
    }
    // Set digit3 value
    displayDigits[3] = Letter;
    // Set digitSwitch option
    displayDigits[4] = B1000;
    
    if ((displayDigits[0] == 0)&&(displayDigits[1] == 0)&&(displayDigits[2] == 0)){
        switchView = !switchView;
        for(int x = 0; x < 5; x++){ displayDigits[x]=0; }        // Reset array
        displayDigits[4] = B0000;
    }
}

void RawDisplay(){
    // HALO
    displayDigits[0] = B01011111;  // 0
    displayDigits[1] = B00011010;  // L
    displayDigits[2] = B11001111;  // A
    displayDigits[3] = B11001110;  // H
     // Set digitSwitch option
    displayDigits[4] = B1111;
    
    if (counter < ShowSegCount){ 
        counter++;
    } else {
        // Reset everything
        counter = 0;
        switchView = !switchView;
        for(int x =0; x<5; x++){ displayDigits[x]=0; }        // Reset array
        displayDigits[4] = B0000;
    }
}

/* ***************************************************
 *                   Void Loop                       *
 *************************************************** */
void loop() {

    DisplaySegments();                                      // Caution: Avoid extra delays
    
    /* *************************************
     *         Control Brightness          *
     * *********************************** */
    delayMicroseconds(1638*((100-brightness)/10));         // largest value 16383
    
    /* *************************************
     *        Selects Display Type         *
     * *********************************** */
    unsigned long nowValue = millis() - onTime;
    if (nowValue >= long(digitDelay)){
        onTime = millis();
        if(switchView==true){ RawDisplay(); } else { HexCounter(); }
    }
}

I've reviewed the code you posted. Frankly, I would start from scratch. That code is overly complex and confusing for what you want to do. As a learning aid, in my opinion, it's more of a hindrance than a help.

Start by forgetting the display for the moment. Write a sketch that produces the 15:00 14:59 14:58 sequence you want, but simply prints it to serial monitor. In that sketch, don't use delay(), only millis(), it's important for reasons that will become clear later.

4 Likes

Hint: when you print to the display do it via a function then when you change displays it is all in one spot. In other words do not user serial.print except in that function.

i take it wrote all the code and then began testing it. i and i believe most experienced developers start with just a simple piece of code (e.g. helloWorld) and then start adding features, testing as they go.

the other thing is to write code to test both hardware and software features. So you might have started with a simple timer that displays the time on the Arduino IDE serial monitor. then write the code to drive and test the display and then display the time.

the timer could be as simple as the following


unsigned time = 15 * 60;
unsigned mins;
unsigned secs;

char digits [4];
char s [90];

void
timer (void)
{
    time--;
    mins = time / 60;
    secs = time % 60;

    digits [0] = mins / 10;
    digits [1] = mins % 10;
    digits [2] = secs / 10;
    digits [3] = secs % 10;

    sprintf (digits, " %6d, %02d%02d, %d %d %d %d", time, mins, secs,
                digits [0],  digits [1],  digits [2],  digits [3]); 
    Serial.println (digits);

    delay (1000);
}

void
loop (void)
{
    timer ();
}

void
setup (void)
{
    Serial.begin (9600);
}

but the a multiplexed 7-segment display is way more complicated.

mulitplexed means only one digit is displayed at a time and that all the digits are displayed rapidly enough (50 Hz) that our eyes see all the digits.

you could call a display function every 20 msec to updates the next digit. It would turn off the currently active digit, clock out the segment bits and turn that digit on.

you might try writing code to make sure the are properly clocked out to just on digit, all 10 digits are displayed correctly, then sequentially turning on each digit and finally being able to display any 4 digit value.

i don't understand why your code has 2 lines calling shiftOut() and why displayDigits [4] is used

int minutes = 15; 
int seconds = 00;  
bool timerRunning = true; 
unsigned long previousMillis = 00; 
const long interval = 1000; 

void setup() {
  Serial.begin(9600); 
  Serial.println("Countdown Timer Started: 15:00");
}

void loop() {
  unsigned long currentMillis = millis(); 
  if (timerRunning) {
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis; 

      
      if (seconds == 00) {
        if (minutes == 00) {
          timerRunning = false; 
        } else {
          minutes--;
          seconds = 59; 
        }
      } else {
        seconds--;
      }

      
      printTime(minutes, seconds);
    }
  } else {
    Serial.println("Time's up! Press the reset button to restart.");
  }
}
void printTime(int mins, int secs) {
  Serial.print("Time Remaining: ");
  if (mins < 10) Serial.print("0"); 
  Serial.print(mins);
  Serial.print(":");
  if (secs < 10) Serial.print("0"); 
  Serial.println(secs);
}

I have this code working in the IDE :slight_smile:
now how would i get the digits in the display to display the correct number??

before worrying about how to display the timer, does that display code work? canit display 4 different #s on each digit display?

yes it can indeed, the original qouted code works perfectly - just not what i need. Hence why i am now concerned with how to correctly display the timer on the 4 digit seven segment display

ok, if the display works then what i posted should work

1 Like

When you say "working in the IDE", do you mean it compiles/verifies?

If so, a good start. Next step is to upload it to the Arduino, to test it. The IDE can't really tell you if it's going to work.

That's the next thing to work on. Currently your code has the minutes and seconds, but they are 2 digits each and you will need those individual digits to be able to display them on the segments.

So instead of variables for minutes and seconds, have variables for minutes_tens, minutes_units, seconds_tens and seconds_units.

@1notsure_2505
Here is another version of 4-digit cc7-segment muliplexed display based Timer which counts down from 15.00 to 00.00. It begins counting down by 1-sec when the START Button is pressed.

1. Build the following circuit (Fig-1).


Figure-1:

2. Uplaod the following tested sketch into UNO.

byte lupTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};//0,1,2,3,4,5,6,7,8,9
byte ccTable[4]; //holds cc-code for DP0, DP1, DP2, DP3
byte decSec = 00;
byte decMin = 15;
#define START 2
#define STOP 3

void setup()
{
  Serial.begin(9600);
  for (byte i = 8; i < 18; i++)//DPin-8 to 13; A0 to A3
  {
    pinMode(i, OUTPUT);
  }
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(START, INPUT_PULLUP);
  pinMode(STOP, INPUT_PULLUP);
  //-------------------------
  ccTable[3] = lupTable[decSec % 10];
  ccTable[2] = lupTable[decSec / 10];
  //---------------------------------
  ccTable[1] = lupTable[decMin % 10];
  ccTable[1] = ccTable[1] | 0x80;       //place dot with MIN
  ccTable[0] = lupTable[decMin / 10];
  //---------------------------------
  refreshDisplay();

  while (digitalRead(START) != LOW)
  {
    refreshDisplay();
  }

}

void loop()
{
  unsigned long prMillis = millis();
  while (millis() - prMillis < 10)
  {
    ccTable[3] = lupTable[decSec % 10];
    ccTable[2] = lupTable[decSec / 10];
    //---------------------------------
    ccTable[1] = lupTable[decMin % 10];
    ccTable[1] = ccTable[1] | 0x80;       //place dot with MIN
    ccTable[0] = lupTable[decMin / 10];
    //---------------------------------
    refreshDisplay();                     //refresh display until 1sec has elapsed
  }
  if (decSec == 00)
  {
    if (decMin != 0)
    {
      decSec = 59;
      decMin = decMin - 1;
      if (decSec == 00)
      {
        if (decMin == 00)
        {
          while (true);
        }
      }
    }
  }
  else
  {
    decSec--;
  }
}
void refreshDisplay()
{
  byte x = ccTable[0];
  PORTB = x;
  digitalWrite(6, bitRead(x, 6));
  digitalWrite(7, bitRead(x, 7));
  PORTC = 0b110111;
  delay(1);
  //-----------------------------
  x = ccTable[1];
  PORTB = x;
  digitalWrite(6, bitRead(x, 6));
  digitalWrite(7, bitRead(x, 7));
  PORTC = 0b111011;
  delay(1);
  //-----------------------------
  x = ccTable[2];
  PORTB = x;
  digitalWrite(6, bitRead(x, 6));
  digitalWrite(7, bitRead(x, 7));
  PORTC = 0b111101;
  delay(1);
  //-----------------------------
  x = ccTable[3];
  PORTB = x;
  digitalWrite(6, bitRead(x, 6));
  digitalWrite(7, bitRead(x, 7));
  PORTC = 0b111110;
  delay(1);
  //-----------------------------
}

3. Press Reset Button. Check that display shows: 15.00.
4. Press START Button. Check that the Timer is counting dowmn by 1-sec.
5. Implement STOP Button to stop counting down at any time.
6. Reduce number of code lines of the refreshDisplay() of the sketch of Step-2 using for() loop.
6. Questons/queries are welcome!

Is it a good idea to drive the cathode terminals directly with an output pin since the pins must potentially sink current from 8 LED’s?

each Arduino pin has a current limit. sinking current from 7 segments, each10 ma, will exceed it. i think it's ~40ma. that's why your circuit uses external transistors

Let us calculate the sink current per IO-pin.

Consider all segments are on:
Total source current: (5.00-2.50)/2200 x 8
==> 9 mA

An IO-pin can sink: 20 mA

So, the circuit of Fig-1 of post #12 will work safely.

Moreover, the the display unt is a multiplexed type; as a result, the vaerage source currens are < 9 mA.

aren't they 220 Ohm resistors and aren't the LEDs in 7-segment displays 1.4V

131 ma = 8 * (5 - 1.4) / 220

The current limiting resistors are physically 2.2k (2200) and are selected so to limit the total sink current below 20 mA when all segments are on.

The average drop across a LED is 2.0 - 2.5V. If I assume 2.0 V drop, the total source current is still: 11 mA much lower that the IO sink current of 20 mA.

If I take your 1.4V drop, the total source current appers as: 13 mA < 20 mA sink current.

With 8x2.2k crrrent limiting resisitors, the brightness of the digits are even and at good level (Fig-1).

image
Figure-1:

when i say working, i mean it verifies/compiles AND uploads to the serial monitor AND displays a countdown sequence as desired.
i think understand what you mean about separating the variables of minutes_tens & minutes_units... however, forgive my ignorance, HOW do i call out to have that display on the 4 digit 7 segment display?
this code does display the timer in the serial monitor when uploaded to the uno board.
i have tried both hexadecimal and binary numbers, yet i still cant seem to get the display to show the digits.
lines 70 - 75 of the code have been commented out.
** these lines of code will display all 4 digits on the display, all be it one at a time starting with the digit on the right.
** also these lines do not display a number on the digit, but rather all seven segments of the digit including the decimal.
** I've used these lines in the current code (the one i am attaching here) to verify that the digits do illuminate...

int minutes = 15;
int seconds = 00;
bool timerRunning = true;
unsigned long previousMillis = 00;
const long interval = 1000;
const int dataPin  = 9;
const int latchPin = 10;
const int clockPin = 11;
const int digit0   = 7;
const int digit1   = 6;
const int digit2   = 5;
const int digit3   = 4;

unsigned char table[] = {/*
    //  AAA
    // F   B
    // F   B
    // F   B
    //  GGG
    // E   C
    // E   C
    // E   C
    //  DDD
    // GFEDCBA
    0b00111111, // 0
    0b00000110, // 1
    0b01011011, // 2
    0b01001111, // 3
    0b01100111, // 4
    0b01101101, // 5
    0b01111101, // 6
    0b00000111, // 7
    0b01111111, // 8
    0b01100111, //9
    0b00000000, // blank
    0b01000000,           */
  0x5F,  // = 0
  0x44,  // = 1
  0x9D,  // = 2
  0xD5,  // = 3
  0xC6,  // = 4
  0xD3,  // = 5
  0xDB,  // = 6
  0x45,  // = 7
  0xDF,  // = 8
  0xC7,  // = 9
  0xCF,  // = A
  0xDA,  // = b
  0x1B,  // = C
  0xDC,  // = d
  0x9B,  // = E
  0x8B,  // = F
  0x00   // blank
};
byte controlDigits[] = { digit0, digit1, digit2, digit3 };
byte  displayDigits[] = { 0, 0, 0, 0, 0 }; // ie: { 1, 0, 7, 13, 0} == d701 (all values from table array)
int brightness = 90;
unsigned int ShowSegCount = 250;
bool commonCathode = true;

void setup() {
  Serial.begin(9600); // Start the Serial communication
  Serial.println("Countdown Timer Started: 15:00");
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  //  for (int x = 0; x < 4; x++) {
  //    pinMode(controlDigits[x], OUTPUT);
  //    digitalWrite(controlDigits[x], HIGH);
  //    delay(300);
  //    digitalWrite(controlDigits[x], LOW);
  //  }
}
void displayTime(int mins, int secs) {
  byte digit0 = mins / 10;
  byte digit1 = mins % 10;
  byte digit2 = secs / 10;
  byte digit3 = secs % 10;
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, table [digit0]);
  shiftOut(dataPin, clockPin, MSBFIRST, table [digit1]);
  //  shiftOut(dataPin, clockPin, MSBFIRST, table [10]); // Colon
  shiftOut(dataPin, clockPin, MSBFIRST, table [digit2]);
  shiftOut(dataPin, clockPin, MSBFIRST, table [digit3]);
  digitalWrite(latchPin, HIGH);
  Serial.print("Time Remaining: ");
  if (mins < 10) Serial.print("0");
  Serial.print(mins);
  Serial.print(":");
  if (secs < 10) Serial.print("0");
  Serial.println(secs);
}
void loop() {
  unsigned long currentMillis = millis();
  if (timerRunning) {
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis; // Save the last time the timer was updated
      if (seconds == 00) {
        if (minutes == 00) {
          timerRunning = false; // Stop the timer at zero
        } else {
          minutes--;
          seconds = 59; // Reset seconds to 59
        }
      } else {
        seconds--;
      }
      displayTime(minutes, seconds);
    }
  } else {
    Serial.println("Time's up! Press the reset button to restart.");
  }
}

but they are 220 in his drawing

sorry but Im clueless as where to put that information so that it will display correctly on the 4 digit 7 segment display

displayDigits[]

I have not worked on OP's schematic which is not suitable for my design approach.