In the second part of building library for 1602 LCD, we shall intialize the LCD and start writing some data to the display.
In this guide, we shall cover the following:
- Creating header and source files.
- Developing the header file.
- Developing the source file.
- Testing the library.
4. Creating Header and Source Files:
After the project has been generated by STM32CubeMX within STM32CubeIDE, we shall add new source file and header file.
Right click on Src folder and add new source file as following:
Give a name for the source file, we shall name it as lcd_driver.c as following:
In similar manner, right click on Inc folder and add new header file with name of lcd_driver.h.
5. Developing the Header File:
Open lcd_driver.h header file.
After the define of the header file, include the main.h header file as following:
#include "main.h"
Next, declare the following function:
void lcd_init (void);
This function shall initialize the display. It takes no parameters and returns nothing.n
Declare also this function:
void setCursor(int a, int b);
This function shall set the cursor at the desired location. It will take the following parameters:
- int a is the column position.
- int b is the row address.
The function shall return nothing.
Declare the following function:
void lcd_send_string (char *str);
This function shall print a string to the lcd. It takes characters array and returns nothing.
Also, this function:
void lcd_clear (void);
This function shall clear the entire display, it takes nothing return nothing.
Next,
void create_custom_char (uint8_t loc, char * data);
This function shall store a custom character to the LCD GRAM. It takes the following:
- uint8_t loc which is the character location in the memory.
- character array that hold the custom character.
The function shall return nothing
The final function is:
void display_custom_char (uint8_t loc);
This function shall draw the custom character that store the specific location.
Hence, the header file as following:
#ifndef INC_LCD_DRIVER_H_ #define INC_LCD_DRIVER_H_ #include "main.h" void lcd_init (void); void setCursor(int a, int b); void lcd_send_string (char *str); void lcd_clear (void); void create_custom_char (uint8_t loc, char * data); void display_custom_char (uint8_t loc); #endif /* INC_LCD_DRIVER_H_ */
Thats all for the header file.
6. Developing the Source File:
We start by including lcd_driver.h header file as following:
#include "lcd_driver.h"
Since the LCD is interfaced in 4-bit mode, we need to send the as 4bit wide, hence a nibble function is used to transmit the data,
The sequence as following:
Set the 4 upper bits of the data, then set the EN pin to high then back to low.
Repeat the same step for the 4 lower bits of the data.
Hence the function as following:
static void LCD_WriteNibble(uint8_t nibble) { // Set D4-D7 according to the nibble value HAL_GPIO_WritePin(D4_GPIO_Port, D4_Pin, (nibble & 0x01)); HAL_GPIO_WritePin(D5_GPIO_Port, D5_Pin, (nibble & 0x02) >> 1); HAL_GPIO_WritePin(D6_GPIO_Port, D6_Pin, (nibble & 0x04) >> 2); HAL_GPIO_WritePin(D7_GPIO_Port, D7_Pin, (nibble & 0x08) >> 3); // Toggle EN pin to latch the nibble HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_SET); //HAL_Delay(1); // Small delay to ensure the LCD latches the data HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_RESET); HAL_Delay(1); // Small delay to ensure the LCD processes the nibble }
Defined as static since it is accessible from the lcd_driver source file and not from any other source file.
In each LCD, there is two functions needed as following:
- Function that will write command to the LCD.
- Function that will write data to the LCD.
Hence, the function to write command to the LCD as following:
static void LCD_WriteCommand(uint8_t command) { // Set RS to 0 for command mode HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_RESET); // Send the higher nibble LCD_WriteNibble(command >> 4); // Send the lower nibble LCD_WriteNibble(command & 0x0F); HAL_Delay(1); // Delay for command execution }
The steps as following:
- Set the RS pin to Low to indicate command mode.
- Write the command as two 4-bits data.
- Wait for 1ms for the command to finish.
For the writing data, it is similar to writing command except that RS is set to 1 as following:
static void LCD_WriteData(uint8_t data) { // Set RS to 1 for data mode HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_SET); // Send the higher nibble LCD_WriteNibble(data >> 4); // Send the lower nibble LCD_WriteNibble(data & 0x0F); HAL_Delay(1); // Delay for data processing }
The initialization sequence as following:
Hence the initialization function as following:
void lcd_init (void) { // 4 bit initialisation HAL_Delay(50); // wait for >40ms LCD_WriteCommand (0x3); HAL_Delay(5); // wait for >4.1ms LCD_WriteCommand (0x3); HAL_Delay(1); // wait for >100us LCD_WriteCommand (0x3); HAL_Delay(10); LCD_WriteCommand (0x2); // 4bit mode HAL_Delay(10); // dislay initialisation LCD_WriteCommand (0x28); // Function set --> DL=0 (4 bit mode), N = 1 (2 line display) F = 0 (5x8 characters) HAL_Delay(1); LCD_WriteCommand (0x08); //Display on/off control --> D=0,C=0, B=0 ---> display off HAL_Delay(1); LCD_WriteCommand (0x01); // clear display HAL_Delay(1); HAL_Delay(1); LCD_WriteCommand (0x06); //Entry mode set --> I/D = 1 (increment cursor) & S = 0 (no shift) HAL_Delay(1); LCD_WriteCommand (0x0C); //Display on/off control --> D = 1, C and B = 0. (Cursor and blink, last two bits) }
For setting the cursor at certain position:
oid setCursor(int a, int b) { int i=0; switch(b){ case 0:LCD_WriteCommand(0x80);break; case 1:LCD_WriteCommand(0xC0);break; case 2:LCD_WriteCommand(0x94);break; case 3:LCD_WriteCommand(0xd4);break;} for(i=0;i<a;i++) LCD_WriteCommand(0x14); }
To write string to the lcd, simple send data to the lcd as following:
void lcd_send_string (char *str) { while (*str) LCD_WriteData (*str++); }
To clear the display:
void lcd_clear (void) { #define LCD_CLEARDISPLAY 0x01 LCD_WriteCommand(LCD_CLEARDISPLAY); HAL_Delay(100); }
For setting a custom character and display it:
For more information about it, please refer to this guide here.
void create_custom_char (uint8_t loc, char * data) { switch (loc) { case 0:LCD_WriteCommand(0x40);break; case 1:LCD_WriteCommand(0x40+8);break; case 2:LCD_WriteCommand(0x40+16);break; case 3:LCD_WriteCommand(0x40+24);break; case 4:LCD_WriteCommand(0x40+32);break; case 5:LCD_WriteCommand(0x40+40);break; case 6:LCD_WriteCommand(0x40+48);break; case 7:LCD_WriteCommand(0x40+56);break; default : break; } uint8_t cc_data[8]; for (int i=0;i<8;i++) { cc_data[i]=*data++; } for (int i=0;i<8;i++) { LCD_WriteData(cc_data[i]); } } void display_custom_char (uint8_t loc) { switch (loc) { case 0:LCD_WriteData(0);break; case 1:LCD_WriteData(1);break; case 2:LCD_WriteData(2);break; case 3:LCD_WriteData(3);break; case 4:LCD_WriteData(4);break; case 5:LCD_WriteData(5);break; case 6:LCD_WriteData(6);break; case 7:LCD_WriteData(7);break; default: break; } }
Hence, the source file as following:
#include "lcd_driver.h" static void LCD_WriteNibble(uint8_t nibble) { // Set D4-D7 according to the nibble value HAL_GPIO_WritePin(D4_GPIO_Port, D4_Pin, (nibble & 0x01)); HAL_GPIO_WritePin(D5_GPIO_Port, D5_Pin, (nibble & 0x02) >> 1); HAL_GPIO_WritePin(D6_GPIO_Port, D6_Pin, (nibble & 0x04) >> 2); HAL_GPIO_WritePin(D7_GPIO_Port, D7_Pin, (nibble & 0x08) >> 3); // Toggle EN pin to latch the nibble HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_SET); //HAL_Delay(1); // Small delay to ensure the LCD latches the data HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, GPIO_PIN_RESET); HAL_Delay(1); // Small delay to ensure the LCD processes the nibble } static void LCD_WriteCommand(uint8_t command) { // Set RS to 0 for command mode HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_RESET); // Send the higher nibble LCD_WriteNibble(command >> 4); // Send the lower nibble LCD_WriteNibble(command & 0x0F); HAL_Delay(1); // Delay for command execution } static void LCD_WriteData(uint8_t data) { // Set RS to 1 for data mode HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, GPIO_PIN_SET); // Send the higher nibble LCD_WriteNibble(data >> 4); // Send the lower nibble LCD_WriteNibble(data & 0x0F); HAL_Delay(1); // Delay for data processing } void setCursor(int a, int b) { int i=0; switch(b){ case 0:LCD_WriteCommand(0x80);break; case 1:LCD_WriteCommand(0xC0);break; case 2:LCD_WriteCommand(0x94);break; case 3:LCD_WriteCommand(0xd4);break;} for(i=0;i<a;i++) LCD_WriteCommand(0x14); } void lcd_send_string (char *str) { while (*str) LCD_WriteData (*str++); } void lcd_init (void) { // 4 bit initialisation HAL_Delay(50); // wait for >40ms LCD_WriteCommand (0x3); HAL_Delay(5); // wait for >4.1ms LCD_WriteCommand (0x3); HAL_Delay(1); // wait for >100us LCD_WriteCommand (0x3); HAL_Delay(10); LCD_WriteCommand (0x2); // 4bit mode HAL_Delay(10); // dislay initialisation LCD_WriteCommand (0x28); // Function set --> DL=0 (4 bit mode), N = 1 (2 line display) F = 0 (5x8 characters) HAL_Delay(1); LCD_WriteCommand (0x08); //Display on/off control --> D=0,C=0, B=0 ---> display off HAL_Delay(1); LCD_WriteCommand (0x01); // clear display HAL_Delay(1); HAL_Delay(1); LCD_WriteCommand (0x06); //Entry mode set --> I/D = 1 (increment cursor) & S = 0 (no shift) HAL_Delay(1); LCD_WriteCommand (0x0C); //Display on/off control --> D = 1, C and B = 0. (Cursor and blink, last two bits) } void lcd_clear (void) { #define LCD_CLEARDISPLAY 0x01 LCD_WriteCommand(LCD_CLEARDISPLAY); HAL_Delay(100); } void create_custom_char (uint8_t loc, char * data) { switch (loc) { case 0:LCD_WriteCommand(0x40);break; case 1:LCD_WriteCommand(0x40+8);break; case 2:LCD_WriteCommand(0x40+16);break; case 3:LCD_WriteCommand(0x40+24);break; case 4:LCD_WriteCommand(0x40+32);break; case 5:LCD_WriteCommand(0x40+40);break; case 6:LCD_WriteCommand(0x40+48);break; case 7:LCD_WriteCommand(0x40+56);break; default : break; } uint8_t cc_data[8]; for (int i=0;i<8;i++) { cc_data[i]=*data++; } for (int i=0;i<8;i++) { LCD_WriteData(cc_data[i]); } } void display_custom_char (uint8_t loc) { switch (loc) { case 0:LCD_WriteData(0);break; case 1:LCD_WriteData(1);break; case 2:LCD_WriteData(2);break; case 3:LCD_WriteData(3);break; case 4:LCD_WriteData(4);break; case 5:LCD_WriteData(5);break; case 6:LCD_WriteData(6);break; case 7:LCD_WriteData(7);break; default: break; } }
7. Testing the Library:
In main.c file, in user code includes, include the lcd_driver header file as following:
#include "lcd_driver.h"
In user begin PV (Private variables):
char cc1[8] = {0x00, 0x00, 0x0A, 0x00, 0x11, 0x0E, 0x00, 0x00}; // smiley char cc2[8] = {0x0E, 0x0E, 0x04, 0x0E, 0x15, 0x04, 0x0A, 0x0A}; // Robo char cc3[8] = {0x08, 0x0C, 0x0E, 0x0F, 0x0E, 0x0C, 0x08, 0x00}; // arrow char cc4[8] = {0x00, 0x04, 0x0E, 0x0E, 0x0E, 0x1F, 0x04, 0x00}; // bell char cc5[8] = {0x00, 0x00, 0x0A, 0x15, 0x11, 0x0E, 0x04, 0x00}; // Heart char cc6[8] = {0x00, 0x0E, 0x11, 0x11, 0x11, 0x0A, 0x1B, 0x00}; // omega char cc7[8] = {0x0E, 0x10, 0x17, 0x12, 0x12, 0x12, 0x10, 0x0E}; // CT char cc8[8] = {0x04, 0x04, 0x1F, 0x04, 0x04, 0x00, 0x1F, 0x00}; // +-
In user code begin 2 in main function:
lcd_init(); create_custom_char(0,cc1); create_custom_char(1,cc2); create_custom_char(2,cc3); create_custom_char(3,cc4); create_custom_char(5,cc6); create_custom_char(6,cc7); create_custom_char(7,cc8);
In user code begin 3 in while 1 loop:
setCursor(0, 0); lcd_send_string("Custom characters"); HAL_Delay(200); setCursor(0, 1); for (int i=0; i<8; i++) {display_custom_char(i); HAL_Delay(300);} HAL_Delay(700); lcd_clear(); HAL_Delay(200);
Save the project, build it and run it on your board as following:
You may download the code from here.
Happy coding 😉
Add Comment