I2C LCD with STM32 Nucleo using STM32CubeIDE

In this tutorial, we will learn to interface I2C LCD with STM32 Nucleo and program it using STM32CubeIDE and HAL libraries. Firstly, we will briefly introduce you to the I2C LCD including its pinout and connection with STM32 Nucleo. Then we will move ahead and program our board in STMCube IDE to demonstrate some basic functionalities that we can display on the LCD.

We have similar guides for I2C LCD with other microcontrollers also:

I2C LCD Introduction

The I2C LCD is a 16×2 device which means it can display 16 columns by two rows of characters.  The characters are alphanumeric, but you can create custom characters for basic graphics, bar graphs that kind of thing.  The LCD has the usual type of hd44780 controller, and it also has an I2C circuit connected with it which makes it easy to connect to the STM board. 16X2 LCD without I2C circuit has sixteen pins.

You can find more information about the pins by going to this article:

16×2 Liquid crystal display 

I2C LCD interfacing with ESP32

If we want to connect this board directly with the STM32 Nucleo board, we have to use at least eight general-purpose input output pins of STM32 which will be a waste of GPIO pins. So the better solution is to use an I2C LCD instead of a typical 16×2 LCD. In this tutorial, we are using 16×2 I2C LCD. The advantage of using an I2C LCD is that we only need to use two pins of STM32 to connect with this display.

I2C LCD Pinout

So now let’s start with the pinout of this display. This display has four pins:

  • Ground pin
  • VCC pin
  • SDA 
  • SCL
I2C LCD Pinout

At the back side of this liquid crystal display, you can also see a variable resistor. This variable resistor is used to modify the brightness of the LCD. This potentiometer is very handy when you are using this display module in different light conditions.

Interfacing I2C LCD with STM32 Nucleo

For this project we will require the following components:

  1. STM32 Nucleo
  2. I2C LCD
  3. Connecting Wires
  4. Breadboard

Connect the components as shown in the schematic diagram below:

Follow the schematic diagram below to connect all the devices together. We will power the I2C LCD with 3.3V pin of STM32. The SCL and SDA pins of I2C LCD will be connected with the I2C1 SDA and SCL pins of the STM32 board respectively. PB7 is I2C1_SDA pin and PB6 is I2C1_SCL pin. Moreover, the grounds will be in common.

STM32 NucleoI2C LCD
PB6SCL
PB7SDA
GNDGND
5VVCC
I2C LCD with STM32 Nucleo using STM32CubeIDE

STM32 Nucleo I2C LCD Code

We will use STM32Cube IDE to program our STM32 board. Open the IDE and head over to a new project.

Then for the target selection, specify the STM32 Nucleo board number. After that click on any column as shown in the picture below. Then click the ‘Next’ button.

select STM32 Nucleo from board selector

Specify the name of your project then click ‘Finish’ to complete the setup of your project.

Now go to System Core > RCC then select ‘Crystal/Ceramic Resonator’ in from the High Speed Clock feature.

Now we have enabled the RCC external clock source

Now head over to Connectivity > I2C1. Select the I2C mode as ‘I2C.’

STM32 Blue Pill with I2C LCD Set up I2C

Clock Configuration

Next, go to the Clock Configuration found at the top. This will open the following window. Here we will select the clock frequency.

STM32 Blue Pill UART Interrupt Clock

You can specify your system clock. We will set it as 72 MHz. These are the configurations we have set:

Blue Pill STM32 Creating project Digital Input picture 9

Now we will save our file. Press Ctrl + S. The following window will appear. Click ‘Yes.’ This will generate a template code for you.

Blue Pill STM32 using STM32Cube creating project pic 11

Another window will appear that will ask if you want to open the perspective. Click ‘Yes.’

Blue Pill STM32 Creating project Digital Input picture 11

STM32 I2C LCD Libraries

As we are working with an I2C LCD with our STM32 Nucleo, we will require the liquidcrystal_i2c library. This library will be used to access different functions that will enable us to display texts and numbers on the I2C display in various ways.

liquidcrystal_i2c.h

Go to Core > Inc and create a new file called ‘liquidcrystal_i2c.hCopy the following code from this link and save it to this file.

You will be required to make the following modifications before saving your file.

  • Change line 4 to #include “stm32f1xx_hal.h”
  • Change line 58 to #define DEVICE_ADDR (0x27 << 1)

liquidcrystal_i2c.c

Similarly, head over to Core > Src and create a new file called ‘liquidcrystal_i2c.cCopy the following code from this link and save it to this file.

STM32 Nucleo I2C LCD STM32CubeIDE Code

We will use I2C LCD library functions to display text and numbers on the I2C LCD, both in a static manner and a scrolling manner.

Now let us look at our main.c file that was generated. Inside the main.c file, make sure the following code is part of your script by including the lines of code given below.

#include "main.h"
#include "liquidcrystal_i2c.h"

I2C_HandleTypeDef hi2c1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);

int main(void)
{

  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_I2C1_Init();

    HD44780_Init(2);
    HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("STM32 NUCLEO");
      HD44780_SetCursor(0,1);
      HD44780_PrintStr("I2C LCD DEMO");
      HAL_Delay(2000);

      HD44780_Clear();
      HD44780_SetCursor(16,0);
      HD44780_PrintStr("SCROLL LEFT");

      for(int i=0; i<20; i++)
      {
        HD44780_ScrollDisplayLeft();
        HAL_Delay(500);
      }

      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("SCROLL RIGHT");

      for(int i=0; i<20; i++)
      {
        HD44780_ScrollDisplayRight();
        HAL_Delay(500);
      }

      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("Lets Count 0-10!");
      HAL_Delay(2000);

      char string[5];
      for(int counter=0;counter<= 10;counter++)
      {
        itoa(counter,string,10);
        HD44780_Clear();
        HD44780_SetCursor(0,0);
        HD44780_PrintStr(string);
        HAL_Delay(1000);
      }
      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("STM32 NUCLEO");
      HD44780_SetCursor(0,1);
      HD44780_PrintStr("I2C LCD DEMO END");


  while (1)
  {

  }
}

