Note

Hello, welcome to the SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasts Community on Facebook! Dive deeper into Raspberry Pi, Arduino, and ESP32 with fellow enthusiasts.

Why Join?

  • Expert Support: Solve post-sale issues and technical challenges with help from our community and team.

  • Learn & Share: Exchange tips and tutorials to enhance your skills.

  • Exclusive Previews: Get early access to new product announcements and sneak peeks.

  • Special Discounts: Enjoy exclusive discounts on our newest products.

  • Festive Promotions and Giveaways: Take part in giveaways and holiday promotions.

👉 Ready to explore and create with us? Click [here] and join today!

5.3 Creating a Time Counter with a 4-Digit 7-Segment Display

In this lesson, we’ll learn how to use a 4-digit 7-segment display with the Raspberry Pi Pico 2 to create a simple time counter. The display will count up every second, showing the elapsed time in seconds.

What You’ll Need

In this project, we need the following components.

It’s definitely convenient to buy a whole kit, here’s the link:

Name

ITEMS IN THIS KIT

LINK

Newton Lab Kit

450+

Newton Lab Kit

You can also buy them separately from the links below.

SN

COMPONENT

QUANTITY

LINK

1

Raspberry Pi Pico 2

1

BUY

2

Micro USB Cable

1

3

Breadboard

1

BUY

4

Jumper Wires

Several

BUY

5

Resistor

4(220Ω)

BUY

6

4-Digit 7-Segment Display

1

7

74HC595

1

BUY

Understanding the 4-Digit 7-Segment Display

A 4-digit 7-segment display consists of four individual 7-segment displays combined into a single module. Each digit shares the same segment control lines (a to g and dp), but each digit has its own common cathode control. This configuration allows us to control which digit is active at any given time.

To display different numbers on each digit using shared segment lines, we use a technique called multiplexing. We rapidly switch between digits, updating one digit at a time, but so quickly that it appears as if all digits are displayed simultaneously due to the persistence of vision.

4digit_control_pins

Circuit Diagram

sch_4dig

Here the wiring principle is basically the same as 5.1 Using the 74HC595 Shift Register, the only difference is that Q0-Q7 are connected to the a ~ g pins of the 4-digit 7-segment display.

Then G10 ~ G13 will select which 7-segment display to work.

Wiring Diagram

wiring_4dig

  • Segment Connections (through 220 Ω resistors):

    • Q0 → Segment a

    • Q1 → Segment b

    • Q2 → Segment c

    • Q3 → Segment d

    • Q4 → Segment e

    • Q5 → Segment f

    • Q6 → Segment g

    • Q7 → Segment dp (decimal point)

  • Common Cathode Connections (Digit Select Pins):

    • Digit 1 (Leftmost Digit): Connect to GP10 on the Pico

    • Digit 2: Connect to GP11

    • Digit 3: Connect to GP12

    • Digit 4 (Rightmost Digit): Connect to GP13

Writing the Code

Note

  • You can open the file 5.3_time_counter.ino from newton-lab-kit/arduino/5.3_time_counter.

  • Or copy this code into Arduino IDE.

  • Select the Raspberry Pi Pico 2 board and the correct port, then click “Upload”.

// Define the connection pins for the shift register
#define DATA_PIN 18   // DS (Serial Data Input)
#define LATCH_PIN 19  // STCP (Storage Register Clock)
#define CLOCK_PIN 20  // SHCP (Shift Register Clock)

// Define the digit control pins for the 4-digit 7-segment display
const int digitPins[4] = { 10, 11, 12, 13 };  // DIG1, DIG2, DIG3, DIG4

// Segment byte maps for numbers 0-9
const byte digitCodes[10] = {
  // Pgfedcba
  0b00111111,  // 0
  0b00000110,  // 1
  0b01011011,  // 2
  0b01001111,  // 3
  0b01100110,  // 4
  0b01101101,  // 5
  0b01111101,  // 6
  0b00000111,  // 7
  0b01111111,  // 8
  0b01101111   // 9
};

unsigned long previousMillis = 0;  // Stores the last time the display was updated
unsigned int counter = 0;          // Counter value

void setup() {
  // Initialize the shift register pins
  pinMode(DATA_PIN, OUTPUT);
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);

  // Initialize the digit control pins
  for (int i = 0; i < 4; i++) {
    pinMode(digitPins[i], OUTPUT);
    digitalWrite(digitPins[i], HIGH);  // Turn off all digits
  }
}

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

  // Update the counter every 1000 milliseconds (1 second)
  if (currentMillis - previousMillis >= 1000) {
    previousMillis = currentMillis;
    counter++;  // Increment the counter
    if (counter > 9999) {
      counter = 0;  // Reset counter after 9999
    }
  }

  // Display the counter value
  displayNumber(counter);
}

void displayNumber(int num) {
  // Break the number into digits
  int digits[4];
  digits[0] = num / 1000;        // Thousands
  digits[1] = (num / 100) % 10;  // Hundreds
  digits[2] = (num / 10) % 10;   // Tens
  digits[3] = num % 10;          // Units

  // Display each digit
  for (int i = 0; i < 4; i++) {
    digitalWrite(digitPins[i], LOW);  // Activate digit
    shiftOutDigit(digitCodes[digits[i]]);
    delay(5);                          // Small delay for multiplexing
    digitalWrite(digitPins[i], HIGH);  // Deactivate digit
  }
}

