Wednesday, August 24, 2016

Library for interfacing alphanumeric LCD modules with AVR microcontrollers

This library provides an interface between the microcontroller and LCD module. Note that this code is not for I2C modules.

Library for interfacing alphanumeric LCD modules with AVR microcontrollers - ATmega328P

Main features:

- Supports 16x1, 16x2, 16x4, 20x4, 20x2, 32x2, 40x2 LCD display modules
- Option for automatically wrapping the text to a new line
- Numbers can be padded with zeros to maintain user interface layout
- Scrolling a string of characters
- Includes two types of big digits numerical fonts for making a clock
- Has support for user defined fonts and other special fonts included by default in the LCD memory
- Support for 8 and 4 bit mode interface
- LCD backlight dimming or on/off control using PWM

Hardware interfacing ATmega328 AVR microcontroller with a 16x2 LCD module with PWM brightness control

This LCD modules can be connected in 4 bit mode or 8 bit mode. Using 4 bit mode is recommended because it uses less pins but the code is a bit more complex. In the following example I use 4 bit mode.


Hardware interfacing AVR ATmega328 with an 16x2 LCD module with PWM brightness control


LCD pins:

1 – VSS: power supply (GND)

2 – VDD: power supply (+5v)

3 – Vo or VEE on other schematics: contrast adjust. I have used a 5k variable resistor for adjusting the contrast. If you power up the LCD and don’t see anything, first check the contrast. The characters could be displayed but you can’t see them.

4 – RS (Register Select): set this LOW for sending instructions to LCD or HIGH for sending data (characters).

5 – R/W (Read/Write): set this to LOW to write to LCD module or HIGH to read from LCD.

6 – E (Enable Signal): signals to LCD that it can process the commands.

7 – 14 – DB0…7 (Data Bus Lines): used for sending binary data.

15 – A (Anode): power supply (+5v) for LCD backlight.

16 – K (Cathode): power supply (GND) for LCD backlight. If you don’t want to control the LCD backlight brightness using PWM, then connect this pin to ground through a 100 ohm resistor. Some LCD modules have a current limiting resistors some don’t. It doesn’t hurt if you put one.




Software interfacing AVR ATmega328 microcontroller with an LCD module

STEP 1 - I/O and LCD Type Configuration:

First you need to tell the library what pins did you chose to connect the LCD and what type of LCD module do you use. This is easily done by opening the library file and in the setup section do the necessary modifications.

Many features can be disabled if not needed by setting the defines to FALSE thus reducing the code size. No point in including support for floating numbers or text scrolling if they will not be used anyway.

The DDR's, PORT's and pins are just as an example. Replace them according to your circuit setup. 

/*************************************************************
	USER SETUP SECTION
**************************************************************/

#define LCD_DATA_DDR 					DDRD 	// Data bus (DB0 to DB7 on LCD pins)
#define LCD_DATA_PORT 					PORTD
#define LCD_DATA_PIN 					PIND 	// Used to check busy flag

// In 8-bit mode pins 0-7 will be used so set this to 0
// In 4-bit mode put here the lowest pin number of the LCD_DATA_PORT
// For example if the 4 data pins are on pins 0 to 3 LCD_DATA_START_PIN is 0
// Or if the 4 data pins are on pins 4, 5, 6, 7 LCD_DATA_START_PIN is 4
#define LCD_DATA_START_PIN					0

// Register selection signal - RS
#define LCD_RS_CONTROL_DDR 				DDRD
#define LCD_RS_CONTROL_PORT 				PORTD
#define LCD_RS_PIN					PD6

// Read/write signal - RW
#define LCD_RW_CONTROL_DDR 				DDRD
#define LCD_RW_CONTROL_PORT 				PORTD
#define LCD_RW_PIN					PD5

// Enable signal - E
#define LCD_E_CONTROL_DDR 				DDRD
#define LCD_E_CONTROL_PORT 				PORTD
#define LCD_E_PIN 					PD4