Working of the Code

We start off by including the liquidcrystal_i2c.h library to access the functions to control the I2C LCD.

#include "liquidcrystal_i2c.h"

Inside the main function, first all the peripherals are initialized, system clock is configured and all the configured peripherals are initialized.

  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_I2C1_Init();

The first step is to initialize the I2C LCD. We use the HD44780_Init() function for initialization. This function takes in only a single parameter which is the number of rows. As we are using a 16×2 LCD, hence we have specified the ‘2’ as the parameter.

HD44780_Init(2);

Next, we clear the LCD screen by calling HD44780_Clear().

 HD44780_Clear();

Before printing any text, we set the position of the cursor using the HD44780_SetCursor(). This function takes in two parameters. The first parameter is starting column and the second parameter is the starting row. Here we have specified the column as ‘0’ and row as ‘0’ which means the cursor is positioned at the extreme left side of the screen.

 HD44780_SetCursor(0,0);

After setting the cursor, we print the string “STM32 NUCLEO” using the function HD44780_PrintStr(). This function takes in a single argument which is an array of characters that you want to print on the screen.

HD44780_PrintStr("STM32 NUCLEO");

Similarly, we set the cursor at column 0 and row 1 and print “I2C LCD DEMO.”

HD44780_SetCursor(0,1);
HD44780_PrintStr("I2C LCD DEMO");

After a delay of 2 seconds, we clear the screen and print the text “SCROLL LEFT” on the screen. The text will scroll in the left direction as we are using the function HD44780_ScrollDisplayLeft() within a for loop which runs for 20 iterations where the function is called after every 500ms.

HAL_Delay(2000);

    HD44780_Clear();
    HD44780_SetCursor(16,0);
    HD44780_PrintStr("SCROLL LEFT");

    for(int i=0; i<20; i++)
    {
      HD44780_ScrollDisplayLeft();
      HAL_Delay(500);
    }

Likewise, after a delay of 2 seconds, we clear the screen and print the text “SCROLL RIGHT” on the screen. The text will scroll in the right direction as we are using the function HD44780_ScrollDisplayRight() within a for loop which runs for 20 iterations where the function is called after every 500ms.

HD44780_Clear();
    HD44780_SetCursor(0,0);
    HD44780_PrintStr("SCROLL RIGHT");

    for(int i=0; i<20; i++)
    {
      HD44780_ScrollDisplayRight();
      HAL_Delay(500);
    }

After that, we will clear the screen and print the text “Lets Count 0-10!” on column position 0 and row position 0.

HD44780_Clear();
    HD44780_SetCursor(0,0);
    HD44780_PrintStr("Lets Count 0-10!");
    HAL_Delay(2000);

Next, we will display a counter on the LCD screen where each number gets displayed on the LCD for a second and then the screen clears.

char string[5];
    for(int counter=0;counter<= 10;counter++)
    {
      itoa(counter,string,10);
      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr(string);
      HAL_Delay(1000);
    }
    HD44780_Clear();

main.c file

This is how a complete main.c file will be after modification.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "liquidcrystal_i2c.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  HD44780_Init(2);
      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("STM32 NUCLEO");
      HD44780_SetCursor(0,1);
      HD44780_PrintStr("I2C LCD DEMO");
      HAL_Delay(2000);

      HD44780_Clear();
      HD44780_SetCursor(16,0);
      HD44780_PrintStr("SCROLL LEFT");

      for(int i=0; i<20; i++)
      {
        HD44780_ScrollDisplayLeft();
        HAL_Delay(500);
      }

      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("SCROLL RIGHT");

      for(int i=0; i<20; i++)
      {
        HD44780_ScrollDisplayRight();
        HAL_Delay(500);
      }

      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("Lets Count 0-10!");
      HAL_Delay(2000);

      char string[5];
      for(int counter=0;counter<= 10;counter++)
      {
        itoa(counter,string,10);
        HD44780_Clear();
        HD44780_SetCursor(0,0);
        HD44780_PrintStr(string);
        HAL_Delay(1000);
      }
      HD44780_Clear();
      HD44780_SetCursor(0,0);
      HD44780_PrintStr("STM32 NUCLEO");
      HD44780_SetCursor(0,1);
      HD44780_PrintStr("I2C LCD DEMO END");

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  /* USER CODE END I2C1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

Save the main.c file after modifying it. Now we are ready to build our project.

Building the Project

To build our project press Ctrl + B or go to Project > Build All.

Your project will start building. After a few moments, your project will be successfully built if there are no errors.

Demonstration

Next press the RUN button in the IDE. The ‘Edit configuration’ window will open up. Click ‘OK’.

After a few moments, the code will be successfully sent to the STM32 board.

STM32 Serial Data Print Run Project 2

Otherwise, press the RESET button on your STM32

Once the code is uploaded to the board, adjust the brightness of the display through the potentiometer until the LCD starts displaying the messages.

I2C LCD with STM32 Nucleo HAL libraries

Demo:

You may also like to read:

I2C LCD tutorials with other microcontrollers:

Leave a Comment