Tag Archives: display

Using older Noritake Itron VFD modules

Now and again you come across interesting parts on ebay, from friends or just rooting around in second-hand stores. One example of this was a huge Noritake Itron 40 x 2 character vacuum-fluorescent display from 1994 (or earlier) which was passed on from a client.

Originally it looked quite complex, however after spending some time the data sheets were found and it was discovered to have a simple serial interface – and with a little work we’ve got it working, so read on if you’re interested in classic VFDs or have a similar unit.

Getting Started

The model number for our display is CU40026SCPB-T20A. Here’s a quick walk-around, the front:

Noritake CU40026SCPB-T20A VFD

… the back:

Noritake CU40026SCPB-T20A VFD

… the interfaces:

Noritake CU40026SCPB-T20A VFD

… and configuration jumpers:

Noritake CU40026SCPB-T20A VFD

The serial interface baud rate is determined by the jumpers (above), for example:

Noritake CU40026SCPB-T20A VFD

So comparing the table above against the jumpers on our module gives us a data speed of 19200 bps with no parity. Great – we can easily create such a connection with a microcontroller with a serial output and 5V logic levels; for our examples we’ll use an Arduino-compatible board.

Wiring up the VFD is simple – see the white jumpers labelled CN2 as shown previously. Pin 1 is 5V (you need an external supply that can offer up to 700 mA), pin 2 to Arduino digital pin 7, and pin 3 to Arduino and power supply GND. We use Arduino D7 with software serial instead of TX so that the display doesn’t display garbage when a sketch is being uploaded. Then it’s a matter of simply sending text to the display, for example here’s a quick demonstration sketch:

// Working with Noritake Itron VFD modules - model CU40026SCPB-T20A

#include <SoftwareSerial.h>
SoftwareSerial VFD(6,7); // RX, TX

void setup()
{
  VFD.begin(19200);
}

void loop()
{
  VFD.print("Hello, world. This is a Noritake VFD "); // You can blast out text 
  do {} while (1);
}

… and the results:

Noritake CU40026SCPB-T20A VFD

If you’re not keen on the colour or intensity of the display, try some Perspex over the top – for example:

Noritake CU40026SCPB-T20A VFD

Controlling the display

At this point you’ll need the data sheet, there’s a couple you can download: data sheet onedata sheet two. As you saw previously, writing text is very simple – just use .print functions. However you may want to send individual characters, as well as special commands to control aspects of the display. These are outlined in the data sheet – see the “Software Commands” and “Character Fonts” tables.

If you need to send single commands – for example “clear display” which is 0x0E, use a .write command, such as:

  VFD.write(0x0E); // clear display

Some commands are in the format of escape codes (remember those?) so you need to send ESC then the following byte, for example to change the brightness to 50%:

    VFD.write(0x1B); // ESC
    VFD.write(0x4C); // brightness
    VFD.write(0x40); // 50% brightness

Armed with that knowledge and the data sheets you can now execute all the commands. According to the data sheet it is possible to change fonts however no matter what the hardware jumper or command we tried it wouldn’t budge from the Japanese katakana font. Your screen may vary. If you use the “screen priority write” function heed the data sheet with respect to the extended “busy” time by delaying subsequent writes to the display by a millisecond.

 Putting it all together

Instead of explaining each and every possible command, I’ve put the common ones inside documented functions in the demonstration sketch below, which is followed by a quick video of the sketch in operation.

// Working with Noritake Itron VFD modules - model CU40026SCPB-T20A
// John Boxall 2013

#include <SoftwareSerial.h>
SoftwareSerial VFD(6,7); // rx, tx

void setup()
{
  VFD.begin(19200); // set speed for software serial port 
  resetVFD();  
  VFDclearsceen();
//  VFD.write(0x12); // vertical scroll mode (on)
}

void resetVFD()
// performs a software reset on the VFD controller
{
  VFD.write(0x1B); // ESC
  VFD.write(0x49); // software reset
}

void VFDnewline()
// moves cursor to start of next line
{
  VFD.write(0x0D); // carriage return
  VFD.write(0x0A); // line feed
}

void VFDclearsceen()
// moves cursor to top-left and clears display
{
  VFD.write(0x0E); // clear display 
  VFD.write(0x0C); // form feed - cursor to top-left
}

void VFDbrightness(int amount)
// sets VFD brightness - 25/50/75/100%
// uses ESC sequences
{
  switch(amount)
  {
  case 25:
    VFD.write(0x1B); // ESC
    VFD.write(0x4C); // brightness
    VFD.print(0); // 25% brightness
    break;
  case 50:
    VFD.write(0x1B); // ESC
    VFD.write(0x4C); // brightness
    VFD.write(0x40); // 50% brightness
    break;
  case 75:
    VFD.write(0x1B); // ESC
    VFD.write(0x4C); // brightness
    VFD.write(0x80); // 75% brightness
    break;
  case 100:
    VFD.write(0x1B); // ESC
    VFD.write(0x4C); // brightness
    VFD.write(0xC0); // 100% brightness
  }
}

