Building a Library for 1602 LCD with STM32F4: Display Initialization and Display Characters (Part 2)

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

Your email address will not be published. Required fields are marked *