[AVR] How to drive 4-Digit 7-Segment LED Display (Arduino)

Hardware issues, electronic components, schemas, Arduino, STM32, Robots, Sensors
Post Reply
Administrator
Site Admin
Posts: 81
Joined: 26-Feb-2014, 17:54

[AVR] How to drive 4-Digit 7-Segment LED Display (Arduino)

Post by Administrator » 29-Mar-2015, 21:05

How to connect and drive 4-Digit 7-Segment LED Display with Ardino UNO.

In previous topic we discussed how to drive a single 7-segment LED Display. I think it was simple, but it requires lots of wires. Usually we need to display big numbers that contain multiple digits. For this purpose there are 4-Digit 7-Segment LED Displays. It is possible to find LED Displays with Common Anode or Common Cathode in the market.
LED4Digit7Segments.jpg
LED4Digit7Segments.jpg (27.71 KiB) Viewed 35329 times
The module contains four 7-segment LED numeric displays. These digits can be turned on and off independently. Each of the four digits in the module uses its own common cathode (or common anode) connection point. Each segment in the module shares the same anode (or cathode) connection points. Segments are labeled: A, B, C, D, E, F, G, H (for dot). Every segment is connected to all four digits. This reduces the wiring required (4+8=12 vs 4x8=32).
LED4x7Pins.png
LED4x7Pins.png (4.85 KiB) Viewed 35285 times
There are 12 connection points = 4 PINs are used for digits + 8 PINs are used for segments.
LED4x7Scheme.png
LED4x7Scheme.png (4.33 KiB) Viewed 35329 times
The most important difference between 1-digit and 4-digit displays is the way we turn ON and OFF segments. To light multiple digits we use 'multiplexing'.

We display 4-digits at once by rapidly cycling through them in infinite loop.
1) Turn ON active digit by switching its PIN connected to common cathode (anode).
2) Determine the list of segments to turn ON and light these segments.
3) Wait some time (2-5 ms).
4) Turn OFF the digit (cathode and all segments).
6) Select next digit in list [1...4].

Arduino UNO R3 Specification says that maximum current per PIN is 40 mA. So we need limiting resitors. Resistor value can be calculated as:
R = V / I,
V = 5 volt
I = 10..20 mA
R = 220...470 Ohms
Some people aware of too much current in the circuit when all the segments are switched on. The current will be 7 * 20mA = 140 mA, or 7 * 10 mA = 70 mA (depends on resistor value). This current is safe for Arduino, but will damage STM32 MCU.

Now its time to connect my SMA420564 4-digit 7-segment LED display to Arduino UNO. Required components are available in The Arduino UNO Basic Learning Kit. We need 4 resistors, breadboard and 12 wires. Connect limiting 470 Ohm resistors to 12, 9, 8, 6 PIN of the 4-Digit LED Display. Wires should be connected according to this table:

Name (LED pin): Arduino pin
=====================
Digit 1 (PIN 12): A0
Digit 2 (PIN 9): A1
Digit 3 (PIN 8): A2
Digit 4 (PIN 6): A3

Segment A (PIN 11): D3
Segment B (PIN 7): D4
Segment C (PIN 4): D5
Segment D (PIN 2): D6
Segment E (PIN 1): D7
Segment F (PIN 10): D8
Segment G (PIN 6): D9
Never use pins 0, 1 and 13. Specialy digital pin 0 and pin 1 are used for serial communication (RX/TX ) like "Serial.begin(9600)"
Analog PINs A0-A3 can work in digital mode, so we use these pins to drive digits. These PINS will be used as common cathodes. The display is multiplexed, so we select the digit by setting its pin LOW (make it GND/cathode), and then we light segments by setting thier pins HIGH (anode).

Lets take a look at the code:

Code: Select all

int segA = 3; // top
int segB = 4; // right-top
int segC = 5; // right-bottom
int segD = 6; // bottom
int segE = 7; // left-bottom
int segF = 8; // left-top
int segG = 9; // middle