// LCD type
#define LCD_NR_OF_CHARACTERS 				16 	// e.g 16 if LCD is 16x2 type
#define LCD_NR_OF_ROWS 					2 	// e.g 2 if LCD is 16x2 type

// MCU bits
#define PORT_SIZE					8 	// 8 bit microcontroller

// Backlight brightness control using PWM
// TRUE or FALSE - TRUE only if you want to dim the backlight using PWM or turn it on/off and the OC0B pin in available for this use
#define LCD_BACKLIGHT_PWM				FALSE

#define LCD_PWM_DDR					DDRD // OC0B DDR for backlight brightness control
#define LCD_PWM_PORT					PORTD
#define LCD_PWM_PIN					PD5 // OC0B pin for backlight brightness control

// Select 4 or 8 bit mode - 4 bit mode is preffered since only 4 pins are needed for the data port instead of 8
#define LCD_DATA_4_BITS					4
#define LCD_DATA_8_BITS					8
#define LCD_DATA_BUS_SIZE				LCD_DATA_4_BITS // LCD_DATA_4_BITS or LCD_DATA_4_BITS

// Text wrap - If the text length is greater than the numbers of LCD characters
// the cursor will be set on the beginning of the next line
#define LCD_WRAP_TEXT					TRUE // TRUE or FALSE

// To be able to use LCDWriteFloat(), make this equal to 1. Enabling this, will load
// the math library and will add over a KB to the program size. If you are not planning to
// display floats, then make this equal to 0. If you are using it, then uncomment MATH_LIB = -lm
// in your Make file to reduce the loaded math library to half. 
#define LCD_DISPLAY_FLOATS 				FALSE // TRUE or FALSE

// Use of custom characters. BIG_DIGITS_1_CHARACTERS and BIG_DIGITS_3_CHARACTERS must be FALSE
#define LCD_CUSTOM_CHARS 				FALSE // TRUE or FALSE

// Use of big double height digits. CUSTOM_CHARS must be FALSE
// Only one type of big digits can be TRUE
#define BIG_DIGITS_1_CHARACTERS	    		FALSE // TRUE or FALSE - double height 1 character wide font
#define BIG_DIGITS_3_CHARACTERS	    		FALSE // TRUE or FALSE - double height 3 character wide font

SETP 2 - Initialization:

Run the setup function inside the main() function

LCDSetup(cursorStyle);

cursorStyle can be: LCD_CURSOR_BLINK, LCD_CURSOR_ULINE, LCD_CURSOR_NONE

Printing strings on the LCD display

LCDWriteString(“Text on LCD”);

After writing something on the display, the cursor will be automatically incremented. At first run the cursor is at the first character and after every character displayed (space included) is incremented. If there is no more space on the LCD and #define LCD_WRAP_TEXT is set to TRUE then the characters will be printed on the next line provided that the LCD display has more than 1 line.

To print a string starting at a certain location use

LCDWriteStringXY(3, 2, “Text on LCD”);

The text will be printed starting from character 3 on line 2 hence the X Y naming.

Printing numbers on the display

LCDWriteInt(number, number_of_digits);
First parameter is the number (int32_t) and if negative the minus sign will be displayed in front of it.

The second parameter number_of_digits is how many digits are desired. If the number has less digits than this second parameter then it will be padded with zeros. This is useful when having a user interface and you want the layout to be consistent regardless of how many digits the number has. Example:

LCDWriteInt(120, 3) will print 120 because 120 has 3 digits so no padding

LCDWriteInt(12, 3) will print 012 as to maintain 3 digits

LCDWriteInt(12, 5) will print 00012

LCDWriteInt(120, 2) will have no effect because 2 is less than 3 digits that 120 is made of

If number_of_digits is 0 then the padding is ignored.

To print a number at a certain position use

LCDWriteIntXY(5, 1, 120, 3);
The number 120 will be printed starting from character 5 on line 1 hence the X Y naming.

Displaying float numbers on the LCD

 LCDWriteFloat(float_number, number_of_digits, number_of_decimals)

