4 Digit 7 Segment Serial LED Module - TM74HC595

aisc:
Ok that codes does displays.
321 and right digit cycles thru all characters.
After each cycle the left 3 digits increases by 1 i.e. 322.

One thing - the display is "shaky" - the characters are not steady.
Does it need a higher scan rate maybe?

I can't really do much until I see it with my own eyes, after the hardware arrives at the door. There are different kinds of "shaky". But you can play with the scan rate by changing:

const int scanRateHz = 60;

I just had another idea to solve the digit dimming issue caused by the lack of digit drivers, varying the digit on time depending on how many segments are illuminated. So I will try that later, too.

I bought one of these displaymodules just recently (although without receiving any code or documentation) and was searching for hints on how to display data on them. Luckily I came across this thread :slight_smile:

While playing around with aarg's code I noticed, that some characters were displayed wrong. Apparently there are some mistakes already in the examplary code from post 1.

For proper cycling throug the characters use the following code.

    {// 0	 1	  2	   3	   4	   5	   6	   7	   8	   9	   A	   b	   c	   d	   E	   F	   -	   .	   off
    	0xC0,    0xF9,    0xA4,    0xB0,   0x99,   0x92,   0x82,   0xF8,   0x80,   0x90,   0x88,   0x83,   0xA7,   0xA1,   0x86,   0x8E,   0xbf,   0x7F,   0xFF
    };

Well, don't leave me hanging! What problem did you find with the characters? Maybe your module is not the same one. Looking at your "0" which is 0xc0, it is the same.

Anyway, I'm long past that code now. Here is the latest version which uses timer2 instead of micros() for timing. Again, if there is something that I have missed, or a possible module incompatibility, I would like to know exactly what it is.

// controls 4 digit 7 segment display
// Unknown manufacturer - labelled "4-bit LED Digital Tube Module"
// working timer interrupt version 1.03
// 2015-05-09 by aarg

// extended character definitions:

#define LETTER_G 16
#define LETTER_H 17
#define LETTER_J 18
#define LETTER_L 19
#define LETTER_P 20
#define LETTER_U 21
#define LETTER_SPACE 22
#define LETTER_MINUS 23

const byte numDigits = 4;

// control pin definitions:
int shiftClock = 5;  //module pin label SCLK
int latchClock = 6;  //module pin label RCLK
int displayOut = 7;  //module pin label DIO

// count variables:

unsigned long previousCountUp = 0;        // will store last time of count
const long intervalCountUp = 100;           // interval at which to count (milliseconds)

unsigned long previousText = 0;        // will store last time of count
const int intervalOnText = 1200;           // interval at which to count (milliseconds)

const unsigned long intervalOffText = 8000;           // interval at which to count (milliseconds)
unsigned long intervalText = intervalOnText;           // interval at which to count (milliseconds)

unsigned long previousDecimalPoint = 0;        // will store last time of count
const int intervalDecimalPoint = 80;           // interval at which to count (milliseconds)


// demo variables
byte PIGS[][numDigits] =
{
  {LETTER_H, 0xE, LETTER_L, LETTER_P},
  {LETTER_P, 1, LETTER_G, 5},
  {LETTER_G, LETTER_U, LETTER_L, LETTER_P},
  {LETTER_J, LETTER_U, LETTER_G, 5},
  {LETTER_SPACE, 0, 0xf, LETTER_SPACE},
  {5, LETTER_L, 0, LETTER_P}
};

byte testDigits[numDigits] = {0};
byte* displayLocation = testDigits;
int showText = 6;

int charCount = -1150;

