4 Digit 7 Segment Serial LED Module - TM74HC595

I bought a 4 Digit 7 Segment serial led module with 2 x TM74HC595 shift registers.
I have the data sheet and some sample code.
I am able to get the sample code to display - which is simply the digits in the void loop.

The connections are 3 wires as listed in the code plus power.

I am trying to understand the meaning/limitations of first 3 lines of the code, which appears to be written in hex format and which I thought would determine the scope of what could be displayed.
However, the second line does not appear to correlate with the third line - so I am a bit lost.

For example I figure 0xC0 (hex) to represent 192 (decimal) - or have I got the wrong end of the stick?

  1. An explanation of what line 3 represents would be appreciated.

  2. How would I display a decimal point - lets say I wanted to display 999.5?

    unsigned char LED_0F[] = 
    {// 0	 1	  2	   3	4	 5	  6	   7	8	 9	  A	   b	C    d	  E    F    -
    	0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x8C,0xBF,0xC6,0xA1,0x86,0xFF,0xbf
    };
    unsigned char LED[4];	//用于LED的4位显示缓存
    int SCLK = 5;
    int RCLK = 6;
    int DIO = 7; //这里定义了那三个脚
    void setup ()
    {
      pinMode(SCLK,OUTPUT);
      pinMode(RCLK,OUTPUT);
      pinMode(DIO,OUTPUT); //让三个脚都是输出状态
    }
    void loop()
    {
        LED[0]=3;
    	LED[1]=2;
    	LED[2]=1;
    	LED[3]=0;
    	while(1)
    	{
    		LED4_Display ();
    	} 
      
    }
    
    void LED4_Display (void)
    {
    	unsigned char *led_table;          // 查表指针
    	unsigned char i;
    	//显示第1位 - Digit 1
    	led_table = LED_0F + LED[0];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x01);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第2位 - Digit 2
    	led_table = LED_0F + LED[1];
    	i = *led_table;
    	LED_OUT(i);		
    	LED_OUT(0x02);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第3位 - Digit 3
    	led_table = LED_0F + LED[2];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x04);	
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第4位 - Digit 4
    	led_table = LED_0F + LED[3];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x08);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    }
    
    void LED_OUT(unsigned char X)
    {
    	unsigned char i;
    	for(i=8;i>=1;i--)
    	{
    		if (X&0x80) 
                {
                  digitalWrite(DIO,HIGH);
                 }  
                else 
                {
                  digitalWrite(DIO,LOW);
                }
    		X<<=1;
                digitalWrite(SCLK,LOW);
                digitalWrite(SCLK,HIGH);
    	}
    }

4x7_led_pic.jpg

4x7_led_data.pdf (33.3 KB)

    unsigned char LED_0F[] =
    {// 0	1	  2	   3	4	5	  6	   7	8	9	  A	   b	C    d	  E    F    -
    	0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x8C,0xBF,0xC6,0xA1,0x86,0xFF,0xbf
    };

Each byte is a segment list. The high bit is set for all of them, that controls the decimal point. 0xC0 is 0b11000000. The segments are ordered from the bottom up ABCDEFG, bit 01234567, bit 8 is the decimal point as I mentioned. A zero bit illuminates the digit. So 0xC0 represents digit zero, because the decimal point is extinguished and all segments are on except for bit 7, segment G, which is the middle segment. Compare with the digit 8, which is of course 0x80 since all the segments are turned on.

I don't like the code.

  led_table = LED_0F + LED[0];
  i = *led_table;
  LED_OUT(i);

could be simply

  LED_OUT( LED_0F[ LED[0] ] );

unless I am confused (however that may have been the purpose).
Also this function:

    void LED4_Display (void)
    {
    	unsigned char *led_table;          // 查表指针
    	unsigned char i;
    	//显示第1位 - Digit 1
    	led_table = LED_0F + LED[0];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x01);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第2位 - Digit 2
    	led_table = LED_0F + LED[1];
    	i = *led_table;
    	LED_OUT(i);		
    	LED_OUT(0x02);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第3位 - Digit 3
    	led_table = LED_0F + LED[2];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x04);	
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第4位 - Digit 4
    	led_table = LED_0F + LED[3];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x08);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    }

can be rewritten as:

void LED4_Display (void)
{
  byte digit = 0;
  for (byte dselect = 1; dselect < 0x10; dselect = dselect << 1)
  {
    LED_OUT(LED_0F[LED[digit]]);
    LED_OUT(dselect);
    digitalWrite(RCLK, LOW);
    digitalWrite(RCLK, HIGH);
    ++digit;
  }
}

However I have no hardware to test.

