Monday, January 18, 2021

7 segment display library for AVR microcontrollers | ATmega328P

There are many ways to control a seven segment display - using a dedicated IC or shift registers which are preferred because they don't require many pins. However this library is made for when you have the segments driven directly from microcontroller pins and each digit is controlled using a transistor.

You have the option of padding the numbers with zeros and displaying them at a certain position, useful for making digital clocks.

Seven segment display library for AVR microcontrollers

What is a 7-segment display

As the name suggests it is a display that is made up of 7 segments. Each segment is simply an LED. Including the dot there are actually 8 LEDs and this fits perfectly on an 8-bit microcontroller's port. This display is mainly made for numerical values but some alphabetical characters can be displayed as well.

Types of 7 segment displays

There are two types of seven segment displays - common cathode and common anode. Common cathode displays have all the ground sides (cathodes) of the LEDs connected together while common anode displays have all the positive sides (anodes) of LEDs tied together.

The 7 segment display can have from 1 up to 6 or 8 digits. One digit can display numbers from 0 to 9 and a dot for numbers with decimals. On 4 digit seven segment display the maximum number that can be displayed is 9999.

7 segment display internal equivalent circuit OPD-Q5621LE-BW
Figure 1: Internal equivalent circuit of a 7 segment display from OPD-Q5621LE-BW datasheet

 

In the diagram above you can see the internal electrical connections of a typical 4 digits seven segment display. Each digit is made up of 7 segments (LEDs) plus the dot. Some displays have the two dots colon and some don't. For a clock you would want the dots. The first diagram illustrates the common anode type and the one below is a common cathode display. Notice that all the segments of a digit are connected to the segments of all other digits. If we were to display number 9 on first digit, the number will be shown on all 4 digits. For this reason the 7 segment displays must be multiplexed meaning that only one digit can be on at a time. The desired digit can be turned on or off by using pins 12, 9, 8 and 6 in the above diagram as an example.

Driving a seven segment display with a microcontroller

Forming numbers on a 7 segment display

By convention each segment is labeled from A to G as in the image above. By turning on only some specific segments, any numbers, minus sign or some alphabetical characters can be displayed. For example to display a "1" segments B and C needs to be on. G will be minus. A, B, C, D, E and F is 0. All segments on will display an 8.

Connecting a common cathode 4 digit 7-segment display to a microcontroller

Connecting a 7-segment display to a microcontroller
OPD-Q5621LE-BW - a 4 digit common cathode display

8 pins of the microcontroller are used to control the 7 segments and the dot and another 4 pins to select the active digit. Regardless of how many digits the display has the number of necessary pins will be the same only the pins for selecting the digits will vary. So for 2 digit display we will need 8 + 2 pins, for 4 digit display 8 + 4 pins. If you are going to use this library it is important that all pins that are connected to A through G segments to be on the same port. The dot pin can be on any other port.

Since each segment is an LED it can draw 20mA of current. The datasheet says 25mA continuous current and even 90mA when they are multiplexed but a single microcontroller pin can output maximum 20mA so the resistors are calculated for 20mA. Even at 14mA and multiplexed the display will be very bright.

Choosing the current limiting resistors for a 7 segment display

Sizing the resistors for an individual segment is the same as for any LED - subtract the LED forward voltage from VCC and divide by the desired current. If VCC is 5V and the forward voltage for a red led is 2V then:

(5V - 2V) / 0.02mA = 150ohm

The forward voltage can vary between 2V and 2.6V for a red led. Pick the lowest value for safety margin. For other led colors the forward voltage will vary. Check the datasheet first. Even if the supply voltage is 5V the output voltage from the microcontroller pin will be a bit lower - something around  0.35V drop - do to internal resistance.

The 4 mosfets are NX7002AK and are used to turn on/off a single digit at a time - multiplexing. You could connect it to a pin and set it to ground but that would be a bad idea since those mosfets will sink the current from all active segments. Say a digit will show the number "8". If each segment draws 20mA then 20mA * 7 segments = 140mA which is way beyond the capability of how much a pin can sink. Even the cheapest mosfets will do. I like to use NX7002AK because they are affordable and more have ESD protection diodes.

Depending of what microcontroller do you use and it's package size, a microcontroller can source up to 200mA from all pins summed together with maximum 20mA per pin. Considering the 7 segments and the dot pin the total current draw when 8. is displayed will be 8 * 20mA = 160mA. If the micro can source 200mA the rest of the pins must not draw more than 200mA - 160mA = 40mA. I personally recommend 14mA or 10mA per segment since nowadays the leds are very bright even at smaller currents. If more current is needed consider using a darlington transistor array IC.

