Thursday, January 2, 2014

Cheap 4-digit displays from SparkFun

Datasheet? We don't need no steeking Datasheet.

Erm. It would have saved a bunch of hassle.... but no problem! Today's most excellent delivery included a whole stack of 4-digit LED displays, in white, for a mere tuppence ha'penny each. That's about 60p allowing for inflation. No, really. 60 New Pence. 6/10ths of a pound. See Sparkfun's product page. Of course, this cheepness comes with a downside: Namely, no datasheet anywhere on t'interwebs. All links to the part number lead to dead Alibaba pages, so not that helpful really. So, some breadboard, a power supply and a tedious amount of plug & pray later, we discover that these odd little chaps have four un-connected decimal points (boo!), a working "clock" style separator, and the following pinout:

  1. digit 1 anode
  2. digit 2 anode
  3. digit 3 anode
  4. digit 4 anode
  5. segment a cathode
  6. segment b cathode
  7. segment c cathode
  8. segment d cathode
  9. segment e cathode
  10. segment f cathode
  11. segment g cathode
  12. lower DP
  13. upper DP

Lower DP & upper DP may or may not be the wrong way around, I forget. I ain't using them anyway, so I've ignore them for the time being. YMMV. If you want to use them, note that lower DP belongs to digit 2, and upper DP belongs to digit 3 - erm, ahem, or vice versa. Between them you get your clock colon thing.

Electrickery!.

So... let's make these babies do something. As I have lots, but only a small amount of interconnectors, I decided on 2 displays which would scroll a message. Why walk, when one can run?

Obviously, we can't hook this straight into the Arduino, lacking as I do the 24 digital pins necessary. I guess you could do it on a Mega - but why would you? So many pins.... instead, why not use a couple of shift registers? The 74HC595 seems like a perfect fit, and at a mere 30p per unit from Farnell, it's a steal. You could use just 2 of them (1 per active digit), but you'd need another 8 pins from the Arduino then to run the common anodes. Or.... heck, throw another '595 on the barbie why not. We shall use 3 then. Here's how it works:

U1 (74HC595) is connected to the 7 segments + decimal point cathodes of the left-hand display. Q0=a, Q1=b, ..., Q6=g, Q7=dp1 & dp2

U2 (74HC595) is connected to the 7 segments + decimal point cathodes of the right-hand display, same as above.

U3 (74HC595) is connected to the anodes of both displays, so Q0=LH Digit 1, Q1=LH Digit 2, ..., Q4=RH Digit 1, Q5=RH Digit 2, etc. Connection to each pin is via a current limiting resistor of 270 ohms. Note that these resistors would be better if they were connected to each segment on the cathode; connecting to the anode means a minus sign is bright, and an "8" is dim, as the same current has to drive all of the segments. But I ran out of interconnects, so it wasn't happening.

Making it work - software...

Each chip is daisy-chained such that U3 feeds into U2 which feeds into U1. Data coming out of U1 is discarded. Therefore, to load all the shift registers, one transmits the LH byte, then the RH byte, then the "digit select" byte to the shift registers. The old data is pushed out of the way, the new data is then latched in. In common with all(?) of these multi-digit displays, one has to rapidly cycle between each digit individually firing the appropriate LEDs, making it look like all 4 digits are lit at once. Hence, to light all 8 digits of the display, you send the following in a tight loop:

  • 1st char, 5th char, bit pattern 10001000
  • 2nd char, 6th char, bit pattern 01001100
  • 3rd char, 7th char, bit pattern 00100010
  • 4th char, 8th char, bit pattern 00010001

The first job was to borrow the 7-segment tutorial from LucidTronix (http://www.lucidtronix.com/tutorials/41), thanks chaps!

Then, just play around adapting things until a test message worked; then put the main message loop code in place, and voila! A perfect scrolling display!

It works best in a darkened room, as the unlit segments are still clearly visible on the naked displays. Fortunately, having access to a complete workshop means I could sandblast a little perspex, which works almost as well :)