Thanks for the explanation and the code review.

Before I start trying to use the provided sample code, I think I will try to use an existing library.

My sketch has an ultrasonic sensor which measures distance and prints the distance to a 1602 LCD screen with a backpack i.e. via I2C serial.

Instead of printing to the LCD screen I want to print the distances to the LED tube display.

Given I have a 3-wire connection, I assume I need an SPI library.
So I will try to make it work with the Arduino Standard SPI library.

I am still searching for some sample code.

I'm stumped.
Could someone please suggest a library with some sample code to get started.
Basically I have an LCD screen displaying the distances from an ultrasonic sensor, and I simply want to replace the LCD screen with the led tube display.

@aarg : I have started using the sample code.
I have tested both code revisions u provided and both work.
This is the current revised code.

    unsigned char LED_0F[] = 
    {// 0	 1	  2	   3	4	 5	  6	   7	8	 9	  A	   b	C    d	  E    F    -
    	0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x8C,0xBF,0xC6,0xA1,0x86,0xFF,0xbf
    };
    unsigned char LED[4];	//用于LED的4位显示缓存
    int SCLK = 5;
    int RCLK = 6;
    int DIO = 7; //这里定义了那三个脚
    void setup ()
    {
      pinMode(SCLK,OUTPUT);
      pinMode(RCLK,OUTPUT);
      pinMode(DIO,OUTPUT); //让三个脚都是输出状态
    }
    void loop()
    {
        LED[0]=5;
    	LED[1]=1;
    	LED[2]=0;
    	LED[3]=2;

//        LED[] = {5,1,0,2};
    	while(1)
    	{
    		LED4_Display ();
    	} 
      
    }
    
    void LED4_Display (void)
    {
        byte digit = 0;
        for (byte dselect = 1; dselect < 0x10; dselect = dselect << 1)
        {
            LED_OUT(LED_0F[LED[digit]]);
            LED_OUT(dselect);
            digitalWrite(RCLK, LOW);
            digitalWrite(RCLK, HIGH);
            ++digit;
        }
    }

    void LED_OUT(unsigned char X)
    {
    	unsigned char i;
    	for(i=8;i>=1;i--)
    	{
    	    if (X&0x80) 
            {
                digitalWrite(DIO,HIGH);
            }  
            else 
            {
                 digitalWrite(DIO,LOW);
            }
        X<<=1;
        digitalWrite(SCLK,LOW);
        digitalWrite(SCLK,HIGH);
    	}
    }

I would appreciate some help making a revision to enter the number to be displayed simply as "x=n".
Let's say I want to display "2015".

Presently I have to specify the array values as follows:
LED[0]=5;
LED[1]=1;
LED[2]=0;
LED[3]=2;

What I would like to do is specify "x=2015;"
and have the code create the array from that.

I have tried applying my knowledge of PHP arrays and functions, but it appears C++ is a bit different.

How about I give you a clue and see if you can run with it?

To get the 1's digit:

LED[0] = x % 10;

Also a hint: integer division has no remainder. :slight_smile:
Also thanks for testing my code.

What does the function LED4 do? It displays four digits.

How does it know which four digits to display? They are put in LED[0] to LED[3].

So, one way to go (not the best way, but one that uses code you already have) is to write a function

void putDecimalIntoLED(int n) {
  // put the ones digit of n into LED[0], the tens digit into LED[1], and so on
}

A better way, IMO, is to work out the series of what actually happens in order to get the number out.

A sequence of 8 bytes is written out in four pairs.

each pair is first, the map of what segments should be lit - LED_0F[digit], then 1,2,4,or 8 to select which digit display the segment map goes to. Then RCLK is toggled LOW-HIGH.

(question - do all four digits have to go out? Or can you just output the digits in any sequence by sending these byte pairs? Is that second byte also a bit map? Can you clear the display to zero by sending LED_0F[0] and then 0x0F ?)

To send a byte (8 bits), send each bit to DIO (most significant bit first), and toggle SCLK from LOW to HIGH.

Given that, it becomes possible to write a function

void putDigitIntoDisplay(int digit, int position) {
  // Digit should be 0-15, position 0-3
}

If you understand what the code is doing , then you can do fun stuff like making the display animate a loop.

@aarg : No need to thank me, it is much more elegant than the original - I should thank you.

Fair enough to work on clues... but - I am wondering why use division.

Just so u know my thought process and can guide me, my knowledge of arrays and functions comes from PHP.

So with a PHP mindset, I would look to do something like this :

$x = "2015";

LED[0] = $x[3];
LED[1] = $x[2];
LED[2] = $x[1];
LED[3] = $x[0];