For numbers with decimals another function is needed. In order to include this function set LCD_DISPLAY_FLOATS to TRUE.

The parameter number_of_digits has the same function like in the LCDWriteInt() function.

number_of_decimals is used to trim the decimals (how many digits after the dot). For example instead of printing pi 3.14159265359 setting number_of_decimals to 4 will print 3.1415

Here are a few examples with different float numbers and their output on the display:

LCDWriteFloat(0.00271234, 0, 5)  0.00271 notice that the number was trimmed to 5 decimals

LCDWriteFloat(-22.00271234, 3, 5)  -022.00271 again the number has 5 decimals and 22 is padded with 1 zero to have 3 digits given by the second parameter.

Tip: uncomment MATH_LIB = -lm in your Make file to reduce the loaded math library to half.
 

Clearing the LCD display

LCDClear();

This function will clear the display. However if the characters are not many it is much faster and efficient to replace them with spaces or overwrite them by moving the cursor to that position and sending new data. For example to display a counter in a loop without using the clear function one could do:

int main(void){
	uint8_t i = 0;

	while(1){
		for(i = 0; i < 255; i++){
			LCDWriteIntXY(1, 1, i, 3);
			_delay_ms(100);
		}
	}
}

This will display 001, 002, 003... at the same location.

Moving the LCD cursor

Go to character 1, line 1 

LCDHome();
Move the cursor to a specific location

LCDGotoXY(character_position, row_number)

Scrolling a string of characters from right to left

LCDScrollText("A long text that doesn't fit on the LCD display and must be scrolled");

To include this function, LCD_ANIMATIONS must be set to TRUE. The scroll speed can be changed using LCD_SCROLL_SPEED define. Check the included video to see this function in action. Looks better in person than on camera.

Controlling the LCD backlight brightness using PWM

LCDBacklightPWM(brightness)
"brightness" can be between 0 - 100
"0" will turn off the backlight and the LCD without clearing DDRAM thus saving power
"100" will turn the backlight fully on and stop PWM

LCD_BACKLIGHT_PWM must be set TRUE for this function to be included

This function dims the LCD backlight using Timer0 in fast PWM mode using OC0B pin and OCR0A as TOP. Frequency is set to 400 to prevent flickering. Circuit: a small signal transistor can be used with emitter connected to ground. Connect OC0B pin to the base of transistor through a resistor. Connect LCD backlight anode to Vcc and cathode to collector. See the schematic at the top of this page. If the LCD backlight LED takes 20mA or less then the transistor can be omitted since an AVR microcontroller can sink 20mA provided that all the pins of the microcontroller are not sourcing more than 200mA.

Custom LCD digits - 3 characters wide digits:

3 characters double height custom LCD digits

LCDWriteIntBig3Chars(number, nrOfDigits);

BIG_DIGITS_3_CHARACTERS must be set to TRUE to use this function.

BIG_DIGITS_1_CHARACTERS and LCD_CUSTOM_CHARS must be both FALSE. The file "double_height_3_characters_round_digits.h" will be included automatically so be sure to download and put it in the same folder.

The parameter number_of_digits has the same function like in the LCDWriteInt() function.

Here is an example on how to use the function. Tested on 16x2 LCD:
#include <avr/io.h>;
#include <util/delay.h>;
#include "OnLCDLib.h"

int main(void){
    uint8_t seconds = 0;
    uint8_t minutes = 0;
 
    LCDSetup(LCD_CURSOR_NONE);
 
    while(1){
        if(seconds > 59){
            minutes += 1;
            seconds = 0;
   
            if(minutes > 59) minutes = 0;
        }
  
        LCDHome();

        LCDWriteIntBig3Chars(minutes, 2);
        LCDWriteBigSeparator();
        LCDWriteIntBig3Chars(seconds, 2);
  
        seconds += 1;
        _delay_ms(1000);
    }
}


Custom LCD digits - sharp digits:


LCDWriteIntBig(number, nrOfDigits);