void VFDchars()
// run through characters for selected font
{
  for (int i = 21 ; i < 256; i++)
  {
    VFD.write(0x16); // underline cursor off
    VFD.write(i);
    delay(100);
  }
}

void moveCursor(byte position)
// moves the cursor - top row is 0~39, bottom row is 40~79
// vertical scroll mode must be turned off if used
{
    VFD.write(0x1B); // ESC
    VFD.write(0x48); // move cursor 
    VFD.write(position); // location
}

void loop()
{
  VFD.write(0x16); // underline cursor off
  VFD.print("Hello, world - line one."); // You can blast out text 
  delay(1000);      
  VFDnewline();
  VFD.print("Hello, world - line two."); 
  delay(1000);    
  VFDclearsceen();
  VFDbrightness(25);
  VFD.print("*** 25% brightness ***");   
  delay(1000);
  VFDclearsceen();  
  VFDbrightness(50);
  VFD.print("*** 50% brightness ***");     
  delay(1000);
  VFDclearsceen();   
  VFDbrightness(75);
  VFD.print("*** 75% brightness ***");       
  delay(1000);
  VFDclearsceen();   
  VFDbrightness(100);
  VFD.print("*** 100% brightness ***");         
  delay(1000);
  VFDclearsceen();

  VFDchars();
  VFDclearsceen();

  for (int i = 0; i < 80; i++)
  {
    VFD.write(0x16); // underline cursor off
    moveCursor(i);
    VFD.print("X");
    delay(100);
    moveCursor(i);    
    VFD.print(" ");    
  }
  VFDclearsceen();
}

Conclusion

We hope you found this interesting and helpful.

This post is brought to you by pmdway.com – everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.

Tutorial – Arduino and MC14489 LED Display Driver

As an increasing number of people enjoy experimenting with retro-hardware and electronics – especially stuff with numerical LED displays – they have discovered the classic MC14489 LED display driver.

The MC14489 (originally from Motorola) can drive five seven-segment LED numbers with decimal point, or a combination of numbers and separate LEDs. You can also daisy-chain more than one to drive more digits, and it’s controlled with a simple serial data-clock method in the same way as a 74HC595 shift register.

Although it’s an older part, sourcing the MC14489 isn’t too difficult – you can order them from PMD Way in a blink.

For the purpose of the tutorial we’ll show you how to send commands easily from your Arduino or compatible board to control a five-digit 7-segment LED display module – and the instructions are quite simple so they should translate easily to other platforms. Once you have mastered the single module, using more than one MC14489 will be just as easy. So let’s get started.

Hardware

Before moving forward, download the data sheet (pdf). You will need to refer to this as you build the circuit(s). And here’s our subject in real life:

MC14489 LED Display Driver from PMD Way

For our demonstration display we’ll be using a vintage HP 5082-7415 LED display module. However you can use almost any 7-segment modules as long as they’re common-cathode. If you’re using a four-digit module and want an extra digit, you can add another single digit display.

If you want a ruler, the design files are here.

Connecting the MC14489 to an LED display isn’t complex at all. From the data sheet consider Figure 9:

MC14489 LED Display Driver from PMD Way

Each of the anode control pins from the MC14489 connect to the matching anodes on your display module, and the BANK1~5 pins connect to the matching digit cathode pins on the display module. You can find the MC14489 pin assignments on page 1 of the data sheet. Seeing as this is chapter fifty-one  – by now you should be confident with finding such information on the data sheets, so I will be encouraging you to do a little more of the work.

Interesting point – you don’t need current-limiting resistors. However you do need the resistor Rx – this controls the current flow to each LED segment. But which value to use? You need to find out the forward current of your LED display (for example 20 mA) then check Figure 7 on page 7 of the data sheet:

MC14489 LED Display Driver from PMD Way

To be conservative I’m using a value of 2k0 for Rx, however you can choose your own based on the data sheet for your display and the graph above.  Next – connect the data, clock and enable pins of the MC14489 to three Arduino digital pints – for our example we’re using 5, 6 and 7 for data, clock and enable respectively.

Then it’s just 5V and GND to Arduino 5V and GND – and put a 0.1uF capacitor between 5V and GND. Before moving on double-check the connections – especially between the MC14489 and the LED display.

Controlling the MC14489

To control the display we need to send data to two registers in the MC14489 – the configuration register  (one byte) and the display register (three bytes). See page 9 of the data sheet for the overview.

The MC14489 will understand that if we send out one byte of data it is to send it the configuration register, and if it receives three bytes of data to send it to the display register. To keep things simple we’ll only worry about the first bit (C0) in the configuration register – this turns the display outputs on or off. To do this, use the following:

digitalWrite(enable, LOW);
shiftOut(data, clock, MSBFIRST, B00000001); // used binary for clarity, however you can use decimal or hexadecimal numbers
digitalWrite(enable, HIGH);
delay(10);

