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 with ESP32 and ESP8266 using MicroPython
- I2C LCD interfacing with Arduino Display Scrolling Text and Custom Characters
- I2C LCD interfacing with ESP32 and ESP8266 in Arduino IDE
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:
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
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:
- STM32 Nucleo
- I2C LCD
- Connecting Wires
- 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 Nucleo | I2C LCD |
---|---|
PB6 | SCL |
PB7 | SDA |
GND | GND |
5V | VCC |
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.
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.’
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.
You can specify your system clock. We will set it as 72 MHz. These are the configurations we have set:
Now we will save our file. Press Ctrl + S. The following window will appear. Click ‘Yes.’ This will generate a template code for you.
Another window will appear that will ask if you want to open the perspective. Click ‘Yes.’
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.h‘ Copy 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.c‘ Copy 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.
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.
Demo:
Conclusion
In conclusion, this tutorial has walked you through the process of interfacing an I2C LCD with the STM32 Nucleo board using STM32CubeIDE and HAL libraries. We began with an introduction to the I2C LCD, including its pinout and how to establish connections with the STM32 Nucleo. Following that, we programmed the board to showcase essential functionalities that can be displayed on the LCD. With this knowledge, you are now equipped to utilize the I2C LCD in your projects, enabling efficient text and data display while honing your skills in embedded programming. We encourage you to experiment further with different functionalities and explore the full potential of your STM32 setup.
You may also like to read:
- HC-SR04 Ultrasonic Sensor with STM32 Nucleo using STM32CubeIDE
- SSD1306 OLED with STM32 Nucleo using STM32CubeIDE
- HC-05 Bluetooth Module with STM32 Nucleo using STM32CubeIDE
- STM32 Nucleo Timer in Counter Mode with STM32CubeIDE and HAL Libraries
- STM32 Nucleo Timer Encoder Mode with Rotary Encoder Example
I2C LCD tutorials with other microcontrollers:
- I2C LCD interfacing with ESP32 and ESP8266 in Arduino IDE
- I2C LCD with STM32 Blue Pill using STM32CubeIDE
- I2C LCD with ESP32 and ESP8266 using MicroPython
- I2C LCD Interfacing with Raspberry Pi Pico Display Text and Custom Characters
- I2C LCD interfacing with Arduino Display Scrolling Text and Custom Characters
- TCA9548A I2C Multiplexer Arduino Use Same Address I2C Devices