BIG_DIGITS_1_CHARACTERS must be set to TRUE to use this function.

BIG_DIGITS_3_CHARACTERS and LCD_CUSTOM_CHARS must be both FALSE. The file "double_height_sharp_digits.h" will be included automatically so be sure to download and put it in the same folder.

The parameter number_of_digits has the same function like in the LCDWriteInt() function. 

Print special characters located inside the HD44780 LCD memory

Apart from the regular alphanumerical characters and punctuation signs, the HD44780 controller has few other special characters inside it's memory.

LCD module charset inside HD44780 memory

Suppose you want to display the omega (ohm) symbol. The address can be found by combining the upper 4 bits (column) with the lower 4 bits (row). In this example the memory address for the omega symbol is: 0b11110100
. The memory address for the pi symbol would be 0b11110111and so on. The following function can be used for displaying the symbols:

LCDPrintExtraChar(char_address);

char_address can also be one of the following defines:

#define LCD_SPECIAL_SYMBOL_DEGREE			0b11011111
#define LCD_SPECIAL_SYMBOL_ARROW_RIGHT		        0b01111110
#define LCD_SPECIAL_SYMBOL_ARROW_LEFT		        0b01111111
#define LCD_SPECIAL_SYMBOL_DIVIDE			0b11111101
#define LCD_SPECIAL_SYMBOL_OHM				0b11110100
#define LCD_SPECIAL_SYMBOL_EPSILON			0b11110110
#define LCD_SPECIAL_SYMBOL_PI				0b11110111
#define LCD_SPECIAL_SYMBOL_MICRO			0b11100100
#define LCD_SPECIAL_SYMBOL_ALPHA			0b11100000
#define LCD_SPECIAL_SYMBOL_BETA				0b11100010

Other characters inside the LCD module memory
Other characters inside the LCD module memory

Using custom LCD symbols

HD44780 LCD controller has room for 8 user defined characters. There are many LCD custom character generators online that can be used to build your own symbols for the LCD. For example you could try https://maxpromer.github.io/LCD-Character-Creator then copy the generated code inside the array to the following array in the library

static const uint8_t LCD_custom_chars[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, //Char0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, //Char1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, //Char2
    0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, //Char3
    0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //Char4
    0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //Char5
    0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //Char6
    0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, //Char7
};

Each row inside the array represents 1 character so there are 8 characters from 0 to 7. Notice that this array has by default 0x1F values that can print on the LCD something like battery level or volume.

Custom battery level or volume for LCD
The default 8 symbols included in LCD_custom_chars array. They can be used for volume or battery level

Of course not all symbols must be displayed at once but this is just a demonstration. They could also be animated inside a loop to indicate that a battery is charging.

LCD_CUSTOM_CHARS must be TRUE and both BIG_DIGITS_1_CHARACTERS and BIG_DIGITS_3_CHARACTERS must be FALSE for this to work.

The function for printing user defined symbols is

LCDPrintCustomChar(char_index);
char_index - is the index of the character inside the LCD_custom_chars[] array from 0 to 7.

Under the LCD_custom_chars[] array there are also some defines that the user can rename depending on the custom symbols and can be passed to the above function. By default the defines are named as follows

// This defines can be renamed and used as a parameter for LCDPrintCustomChar function
#define BATTERY_LEVEL_1			0 // these indicates an index for LCD_custom_chars array
#define BATTERY_LEVEL_2			1
#define BATTERY_LEVEL_3			2
#define BATTERY_LEVEL_4			3
#define BATTERY_LEVEL_5			4
#define BATTERY_LEVEL_6			5
#define BATTERY_LEVEL_7			6
#define BATTERY_LEVEL_8			7

and they can be used like so: LCDPrintCustomChar(BATTERY_LEVEL_8) will display the full bar symbol.


Download LCD Library:

Version 2.1

Download big digits LCD characters:


Other resources: 

If you want to learn more in depth about LCDs and their protocols used to communicate with a microcontroller I've provided some links bellow.

