[Home] [Downloads] [Search] [Help/forum]

Gammon Forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  4-digit display made from minimal parts
Home  |  Users  |  Search  |  FAQ
Register forum user name
Forgotten password?

4-digit display made from minimal parts

Postings by administrators only.

[Refresh] Refresh page

Posted by Nick Gammon   Australia  (21,322 posts)  [Biography] bio   Forum Administrator
Date Fri 03 Feb 2012 05:30 AM (UTC)

Amended on Mon 25 Nov 2013 11:48 PM (UTC) by Nick Gammon

This project describes a 4-digit LED display made from a minimal parts count. The intention of this project was:

  • Display a 4-digit number
  • Use as few pins on the Arduino as possible (to keep them free for other uses)
  • Have as little soldering and wiring as possible
  • Use minimal extra parts like resistors etc.
  • Be easy to use from your program
  • Be cheap

The finished board looks like this:

Apart from the LED display itself there are 2 x 74HC595 shift registers, one on each side. There are a couple of decoupling capacitors on the 5V power line, and a pull-down resistor on the data line. The 74HC595 registers cost under $US 1 each, and the LED display cost about $US 3.

One 3 wires are needed between the board and the Arduino (plus Gnd and +5V).

This is how it looks connected up, with the 5 wires between the board and the Arduino:

Getting started

To get started I took a bit of prototyping board, like this:

By carefully orienting the two shift register chips so that the "output" pins were adjacent to the LED pins, it was possible to avoid having to run any wires from the shift registers to the LED.


[EDIT] Despite the simplicity of this scheme, there should be current-limiting resistors in series with the LEDs. See further down for further comment about that.

Note that one shift register is turned around 180 degrees, so that both of them have their data pins facing the LED pins:

This rear view shows how things line up:

Program code

Note the absence of current-limiting resistors. Potentially we could damage both the shift register chip and the LEDs by sending unlimited current through them. We'll try to not do that by multiplexing the output, and having "off" periods so that things can cool down.

[EDIT] See further comments below about this. (April 2012)

The demo program below shows how we can address the LEDs. The main work is done by Timer 2, which is set up to fire every 1 mS (every thousandth of a second).

The main part of the program just puts into the variable array "ledOutput" the digits it wants displayed. Then the timer calls an interrupt service routine (ISR) which sequences through each digit and displays it. For example you might do this:

ledOutput [0] = 5;
ledOutput [1] = 6;
ledOutput [2] = 7;
ledOutput [3] = 8;

That would display "5678".

The code has an array "digitPatterns" which has the various segments defined which require to be on to show each letter, as per this diagram:

#include <SPI.h>

const byte LATCH = 10;  // pin for latch (slave select)

const byte MAX_DIGITS = 4;
const byte TIME_BETWEEN_DIGITS = 8;  // blanking period between digits in loop iterations

// which bit to turn on to make a particular pin of the LED go high

const unsigned int DIG1  = 0x0200;  // Q1 of second IC
const unsigned int DIG2  = 0x1000;  // Q4 of second IC
const unsigned int DIG3  = 0x2000;  // Q5 of second IC
const unsigned int DIG4  = 0x0002;  // Q1 of first IC