void shiftOutDigit(byte data) {
  // Send data to the shift register
  digitalWrite(LATCH_PIN, LOW);
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, data);
  digitalWrite(LATCH_PIN, HIGH);
}

After uploading the code, the 4-digit 7-segment display should start counting up from 0000, incrementing by 1 every second. The count should progress as follows: 0000, 0001, 0002, …, 9999, then reset to 0000.

Understanding the Code

  1. Defining Control Pins:

    • DATA_PIN (DS): Receives serial data to be shifted into the 74HC595.

    • LATCH_PIN (STCP): Controls the latching of data from the shift register to the output pins.

    • CLOCK_PIN (SHCP): Controls the shifting of data into the shift register.

    #define DATA_PIN   18  // DS (Serial Data Input)
    #define LATCH_PIN  19  // STCP (Storage Register Clock)
    #define CLOCK_PIN  20  // SHCP (Shift Register Clock)
    
  2. Defining Digit Control Pins:

    • Each digit’s common cathode is connected to a separate GPIO pin.

    • Setting a digit pin LOW activates that digit, while HIGH deactivates it.

    const int digitPins[4] = {10, 11, 12, 13}; // DIG1, DIG2, DIG3, DIG4
    
  3. Creating Segment Byte Maps:

    • Each byte represents the segments that need to be lit to display numbers 0 to 9 on a common cathode 7-segment display.

    • The bits correspond to segments a to g and dp:

    const byte digitCodes[10] = {
      0b00111111, // 0
      0b00000110, // 1
      0b01011011, // 2
      0b01001111, // 3
      0b01100110, // 4
      0b01101101, // 5
      0b01111101, // 6
      0b00000111, // 7
      0b01111111, // 8
      0b01101111  // 9
    };
    
  4. Setup Function:

    • Sets the DATA_PIN, LATCH_PIN, and CLOCK_PIN as outputs.

    • Sets all digit control pins to HIGH to deactivate all digits at startup.

    void setup() {
      // Initialize the shift register pins
      pinMode(DATA_PIN, OUTPUT);
      pinMode(LATCH_PIN, OUTPUT);
      pinMode(CLOCK_PIN, OUTPUT);
    
      // Initialize the digit control pins
      for (int i = 0; i < 4; i++) {
        pinMode(digitPins[i], OUTPUT);
        digitalWrite(digitPins[i], HIGH); // Turn off all digits initially
      }
    }
    
  5. Loop Function:

    • Uses the millis() function to track elapsed time without blocking the program.

    • Increments the counter every second and resets it after reaching 9999.

    void loop() {
      unsigned long currentMillis = millis();
    
      // Update the counter every 1000 milliseconds (1 second)
      if (currentMillis - previousMillis >= 1000) {
        previousMillis = currentMillis;
        counter++; // Increment the counter
        if (counter > 9999) {
          counter = 0; // Reset counter after 9999
        }
      }
    
      // Display the counter value
      displayNumber(counter);
    }
    
  6. Displaying the Number:

    • Breaks the counter value into thousands, hundreds, tens, and units.

    • Activates each digit one by one, sends the corresponding segment data, and deactivates the digit.

    • The rapid cycling between digits creates the illusion that all digits are lit simultaneously.

    void displayNumber(int num) {
      // Break the number into individual digits
      int digits[4];
      digits[0] = num / 1000;         // Thousands
      digits[1] = (num / 100) % 10;   // Hundreds
      digits[2] = (num / 10) % 10;    // Tens
      digits[3] = num % 10;           // Units
    
      // Display each digit one by one
      for (int i = 0; i < 4; i++) {
        digitalWrite(digitPins[i], LOW); // Activate current digit
    
        // Shift out the segment data for the current digit
        shiftOutDigit(digitCodes[digits[i]]);
    
        delay(5);                        // Small delay for multiplexing
        digitalWrite(digitPins[i], HIGH); // Deactivate current digit
      }
    }
    
  7. Shifting Out the Segment Data:

    • Sends the segment data to the 74HC595 shift register.

    • shiftOut() sends the data one bit at a time, starting with the most significant bit (MSBFIRST).

    • Latches the data to the output pins by toggling the LATCH_PIN.

    void shiftOutDigit(byte data) {
      // Send data to the shift register
      digitalWrite(LATCH_PIN, LOW);
      shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, data);
      digitalWrite(LATCH_PIN, HIGH);
    }
    

Experimenting Further

  • Add a Reset Button:

    Connect a button to the Pico to reset the counter when pressed.

  • Display Different Data:

    Modify the code to display sensor readings, such as temperature or light levels.

  • Create a Stopwatch:

    Implement start, stop, and reset functionality to use the display as a stopwatch.

Conclusion

This project demonstrates how to control a 4-digit 7-segment display using a shift register and multiplexing techniques. By efficiently managing timing with millis(), we create a responsive and accurate time counter without hindering the display’s performance.