void setup ()
{
  // configure outputs:
  pinMode(shiftClock, OUTPUT);
  pinMode(latchClock, OUTPUT);
  pinMode(displayOut, OUTPUT);

  //******************************
  // timer code

  // Timer 2 - gives us the display segment refresh interval

  TCCR2A = 0;  // reset Timer 2
  TCCR2B = 0;
  TCCR2A = bit (WGM21) ;   // configure as CTC mode

  // 16 MHz clock (62.5 nS per tick) - prescaled by 256
  //  counter increments every 16 µS.
  // There are 8 segments and 4 digits to scan every 1/60th of a second
  // so we count 32 of them at 60Hz, giving a display refresh interval of 512 µS.

#define scanRateHz 60  // tested, maximum that works is 88 using Wire library and Serial
#define displayScanCount 1000000L / numDigits / 8 / scanRateHz / 16

  OCR2A  = displayScanCount;            // count up to 32 @ 60Hz

  // Timer 2 - interrupt on match (ie. every segment refresh interval)
  TIMSK2 = bit (OCIE2A);   // enable Timer2 Interrupt
  TCNT2 = 0;  // counter to zero

  // Reset prescalers
  GTCCR = bit (PSRASY);        // reset prescaler now
  // start Timer 2
  TCCR2B =  bit (CS21) | bit (CS22) ;  // prescaler of 256

  // ***** end of timer setup ******************************

} // end of setup()

void loop()
{
  // no need to call the display scan routine, as it is timer driven
  // Here is some non-blocking test code...
  //
  // see if it's time to increment the counter
  unsigned long currentMillis = millis();

  if (currentMillis - previousCountUp >= intervalCountUp)
  {
    previousCountUp = currentMillis;

    // actions to perform for this interval:
    if (++charCount > 10010)
    {
      charCount = -1010;
    }
    modulePrint(charCount);
    // modulePrint() can be used to display any valid integer value at any time.
    // So it could also display sensor readings or other information.
  }

  if (currentMillis - previousText >= intervalText)
  {
    previousText = currentMillis;

    // actions to perform for this interval:
    showText = ++showText % 7;
    if (showText == 6)
    {
      displayLocation = testDigits;
      intervalText = intervalOffText;
    }
    else
    {
      displayLocation = PIGS[showText];
      intervalText = intervalOnText;
    }
  }

  if (currentMillis - previousDecimalPoint >= intervalDecimalPoint)
  {
    previousDecimalPoint = currentMillis;

    // actions to perform for this interval:
    for (int i=0; i<numDigits; i++)
    {
      PIGS[showText%6][random(4)] |= 0x80;
      PIGS[showText%6][random(4)] &= 0x7f;
    }
  }


} // end of loop()



//***********************************************
//
// display update ISR
//
// every time this routine is called, 16 bits are shifted into the display
// and latched.
// The first 8 bits is the segment data, and first 4 bits of the second byte are
// the segment select.

ISR (TIMER2_COMPA_vect)
{

  // segment list to make a seven segment font

  const byte NUM_PLUS_SYMBOL_FONT[] = {
    0b00111111, // 0
    0b00000110, // 1
    0b01011011, // 2
    0b01001111, // 3
    0b01100110, // 4
    0b01101101, // 5
    0b01111101, // 6
    0b00000111, // 7
    0b01111111, // 8
    0b01101111, // 9
    0b01110111, // A
    0b01111100, // b
    0b00111001, // C
    0b01011110, // d
    0b01111001, // E
    0b01110001, // F
    0b00111101, // (71)	G
    0b01110110, // (72)	H
    0b00011110, // (74)	J
    0b00111000, // (76)	L
    0b01110011, // (80)	P
    0b00111110, // (85)	U
    0b00000000, // (32)  <space>
    0b01000000, // (45)	-
  };

  static byte digit = 0;
  static byte segment = 0x80;
  byte tempDigit = displayLocation[digit];

  // send segment data
  shiftOut(displayOut, shiftClock, MSBFIRST,
    ~(segment & ((NUM_PLUS_SYMBOL_FONT[tempDigit & 0x7f]) | (tempDigit & 0x80))) );
    
  // send digit select data
  shiftOut(displayOut, shiftClock, MSBFIRST, 8 >> digit);
  
  // data is now in the display shift register, so latch to LEDs
  digitalWrite(latchClock, LOW);
  digitalWrite(latchClock, HIGH);

  // increment variables to select the next segment and possibly the next digit:
  //
  segment = segment >> 1;
  if (segment == 0)
  {
    segment = 0x80;
    digit++;
    if (digit >= numDigits)
      digit = 0;
  }
}
// *********** end of display update ISR *****************