const unsigned int SEGA  = 0x0400;  // Q2 of second IC
const unsigned int SEGB  = 0x4000;  // Q6 of second IC
const unsigned int SEGC  = 0x0008;  // Q3 of first IC
const unsigned int SEGD  = 0x0020;  // Q5 of first IC
const unsigned int SEGE  = 0x0040;  // Q6 of first IC
const unsigned int SEGF  = 0x0800;  // Q3 of second IC
const unsigned int SEGG  = 0x0004;  // Q2 of first IC
const unsigned int SEGDP = 0x0010;  // Q4 of first IC
// which segments to light for each digit                    
const unsigned int digitPatterns [15] = {
 SEGB | SEGC,  // 1
 SEGA | SEGB | SEGD | SEGE | SEGG,  // 2
 SEGA | SEGB | SEGC | SEGD | SEGG,  // 3
 SEGB | SEGC | SEGF | SEGG,  // 4
 SEGA | SEGC | SEGD | SEGF | SEGG,  // 5
 SEGA | SEGB | SEGC,  // 7
 0,  // space
 SEGG,  // hyphen
 SEGD | SEGE | SEGF,  // L
// how long to leave the digit on for
const byte digitOnTimes [15] = { 3, 1, 3, 3, 2, 3, 3, 2, 3, 3, 0, 0, 3, 3, 2 };
// index into digits
const unsigned int digits [4] = { DIG1, DIG2, DIG3, DIG4 };

// 0 to 9 are themselves in the patterns array
//  there are the other entries:
#define PAT_SPACE 10
#define PAT_HYPHEN 11
#define PAT_A 12
#define PAT_F 13
#define PAT_L 14

// what to display (0 to 15, plus 0x10 to light the decimal point)
// see above (eg. 1 displays 1)
volatile byte ledOutput [MAX_DIGITS];

// global variables used by the ISR
unsigned int countdown;
byte digit;

//  Timer2 Interrupt Service is invoked by hardware Timer 2 every 1ms = 1000 Hz
//  16Mhz / 128 / 125 = 1000 Hz

  // keep doing what we were doing before?
  if (countdown-- > 0)
  if (digit >= MAX_DIGITS)
    digit = 0;
    countdown = TIME_BETWEEN_DIGITS;
    // blanking period to allow output chip and LEDs to cool down
    digitalWrite (LATCH, LOW);
    SPI.transfer (0);
    SPI.transfer (0);
    digitalWrite (LATCH, HIGH);
    } // end of done all digits
  // make all digits high  
  unsigned int output = DIG1 | DIG2 | DIG3 | DIG4;

  // bring low the wanted digit (this will sink)
  output &= ~digits [digit];

  // turn on wanted segments (this will source)
  output |=  digitPatterns [ledOutput [digit] & 0xF];
  // add decimal place if required
  if (ledOutput [digit] & 0x10)
    output |= SEGDP;
  // send to shift registers
  digitalWrite (LATCH, LOW);
  SPI.transfer (highByte (output));
  SPI.transfer (lowByte (output));
  digitalWrite (LATCH, HIGH);
  // adjust time to allow for different numbers of segments
  countdown = digitOnTimes [ledOutput [digit] & 0xF] ;
  // onto next digit next time
}  // end of TIMER2_COMPA_vect

void setup () 
  SPI.begin ();
  // set up Timer 2
  // Timer 2 - gives us our 1 mS counting interval
  // 16 MHz clock (62.5 nS per tick) - prescaled by 128
  //  counter increments every 8 uS. 
  // So we count 125 of them, giving exactly 1000 uS (1 mS)
  TCCR2A = 0;              // stop timer 2
  TCCR2B = 0;
  TCCR2A = _BV (WGM21) ;   // CTC mode
  OCR2A  = 124;            // count up to 125  (zero relative!!!!)
  TIMSK2 = _BV (OCIE2A);   // enable Timer2 Interrupt (ie. every 1 mS)
  TCNT2 = 0;               // reset counter
  TCCR2B =  _BV (CS20) | _BV (CS22) ;  // start Timer with a prescaler of 128

} // end of setup

void loop () 

  unsigned long now = millis () / 100;
  noInterrupts ();
  for (int i = 0; i < 4; i++)
    ledOutput [3 - i] = now % 10;
    now /= 10;   

  // turn leading zeroes into spaces    
  for (int i = 0; i < 4; i++)
    if (ledOutput [i] == 0)
      ledOutput [i] = PAT_SPACE;
  // decimal point before 10ths of a second
  ledOutput [2] |= 0x10;
  interrupts ();
  delay (10);
}   // end of loop

To get each digit to display in turn the display routine sets the appropriate "digit bit" for the LED low (effectively sinking current). Then it consults the table of patterns for that particular digit and turns the bit for those segments high (sourcing current). Those segments that are on at one end, and off at the other, conduct current and light up. Those that are either on at both ends, off at both ends, or off at one end "the wrong way around" will be dark.

The example code just displays the counter from millis() (to the nearest tenth of a second). Note that you don't have to call any special code to make the number appear as the ISR is always displaying the current contents of ledOutput.

I turned off interrupts while refreshing the count, otherwise leading zeroes might flicker into view if the interrupt fired between putting the zeroes into the array, and removing them a moment later.

Video of it in operation:

Current limiting resistors

In my original design I omitted current limiting resistors because the LEDs are being pulsed for brief intervals.

Mike Cook has pointed out that this is a flawed design:


Based on Mike's advice I suggest you incorporate suitable LEDs (say 220 ohms) in series with the A-G/DP pins on the LED matrix.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum

[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at FutureQuest]