int digit1 = 14; // common cathode for digit1 (i.e. A0)
int digit2 = 15; // common cathode for digit2 (i.e. A1)
int digit3 = 16; // common cathode for digit3 (i.e. A2)
int digit4 = 17; // common cathode for digit4 (i.e. A3)

// Number to display
static int number = 0;

//==============================================================//
void setup()
{
    // All pins in digital mode
    pinMode(digit1, OUTPUT);
    pinMode(digit2, OUTPUT);
    pinMode(digit3, OUTPUT);
    pinMode(digit4, OUTPUT);

    pinMode(segA, OUTPUT);
    pinMode(segB, OUTPUT);
    pinMode(segC, OUTPUT);
    pinMode(segD, OUTPUT);
    pinMode(segE, OUTPUT);
    pinMode(segF, OUTPUT);
    pinMode(segG, OUTPUT);
}

//==============================================================//
void loop() {
    // put your main code here, to run repeatedly:

    if( millis() / 1000 > ms )
    {
        ms = millis() / 1000;
        if( ++number > 9999 ) number = 0;
    }

    // activate common cathode for digit1
    digitalWrite(digit1, LOW);
        // light on segments for 'thousands'
        drawDigitFast( (number/1000) % 10 );
        // wait 2 ms
        delay(2);
        // turn off all segments
        drawDigitFast( -1 );
    // turn off cathode for digit1
    digitalWrite(digit1, HIGH);

    digitalWrite(digit2, LOW);
        drawDigitFast( (number/100) % 10 );
        delay(2);
        drawDigitFast( -1 );
    digitalWrite(digit2, HIGH);

    digitalWrite(digit3, LOW);
        drawDigitFast( (number/10) % 10 );
        delay(2);
        drawDigitFast( -1 );
    digitalWrite(digit3, HIGH);

    digitalWrite(digit4, LOW);
        drawDigitFast( number % 10 );
        delay(2);
        drawDigitFast( -1 );
    digitalWrite(digit4, HIGH);
}

//-----------------------------------------------------//
void drawDigitFast(int n)
{
    const byte aPins[8] = {
        segA, segB, segC, segD, segE, segF, segG
    };
    const byte aSegments[11][8] = {
        //  A     B     C     D     E     F     G
        { HIGH, HIGH, HIGH, HIGH, HIGH, HIGH,  LOW }, // 0
        {  LOW, HIGH, HIGH,  LOW,  LOW,  LOW,  LOW }, // 1
        { HIGH, HIGH,  LOW, HIGH, HIGH,  LOW, HIGH }, // 2
        { HIGH, HIGH, HIGH, HIGH,  LOW,  LOW, HIGH }, // 3
        {  LOW, HIGH, HIGH,  LOW,  LOW, HIGH, HIGH }, // 4
        { HIGH,  LOW, HIGH, HIGH,  LOW, HIGH, HIGH }, // 5
        { HIGH,  LOW, HIGH, HIGH, HIGH, HIGH, HIGH }, // 6
        { HIGH, HIGH, HIGH,  LOW,  LOW,  LOW, LOW  }, // 7
        { HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH }, // 8
        { HIGH, HIGH, HIGH, HIGH,  LOW, HIGH, HIGH }, // 9
        {  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW }  // all off
    };

    if( n < 0 || n > 10 )
    {
        n = 10;
    }

    for( int i = 0; i < 7; i++ )
    {
        digitalWrite( aPins[i], aSegments[n][i] );
    }
}
 
What we've got:
In this video you can see flickering and also some segments are still turned on. I made this video with bug in the sketch. It is important to turn off all segments after the delay, i.e. call drawDigitFast(-1);
P.S. I like these articles:
http://learn.parallax.com/4-digit-7-seg ... duino-demo (uses transistors)
http://www.instructables.com/id/Arduino ... t-display/ (flickering, but simple code)
http://www.hobbytronics.co.uk/arduino-4digit-7segment (nice brightness control)
http://www.hacktronics.com/Tutorials/ar ... t-led.html (cool blue led)

