This is the second of three articles that will examine the LM391x series of LED driver ICs. The first covered the LM3914, this will cover the LM3915 and the LM3916 will follow. The goal of these is to have you using the parts in a small amount of time and experiment with your driver ICs, from which point you can research further into their theory and application.
Although these parts have been around for many years, the LM3915 isn’t used that much however for the sake of completeness we’re writing the tutorial. The LM3915 offers a simple way to display a logarithmic voltage level using one or more groups of ten LEDs with a minimum of fuss. If you’re wanting to make a VU meter, you should use the LM3916 which we will cover in the final instalment of this trilogy.
Instead of having each LED represent a voltage level as with the LM3914, each LED connected to the LM3915 represents a 3 dB (decibel) change in the power level of the signal. For more on decibels, check out Wikipedia.
To display these power level changes we’ll run through a couple of examples that you can use in your own projects and hopefully give you some ideas for the future. Originally by National Semiconductor, the LM391X series is now handled by Texas Instruments.
Getting Started
You will need the LM3915 data sheet, so please download that and keep it as a reference. First – back to basics. The LM3915 controls ten LEDs. It controls the current through the LEDs with the use of only one resistor, and the LEDs can appear in a bar graph or single ‘dot’ when in use. The LM3915 contains a ten-stage voltage divider, each stage when reached will illuminate the matching LED (and those below it in level meter mode).
Let’s consider the most basic of examples (from page two of the data sheet) – a simple logarithmic display of voltage between 0 and 10V:
After building the circuit you can connect a signal to measure via pin 5, and the GND to pin 2. We’ve built the circuit exactly as above on some stripboard for demonstration purposes, with the only difference being the use of an 8.2kΩ resistor for R2:
To show this in action we use a signal of varying AC voltage – a sine wave at around 2 kHz. In the following video, you can see the comparison of the signal’s voltage against the LEDs being illuminated, and you will see the logarithmic voltage increase represented by the LEDs:
We used the bar display mode for the voltage increase, and the dot display mode for the voltage decrease. Did you notice that during the voltage decrease, the LEDs below the maximum level being displayed were dim?
As the signal’s voltage was varying very quickly, the change in the LED’s location is a blur due to the speed of change. In the video below, we’ve slowed the frequency right down but kept the same maximum voltage.
Well that was a lot of fun, and gives you an idea of what is possible with the LM3915.
Displaying weaker signals
In non-theoretical situations your input signal won’t conveniently be between 0 and 10 V. For example the line level on audio equipment can vary between 1 and 3V peak to peak. For example, here’s a random DSO image from measuring the headphone output on my computer whilst playing some typical music:
Although it’s an AC signal we’ll treat it as DC for simplicity. So to display this random low DC voltage signal we’ll reduce the range of the display to 0~3V DC. This is done using the same method as with the LM3914 – with maths and different resistors.
Consider the following formulae:
As you can see the LED current (Iled) is simple, however we’ll need to solve for R1 and R2 with the first formula to get our required Vref of 3V. For our example circuit I use 2.2kΩ for R2 which gives a value of 1.8kΩ for R1. However putting those values in the ILED formula gives a pretty low current for the LEDs, about 8.3 mA.
Live and learn – so spend time experimenting with values so you can match the required Vref and ILED.
Nevertheless in this video below we have the Vref of 3V and some music in from the computer as a sample source of low-voltage DC. This is not a VU meter!Wait for the LM3916 article to do that.
Again due to the rapid rate of change of the voltage, there is the blue between the maximum level at the time and 0V.
Chaining multiple LM3915s
This is covered well in the data sheet, so read it for more on using two LM3915s. Plus there are some great example circuits in the data sheet, for example the 100W audio power meter on page 26 and the vibration meter (using a piezo) on page 18.
Conclusion
As always we hope you found this useful. Don’t forget to stay tuned for the final instalment about the LM3916.
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.
This is the first of three tutorials that will examine the LM391x series of LED driver ICs. In this first tutorial we cover the LM3914, then the LM3915 and LM3916 will follow. The goal of these tutorials is to have you using the parts in a small amount of time and experiment with your driver ICs, from which point you can research further into their theory and application.
Although these parts have been around for many years, the LM3914 in particular is still quite popular. It offers a simple way to display a linear voltage level using one or more groups of ten LEDs with a minimum of fuss.
With a variety of external parts or circuitry these LEDs can then represent all sorts of data, or just blink for your amusement. We’ll run through a few example circuits that you can use in your own projects and hopefully give you some ideas for the future. Originally by National Semiconductor, the LM391X series is now handled by Texas Instruments.
Getting Started
You will need the LM3914 data sheet, so please download that and keep it as a reference. So – back to basics. The LM3914 controls ten LEDs. It controls the current through the LEDs with the use of only one resistor, and the LEDs can appear in a bar graph or single ‘dot’ when in use. The LM3914 contains a ten-stage voltage divider, each stage when reached will illuminate the matching LED (and those below it in level meter mode).
Let’s consider the most basic of examples (from page two of the data sheet) – a voltmeter with a range of 0~5V.
The Vled rail is also connected to the supply voltage in our example. Pin 9 controls the bar/dot display mode – with it connected to pin 3 the LEDs will operate in bar graph mode, leave it open for dot mode.
The 2.2uF capacitor is required only when “leads to the LED supply are 6″ or longer”. We’ve hooked up the circuit above, and created a 0~5V DC source via a 10kΩ potentiometer with a multimeter to show the voltage – in the following video you can see the results of this circuit in action, in both dot and bar graph mode:
Customising the upper range and LED current
Well that was exciting, however what if you want a different reference voltage? That is you want your display to have a range of 0~3 V DC? And how do you control the current flow through each LED? With maths and resistors. Consider the following formulae:
As you can see the LED current (Iled) is simple, our example is 12.5/1210 which returned 10.3 mA – and in real life 12.7 mA (resistor tolerance is going to affect the value of the calculations).
Now to calculate a new Ref Out voltage – for example we’ll shoot for a 3 V meter, and keep the same current for the LEDs. This requires solving for R2 in the equation above, which results with R2 = -R1 + 0.8R1V. Substituting the values – R2 = -1210 + 0.8 x 1210 x 3 gives a value of 1694Ω for R2. Not everyone will have the E48 resistor range, so try and get something as close as possible. We found a 1.8 kΩ for R2 and show the results in the following video:
You can of course have larger display range values, but a supply voltage of no more than 25 V will need to be equal to or greater than that value. E.g. if you want a 0~10 V display, the supply voltage must be >= 10V DC.
Creating custom ranges
Now we’ll look at how to create a lower range limit, so you can have displays that (for example) can range from a non-zero positive value. For example, you want to display levels between 3 and 5V DC. From the previous section, you know how to set the upper limit, and setting the lower limit is simple – just apply the lower voltage to pin 4 (Rlo).
You can derive this using a resistor divider or other form of supply with a common GND. When creating such circuits, remember that the tolerance of the resistors used in the voltage dividers will have an affect on the accuracy. Some may wish to fit trimpots, which after alignment can be set permanently with a blob of glue.
Finally, for more reading on this topic – download and review the TI application note.
Chaining multiple LM3914s
Two or more LM3914s can be chained together to increase the number of LEDs used to display the levels over an expanded range. The circuitry is similar to using two independent units, except the REFout (pin 7) from the first LM3914 is fed to the REFlo (pin 4) of the second LM3914 – whose REFout is set as required for the upper range limit. Consider the following example schematic which gave a real-world range of 0~3.8V DC:
The 20~22kΩ resistor is required if you’re using dot mode (see “Dot mode carry” in page ten of the data sheet). Moving on, the circuit above results with the following:
Where to from here?
Now you can visually represent all sorts of low voltages for many purposes. There’s more example circuits and notes in the LM3914 data sheet, so have a read through and delve deeper into the operation of the LM3914.
Furthermore Dave Jones from eevblog.com has made a great video whcih describes a practical application of the LM3914:
Conclusion
As always we hope you found this useful. Don’t forget to stay tuned for the second and third instalments using the LM3915 and LM3916.
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.
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.
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:
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.
Connecting the MC14489 to an LED display isn’t complex at all. From the data sheet consider Figure 9:
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:
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.
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.
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:
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 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…
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:
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.
You must be logged in to post a comment.