and to turn it off, send bit C0 as zero. The small delay is necessary after each command.

Once you have turned the display on – the next step is to send three bytes of data which represent the numbers to display and decimal points if necessary. Review the table on page 8 of the data sheet. See how they have the binary nibble values for the digits in the third column.

Thankfully the nibble for each digit is the binary value for that digit. Furthermore you might want to set the decimal point – that is set using three bits in the first nibble of the three bytes (go back to page 9 and see the display register). Finally you can halve the brightness by setting the very first bit to zero (or one for full brightness).

As an example for that – if you want to display 5.4321 the three bytes of data to send in binary will be:

1101 0101 0100 0011 0010 0001

Let’s break that down. The first bit is 1 for full brightness, then the next three bits (101) turn on the decimal point for BANK5 (the left-most digit). Then you have five nibbles of data, one for each of the digits from left to right. So there’s binary for 5, then four, then three, then two, then one.

digitalWrite(enable, LOW); 
shiftOut(data, clock, MSBFIRST, B11010101); // D23~D16 
shiftOut(data, clock, MSBFIRST, B01000011); // D15~D8
shiftOut(data, clock, MSBFIRST, B00100001); // D7~D0
digitalWrite(enable, HIGH);
delay(10);

To demonstrate everything described so far, it’s been neatly packaged into our first example sketch:

// Example 51.1
// Motorola MC14489 with HP 5082-7415 5-digit, 7-segment LED display
// 2k0 resistor on MC14489 Rx pin
// John Boxall 2013 CC by-sa-nc
// define pins for data from Arduino to MC14489
// we treat it just like a 74HC595
int data = 5;
int clock = 6;
int enable = 7;
void setup()
{
 pinMode(data, OUTPUT);
 pinMode(enable, OUTPUT);
 pinMode(clock, OUTPUT);
 displayOn(); // display defaults to off at power-up
}
void displayTest1()
// displays 5.4321
{
 digitalWrite(enable, LOW); // send 3 bytes to display register. See data sheet page 9
 // you can also insert decimal or hexadecimal numbers in place of the binary numbers
 // we're using binary as you can easily match the nibbles (4-bits) against the table
 // in data sheet page 8
 shiftOut(data, clock, MSBFIRST, B11010101); // D23~D16
 shiftOut(data, clock, MSBFIRST, B01000011); // D15~D8
 shiftOut(data, clock, MSBFIRST, B00100001); // D7~D0
 digitalWrite(enable, HIGH);
 delay(10);
}
void displayTest2()
// displays ABCDE
{
 digitalWrite(enable, LOW); // send 3 bytes to display register. See data sheet page 9
 // you can also insert decimal or hexadecimal numbers in place of the binary numbers
 // we're using binary as you can easily match the nibbles (4-bits) against the table
 // in data sheet page 8
 shiftOut(data, clock, MSBFIRST, B10001010); // D23~D16
 shiftOut(data, clock, MSBFIRST, B10111100); // D15~D8
 shiftOut(data, clock, MSBFIRST, B11011110); // D7~D0
 digitalWrite(enable, HIGH);
 delay(10);
}
void displayOn()
// turns on display
{
 digitalWrite(enable, LOW);
 shiftOut(data, clock, MSBFIRST, B00000001);
 digitalWrite(enable, HIGH);
 delay(10);
}
void displayOff()
// turns off display
{
 digitalWrite(enable, LOW);
 shiftOut(data, clock, MSBFIRST, B00000000);
 digitalWrite(enable, HIGH);
 delay(10);
}
void loop()
{
 displayOn();
 displayTest1();
 delay(1000);
 displayTest2();
 delay(1000);
 displayOff();
 delay(500);
}

… with the results in the following video:

Now that we can display numbers and a few letters with binary, life would be easier if there was a way to take a number and just send it to the display.

So consider the following function that takes an integer between 0 and 99999, does the work and sends it to the display:

void displayIntLong(long x)
// takes a long between 0~99999 and sends it to the MC14489
{
 int numbers[5];
 byte a=0; 
 byte b=0; 
 byte c=0; // will hold the three bytes to send to the MC14489 

 // first split the incoming long into five separate digits
 numbers[0] = int ( x / 10000 ); // left-most digit (will be BANK5)
 x = x % 10000; 
 numbers[1] = int ( x / 1000 );
 x = x % 1000; 
 numbers[2] = int ( x / 100 );
 x = x % 100; 
 numbers[3] = int ( x / 10 );
 x = x % 10; 
 numbers[4] = x % 10; // right-most digit (will be BANK1)

 // now to create the three bytes to send to the MC14489
 // build byte c which holds digits 4 and 5
 c = numbers[3];
 c = c << 4; // move the nibble to the left
 c = c | numbers[4];
 // build byte b which holds digits 3 and 4
 b = numbers [1];
 b = b << 4;
 b = b | numbers[2];
 // build byte a which holds the brightness bit, decimal points and digit 1
 a = B10000000 | numbers[0]; // full brightness, no decimal points

 // now send the bytes to the MC14489
 digitalWrite(enable, LOW);
 shiftOut(data, clock, MSBFIRST, a);
 shiftOut(data, clock, MSBFIRST, b);
 shiftOut(data, clock, MSBFIRST, c); 
 digitalWrite(enable, HIGH);
 delay(10); 
}