The sketch source code is available in this GitHub repository.
Attachments
LED4Digits7Segments95220 (Agilent HDSP-B0xG).zip
DataSheet for similar LED displays.
(338.64 KiB) Downloaded 2710 times

Administrator
Site Admin
Posts: 81
Joined: 26-Feb-2014, 17:54

Re: [AVR] How to drive 4-Digit 7-Segment LED Display (Arduin

Post by Administrator » 06-Apr-2015, 22:28

In the previous post, we discussed 4-digit 7-segment display. This topic is so important we felt it deserved additional attention, because almost any DIY-project needs OUTPUT device. We will inroduce new effective concept based on interrupts and timers. The code will be transparent for the main application loop and will run in the background.

First of all we need a timer. The code in the previous post turns on and off LED segments in infinite loop. Multiplexing requires to cycle between digits all the time. It is a good idea to put cycling code into running in the background thread or interrupt service routine (ISR).

In the Arduino UNO R3 board there are two 8-bit timers and one 16-bit timer. The hardware can be configured with the special timer registers.
In the Arduino world timer#0 is used for the software timer functions, like delay(), millis() and micros(). If you change timer0 registers, this may influence the Arduino timer function. So you should know what you are doing.
In the setup() routine we will configure timer #1 (16-bit timer). It is possible to use TimerOne Library, but for educational purposes it is much better to configure timer#1 directly through registers. (113 page of the datasheet)

Code: Select all

    // Reset config registers
    TCCR1A = 0;
    TCCR1B = 0;
    // Turn on CTC mode - Clear Timer on Compare match (counter in OCR1A).
    TCCR1B |= (1 << WGM12);
    // Set 1024 prescaler
    TCCR1B |= (1 << CS12) | (1 << CS10); 
    // Setup tick 1000Hz (for 16MHz CPU, 1024 prescaler)
    OCR1A = (16000000 / 1024 - 1) / 1000;
    // Enable CTC-interrupt on channel A
    TIMSK1 |= (1 << OCIE1A);
Timer/Counter Control Register bits (TCCR1A, TCCR1B) are initialized with zeros. It is recommended to turn off interrupts before configuring these registers (page 134). Timers can be used for PWM-output, but we use Timer #1 in "normal port operation" mode (TCCR1A=0).

"TCCR1B |= (1 << WGM12)" - means that we use "Clear Timer on Compare Match (CTC) Mode" (page 125). In CTC mode the counter is cleared to zero when the counter value (TCNT1) matches the OCR1A. The OCR1A defines the top value for the counter, hence also its resolution. The counter value (TCNT1)
increases until a compare match occurs with OCR1A, and then counter (TCNT1) is cleared.

"TCCR1B |= (1 << CS12) | (1 << CS10)" - these bits select the clock source to be used by the Timer/Counter. CS12 and CS10 bits configures clk/1024 from prescaler.

OCR1A register holds the upper limit for the counter. This value depends on CPU Frequency (16 MHz), Prescaler value (1024) and desired tick count (1000 Hz).That means the frequency of the timer will be set by the OCR1A register. It will count from 0x0000 to (16000000/1024-1)/1000 = 15,6 in each cycle.

In the "Timer/Counter1 Interrupt Mask Register" we set OCIE1A bit. When this bit is written to one, and the I-flag in the Status Register is set (interrupts globally enabled), the Timer/Counter1 Output Compare A Match interrupt is enabled. The corresponding Interrupt Vector named ISR(TIMER1_COMPA_vect) is executed when the OCF1A Flag is set.
Timer #1 has two channels A and B with the corresponding ISRs: TIMER1_COMPA_vect, TIMER1_COMPB_vect. It is possible to use both handlers by setting counters in (OCR1A, OCR1B) and enabling bits in TIMSK1 register (OCIE1A, OCIE1B). But CTC mode depends on OCR1A alone. If OCR1B > OCR1A then we will never get a COMPB interrupt.
It is a good idea to set the interrupt rate to a common denominator of all the periods you want to achieve and for each function count to N times that denominator.
The second step is to write Interrupt Service Routine. We need to replace the code in main application loop:

Code: Select all

    digitalWrite(digit1, LOW);
        drawDigitFast( (n/1000) % 10 );
        delay(4);
        drawDigitFast( -1 );
    digitalWrite(digit1, HIGH);
We will use variable int nDelay as a counter to emulate delays. If nDelay is greater than zero we will do nothing in the ISR.
Another variable is named: int nDigit. It will hold the digit index that is currently displayed on the LED. It will cycle from 1 to 4.
Also we use array that will help us to cycle through PINs: int aDigitPins[4].

Code: Select all

static int nDelay = 0; // delay counter
static int nDigit = 1; // active digit on LED
static int aDigitPins[4] = { digit1, digit2, digit3, digit4 };
//==============================================================//
ISR(TIMER1_COMPA_vect)
{
    if( nDelay > 0 )
    {
        // do some delay
        nDelay--;
        if( nDelay==0 )
        {
            // Delay is over -> turn off the Digit
            digitalWrite(aDigitPins[nDigit-1], HIGH);
            // Turn OFF all segments
            drawDigitFast( -1 );
            // Cycle to next Digit (1..4)
            nDigit++;
            if( nDigit > 4 ) nDigit = 1;
        }
    }
    else
    {
        // Turn ON digits and let them light 'nDelay' ticks
        switch( nDigit )
        {
            case 1:
                digitalWrite(digit1, LOW);
                drawDigitFast( (number/1000) % 10 );
                nDelay = 4;
                break;
            case 2:
                digitalWrite(digit2, LOW);
                drawDigitFast( (number/100) % 10 );
                nDelay = 4;
                break;
            case 3:
                digitalWrite(digit3, LOW);
                drawDigitFast( (number/10) % 10 );
                nDelay = 4;
                break;
            case 4:
                digitalWrite(digit4, LOW);
                drawDigitFast( number % 10 );
                nDelay = 4;
                break;
        }
    }
} 
Once developers can iron out all the user-interface elements, they will be able to create a truly engaging devices and see big results in their work.

The source code with the Timer-ISR is available in this GitHub repository.

Administrator
Site Admin
Posts: 81
Joined: 26-Feb-2014, 17:54

Re: [AVR] How to drive 4-Digit 7-Segment LED Display (Arduin

Post by Administrator » 07-Apr-2015, 19:43

Hi, All.

The simple sketch that displays digits is not usefull and can only be used for learning purposes. It is much interesting to concentrate on some real task that could be useful in your project. The starting point for any project is to be clear on the needs it is addressing. Now we will demonstrate how to build temperature sensor circuit using a LM35 sensor and output its value into 4-digit 7-segment LED display.
LM35.jpg
LM35.jpg (5.76 KiB) Viewed 34236 times
LM35 Temperature Sensor
The LM35 series are precision integrated-circuit temperature sensors. The LM35 is analog sensor by nature. The output pin provides an analog voltage output that is linearly proportional to the Celsius (Centigrade) temperature. That means it gives a voltage output that varies directly (and linearly) with the sensed quantity.

For example in LM35 temperature sensor, if the output is 300mV then the temperature is 30 degrees by Celsius. To get the degree value in Celsius, all that must be done is to take the voltage output and divide it by 10 this give out the value degrees in celsius (300mV / 10 = 30 C).
You can easily convert this celsius value into Fahrenheit by plugging in the appropriate conversion equation.
T(°F) = T(°C) × 9/5 + 32
LM35 Features:
  • Calibrated directly in ˚ Celsius (Centigrade)
  • Linear +0.01 V /˚C scale factor
  • 0.5˚C accuracy guaranteeable (at +25˚C)
  • Rated for full −55˚ to +150˚C range
  • Operates from 4 to 30 volts
Connect the +Vs Pin to 5v and GND to GND. The output (middle pin) must be connected to the analog input pin A5 of the Arduino MCU. It is possible to use any analog PIN of the Arduino UNO. Analog PIN has ADC (analog-to-digital conveter) and it gives us the value between 0-1023 for input voltage of 0 to 5v. So if the reading is 0 then input is 0v, if reading is 1023 then input is 5v. By default, the ADC uses 5V as the highest possible value.
LM35Arduino.jpg
LM35Arduino.jpg (26.94 KiB) Viewed 34236 times
The code is very simple:

Code: Select all

    float t = analogRead(A5);
    number = (t * 5 * 100) / 1024;
    //number = (t * 1.1 * 100) / 1024;
 
We will use a trick to make LM35 more accurate for home use. The sensor range is -55 to 150 °C that is mapped by the Arduino ADC to 0..1023 integer values. The Arduino Playground recommends to use 1.1 V for ADC and it will give more accurate results from the LM35 sensor. A call to "analogReference(INTERNAL);" is required in the setup() procedure.
I think LM35 is not a precision sensor, becuse 0.5 C is too much for, example, human body. There are two tips to make it more accurate:
1) Read output value several times
2) Use full-range otput values of this sensor.
Take a look at the most popular connection schema:
LM35-schema.png
LM35-schema.png (5.81 KiB) Viewed 34236 times
Now take a look at the sketch code for Arduino UNO R3 with ISR, Timer and ADC.

