Tutorial: Arduino and the NXP SAA1064 4-digit LED display driver
This is chapter thirty-nine of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – a series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here. Any files from tutorials will be found here.
Please note that the tutorials are not currently compatible with Arduino IDE v1.0. Please continue to use v22 or v23 until further notice.
Welcome back fellow arduidans!
In this article we investigate controlling the NXP (formerly Philips) SAA1064 4-digit LED display driver IC with Arduino and the I2C bus interface. If you are not familiar with using the I2C bus, please read my tutorials (parts one and two) before moving on. Although the SAA1064 is not the newest on the market, it is still popular, quite inexpensive and easy to source. 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:
It measures around 15mm in length. For use in a solderless breadboard, I have soldered the IC onto a through-hole adaptor:
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:
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:
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:
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:
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 0×70. 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.
- 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.
Wire.beginTransmission(saa1064);
Wire.send(B00000000);
Wire.send(B01000111);
Wire.beginTransmission(saa1064);
Wire.send(B00000001);
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 (download):/*
Example 39.1 - NXP SAA1064 I2C LED Driver IC Demo I
Demonstrating display of digits
http://tronixstuff.wordpress.com/tutorials > chapter 39
John Boxall July 2011 | CC by-sa-nc
*/
#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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(1); // instruction byte - first digit to control is 1 (right hand side)
Wire.send(digits[z]); // digit 1 (RHS)
Wire.send(digits[z]); // digit 2
Wire.send(digits[z]); // digit 3
Wire.send(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.send(1); // instruction byte - first digit to control is 1 (right hand side)
Wire.send(digits[z]+128); // digit 1 (RHS)
Wire.send(digits[z]+128); // digit 2
Wire.send(digits[z]+128); // digit 3
Wire.send(digits[z]+128); // digit 4 (LHS)
Wire.endTransmission();
delay(500);
}
}
void clearDisplay()
// clears all digits
{
Wire.beginTransmission(saa1064);
Wire.send(1); // instruction byte - first digit to control is 1 (right hand side)
Wire.send(0); // digit 1 (RHS)
Wire.send(0); // digit 2
Wire.send(0); // digit 3
Wire.send(0); // digit 4 (LHS)
Wire.endTransmission();
}
void loop()
{
displayDigits();
clearDisplay();
delay(1000);
}
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 (download):
/*
Example 39.2 - NXP SAA1064 I2C LED Driver IC Demo II
Demonstrating brightness levels via adjusting segment current
http://tronixstuff.wordpress.com/tutorials > chapter 39
John Boxall July 2011 | CC by-sa-nc
*/
#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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(0); // instruction byte - Zero means the next byte is the control byte
Wire.send(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 (download):
/*
Example 39.3 - NXP SAA1064 I2C LED Driver IC Demo III
Displaying numbers on command
http://tronixstuff.wordpress.com/tutorials > chapter 39
John Boxall July 2011 | CC by-sa-nc
*/
#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.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.send(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.send(1); // start with digit 1 (right-hand side)
Wire.send(0); // blank digit 1
Wire.send(0); // blank digit 2
Wire.send(0); // blank digit 3
Wire.send(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.send(1);
Wire.send(digits[one]);
Wire.send(digits[ten]);
Wire.send(digits[hundred]);
Wire.send(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.send(1);
Wire.send(digits[one]);
Wire.send(digits[ten]);
Wire.send(digits[hundred]);
Wire.send(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.
Have fun and keep checking into tronixstuff.com. Why not follow things on twitter, Google+, subscribe for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other – and we can all learn something.











This is great stuff… thank you.
I may have mistakenly ordered the wrong display. I have a 4 digit, 4 common anode, 12 pin chip. I’m novice enough that it’s not obvious to me if I can still connect it to the SAA1064.
Any thought/advice would be appreciated.
If you can email the data sheet or the link from where you got the display to john at tronixstuff dot com I’ll have a look for you.
That’s very good of you John. Thank you.
Feeling even more like a dummy, I don’t see how to attach a file like a data sheet. However, I got the info via the Digi-Key site; the part is the LDQ-M516RI
Thanks – will email you shortly
Hi John,
Another great article – every project I do involves a visit to your site!
I’m considering using an SAA1064 in a clock project – could you tell me if it possible to use less than the recommended 5v to drive the 7-Segs? I will use the brightness control built into the chip but want to make sure they can go extremely dim on the lowest setting so it’s not too bright at night and also to lower the power consumption.
Many thanks
Russ
The brightness is controlled by the SAA1064 itself, which will need the 5V. From my own experience you can go quite dim, for example setting the display current to 3mA – however it’s subjective.
thanks for the reply John, I’ve ordered one and will do some experimenting
thanks for the Tutorial! i´m a bloody beginner – can you tell me which pins i should use on an Arduino Duemilanove? I can´t see where to connect SCL and SDA… segments on my display are shining, but nothing is happening like in your videos.
Thanks!
SDA is analogue 4, SCL is analgue 5. For more on I2C see tronixstuff.com/tutorials > chapters 20 and 21.
Ive got a lot of work to do, I need to run 8 x 4-digit displays. Is this chip suitable, do I need to just buy more? Completely new to Arduino and have jumped in at the deep end. Happy doing programming stuff as I have an IT background and have done stuff like run relays, temp probes, but this massive ball of wiring is all new to me
Possible?
Cheers
Mike
Sure it’s possible. However you can only run 4 x SAA1064s from one Arduino – giving you 4 x 4 digits. You may want to investigate other options like the TM1638 modules reviewed here – http://tronixstuff.wordpress.com/2012/03/11/arduino-and-tm1638-led-display-modules/
I like that look of that! Just grabbed 4 of those for £20. Bargain
It’s for automating a home brewery so it’s a good cause!
Cheers,
Mike.
This is a great writeup. I made the code into an Arduino 1.0 library.
https://github.com/mymaestro/Arduino-projects
Well done