// Print an integer to the display buffer.
// Values between -999 and 9999 will be displayed, else an error message.
//
void modulePrint(int val)
{

  const byte HIGH_ERROR[] = {LETTER_H, 1, LETTER_G, LETTER_H};
  const byte LOW_ERROR[] = {LETTER_MINUS, LETTER_L, 0, LETTER_MINUS};

  // validate number size for 4 digits:
  if (val > 9999)
    memcpy(testDigits, HIGH_ERROR, 4);  //high number error message
  else if (val < -999)
    memcpy(testDigits, LOW_ERROR, 4);  //low number error message
  //negative number
  else if (val < 0)
  {
    // prepare first symbol to print when digits are exhausted
    boolean needsMinusSign = true;
    int posval = -val;  //use the absolute value of the number
    for (int i = 3; i >= 0; i--)
    {
      if (posval > 0)
        testDigits[i] = posval % 10;  // digits to print
      else if (needsMinusSign)
      {
        testDigits[i] = LETTER_MINUS;  // print one minus sign
        needsMinusSign = false;
      }
      else
        testDigits[i] = LETTER_SPACE;  // the rest are spaces
      posval /= 10;
    }
  }
  else  // it is a positive number
  {
    for (int i = 3; i >= 0; i--)
    {
      if (val > 0 || i == 3)
        testDigits[i] = val % 10;  // digits to print
      else
        testDigits[i] = LETTER_SPACE;  // the rest are spaces
      val /= 10;
    }
  }
}

I abandoned the inverted bit logic for font definitions, to make them easier to maintain. It also enabled me to adapt a font from another source. :slight_smile:

I'm putting this out early to meet your response. As such, it lacks complete documentation of all the features. Here are some changes:

  • display driver supports a decimal point attribute for each character. If bit 7 of the byte in the display buffer is set, the decimal point will be displayed.
  • indexed display location. A pointer is set to the display location. You can change the display buffer location just by changing the pointer, thus you can support multiple display "windows"
    -improved numeric print function. Has range check and error display, also supports negative numbers
    -allows the use of delay() and other blocking code in loop() - not that you should do that...

Also, this morning I reworked the print routine to accept different number bases, and digit length. It's tested with base 2, 10 and 16. I haven't tested it for digit lengths longer than 4. I thought it would be appropriate to include it here, because of the frequency of questions on the board about number conversion.

void modulePrint(long val, byte base, byte* printLocation)
{

  const byte HIGH_ERROR[numDigits] = {LETTER_H, 1, LETTER_G, LETTER_H};
  const byte LOW_ERROR[numDigits] = {LETTER_MINUS, LETTER_L, 0, LETTER_MINUS};

  long maxNegativeDigits = base;
  for (int i=0; i<numDigits-2; i++) maxNegativeDigits *= base;
  
  // validate number size for digits:
  if (val >= maxNegativeDigits * base)
    memcpy(printLocation, HIGH_ERROR, numDigits);  //high number error message
  else if (val <= -maxNegativeDigits)
    memcpy(printLocation, LOW_ERROR, numDigits);  //low number error message
  //negative number
  else if (val < 0)
  {
    // prepare first symbol to print when digits are exhausted
    boolean needsMinusSign = true;
    int posval = -val;  //use the absolute value of the number
    for (int i = numDigits-1; i >= 0; i--)
    {
      if (posval > 0)
        printLocation[i] = posval % base;  // digits to print
      else if (needsMinusSign)
      {
        printLocation[i] = LETTER_MINUS;  // print one minus sign
        needsMinusSign = false;
      }
      else
        printLocation[i] = LETTER_SPACE;  // the rest are spaces
      posval /= base;
    }
  }
  else  // it is a positive number
  {
    for (int i = numDigits-1; i >= 0; i--)
    {
      if (val > 0 || i == numDigits-1)
        printLocation[i] = val % base;  // digits to print
      else
        printLocation[i] = LETTER_SPACE;  // the rest are spaces
      val /= base;
    }
  }
}

Note that it doesn't produce an ASCII string. To do that, you would have modify it to add the ASCII offsets, for example 48 for digits 0-9, and 65 for A-Z for the extended bases that include hex. Also the terminating null. For a variable length string, you would omit leading spaces.