https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller 

https://www.8051projects.net/lcd-interfacing/introduction.php 

https://www.engineersgarage.com/knowledge_share/making-custom-characters-on-16x2-lcd 

9 comments:

  1. Give an example of a function please!
    LCDWriteIntBig(int16_t number, int8_t nrOfDigits);
    LCDWriteIntBig3Chars(int16_t number, int8_t nrOfDigits);
    LCDWriteBigSeparator(void);

    ReplyDelete
    Replies
    1. Sure. Sorry for the delay; i noticed it was a problem with the function when number 1 was displayed so i will update the library in a few hours.

      First you need to open the OnLCDLib header file and uncomment these two lines
      #define CUSTOM_CHARS
      #define BIG_DIGITS

      Then download double_height_3_characters_round_digits_v1.0.h
      and copy the content of static const uint8_t LCD_custom_chars[] array over the one in the library.

      Now the function LCDWriteIntBig3Chars() can be used. I've made an example that imitates a clock and i've tested it on a 16 characters 2 lines LCD.

      int main(void){
      uint8_t seconds = 0;
      uint8_t minutes = 0;

      LCDSetup(LCD_CURSOR_NONE);

      while(1){
      if(seconds > 59){
      minutes += 1;
      seconds = 0;

      if(minutes > 59) minutes = 0;
      }

      LCDHome();
      LCDWriteIntBig3Chars(minutes, 2); // 2 represents the number of digits to be displayed. If minutes is 1 digit only, then it will be padded with 1 zero
      LCDWriteBigSeparator();
      LCDWriteIntBig3Chars(seconds, 2);

      seconds += 1;
      _delay_ms(1000);
      }
      }

      I will update this post with an example for the other function but the principle is the same.

      Delete
    2. Thank you!

      Delete
  2. Replies
    1. I will see if I can implement this in the library, but for now here is a solution.

      // Extract integer part and fractional part and put them in two integer vars

      float aFloatNumber;
      uint16_t integer_part_1 = aFloatNumber;
      float fractional_part = aFloatNumber - integer_part;
      uint16_t integer_part_2 = (int)(fractional_part * 100);

      Now you can write it on LCD as follows
      LCDWriteInt(integer_part_1, 3);
      LCDWriteString(".");
      LCDWriteInt(integer_part_2, 3);

      I didn't have the time to test it but it should work.

      Delete
  3. Hi, im tryin to use your library, but i cant configure it, i get a rare characters and im trying to conect in diferents ways, can you help me please? i want to use por C for data, 4bits, or well, configure it to used like your schematic in the top, ty:(

    ReplyDelete
    Replies
    1. can you be a little more explicit with LCD_DATA_PIN please c:

      Delete
    2. LCD_DATA_PIN depends on the port where you have connected the 4 or 8 data pins. For port C LCD_DATA_PIN will be PINC or PIND for D port. So LCD_DATA_PIN is defined as PINx where x is the port letter.

      Delete
    3. Hi. If the LCD is connected as in my example the code with the IO pins must be configured like so:

      #define LCD_DATA_DDR DDRC // Data bus (DB0 to DB7 on LCD pins)
      #define LCD_DATA_PORT PORTC
      #define LCD_DATA_PIN PINC // Used to check busy flag

      #define LCD_DATA_START_PIN 2

      // Register selection signal - RS
      #define LCD_RS_CONTROL_DDR DDRD
      #define LCD_RS_CONTROL_PORT PORTD
      #define LCD_RS_PIN PD0

      // Read/write signal - RW
      #define LCD_RW_CONTROL_DDR DDRD
      #define LCD_RW_CONTROL_PORT PORTD
      #define LCD_RW_PIN PD1

      // Enable signal - E
      #define LCD_E_CONTROL_DDR DDRD
      #define LCD_E_CONTROL_PORT PORTD
      #define LCD_E_PIN PD2

      #define LCD_DATA_BUS_SIZE LCD_DATA_4_BITS

      Delete