So how does that work? First it splits the 5-digit number into separate digits and stores them in the array numbers[]. It then places the fourth digit into a byte, then moves the data four bits to the left – then we bitwise OR the fifth digit into the same byte. This leaves us with a byte of data containing the nibbles for the fourth and fifth digit.

The process is repeated for digits 2 and 3. Finally the brightness bit and decimal point bits are assigned to another byte which then has the first digit’s nibble OR’d into it. Which leaves us with bytes a, b and c ready to send to the MC14489.

Note that there isn’t any error-checking – however you could add a test to check that the number to be displayed was within the parameter, and if not either switch off the display (see example 51.1) or throw up all the decimal points or … whatever you want.

Here’s our final demonstration sketch (example 51.2)

// Example 51.2
// Motorola MC14489 with HP 5082-7415 5-digit, 7-segment LED display
// 2k0 resistor on MC14489 Rx pin
// John Boxall 2013 CC by-sa-nc

// define pins for data from Arduino to MC14489
// we treat it just like a 74HC595
int data = 5;
int clock = 6;
int enable = 7;
long i;
void setup()
{
randomSeed(analogRead(0));
pinMode(data, OUTPUT);
pinMode(enable, OUTPUT);
pinMode(clock, OUTPUT);
displayOn(); // display defaults to off at power-up
}

void displayOn()
// turns on display
{
digitalWrite(enable, LOW);
shiftOut(data, clock, MSBFIRST, B00000001);
digitalWrite(enable, HIGH);
delay(10);
}

void displayOff()
// turns off display
{
digitalWrite(enable, LOW);
shiftOut(data, clock, MSBFIRST, B00000000);
digitalWrite(enable, HIGH);
delay(10);
}

void displayIntLong(long x)
// takes a long between 0~99999 and sends it to the MC14489
{
int numbers[5];
byte a=0;
byte b=0;
byte c=0; // will hold the three bytes to send to the MC14489

// first split the incoming long into five seperate digits
numbers[0] = int ( x / 10000 ); // left-most digit (will be BANK5)
x = x % 10000;
numbers[1] = int ( x / 1000 );
x = x % 1000;
numbers[2] = int ( x / 100 );
x = x % 100;
numbers[3] = int ( x / 10 );
x = x % 10;
numbers[4] = x % 10; // right-most digit (will be BANK1)

// now to create the three bytes to send to the MC14489
// build byte a which holds the brightness bit, decimal points and digit 1
a = B10000000 | numbers[0]; // full brightness, no decimal points
// build byte b which holds digits 3 and 4
b = numbers [1];
b = b << 4;
b = b | numbers[2];
// build byte c which holds digits 4 and 5
c = numbers[3];
c = c << 4; // move the nibble to the left
c = c | numbers[4];

// now send the bytes to the MC14489
digitalWrite(enable, LOW);
shiftOut(data, clock, MSBFIRST, a);
shiftOut(data, clock, MSBFIRST, b);
shiftOut(data, clock, MSBFIRST, c);
digitalWrite(enable, HIGH);
delay(10);
}

void loop()
{
i = random(0,100000);
displayIntLong(i);
delay(200);
}

…which results in the following video:

You can also display the letters A to F by sending the values 10 to 15 respectivel to each digit’s nibble. However that would be part of a larger application, which you can (hopefully) by now work out for yourself. Furthermore there’s some other characters that can be displayed – however trying to display the alphabet using 7-segment displays is somewhat passé. Instead, get some 16-segment LED modules or an LCD.

Finally, you can cascade more than one MC14489 to control more digits. Just run a connection from the data out pin on the first MC14889 to the data pin of the second one, and all the clock and enable lines together. Then send out more data – see page 11 of the data sheet. If you’re going to do that in volume other ICs may be a cheaper option and thus lead you back to the MAX7219.

Conclusion 

For a chance find the MC14489 is a fun an inexpensive way to drive those LED digit displays. We haven’t covered every single possible option or feature of the part – however you will now have the core knowledge to go further with the MC14489 if you need to move further with it.

This post brought to you by pmdway.com – everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.

Hewlett-Packard 5082-7415 LED Display from 1976

In this article we examine a five digit, seven-segment LED display from Hewlett-Packard, the 5082-7415:

5082-7415

5082-7415rear

We realise they’re most likely now pure unobtanium, but we like some old display p0rn so here you go.

According to the data sheet (HP 5082-series.pdf) and other research this was available for a period of time around 1976 and used with other 5082-series modules in other HP products. Such as the Hewlett-Packard 3x series of calculators, for example:

Using the display is very easy – kudos to the engineers at HP for making a simple design that could be reusable in many applications. The 5082-7415 is a common-cathode unit and wiring is very simple – there are the usual eight anodes for segments a~f and the decimal point, and the five cathodes.

As this module isn’t too easily replaceable, I was very conservative with the power supply – feeding just under 1.6V at 10mA to each of the anode pins. A quick test proved very promising:

firsttest

Excellent – it worked! But now to get it displaying some sort of interesting way. Using the following hardware…

Don’t forget to use the data sheet (HP 5082-series.pdf). You don’t have to use Arduino – any microcontroller with the appropriate I/O can take care of this.

Here is a simple Arduino sketch that scrolls through the digits with and then without the decimal point:

// Arduino sketch to demonstrate HP 5082-7415 LED Display unit
// John Boxall, April 2012
int clockPin=6;
int latchPin=7;
int dataPin=8;
// array for cathodes - sent to second shift register
byte digits[]={
  B10000000,
  B01000000,
  B00100000,
  B00010000,
  B00001000,
  B11111000}; // use digits[6] to turn all on
// array for anodes (to display 0~0) - sent to first shift register
byte numbers[]={
  B11111100,
  B01100000,
  B11011010,
  B11110010,
  B01100110,
  B10110110,
  B10111110,
  B11100000,
  B11111110,
  B11110110};
void setup()
{
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}
void loop()
{
  int i;
  for ( i=0 ; i<10; i++ )
  {
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, LSBFIRST, digits[6]);
    shiftOut(dataPin, clockPin, LSBFIRST, numbers[i]);
    digitalWrite(latchPin, HIGH);
    delay(250);
  }
  // now repeat with decimal point
  for ( i=0 ; i<10; i++ )
  {
    digitalWrite(latchPin, LOW);
    shiftOut(dataPin, clockPin, LSBFIRST, digits[6]);
    shiftOut(dataPin, clockPin, LSBFIRST, numbers[i]+1);
    digitalWrite(latchPin, HIGH);
    delay(250);
  }
}

And the results:

Now for something more useful. Here is a function that sends a single digit to a position on the display with the option of turning the decimal point on or off:

void displayDigit(int value, int posit, boolean decPoint)
// displays integer value at digit position posit with decimal point on/off
{
 digitalWrite(latchPin, LOW);
 shiftOut(dataPin, clockPin, LSBFIRST, digits[posit]);
 if (decPoint==true)
 {
 shiftOut(dataPin, clockPin, LSBFIRST, numbers[value]+1); 
 } 
 else 
 {
 shiftOut(dataPin, clockPin, LSBFIRST, numbers[value]); 
 }
 digitalWrite(latchPin, HIGH);
}

So if you wanted to display the number three in the fourth digit, with the decimal point – use

displayDigit(3,3,true);

with the following result:

three

We make use of the displayDigit() function in our next sketch. We introduce a new function:

displayInteger(number,cycles);

It accepts a long integer between zero and 99999 (number) and displays it on the module for cycles times:

// Arduino sketch to demonstrate HP 5082-7415 LED Display unit
// Displays numbers on request
// John Boxall, April 2012
int clockPin=6;
int latchPin=7;
int dataPin=8;
// array for cathodes - sent to second shift register
byte digits[]={
 B10000000,
 B01000000,
 B00100000,
 B00010000,
 B00001000,
 B11111000}; // use digits[6] to turn all on
// array for anodes (to display 0~0) - sent to first shift register
byte numbers[]={
 B11111100,
 B01100000,
 B11011010,
 B11110010,
 B01100110,
 B10110110,
 B10111110,
 B11100000,
 B11111110,
 B11110110};