Code: Select all

/*
    LED4x7SegmentsLM35 - sketch to display data from LM35 temperature
                         sensor on a 4-digit 7-segment LED display.

    LM35 is connected to analog PIN A5.
    LED Display uses digital pins 3..10 and analog pins A0, A1, A2, A3.

    This program drives 4-digit LED Display with common cathode.
    It uses Timer #1 interrupt to display temerature in background.

    The temerature in celcius will be updated every 1/5 second.

    This file is free software; you can redistribute it or modify it.
    Created 5 April 2015 by K.M.
*/

int segA = 3; // top
int segB = 4; // right-top
int segC = 5; // right-bottom
int segD = 6; // bottom
int segE = 7; // left-bottom
int segF = 8; // left-top
int segG = 9; // middle

int digit1 = 14; // common cathode for digit1 (i.e. A0)
int digit2 = 15; // common cathode for digit2 (i.e. A1)
int digit3 = 16; // common cathode for digit3 (i.e. A2)
int digit4 = 17; // common cathode for digit4 (i.e. A3)

static int number = 0;
static int ms = 0;

//==============================================================//
static int nDelay = 0; // delay counter
static int nDigit = 1; // active digit on LED
static int aDigitPins[4] = { digit1, digit2, digit3, digit4 };
//==============================================================//
ISR(TIMER1_COMPA_vect)
{
    if( nDelay > 0 )
    {
        // do some delay
        nDelay--;
        if( nDelay==0 )
        {
            // Delay is over -> turn off the Digit
            digitalWrite(aDigitPins[nDigit-1], HIGH);
            // Turn OFF all segments
            drawDigitFast( -1 );
            // Cycle to next Digit (1..4)
            nDigit++;
            if( nDigit > 4 ) nDigit = 1;
        }
    }
    else
    {
        // Turn ON digits and let them light 'nDelay' ticks
        switch( nDigit )
        {
            case 1:
                digitalWrite(digit1, LOW);
                drawDigitFast( (number/1000) % 10 );
                nDelay = 3;
                break;
            case 2:
                digitalWrite(digit2, LOW);
                drawDigitFast( (number/100) % 10 );
                nDelay = 3;
                break;
            case 3:
                digitalWrite(digit3, LOW);
                drawDigitFast( (number/10) % 10 );
                nDelay = 3;
                break;
            case 4:
                digitalWrite(digit4, LOW);
                drawDigitFast( number % 10 );
                nDelay = 3;
                break;
        }
    }
}