Also I would get length of $x, and only build the array with the number of digits in $x.

Switching to C++.

I have tried using the above with appropriate adjustments for C++, but it wont compile.

First I declared x as a variable : int x;

And I added this in the void loop :
x = 2015;
LED[0]=x[3];
LED[1]=x[2];
LED[2]=x[1];
LED[3]=x[0];

I got this error : _4bitLED_V12.ino:24:17: error: invalid types 'int[int]' for array subscript

Anyway that was before I posted and u replied. My thinking was I effectively have a value and I need to split it into single digits.

So now I am trying to get my head around the division...

I can see that taking 2015 and dividing by 1000 will give me 2, which would be LED[3].
i.e. LED[3]=x % 1000;
Now I actually tested this and LED[3] does not illuminate, LED[0 thru 2] show zero as expected.

However, running your code of LED[0] = x % 10; gives me 5 i.e. the first digit.

Now mathematically that does not make sense, since 2015/10 = 201.
Hmmm so I am obviously missing something.
The divisor would appear to be representing something other than a decimal value.
On face value it is discarding everything but the first digit....
Sorry I need another clue for the next digit.

PaulMurrayCbr:
What does the function LED4 do? It displays four digits.

How does it know which four digits to display? They are put in LED[0] to LED[3].

So, one way to go (not the best way, but one that uses code you already have) is to write a function

This is in essence what I wish to do - see post #4.

PaulMurrayCbr:
(question - do all four digits have to go out? Or can you just output the digits in any sequence by sending these byte pairs? Is that second byte also a bit map? Can you clear the display to zero by sending LED_0F[0] and then 0x0F ?)

The output will eventually be a readout from a sensor. See post #2.
From what I can see, any digit that is NOT output, will automatically display a zero. I will probably not want it to display at all. I have not yet tried to deliberately clear it to zero with code.

Thanks aarg.

x = 2015;

LED[0] = x % 10;
LED[1] = (x % 100 - LED[0]) / 10;
LED[2] = (x % 1000 - LED[1]*10 - LED[0])/100;
LED[3] = (x % 10000 - LED[2]*100 - LED[1]*10 - LED[0])/1000;

I was confused when u mentioned division and assume "%" was division in C==.
Found out it was modulo giving remainder.

Not elegant I know, but it gives me what I want.

Thanks

aisc:
Thanks aarg.

x = 2015;

LED[0] = x % 10;
LED[1] = (x % 100 - LED[0]) / 10;
LED[2] = (x % 1000 - LED[1]*10 - LED[0])/100;
LED[3] = (x % 10000 - LED[2]*100 - LED[1]*10 - LED[0])/1000;

I was confused when u mentioned division and assume "%" was division in C==.
Found out it was modulo giving remainder.

Not elegant I know, but it gives me what I want.

Thanks

Well, what I said was confusing, actually I meant that the remainder is discarded with integer division.
I only realized it a half hour after I left the computer.

But I think there is a slightly easier way:

      LED[0] = x % 10;
      LED[1] = x/10 % 10;
      LED[2] = x/100 % 10;
      LED[3] = x/1000;

Only if x<10000.
Also, can you see how this could be made into a loop? :slight_smile:

Much more elegant - thanks.

I will wrap my head around a loop another day, but for the moment my object is to print out the value of a calculation, which I can now call x :slight_smile:

Three follow-on questions.

  1. This module has no resistors on board. I have been doing my testing with 3.3V output from the arduino. Just wondering I need to add a resistor. FWIW the only resistor shown on the datasheet is for an external led.

  2. I notice the fewer segments lit, the brighter the digit, so 1 will always be brighter than 8.
    I have come across brightness topics in other threads, so just wondering if this module's brightness can be controlled.

  3. The hard one - at the moment x is an integer. What if I wanted x to be a float i.e. if I wanted to display the decimal point. A clue please....

aisc:
Much more elegant - thanks.

I will wrap my head around a loop another day, but for the moment my object is to print out the value of a calculation, which I can now call x :slight_smile:

Three follow-on questions.

  1. This module has no resistors on board. I have been doing my testing with 3.3V output from the arduino. Just wondering I need to add a resistor. FWIW the only resistor shown on the datasheet is for an external led.

  2. I notice the fewer segments lit, the brighter the digit, so 1 will always be brighter than 8.
    I have come across brightness topics in other threads, so just wondering if this module's brightness can be controlled.

  3. The hard one - at the moment x is an integer. What if I wanted x to be a float i.e. if I wanted to display the decimal point. A clue please....