Why not connecting the pins directly to the segments and ditch the 8 resistors? We could put just one resistor on the drain of the mosfet. I have seen people do it but is a bad idea. Depending on how many segments are lit the current draw will vary but the resistor is fixed. If  the resistor is sized for 160mA when the number "1" will be displayed each of the two segments will have 80mA through them. According to the datasheet if the multiplexer is fast enough then a led can take 90mA. But what if the microcontroller gets stuck in a loop? Then the leds will burn out. Sure we can use a watchdog for that. But then you have the issue with the brightness which will be uneven and that will depend on how many segments are on, the resistor tolerances for each digit, the led forward voltage that will vary... Given that the resistors cost almost nothing is better to use one for each segment.

Multiplexing the 7 segment display

We have seen earlier that we can not simply display something on all digits at the same time. Only one digit can be on at a given time. The frequency at which each digit is turned on and off must be high enough to avoid flickering. This library does that by using a timer interrupt that triggers every 1ms. At first millisecond the digit 1 is on and the rest off. The next millisecond only digit 2 is on and so on. This is one of the drawbacks of a 7 segment display - it must be continuously refreshed by the microcontroller if an external IC is not used.

The rate at which the interrupt must trigger depends on the number of digits the display has. The formula for calculating the 7 segment display refresh rate is:

(1 / ISR period) / number of digits

If the interrupt triggers every 1ms and there are 4 digits: (1 / 0.001) = 1000Hz / 4 = 250Hz refresh rate. The more digits the higher the frequency must be to avoid flickering.

7-Segment display library

This library uses the millis library for triggering 1ms interrupt however if you have an interrupt that you already use you can place the multiplexer function in there instead.

Before using the library there are few things for setup inside the file.

#define DISPLAY_7SEG_NR_OF_DIGITS - how many digits the display has

#define DISPLAY_7SEG_TYPE - what type of display: common cathode or common anode. Can be DISPLAY_7SEG_COMMON_CATHODE or DISPLAY_7SEG_COMMON_ANODE

#define DISPLAY_7SEG_PORT - the port to which the 7 segments are connected. For example PORTD

// IMPORTANT. All pins must be on the same port
#define SEGMENT_A_PIN			7
#define SEGMENT_B_PIN			5
#define SEGMENT_C_PIN			1
#define SEGMENT_D_PIN			3
#define SEGMENT_E_PIN			4
#define SEGMENT_F_PIN			6
#define SEGMENT_G_PIN			0

The pin number for each segment. The numbers assigned above are just for guidance. Replace them according to your circuit.

// The dot can be on a different port
#define SEGMENT_DOT_PORT		PORTD
#define SEGMENT_DOT_PIN			PD2

Port and pin number where the dot is connected.

// Common anode or common cathode pin for each digit
#define DISPLAY_DIGIT_1_DDR		DDRC
#define DISPLAY_DIGIT_1_PORT		PORTC
#define DISPLAY_DIGIT_1_PIN		PC0

#define DISPLAY_DIGIT_2_DDR		DDRC
#define DISPLAY_DIGIT_2_PORT		PORTC
#define DISPLAY_DIGIT_2_PIN		PC1

#define DISPLAY_DIGIT_3_DDR		DDRC
#define DISPLAY_DIGIT_3_PORT		PORTC
#define DISPLAY_DIGIT_3_PIN		PC2

#define DISPLAY_DIGIT_4_DDR		DDRC
#define DISPLAY_DIGIT_4_PORT		PORTC
#define DISPLAY_DIGIT_4_PIN		PC3

#define DISPLAY_DIGIT_5_DDR		NC
#define DISPLAY_DIGIT_5_PORT		NC
#define DISPLAY_DIGIT_5_PIN		NC

#define DISPLAY_DIGIT_6_DDR		NC
#define DISPLAY_DIGIT_6_PORT		NC
#define DISPLAY_DIGIT_6_PIN		NC

This library supports up to 6 digits so depending on how many digits you have place NC (Not Connected) on the rest. Not really necessary since the setup function will only use the first depending on how many digits are at DISPLAY_7SEG_NR_OF_DIGITS.

#define LCD_DISPLAY_FLOATS - TRUE or FALSE. If you are not displaying any float numbers setting this to FALSE can save lots of space.

Setup function

display7seg_init()

This function sets the pins as output, low or high depending on the display type. The millis library must also be initiated and then activate global interrupts using sei() function.

    display7seg_init();
    millis_init();

    // Enable global interrupts
    sei();

Display integer numbers on the 7 segment display

display7seg_printInt(number, start_position, nr_of_digits)

number: a positive or negative integer number

start_position: the digit from which the number will start. From 1 to DISPLAY_7SEG_NR_OF_DIGITS. If the number is negative the minus sign will be placed in front and this must be counted to the length of the number.

nr_of_digits: this is useful for padding the numbers with 0. Say you have a digital clock and you want the minutes to be hh:01 instead of hh: 1 then this parameter would be 2 and that will keep the number of digits to 2. If the number will be from 0 to 9 one zero will be placed in front to be 2 digits, if the number already has 2 digits than no padding is necessary. If this parameter is 0 then the number will be displayed as is.