//==============================================================//
void setup()
{
    //======================
    // Configure PINs
    pinMode(digit1, OUTPUT);
    pinMode(digit2, OUTPUT);
    pinMode(digit3, OUTPUT);
    pinMode(digit4, OUTPUT);

    pinMode(segA, OUTPUT);
    pinMode(segB, OUTPUT);
    pinMode(segC, OUTPUT);
    pinMode(segD, OUTPUT);
    pinMode(segE, OUTPUT);
    pinMode(segF, OUTPUT);
    pinMode(segG, OUTPUT);

    //======================
    // !!! Configure LM35 Sensor pin for high resolution, [0 .. 1.1] Volt
    analogReference(INTERNAL);

    //======================
    // initialize Timer1
    //======================
    noInterrupts();
    // Reset config registers
    TCCR1A = 0;
    TCCR1B = 0;
    // Turn on CTC mode - Clear Timer on Compare match (counter in OCR1A).
    TCCR1B |= (1 << WGM12);
    // Set 1024 prescaler
    TCCR1B |= (1 << CS12) | (1 << CS10); 
    // Setup tick 1000Hz (for 16MHz CPU, 1024 prescaler)
    OCR1A = (16000000 / 1024 - 1) / 1000;
    // Enable Clear Timer on Compare match (CTC) interrupt (channel A)
    TIMSK1 |= (1 << OCIE1A);
    interrupts();
}