Well according to the pictures and every other information I found in this thread, the modules should be identical.

However if you look closely at the (chinese code example) hexcodes for displaying letters, you will find that instead of "A" they display "P", "b" is the same as "-", "c" is wrong as well and instead of an "F" all segments would be turned off (0xFF)

See the attached screenshot for reference.

differing display.PNG

Hi,

I just bought a "4-bit LED Digital Tube Module" with VCC, SCLK, RCLK, DIO, GND.
I see two chips named "TM74HC595".

I've read a few pages of this topic, but I'm not sure about this:

  • Does this 7segment display need a "loop" that constantly sends data to the 7segment display?

or

  • Is it enough to send some bytes to the display, and then it will stay with the same letters on the display, not needing any loop?

Thanks a lot in advance.

If you had a MAX7219, it would multiplex in the background for you. Those chips are incredibly cheap these days on eBay. My post about them: Interfacing LED displays with the MAX7219 driver

However 2 x TM74HC595 chips will (probably) require manual multiplexing (unless there are only 16 segments that need lighting). I have a post about an LED sign that uses the 595 chips: Hacking a scrolling LED strip.

That requires that you constantly refresh the chips because only one segment is lit at once, and it relies on persistence of vision to see the whole letter.

Thanks a lot @NickGammon for your answer!

Ok, this means I'll need a constant loop that updates the display.

What is the cheapest ready-to-use 4digit 7display you know that doesn't need a loop?

I bought this SparkFun 7-Segment Serial Display - Red, it works, but it's not very cheap.

As I'm currently sourcing components for my opensource project www.samplerbox.org, I really want to find the cheapest solution (so that it will be cheap for everyone building it!)

Do you know some ready-to-use 4 pins maximum (using SPI or I2C or serial) 4digit display, that don't require constant loop to refresh the display?

As mentioned in my link above you can get the MAX7219 displays cheaply: Gammon Forum : Electronics : Microprocessors : Interfacing LED displays with the MAX7219 driver

The one there is actually 8 digits, and comes complete with LEDs, MAX7219 chip and board. They are around $3 on eBay. You could always not solder on the second 4 digits, or just use the parts on your own board.

Since that uses the 7219 it does not need refreshing, except when you want to change what it displays. Those displays require the usual: 3 pins plus 2 for power/ground.

If that is one pin too many you might need to look for an I2C equivalent. I found a few on eBay but they weren't quite as cheap.

Build the electronic parts
BILL OF MATERIALS

Raspberry Pi 2 : ~40€

Are you really using a Pi? That is a lot more expensive than the AVR chips. Still, you may need the processing power.

I have nothing against displays based on the MAX7219 or similar IC TM16xx, in fact I have several and I recommend them over the 74HC595 type. However, the constant loop to display the digits that Nick mentioned, need not necessarily be part of your loop() function. If you copy the code I posted above and experiment with it, you will find that, although it uses millis() procedures to provide information to the display, they are not needed to run the display.

Because I have an interrupt routine running the display, you can simply write to the display buffer in memory (using my print method or any of your own code) and it will magically appear on the display. You can even use the dreaded delay() function and it will work! So if you already have such a module and want to play with it, don't feel that you have to abandon it right away.

So @blahop, yes, you need a display loop. But it doesn't have to be connected with the logic of your code. It just has to be included so it will run. Try it! It's just before your post.

@Mitschgel, I never looked at the font of the Chinese software because the demo only displays "0123" and it was such obvious c**p that I didn't deem it worthy of using. Please never call it "aarg's code" again... I assure you I had nothing to do with it. :slight_smile:

He's quite right. In fact my "scrolling LED sign" code uses an ISR to update the display (code available from my website).

That lets the main code just bumble around gathering readings, and let the ISR keep the display refreshed. Quite simple and clean.

By the way, thanks Nick, for the great info on your site. I condensed some of your timer code to help put together my sketch. I overlooked the '595 code because one of the other timer examples had what I needed anyway.

I was a bit lost in this forum post, I couldn't find the ebay link, do you still have it?
Do you mean 3$ for everything ? 7segment display + board + IC ? Really all these things?

