This library provides an interface between the microcontroller and LCD module. Also supports I2C adapters.
Main features:
- Supports 16x1, 16x2, 16x4, 20x4, 20x2, 32x2, 40x2 LCD display modules
- Text wrapping to a new line
- Scrolling text
- 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 and I2C adapters
- LCD backlight dimming or on/off control using PWM
Contents:
- Hardware interfacing
- LCDs with I2C backpack adapter
- Software interfacing
- Printing strings
- Printing numbers
- Displaying float numbers
- Clearing the display
- Moving the cursor
- Scrolling a string of characters from right to left
- Controlling the LCD backlight brightness using PWM
- Custom LCD digits - 3 characters wide digits
- Custom LCD digits - sharp digits
- Print special characters located inside the HD44780 LCD memory
- Using custom LCD symbols
- Links
Hardware interfacing a microcontroller to an 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. The following example shows the 4 bit mode.
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.
LCDs with I2C backpack adapter
If your display has an I2C adapter, you can read more about it here: https://www.programming-electronics-diy.xyz/2025/01/library-for-pcf8574-io-expander-for-avr.html.
Library usage
LCD pins and display type
In the header file in the user settings section, you need to specify the port and pin numbers for controlling the display. However, this is not necessary for I2C adapters.
There you can also specify the display type, such as number of lines or characters.
To enable I2C mode, set LCD_HAS_I2C_MODULE to true. To use the display in 4 or 8 bit mode, set it to false.
Initialization
void LCDSetup(uint8_t cursorStyle)
Used to initialize the display by issuing a series of commands. Also used to initialize the I2C library if LCD_HAS_I2C_MODULE is true.
cursorStyle
One of the following constants: LCD_CURSOR_BLINK, LCD_CURSOR_ULINE,
LCD_CURSOR_NONE
Printing strings
void LCDWriteString(const char *msg)
Print a string of characters. After writing 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) will be incremented. If there is no more space on the LCD and 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(x, y, text). Example:
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
void LCDWriteInt(INT_SIZE number, int8_t nrOfDigits)
number
If negative the minus sign will be
displayed in front of it. INT_SIZE is defined in utils.h and by default is int32_t. If you need to display bigger numbers, set it to int64_t.
number_of_digits
How many digits to be displayed. If the number has less digits than this, 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 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 has
If number_of_digits is 0 then the padding is ignored.
To print a number at a certain position use LCDWriteIntXY(x, y, number, nrOfDigits). Example:
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
void LCDWriteFloat(float float_number, int8_t nrOfDigits, uint8_t nrOfDecimals)
float_number
The float number.
number_of_digits
See LCDWriteInt().
number_of_decimals
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.
Clearing the display
void LCDClear(void)
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, line_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")
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)
LCD_BACKLIGHT_PWM must be set TRUE for this function to be included. Not supported when the display has an I2C adapter.
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:
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.
#include "LCDdotMatrix.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.
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
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.
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 since they all use the same array and memory space.
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
They can be used like so: LCDPrintCustomChar(BATTERY_LEVEL_8) will display the full bar symbol.
Links
LCD library v3.0 |
LCDdotMatrix.h LCDdotMatrix.c |
Dependencies | |
Utils |
utils.h utils.c |
Big digits |
double_height_sharp_digits.h double_height_3_characters_round_digits.h |
PCF8574 library project page |
PCF8574.h PCF8574.c |
I2C (TWI) library project page |
twi.h twi.c |
Changelog | |
v3.0 (23-01-2025) |
- Added support for I2C adapters. - Fixed some bugs with text wrapping. - The integer to string conversion function was moved to a global 'utils' file used by other libraries. |
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
Give an example of a function please!
ReplyDeleteLCDWriteIntBig(int16_t number, int8_t nrOfDigits);
LCDWriteIntBig3Chars(int16_t number, int8_t nrOfDigits);
LCDWriteBigSeparator(void);
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.
DeleteFirst 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.
Thank you!
Deletehow to write float on lcd ?
ReplyDeleteI will see if I can implement this in the library, but for now here is a solution.
Delete// 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.
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:(
ReplyDeletecan you be a little more explicit with LCD_DATA_PIN please c:
DeleteLCD_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.
DeleteHi. If the LCD is connected as in my example the code with the IO pins must be configured like so:
Delete#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
hi
ReplyDeletei just used your header file in my project and wanna say thank you ..
wish the best
I appreciate your comment. Best wishes to you too.
Delete