Code:

 /*   
 --------------------------------------------------------------------------------  
 -- 7 Segment Dual Scrolling Display  
 -- by Ade Vickers  
 --  
 -- Adapted from LucidTronix 7 Segment LED and 74HC595 shift   
 -- register tutorial at: http://www.lucidtronix.com/tutorials/41  
 --  
 -- Circuit requirements:  
 --  3x 74XX595 shift registers  
 --  2x 4-character LED displays (common anode or cathode)  
 --  Current limiting resistors - 1 per segment. Use resistor arrays, much neater  
 --  Decoupling caps to taste  
 --  An Arduino.  
 --  
 -- Version On     By Comment  
 -- -------- ----------- --- ----------------------------------------------------  
 -- 1.00   02-Jan-2014 JAV Created  
 --  
 --------------------------------------------------------------------------------  
 */  
 // SPI interface.  
 int dataPin = 2;  
 int latchPin = 3;  
 int clockPin = 4;  
 /*  
 Alphanumerics  
   0 1 2 3 4 5 6 7 8 9  
   a b c d e f g h i j  
   k l m n o p q r s t  
   u v w z y z -  _ ^  
 Invert all 0/1s if you are using a common cathode display  
 */  
 byte dec_digits[] = { 0b00000011,0b10011111,0b00100101,0b00001101,0b10011001,0b01001001,0b01000001,0b00011111,0b00000001,0b00001001,  
            0b00010001, 0b11000001, 0b01100011, 0b10000101, 0b01100001, 0b01110001, 0b01000011, 0b11010001, 0b10011111, 0b10001111,   
            0b10010001, 0b11100011, 0b01010111, 0b11010101, 0b00000011, 0b00110001, 0b00011001, 0b11110101, 0b01001001, 0b11100001,   
            0b10000011, 0b10100011, 0b10101011, 0b11111111, 0b10011001, 0b11111111, 0b11111101, 0b11111111, 0b01111111, 0b11101111 };  
 // Position codes  
 byte dec_display[]= { 0b00010001, 0b00100010 ,0b01000100, 0b10001000 };   
 // The message to display. Terminates with char 255 (because it turns out zero is a valid character, doh!).  
 // TODO: Read from serial port?  
 byte message[] = {38, 36, 39, 36, 38, 37, 7, 36, 28, 14, 16, 22, 14, 23, 29, 37, 13, 18, 28, 25, 21, 10, 34, 37, 12, 24, 30, 27, 29, 14, 28, 14,   
          34, 37, 24, 15, 37, 28, 25, 10, 27, 20, 15, 30, 23, 37, 37, 37, 29, 17, 10, 23, 20, 28, 37, 16, 30, 34, 28, 37, 37, 39, 36, 38,   
          36, 39, 37, 37, 37, 255};  
 int msgLength;  
 // Loop variables  
 int iPos = 0;  // Position in message to start sending digits this time (assuming we're scrolling the message)  
 int iCount = 0; // Counter to provide a delay  
 void setup() {  
  //set pins to output so you can control the shift register  
  pinMode(latchPin, OUTPUT);  
  pinMode(clockPin, OUTPUT);  
  pinMode(dataPin, OUTPUT);  
  // Work out how long the message is  
  // Max 1000 chars.  
  // Use a Geordie loop for this  
  for (int yi = 0; yi <= 999; yi++) {  
   if (message[yi]==255) {  
     msgLength = yi;  
     yi=1000; // Quit the loop now  
   }  
  }   
 }  
 void loop() {  
  int _d0, _d1; // The 2 digits we will activate  
  // HOW IT WORKS  
  // There are 3 shift registers chained together, in left-to-right order DIG1, DIG2, DIGSELECT  
  // DIG1 can display any one of the 4 digits at a time. The 4 MSBits of DIGSELECT choose which one is ON.  
  // DIG2 is identical. The least significant 4 bits of DIGSELECT choose which one.  
  // e.g. to show the number 1 at position 1 (counting L->R), we load 3 bytes: 1001 1111, 1111 1111, 1000 0000  
  // So DIG1 gets 1001 1111 (= segs b & c switched on - remember, active LOW on the cathode),   
  // DIG2 gets 1111 1111 = all off  
  // DIGSELECT gets 1000 0000 = display digit 1 in DIG1  
  //  
  // e.g. to show 1234 5678, we would:  
  // - Send 1 (1001 1111), 5 (0100 1000) and 1000 1000, pause a ms or 2, then  
  // - send 2 (0010 0101), 6 (0100 0001) and 0100 0100, pause a ms or 2.... then the next 2 digits.  
  // Then repeat.  
  //   
  for (int digitOffset = 0; digitOffset < 4; digitOffset++) {  
   // Set the appropriate digits.  
   _d0 = iPos + 4 + digitOffset;  
   _d1 = iPos + digitOffset;  
   // These 2 lines cause the message to wrap around when we run out of characters.  
   if (_d0 >= msgLength) _d0 = _d0 - msgLength;   
   if (_d1 >= msgLength) _d1 = _d1 - msgLength;  
   // Write the appropriate digits  
   digitalWrite(latchPin, LOW);  
   shiftOut(dataPin, clockPin, LSBFIRST, dec_digits[message[_d0]]);   
   shiftOut(dataPin, clockPin, LSBFIRST, dec_digits[message[_d1]]);   
   shiftOut(dataPin, clockPin, LSBFIRST, dec_display[3 - digitOffset]);   
   digitalWrite(latchPin, HIGH);  
   // Wait 5ms to allow POV, then move to the next digit  
   // Turns out we don't need this. Run the bugger as fast as possible, otherwise  
   // it just flickers.  
   //delay(2);  
  }  
  // Cheesy delay loop. After 200 counters, move to the next digit in the message.  
  // Comment out to prevent scrolling. Adjust value to adjust scrolling speed.  
  iCount++;  
  if (iCount > 200) {  
   //Shift to the next digit  
   iCount = 0;  
   iPos++;  
   if (iPos >= msgLength) iPos = 0;  
  }  
 }  

A short video:

2 comments:

  1. Thanks for this post. Your wiring diagram really helped me sort out how the LED display worked. My Nano and I thank you.

    ReplyDelete
  2. Thanks for posting this. Your layout of the shift register really helped me understand how this worked. My Nano and I thank you.

    (Apologies if this double posts.)

    ReplyDelete