void setup()
{
 pinMode(clockPin, OUTPUT);
 pinMode(latchPin, OUTPUT);
 pinMode(dataPin, OUTPUT);
 randomSeed(analogRead(0));
}
void clearDisplay()
// turns off all digits
{
 digitalWrite(latchPin, LOW);
 shiftOut(dataPin, clockPin, LSBFIRST, 0);
 shiftOut(dataPin, clockPin, LSBFIRST, 0); 
 digitalWrite(latchPin, HIGH);
}
void displayDigit(int value, int posit, boolean decPoint)
// displays integer value at digit position posit with decimal point on/off
{
 digitalWrite(latchPin, LOW);
 shiftOut(dataPin, clockPin, LSBFIRST, digits[posit]);
 if (decPoint==true)
 {
 shiftOut(dataPin, clockPin, LSBFIRST, numbers[value]+1); 
 } 
 else 
 {
 shiftOut(dataPin, clockPin, LSBFIRST, numbers[value]); 
 }
 digitalWrite(latchPin, HIGH);
}
void displayInteger(long number,int cycles)
// displays a number 'number' on the HP display. 
{
 long i,j,k,l,z;
 float f;
 clearDisplay();
 for (z=0; z
void loop()
{
 long l2;
 l2=random(0,100001);
 displayInteger(l2,400);
}

For demonstration purposes the sketch displays random numbers, as shown in the video below:

Update – four-digit versions…

They worked very nicely and can be driven in the same method as the 5082-7415s descibed earlier. In the following video we have run the same sketches with the new displays:

In the meanwhile, I hope you found this article of interest. Thanks to the Vintage Technology Association website and the Museum of HP Calculators for background information.

This post brought to you by pmdway.com – offering everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.

Tutorial: Arduino and the NXP SAA1064 4-digit LED display driver

In this article we investigate controlling the NXP (formerly Philips) SAA1064 4-digit LED display driver IC with Arduino and the I2C bus interface. The SAA1064 has been discontinued, however this article still gets a lot of traffic so we’ve updated it for 2019.

If you are not familiar with using the I2C bus, please read our tutorials (parts one and two) before moving on.

Furthermore as it is controlled over the I2C bus – you don’t waste any digital I/O pins on your Arduino, and you can also operate up to four SAA1064s at once (allowing 16 digits!). Finally, it has a constant-current output – keeping all the segments of your LED display at a constant brightness (which is also adjustable).  So let’s get started…

Here is an example of the SAA1064 in SOIC surface mount packaging:

saa1064-smd

It measures around 15mm in length. For use in a solderless breadboard, I have soldered the IC onto a through-hole adaptor:

saa1064dipmountedss

The SAA1064 is also available in a regular through-hole DIP package. At this point, please download the data sheet (.pdf) as you will need to refer to it during the article. Next, our LED display examples. We need common-anode displays, and for this article we use two Agilent HDSP521G two-digit modules (data sheet [.pdf]) as shown below:

2digitss

For the uninitiated – a common anode display has all the segments’ anodes connected together, with the cathodes terminated separately. For example, our LED displays are wired as such:

commonanodes

Notice the anodes for the left digit are pin 14, and the right digit pin 13. A device that is connected to all the cathodes (e.g. our SAA1064) will control the current flow through each element – thereby turning each segment on (and controlling the brightness) or off. Our SAA1064 is known as a current-sink as the current flows through the LED, and then sinks into the IC.

Now, let’s get it connected. There is an excellent demonstration circuit on page twelve of the data sheet that we will follow for our demonstrations:

demoschematicss

It looks pretty straight-forward, and it is. The two transistors are standard NPN-type, such as PN2222. The two transistors are used to each turn on or off a pair of digits – as the IC can only drive digits 1+3 or 2+4 together. (When presented in real life the digits are numbered 4-3-2-1).

So the pairs are alternatively turned on and off at a rapid rate, which is controlled by the capacitor between pin 2 and GND. The recommended value is 2.7 nF. At the time of writing, I didn’t have that value in stock, so chose a 3.3 nF instead. However due to the tolerance of the ceramic capacitor it was actually measured to be 2.93 nF:

capmeterss

So close enough to 2.7 nF will be OK. The other capacitor shown between pins 12 and 13 is a standard 0.1 uF smoothing capacitor. Pin 1 on the SAA1064 is used to determine the I2C bus address – for our example we have connected it straight to GND (no resistors at all) resulting in an address of 0x70.

See the bottom page five of the data sheet for other address options. Power for the circuit can be taken from your Arduino’s 5V pin – and don’t forget to connect the circuit GND to Arduino GND. You will also use 4.7k ohm pull-up resistors on the SDA and SCL lines of the I2C bus.

The last piece of the schematic puzzle is how to connect the cathodes of the LED displays to the SAA1064. Display pins 14 and 13 are the common anodes of the digits.

The cathodes for the left-hand display module:

  • LED display pins 4, 16, 15, 3, 2, 1, 18 and 17 connect to SAA1064 pins 22, 21, 20, 19, 18, 17, 16 and 15 respectively (that is, LED pin 4 to IC pin 22, etc.);
  • LED display pins 9, 11, 10, 8, 6, 5, 12 and 7 also connect to SAA1064 pins 22, 21, 20, 19, 18, 17, 16 and 15 respectively.
The cathodes for the right-hand display module:
  • LED display pins 4, 16, 15, 3, 2, 1, 18 and 17 connect to SAA1064 pins 3, 4, 5, 6, 7, 8, 9 and 10 respectively;
  • LED display pins  9, 11, 10, 8, 6, 5, 12 and 7 also connect to SAA1064 pins 3, 4, 5, 6, 7, 8, 9 and 10 respectively.
Once your connections have been made, you could end up with spaghetti junction like this…
breadboardss
Now it is time to consider the Arduino sketch to control out SAA1064. Each write request to the SAA1064 requires several bytes. We either send a control command (to alter some of the SAA1064 parameters such as display brightness) or a display command (actually display numbers).
For our example sketches the I2C bus address “0x70 >> 1” is stored in the byte variable saa1064. First of all, let’s look at sending commands, as this is always done first in a sketch to initiate the SAA1064 before sending it data.
As always, we send the address down the I2C bus to awaken the SAA1064 using
Wire.beginTransmission(saa1064);

Then the next byte is the instruction byte. If we send zero:

Wire.write(B00000000);

… the IC expects the next byte down the bus to be the command byte. And finally our command byte:

Wire.write(B01000111);

The control bits are described on page six of the data sheet. However – for four-digit operation bits 0, 1 and 2 should be 1; bit 3 should be 0; and bits 4~6 determine the amount of current allowed to flow through the LED segments. Note that they are cumulative, so if you set bits 5 and 6 to 1 – 18 mA of current will flow. We will demonstrate this in detail later on.

Next, to send actual numbers to be displayed is slightly different. Note that the digits are numbered (from left to right) 4 3 2 1. Again, we first send the address down the I2C bus to awaken the SAA1064 using
Wire.beginTransmission(saa1064);

Then the next byte is the instruction byte. If we send 1, the next byte of data will represent digit 1. If that follows with another byte, it will represent digit 2. And so on. So to send data to digit 1, send

Wire.write(B00000001);

Although sending binary helps with the explanation, you can send decimal equivalents. Next, we send a byte for each digit (from right to left).

Each bit in the byte represents a single LED element of the digit as well as the decimal point. Note how the elements are labelled (using A~G and DP) in the following image:

7segpinout

The digit bytes describe which digit elements to turn on or off. The bytes are described as such: Bpgfedcba. (p is the decimal point).
So if you wanted to display the number 7, you would send B00000111 – as this would turn on elements a, b and c. To add the decimal point with 7 you would send B10000111.
You can also send the byte as a decimal number. So to send the digit 7 as a decimal, you would send 7 – as 00000111 in base-10 is 7.
To include the decimal point, send 135 – as 100000111 in base-10 is 135. Easy! You can also create other characters such as A~F for hexadecimal. In fact let’s do that now in the following example sketch:
/*
 Example 39.1 - NXP SAA1064 I2C LED Driver IC Demo I
 Demonstrating display of digits
 http://tronixstuff.com
 */
#include "Wire.h" // enable I2C bus
byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND) ****
int digits[16]={63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113};
// these are the byte representations of pins required to display each digit 0~9 then A~F
void setup()
{
 Wire.begin(); // start up I2C bus
 delay(500);
 initDisplay();
}
void initDisplay()
// turns on dynamic mode and adjusts segment current to 12mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current
 Wire.endTransmission();
}
void displayDigits()
// show all digits 0~9, A~F on all digits of display
{
 for (int z=0; z<16; z++)
 {
 Wire.beginTransmission(saa1064);
 Wire.write(1); // instruction byte - first digit to control is 1 (right hand side)
 Wire.write(digits[z]); // digit 1 (RHS)
 Wire.write(digits[z]); // digit 2
 Wire.write(digits[z]); // digit 3
 Wire.write(digits[z]); // digit 4 (LHS)
 Wire.endTransmission();
 delay(500);
 }
// now repeat but with decimal point
 for (int z=0; z<16; z++)
 {
 Wire.beginTransmission(saa1064);
 Wire.write(1); // instruction byte - first digit to control is 1 (right hand side)
 Wire.write(digits[z]+128); // digit 1 (RHS)
 Wire.write(digits[z]+128); // digit 2
 Wire.write(digits[z]+128); // digit 3
 Wire.write(digits[z]+128); // digit 4 (LHS)
 Wire.endTransmission();
 delay(500);
 }
}
void clearDisplay()
// clears all digits
{
 Wire.beginTransmission(saa1064);
 Wire.write(1); // instruction byte - first digit to control is 1 (right hand side)
 Wire.write(0); // digit 1 (RHS)
 Wire.write(0); // digit 2
 Wire.write(0); // digit 3
 Wire.write(0); // digit 4 (LHS)
 Wire.endTransmission();
}
void loop()
{
 displayDigits();
 clearDisplay();
 delay(1000);
}
/* **** We bitshift the address as the SAA1064 doesn't have the address 0x70 (ADR pin
to GND) but 0x38 and Arduino uses 7-bit addresses- so 8-bit addresses have to
be shifted to the right one bit. Thanks to Malcolm Cox */

