In the previous post I showed how to build a single digit display using one 7 segment LED unit. That’s nice but not very useful as such, since single digits can only show limited amounts of data. To go further, you can either add digits and chain them together, as will be seen in Part 3 of this project, or, you can use a four segment LED unit and one shift register.
This time we will use a four segment LED to produce a countdown clock. You will see that this setup is very versatile and you can easily modify it to show the current time, for example, or temperature as in 23°C or 77°F, with the third digit providing the degree character and the last digit the temperature scale.
This is the most common four digit, seven segment LED unit. It has 12 pins, not ten like the one digit thing. That’s because it has been wired so that every digit has its own anode, and the remaining 8 pins correspond to the segments.
The four segment LED works due to the persistence of vision. Not every digit is on all the time, but rather, the four digits are refreshed at 5 millisecond intervals, ie. 200 times per second, that the eye can’t discern them as individually lit. For this, you need to send Arduino four bytes of data via a shift register as before, to be displayed in quick succession.
Parts and connections
So, your parts list is the same as before with a breadboard, a 74HC595 shift register, a bunch of male-male jumper wires, and a display as shown above. My application for this little device is a countdown timer, and the code includes a method for taking a value in seconds, then finding out the minutes and seconds, further dividing them into tens of minutes, minutes, tens of seconds and seconds. These four, single digit, values are sent to the shift register as four consecutive bytes. This is the pins list:
PIN NUMBER | PIN NAME | GOES TO | |
74HC595 | 1 | Q1 | LED 5 |
74HC595 | 2 | Q2 | LED 10 |
74HC595 | 3 | Q3 | LED 1 |
74HC595 | 4 | Q4 | LED 2 |
74HC595 | 5 | Q5 | LED 4 |
74HC595 | 6 | Q6 | LED 7 |
74HC595 | 7 | Q7 | LED 11 |
74HC595 | 8 | GND | GND |
74HC595 | 9 | DATA OUT | NEXT SHIFT REGISTER |
74HC595 | 10 | MR | 5V |
74HC595 | 11 | SH_CP | ARD Clock Pin |
74HC595 | 12 | ST_CP | ARD Latch Pin |
74HC595 | 13 | OE | GND |
74HC595 | 14 | DS | ARD Data Pin |
74HC595 | 15 | Q0 | LED 3 |
74HC595 | 16 | VCC | 5V |
The connections on the 74HC595 are as before, with the difference that you need to supply all four anodes with their own power. This is achieved by connecting the anodes on the LED unit as follows:
PIN NUMBER | GOES TO | ||
LED PIN | 1 | SHIFT Q3 | |
LED PIN | 2 | SHIFT Q4 | |
LED PIN | 3 | SHIFT Q0 | |
LED PIN | 4 | SHIFT Q5 | |
LED PIN | 5 | SHIFT Q1 | |
LED PIN | 6 | ARD 4 | Anode pin |
LED PIN | 7 | SHIFT Q6 | |
LED PIN | 8 | ARD 5 | Anode pin |
LED PIN | 9 | ARD 6 | Anode pin |
LED PIN | 10 | SHIFT Q2 | |
LED PIN | 11 | SHIFT Q7 | |
LED PIN | 12 | ARD 7 | Anode pin |
Logic and coding
Your application may vary, but the essential thing is to be able to separate each digit into its own byte, so 27 isn’t 27, but a 2 and a 7. When I made the countdown timer, I did some Excel first to find out the digits in every moment (45 minutes is 2700 seconds):
sec | mins | secs | splitmins1 | splitmins2 | splitsecs1 | splitsecs2 |
2700 | 45 | 0 | 4 | 5 | 0 | 0 |
Mins is derived from int(seconds/60), which drops the remainder.
Secs is the modulo 60 division of Minutes, which gives just the remainder.
sec | mins | secs | splitmins1 | splitmins2 | splitsecs1 | splitsecs2 |
2700 | 45 | 0 | 4 | 5 | 0 | 0 |
2699 | 44 | 59 | 4 | 4 | 5 | 9 |
2698 | 44 | 58 | 4 | 4 | 5 | 8 |
2697 | 44 | 57 | 4 | 4 | 5 | 7 |
2696 | 44 | 56 | 4 | 4 | 5 | 6 |
2695 | 44 | 55 | 4 | 4 | 5 | 5 |
2694 | 44 | 54 | 4 | 4 | 5 | 4 |
etc.
Splitmins 1 and 2 are again an integer division of minutes, and modulo division of minutes. Splitsecs are the same for seconds. After I got this working in Excel, I did the same thing for Arduino:
int second = 2700; // Define variable for starting time in seconds int mins= 0; // variable for holding minutes int secs = 0; // variable for holding seconds int splitmins1 = 0; // variable for holding first digits of minutes int splitmins2 = 0; // variable for holding second digits of minutes int splitsecs1 = 0; // variable for holding first digits of seconds int splitsecs2 = 0; // variable for holding second digits of seconds byte myArray[] = {0x99, 0x92, 0xc0, 0xc0}; // an array holding the four digits initially with the numbers 4,5,0,0 in it
There is also a second array that holds the bytes that form the numbers and letters:
byte num[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
These are the values of 0 through F in hexadecimal notation. So, if you want to send the values 1 2 3 4 to the display, you place 0xf9, 0xa4, 0xb0, and 0x99 in myArray. You remember the picking of values from the previous post, where it is handled in more detail.
This is the actual maths:
second--; // second minus 1 if (second==0) { // handling the time out issue second = 2700; } mins = int(second/60); // minutes as 45, 44, 43... secs = int(second % 60); // seconds of every minute splitmins1 = int(mins / 10); // first digit of minutes splitmins2 = int(mins % 10); // second digit of minutes splitsecs1 = int(secs / 10); // first digit of seconds splitsecs2 = int(secs % 10); // second digit of seconds myArray[0] = num[splitmins1]; // get first digit hexcode from num array myArray[1] = num[splitmins2]; // get second digit hexcode from num array myArray[2] = num[splitsecs1]; // get third digit hexcode from num array myArray[3] = num[splitsecs2]; // get fourth digit hexcode from num array
After this it’s just the issue of sending this out to the shift register, then having it deal out the four digits in quick succcession. The code below is again based on Freenove’s excellent tutorials, but has been edited to do more than just show 0123.
#include <FlexiTimer2.h> // Contains FlexiTimer2 Library int latchPin = 5; //12; // Pin connected to ST_CP of 74HC595(Pin12) int clockPin = 6;//13; // Pin connected to SH_CP of 74HC595(Pin11) int dataPin = 7; //11; // Pin connected to DS of 74HC595(Pin14) int comPin[] = {13, 12, 11, 10}; //{7, 6, 5, 4};// Common pin (anode) of 4 digit 7-segment display int doorPin = 3; // pin for monitoring room door being closed int second = 2700; // Define variable for starting time in seconds int mins= 0; // variable for holding minutes int secs = 0; // variable for holding seconds int splitmins1 = 0; // variable for holding first digits of minutes int splitmins2 = 0; // variable for holding second digits of minutes int splitsecs1 = 0; // variable for holding first digits of seconds int splitsecs2 = 0; // variable for holding second digits of seconds // Define the encoding of characters 0-F for the common-anode 7-Segment Display byte num[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e}; byte myArray[] = {0x99, 0x92, 0xc0, 0xc0}; //array for representing led characters, inititally 4500 void setup() { // set pins to output pinMode(latchPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); //pinMode(doorPin, INPUT); for (int i = 0; i < 4; i++) { pinMode(comPin[i], OUTPUT); } digitalWrite(doorPin, HIGH); // set door lock monitor pin to high FlexiTimer2::set(1000, timerInt); // configure the timer and interrupt function FlexiTimer2::start(); // start timer } void loop() { showArray(); } void showArray(){ for (int i = 0; i < 4; i++) { // Select a single 7-segment display chooseCommon(i); // Send data to 74HC595 writeData(myArray[i]); delay(5); // Clear the display content writeData(0xff); } } // the timer interrupt function of FlexiTimer2 is executed every 1s void timerInt() { //if(digitalRead(doorPin)==LOW){ // while the switch at pin 3 is closed, this will run second--; // second minus 1 if (second==0) { // handling the time out issue second = 2700; } mins = int(second/60); // minutes as 45, 44, 43… secs = int(second % 60); // seconds of every minute splitmins1 = int(mins / 10); // first digit of minutes splitmins2 = int(mins % 10); // second digit of minutes splitsecs1 = int(secs / 10); // first digit of seconds splitsecs2 = int(secs % 10); // second digit of seconds myArray[0] = num[splitmins1]; // get first digit hexcode from num array myArray[1] = num[splitmins2]; // get second digit hexcode from num array myArray[2] = num[splitsecs1]; // get third digit hexcode from num array myArray[3] = num[splitsecs2]; // get fourth digit hexcode from num array //} //else { // switch opens, the clock shows the time left at opening // delay(10); //} } void chooseCommon(byte com) { // Close all single 7-segment display for (int i = 0; i < 4; i++) { digitalWrite(comPin[i], LOW); } // Open the selected single 7-segment display digitalWrite(comPin[com], HIGH); } void writeData(int value) { // Make latchPin output low level digitalWrite(latchPin, LOW); // Send serial data to 74HC595 shiftOut(dataPin, clockPin, LSBFIRST, value); // Make latchPin output high level, then 74HC595 will update the data to parallel output digitalWrite(latchPin, HIGH); }
This works when you have just one 4 digit, 7 segment LED. If you want to use more such units, you can connect the shift registers to eah other via the data output of the first register wired to the data input of the next. This will hopefully be covered in the fourth episode of this three-part blog on LEDs, but first I will show you how to convert this small scale countdown timer to a big digit timer. The elements I got are 4″, 10cm tall, red ones, and it looks rather impressive. They also have to be fed 12V, which is not manageable via 74HC595, but instead you need a chip called TPIC6B595 which can handle the voltage.
Final product
But just to show you how this project turned out, here’s a little video of it in operation.