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.

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 acurrent-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.
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…
Note the use of the Arduino Nano – they are very convenient when doing small prototyping on solderless breadboards.
-=-
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 “0×70 >> 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.send(B00000000);
… the IC expects the next byte down the bus to be the command byte. And finally our command byte:

Wire.send(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.send(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:
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. I hope you enjoyed reading this as much as I did writing it for you.

If you have any questions about the processes or details in this article, please ask in our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, there is the odd competition or give-away –  and we can all learn something. Or follow tronixstuff on twitter and facebook. High resolution images available on flickr.

Otherwise, have fun, stay safe, be good to each other – and make something!

Advertisements
By John Boxall

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s