Answers:

  1. Normally you have resistors in series with an LED. But in this application, the LED is not on all the time. An LED can exceed its rated maximum continuous current for short periods of time, if the average current is the same. This is how they can get away without resistors. Indeed, you should avoid any code that leaves a segment on continuously.

  2. Yes. Usually each digit has a high current driver such as a transistor. But this board doesn't have any. So one digit driver has to share the current for 8 LEDs and they will dim. That is, if the segments are enabled simultaneously. The solution is to modify the software so that only one segment in the entire display is on at any given time.

  3. That one is no fun. :slight_smile: I don't have any ideas other than sluggo coding.

Normally you have resistors in series with an LED. But in this application, the LED is not on all the time. An LED can exceed it's rated maximum continuous current for short periods of time, if the average current is the same.

I'm tempted to get Grumpy Mike involved here. He would disagree that exceeding the chip's maximum ratings is a good idea for any length of time. I know, because I tried it once.

Feel free. I noticed a recent thread on the subject in passing. It is true that what is okay for the LED is not necessarily good for the chip that is driving it. I think it is thermal dissipation ability of the 74HC595 that determines the safety in this case. I would be the last to vouch for the engineering of a cheap display module from China. But I guess you'd have to look up the 595 specs. @aisc, how warm do they get?

Also, before you sic @Grumpy on me, consider that in this circuit, the LED is connected to a gate output at both terminals. Whereas the other thread discussed the case where one end is connected directly to a ground or supply voltage. Thus the series resistance of the FET driver circuit is almost doubled, giving more leeway. The specified maximum current for the 595 is 35mA and the max power dissipation for an SO package is 500mW.

aarg:
Answers:

  1. Normally you have resistors in series with an LED. But in this application, the LED is not on all the time. An LED can exceed its rated maximum continuous current for short periods of time, if the average current is the same. This is how they can get away without resistors. Indeed, you should avoid any code that leaves a segment on continuously.

Noted.

  1. Yes. Usually each digit has a high current driver such as a transistor. But this board doesn't have any. So one digit driver has to share the current for 8 LEDs and they will dim. That is, if the segments are enabled simultaneously. The solution is to modify the software so that only one segment in the entire display is on at any given time.

Ok I understand that by lighting individual segments, the brightness will be consistent - but of course the voltage through the lit segment will be higher.
I am wondering if the lighting of a whole digit is deliberate to ensure the voltage through each segment is lower.

Next time I will buy a different module, or roll my own.

  1. That one is no fun. :slight_smile: I don't have any ideas other than sluggo coding.

I guess I will give this a miss for now.

aarg:
@aisc, how warm do they get?

When I noticed the lack of resistors, I deliberately ran the unit at 3.3V and it does not get hot at all.
I have not tried at 5V.

Thanks for the help.

I'm using TM1638 based modules. They're sweet.

aisc:
I am wondering if the lighting of a whole digit is deliberate to ensure the voltage through each segment is lower.

Not likely, since they include the character "-" which only has one segment. I thought that he best driver algorithm would be the reverse of what is normal - instead of cycling through the digits and selecting the segments, you should cycle through the segments, and select (turn on or off) the digits. But I realized that isn't possible either. So you have to cycle through all 8*4=32 segments if you want equal intensity. Each segment should get a 1/32 duty cycle. So it may not be very bright.

aarg:
Answers:

  1. Normally you have resistors in series with an LED. But in this application, the LED is not on all the time. An LED can exceed its rated maximum continuous current for short periods of time, if the average current is the same. This is how they can get away without resistors. Indeed, you should avoid any code that leaves a segment on continuously.

Absolute and utter bilge. You ought to be made to hand in your soldering iron, that is if they allow hot objects like that where you are.

can exceed its rated maximum continuous current for short periods of time, if the average current is the same.

So on for an hour and off for 48 hours then? - Grow up. How do you define continuous anyway?

There are thermal considerations but that is not the only effect. Depletion of charge carriers in the semiconductor material will also happen which will degrade the semiconductors and lead to a shorter life.

The only reason why people "get away" with this sort of thing is that they don't know how to test for the damage that this sort of thing actually does.

Do you take this attitude to driving? Have you driven hundreds of times over the speed limit and not got caught? So that's alright then. So either grow up or go somewhere else to peddle your clap trap.

aisc:
I will wrap my head around a loop another day

Something like this:

for(int i = 0; i<4; i++) {
  LED[i] = x % 10;
  x /= 10;
}

If this loads the digits into the array in the reverse order of how you actually want it, then the loop should be:

for(int i = 3; i>=0; i--) {
  LED[i] = x % 10;
  x /= 10;
}