In the function initDisplay() you can see an example of using the instruction then the control byte. In the function clearDisplay() you can see the simplest form of sending digits to the display – we send 0 for each digit to turn off all elements in each digit.

The bytes that define the digits 0~9 and A~F are stored in the array digits[]. For example, the digit zero is 63 in decimal, which is B00111111 in binary – which turns on elements a,b,c,d,e and f.

Finally, notice the second loop in displayDigits() – 128 is added to each digit value to turn on the decimal point. Before moving on, let’s see it in action:

Our next example revisits the instruction and control byte – we change the brightness of the digits by setting bits 4~6 in the control byte. Each level of brightness is separated into a separate function, and should be self-explanatory. Here is the sketch:

/*
 Example 39.2 - NXP SAA1064 I2C LED Driver IC Demo II
 Demonstrating brightness levels via adjusting segment current
 http://tronixstuff.com*/
#include "Wire.h" // enable I2C bus
byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)
int digits[16]={63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113};
// these are the byte representations of pins required to display each digit 0~9 then A~F
void setup()
{
 Wire.begin(); // start up I2C bus
 delay(500);
 init12ma();
}
void init3ma()
// turns on dynamic mode and adjusts segment current to 3mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B00010111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 3mA segment current
 Wire.endTransmission();
}
void init6ma()
// turns on dynamic mode and adjusts segment current to 6mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B00100111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 6mA segment current
 Wire.endTransmission();
}
void init9ma()
// turns on dynamic mode and adjusts segment current to 9mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B00110111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 9mA segment current
 Wire.endTransmission();
}
void init12ma()
// turns on dynamic mode and adjusts segment current to 12mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current
 Wire.endTransmission();
}
void init18ma()
// turns on dynamic mode and adjusts segment current to 12mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B01100111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 18mA segment current
 Wire.endTransmission();
}
void init21ma()
// turns on dynamic mode and adjusts segment current to 12mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B01110111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 21mA segment current
 Wire.endTransmission();
}
void loop()
{
 int d=250; // for delay
 // first, light up all segments
 Wire.beginTransmission(saa1064);
 Wire.write(0); // instruction byte - Zero means the next byte is the control byte
 Wire.write(B01001111); // set current and bit 3 to 1 for all segments on
 Wire.endTransmission();
// now loop through the brightness levels
 do
 {
 init3ma();
 delay(d);
 init6ma();
 delay(d);
 init9ma();
 delay(d);
 init12ma();
 delay(d);
 init18ma();
 delay(d);
 init21ma();
 delay(d);
 }
 while (1>0);
}