Display float numbers on a 7 segment display

display7seg_printFloat(float_number, start_position, nr_of_digits,  nr_of_decimals)
float_number: a positive or negative float number

start_position and nr_of_digits: the same as for the function for printing integer numbers

nr_of_decimals: how many decimals (digits after the dot) to be displayed. If the float number has many decimals like 3.14159265359 and this parameter is 2 then the printed number will be 3.14. If the number is 3.1 and nr_of_decimals is 2 then the printed number will be 3.10 - one zero is added at the end to make for 2 decimals.

Printing alphabetic characters on the seven segment display

display7seg_printChar(start_position, character)
Prints some of the alphabet letters. Due to the limited number of segments not all alphabetical characters can be formed by a 7 segment display.

start_position: the same as for the function for printing integer numbers

character: this must be one of the following defines and it can not be a char like 'a' or "a".

    // Minus "-" sign
    #define DISPLAY_7SEG_DIGIT_MINUS

    // Letters
    #define DISPLAY_7SEG_DIGIT_a
    #define DISPLAY_7SEG_DIGIT_b
    #define DISPLAY_7SEG_DIGIT_c
    #define DISPLAY_7SEG_DIGIT_d
    #define DISPLAY_7SEG_DIGIT_r	
    #define DISPLAY_7SEG_DIGIT_t	

    #define DISPLAY_7SEG_DIGIT_A	
    #define DISPLAY_7SEG_DIGIT_C	
    #define DISPLAY_7SEG_DIGIT_E	
    #define DISPLAY_7SEG_DIGIT_F	
    #define DISPLAY_7SEG_DIGIT_G	
    #define DISPLAY_7SEG_DIGIT_H	
    #define DISPLAY_7SEG_DIGIT_I	
    #define DISPLAY_7SEG_DIGIT_J	
    #define DISPLAY_7SEG_DIGIT_L	
    #define DISPLAY_7SEG_DIGIT_N	
    #define DISPLAY_7SEG_DIGIT_O	
    #define DISPLAY_7SEG_DIGIT_P	
    #define DISPLAY_7SEG_DIGIT_Q	
    #define DISPLAY_7SEG_DIGIT_S	
    #define DISPLAY_7SEG_DIGIT_U	
    #define DISPLAY_7SEG_DIGIT_Y	

Each define represents the bits of the segments port.

Turning on/of the dots

display7seg_dotOn(dot_number)

display7seg_dotOff(dot_number)

dot_number: the dot of which digit to turn on or off

Animating the dots

display7seg_dotAnimation()
Will turn on and off the dots from left to right. Warning: this is a simple function that uses the _delay_ms() and adds a delay of number of digits * 200 milliseconds. Could be used as an activity indicator.

Clearing the display

display7seg_clearDisplay()
Clearing the display is not always needed. For example is not needed when the same number of digits are replaced with new values. Clearing is needed only if some parts of the display have been used and nothing will be printed there anymore. Say number 10 is printed and then 9 will be printed where 1 was. Not the 0 will remain there showing 90 if the clear function is not used before this.

The multiplexer

display7seg_multiplexer()
This function must be placed inside the ISR - Interrupt Service Routine. It sets the port pins according to what is to be displayed. The above printing functions calculated what pins must be on and put the bits in an array so the ISR can be very fast.

Example 1 - Digital clock using millis library

#include "display7seg.h"
#include "millis.h"

volatile uint8_t seconds = 0;
volatile uint8_t minutes = 0;
volatile uint8_t hours = 0;

volatile int16_t timerISRCount = 0;
volatile uint8_t update_display = 0;

int main(void){
	display7seg_init();
	millis_init();
	
	// Enable global interrupts
	sei();

	while(1){
	
		// Update the display only if something changes
		if(update_display){
			update_display = 0;
			
			if(hours > 9){
				display7seg_printInt(hours, 1, 0);
			}else{
				display7seg_printInt(hours, 2, 0);  
			}
			
			display7seg_printInt(minutes, 3, 2);
			
			//display7seg_printInt(seconds, 3, 2);
		}
	}
	
	return 0;
}


ISR(TIMER2_COMPA_vect){

	timerISRCount++;
		
	// 1/2 second elapsed
	if(timerISRCount == 500){
		// Turn off colon
		display7seg_dotOff(2);
		
	// 1 second elapsed
	}else if(timerISRCount == 1000){
		timerISRCount = 0;

		seconds++;
		update_display = 1;
		
		// Display colon
		display7seg_dotOn(2);

		if(seconds > 59){
			seconds = 0;
			
			minutes++;
			
			if(minutes > 59){
				minutes = 0;
				hours++;
				
				if(hours > 23){
					hours = 0;
				}
			}
		}
	}
	
	// 7 segment display
	display7seg_multiplexer();
    
} 


Download

v1.0

display7seg.h

millis.h

No comments:

Post a Comment