//==============================================================//
void loop()
{
    // Temperature sensor LM35 is connected to A5-pin
    // http://playground.arduino.cc/Main/LM35HigherResolution
    float t = analogRead(A5);
    // 1.1 Volt (High resolution mode), celcius
    number = (t * 1.1 * 100) / 1024;
    // 5.0 Volt (Low resolution mode), celcius
    //number = (t * 5 * 100) / 1024;
    delay(200);
}

//-----------------------------------------------------//
void drawDigitFast(int n)
{
    const byte aPins[8] = {
        segA, segB, segC, segD, segE, segF, segG
    };
    const byte aSegments[11][8] = {
        //  A     B     C     D     E     F     G
        { HIGH, HIGH, HIGH, HIGH, HIGH, HIGH,  LOW }, // 0
        {  LOW, HIGH, HIGH,  LOW,  LOW,  LOW,  LOW }, // 1
        { HIGH, HIGH,  LOW, HIGH, HIGH,  LOW, HIGH }, // 2
        { HIGH, HIGH, HIGH, HIGH,  LOW,  LOW, HIGH }, // 3
        {  LOW, HIGH, HIGH,  LOW,  LOW, HIGH, HIGH }, // 4
        { HIGH,  LOW, HIGH, HIGH,  LOW, HIGH, HIGH }, // 5
        { HIGH,  LOW, HIGH, HIGH, HIGH, HIGH, HIGH }, // 6
        { HIGH, HIGH, HIGH,  LOW,  LOW,  LOW, LOW  }, // 7
        { HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH }, // 8
        { HIGH, HIGH, HIGH, HIGH,  LOW, HIGH, HIGH }, // 9
        {  LOW,  LOW,  LOW,  LOW,  LOW,  LOW,  LOW }  // all off
    };

    if( n < 0 || n > 10 )
    {
        n = 10;
    }

    for( int i = 0; i < 7; i++ )
    {
        digitalWrite( aPins[i], aSegments[n][i] );
    }
}
Full source code is available in this GitHub repository.

Datasheets:
Attachments
LM35 (TI).pdf
Texas Instruments
(1.31 MiB) Downloaded 2549 times
LM35 (NS).pdf
National Semiconductor
(303.72 KiB) Downloaded 2492 times

Post Reply