And again, see it in action:

For our final example, there is a function displayInteger(a,b) which can be used to easily display numbers from 0~9999 on the 4-digit display. The parameter a is the number to display, and b is the leading-zero control – zero – off, one – on.

The function does some maths on the integet to display and separates the digits for each column, then sends them to the SAA1064 in reverse order. By now you should be able to understand the following sketch:

/*
 Example 39.3 - NXP SAA1064 I2C LED Driver IC Demo III
 Displaying numbers on command
 https://tronixstuff.com/tutorials 
 */
#include "Wire.h" // enable I2C bus
byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)
int digits[17]={
 63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113, 0};
// these are the byte representations of pins required to display each digit 0~9 then A~F, and blank digit
void setup()
{
 Wire.begin(); // start up I2C bus
 delay(500);
 initDisplay();
}
void initDisplay()
// turns on dynamic mode and adjusts segment current to 12mA
{
 Wire.beginTransmission(saa1064);
 Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
 Wire.write(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current
 Wire.endTransmission();
}
void clearDisplay()
{
 Wire.beginTransmission(saa1064);
 Wire.write(1); // start with digit 1 (right-hand side)
 Wire.write(0); // blank digit 1
 Wire.write(0); // blank digit 2
 Wire.write(0); // blank digit 3
 Wire.write(0); // blank digit 4
 Wire.endTransmission();
}
void displayInteger(int num, int zero)
// displays the number 'num'
// zero = 0 - no leading zero
// zero = 1 - leading zero
{
 int thousand, hundred, ten, one;
 // breakdown number into columns
 thousand = num/1000;
 hundred = (num-(thousand*1000))/100;
 ten = (num-((thousand*1000)+(hundred*100)))/10;
 one = num-((thousand*1000)+(hundred*100)+(ten*10));
 if (zero==1) // yes to leading zero
 {
 Wire.beginTransmission(saa1064);
 Wire.write(1);
 Wire.write(digits[one]);
 Wire.write(digits[ten]);
 Wire.write(digits[hundred]);
 Wire.write(digits[thousand]);
 Wire.endTransmission();
 delay(10);
 }
 else
 if (zero==0) // no to leading zero
 {
 if (thousand==0) { thousand=16; }
 if (hundred==0 && num<100) { hundred=16; }
 if (ten==0 && num<10) { ten=16; }
 Wire.beginTransmission(saa1064);
 Wire.write(1);
 Wire.write(digits[one]);
 Wire.write(digits[ten]);
 Wire.write(digits[hundred]);
 Wire.write(digits[thousand]);
 Wire.endTransmission();
 delay(10);
 }
}
void loop()
{
 for (int a=0; a<251; a++)
 // display 0~250 without leading zero
 {
 displayInteger(a,0);
 delay(20);
 }
 delay(1000);
 clearDisplay();
 delay(1000);
for (int a=0; a<1000; a++)
 // display 0~999 with leading zero
 {
 displayInteger(a,1);
 delay(5);
 }
}

And the final example in action:

So there you have it – another useful IC that can be used in conjunction with our Arduino systems to make life easier and reduce the required digital output pins.

This post brought to you by pmdway.com everything for makers and electronics enthusiasts, with free delivery worldwide.

To keep up to date with new posts at tronixstuff.com, please subscribe to the mailing list in the box on the right, or follow us on twitter @tronixstuff.