In fact I don't have anything at home to produce my own PCB, that's why I'm looking for the easiest solution (ready-to-use 7seg display) without having to produce my own PCB to host the MAX7219...

Are you really using a Pi? That is a lot more expensive than the AVR chips. Still, you may need the processing power.

Yes I need that for the audio sampler itself (it can load piano sample sets of 500MB in memory for example)

@aarg: I have one question remaining.
The project I'm currently working on is basically a DMX controlled Infrared Remote.
(DMX Reception and sending Infrared Commands is already working great, but I wanted to add the 7segment display to show the current DMX Adress of my Device.)

The DMX signal reception is using Timer2 ( for reference: the DMX code can be found here: receive-dmx-512-with-an-arduino)
and i have successfully added my 7segment display with your micros() based code.

Since I have unfortunately not much experience with arduino programming and all the timer based ISR stuff is a bit confusing, too...
is it advisable to leave the 7segment display output micros() based or should one better be working with your new timer2 based code?

In this case, I guess that I would have to switch your code from timer2 to timer1,
which I already tried (out of curiosity) and failed totally at :smiley:

blahop:
Do you mean 3$ for everything ? 7segment display + board + IC ? Really all these things?

Yes.

Here's one example ($US 2.99 + $0.99 shipping)): http://www.ebay.com/itm/0-56-LED-7-Segment-4-Digit-Common-cathode-MAX7219-arduino-Blue-White-Green-/121031922000

Another ($US 1.99 + $0.99 shipping): http://www.ebay.com/itm/MAX7219-Red-Module-8-Digit-7-Segment-Digital-LED-Display-Tube-for-Arduino-MCU-/181723525156

Those are not assembled. I bought a heap of them because they were so cheap. For example:


This one ($US 3.42 with free shipping): http://www.ebay.com/itm/2PCS-MAX7219-EWG-8-Digit-Digital-Tube-Display-Control-Module-Red-for-arduino-/181736934655

Assembled (I think).

OK, so I may have underestimated by 42 cents. :stuck_out_tongue:


Today I got in the mail a 8x8 dot matrix display (4 modules) for $US 8.50 as described here Gammon Forum : Electronics : Microprocessors : Interfacing LED displays with the MAX7219 driver.

Not bad for the price because you can show any characters (it isn't just a 7-segment display).

@NickGammon thanks for the links!

(I may be wrong but it seems that the first link is "only the 7segment display", not with the MAX7219).

What I would need now is a kit (presoldered or not presoldered) with : MAX7219 + board (for 4 digits only) + 4digits 7segment display.

The 8digit board won't fit in my enclosure...

Any idea?

Search eBay? Something like this:

http://www.ebay.com/itm/I2C-4-Digit-7-Seven-Segment-Display-Arduino-uController-5V-Choose-Red-or-Green-/231556862719 That's $8 rather than $3, but you don't always find exactly what you want. Or get one of the 8-digits ones which are not assembled and just not solder on the second 4-digit LED module.

So has anyone an answer to my question from page 5?
Thanks a lot!

Mitschgel:
@aarg: I have one question remaining.
The project I'm currently working on is basically a DMX controlled Infrared Remote.
(DMX Reception and sending Infrared Commands is already working great, but I wanted to add the 7segment display to show the current DMX Adress of my Device.)

The DMX signal reception is using Timer2 ( for reference: the DMX code can be found here: receive-dmx-512-with-an-arduino)
and i have successfully added my 7segment display with your micros() based code.

Since I have unfortunately not much experience with arduino programming and all the timer based ISR stuff is a bit confusing, too...
is it advisable to leave the 7segment display output micros() based or should one better be working with your new timer2 based code?

In this case, I guess that I would have to switch your code from timer2 to timer1,
which I already tried (out of curiosity) and failed totally at :smiley:

Mitschgel:
So has anyone an answer to my question from page 5?
Thanks a lot!

I am just learning the timers. But it seems to me, it must be easy to change to timer 1 from timer 2. I will investigate and post a solution if I find it.

@Mitschgel. Okay, here is a version that will work with either timer 1 or timer 2. There's a #define variable near the top, you just set it to 1 or 2 to choose the timer.

tubeSevenSegment_105.ino (9.47 KB)