![]() |
Previous Tutorial | Tutorial 19 | Next Tutorial | ![]() |
|||
STM32 LCD 16×2 Tutorial & Library | Alphanumeric LCD 16×2 Interfacing | |||||||
STM32 Course Home Page 🏠 |
In this tutorial, we’ll discuss the alphanumeric LCD 16×2 interfacing with STM32 microcontrollers. Starting with an introduction to the LCD 16×2 display, then how to implement a driver for it on STM32 blue pill board. We’ll set up all the configuration parameters and get our first ECUAL layer driver done, so we can make our next applications more portable. This will be detailed by the end of this tutorial and in the next one, so let’s now get started!
[toc]
Required Components For LABs
All the example code/LABs/projects in the course are going to be done using those boards below.
- Nucleo32-L432KC (ARM Cortex-M4 @ 80MHz) or (eBay)
- Blue Pill STM32-F103 (ARM Cortex-M3 @ 72MHz) or (eBay)
- ST-Link v2 Debugger or (eBay)
QTY | Component Name | 🛒 Amazon.com | 🛒 eBay.com |
2 | BreadBoard | Amazon | eBay |
1 | LEDs Kit | Amazon Amazon | eBay |
1 | Resistors Kit | Amazon Amazon | eBay |
1 | Capacitors Kit | Amazon Amazon | eBay & eBay |
2 | Jumper Wires Pack | Amazon Amazon | eBay & eBay |
1 | 9v Battery or DC Power Supply | Amazon Amazon Amazon | eBay |
1 | Micro USB Cable | Amazon | eBay |
1 | Push Buttons | Amazon Amazon | eBay |
1 | Alphanumeric LCD Module | Amazon | eBay |
★ Check The Full Course Complete Kit List
Some Extremely Useful Test Equipment For Troubleshooting:
- My Digital Storage Oscilloscope (DSO): Siglent SDS1104 (on Amazon.com) (on eBay)
- FeelTech DDS Function Generator: KKMoon FY6900 (on Amazon.com) (on eBay)
- Logic Analyzer (on Amazon.com) (on eBay)
Affiliate Disclosure: When you click on links in this section and make a purchase, this can result in this site earning a commission. Affiliate programs and affiliations include, but are not limited to, the eBay Partner Network (EPN) and Amazon.com.
LCD 16×2 Interfacing
We typically add a 16×2 Alphanumeric LCD to small embedded systems & projects to enhance the user experience and UI of the device/project. You can use it to display text messages to the user, number, etc. Other types of LCDs provide different features such as the number of columns and rows (characters) and maybe colored display, and also different interfaces (parallel, spi, i2c, etc).
For this tutorial, we’ll consider the 16×2 LCD with a 16-pin header interface. Assuming it has the standard Hitachi LCD driver HD44780 controller. The Alphanumeric LCD 16×2 Tutorial did highlight everything you need to know. That’s why I highly recommend that you check it out right now. In order to know, the internals of the LCD driver IC, it’s registers, commands, and how it works and gets initialized, etc.
Today’s tutorial is built upon the previous LCD one, and it’s assumed that you’ve got a basic understanding of the topics discussed earlier. We’ll port the LCD driver in 4-Bit mode to make it easily configurable and portable across most STM32 microcontroller devices.
STM32 LCD 16×2 Interfacing
STM32 LCD Interface Connection
The best way in my opinion for interfacing alphanumeric LCD screens is using an external I2C LCD driver chip. In this way, you save up a lot of valuable GPIO pins for other uses and it only requires 2 wires on the I2C bus. However, it’s going to be a topic for a future tutorial as we didn’t cover the I2C in STM32 MCUs yet.
Therefore, in this tutorial, we’ll be interfacing the LCD 16×2 display in the 4-bit mode which requires 6 GPIO pins. And as you know the STM32 microcontroller is a 3.3v logic device and the LCD is 5v. But it is not a big deal, as the STM32 output (3.3v) pins will be correctly detected by the LCD (5v) input pins. And the only 5v line that is required is the LCD VDD, and you can supply it from the blue pill board 5v pin.
Don’t also forget to connect the contrast control potentiometer as indicated in the diagram shown above. Low contrast may seem to you like a not-working-LCD and hence unnecessarily waste so much time debugging a code that actually works!
After flashing the code to your microcontroller, the LCD may not work from the USB programmer set up. It’s recommended to un-plug the programmer and use external power supply or USB power bank. The LCD may not work at all from the laptop USB or in some cases misbehave, so stay safe with an external power source.
LCD Initialization Procedure
The STM32 microcontroller has to first initialize the LCD display before it can actually send any characters to be displayed correctly. The initialization procedure is step-by-step indicated in the LCD driver datasheet for both modes 4-bit and 8-bit. And it requires a few delay instructions, so I’ll be using the DWT delay which we’ve developed in the previous tutorial.
LCD Instructions (Commands)
The available instructions that the LCD driver IC can execute are listed in the datasheet. As well as the execution time for each instruction. Therefore, you should be careful at this time! you can use an extra pin to read the busy flag bit from the LCD to know whether it did execute the last instruction or not. Otherwise, it’s mandatory to use time delays according to the datasheet specs. Here is a list of the LCD instructions.
LCD Signals Timing
I’ve received a lot of questions and suggestions from you since the last LCD tutorial that I’ve published. The conclusion that I’ve settled for is that maybe there are various versions of the LCD modules and drivers ICs that can be the direct reason why the signal’s timing differs from a user to another.
Here I’m speaking about the enable pulse that you should send to the LCD driver after each command in order to transfer the 8-bit CMD word (whether at once or at 2 stages in 4bit mode).
The datasheet says it should be no less than 200nSec. However, an old LCD with me didn’t receive any data until this pulse delay was up to 500uSec (which is so long in fact). Another LCD could work just fine with 50uSec pulses but no less than that. Another one with a different color did work absolutely fine with a 1uSec pulse. Which is pretty reasonable amount of delay.
The concluding point here, this parameter was and will stay there in my configurations file so that the programmer (you) can experiment with different values as I don’t know the exact hardware setup you’re using. And try to make it as short as possible.
STM32 LCD 16×2 Example Driver
Preface
The LCD 16×2 driver is going to be our first ECUAL (ECU Abstraction Layer), driver. This software layer is added to abstract the hardware dependencies from the application layer. All the onboard ECU peripherals, sensors, memory, and so on do depend on the MCU peripherals and their HAL drivers. The procedure followed by calling some HAL drivers and doing some initialization and calculations work will also get abstracted from the application by introducing the ECUAL layer.
Recall our software layered architecture diagram.

The software component (LCD Driver) in the ECUAL layer will call some HAL_GPIO pin manipulation functions, DWT_Delays, and other HAL & utilities. So that the application code can be more portable, and you can easily change the platform (microcontroller) and have your application running with a high level of portability. And you’ll also have configuration files in each driver to add further adjustability to our software.
The diagram will look like this shown below after introducing the ECUAL layer.
This topic will be discussed in detail in the next tutorial, as I’ve decided to make a separate tutorial to have all the required steps in one article for anyone who would like to download any ECUAL driver and add it to his/her project and step fast forward. But today, we’ll develop the first driver example here from scratch.
Create The Driver Directory & Files
The first step is to create the source code directory for the ECUAL layer in which we’ll also create the first driver directory called LCD16x2, and finally create the following 4 files.
|
![]() |
And let’s begin with the configuration files.
The purpose of having these files in our driver is to make it easily configurable by the user (the application programmer). We shall put all the important parameters in there in a structure that encapsulates all the config parameters together. I’ve chosen to put in there the LCD GPIO pins, GPIO port, and the enable pulse width time.
This means that my driver in this way of implementation assumes that the user will hook the LCD pins to the sam MCU port whatever the pin numbers are. But you can actually make it even more portable so that the user can use pins from multiple GPIO ports! but the config structure will be a bit larger and it’s not a big deal however, it’s a design decision that I’ve made and preferred to tell you that I did that for simplicity’s sake and can be adjusted by you if it’s really needed.
LCD16x2_cfg.c File
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include "LCD16x2.h" const LCD16x2_CfgType LCD16x2_CfgParam = { GPIOB, GPIO_PIN_12, GPIO_PIN_13, GPIO_PIN_14, GPIO_PIN_15, GPIO_PIN_3, GPIO_PIN_4, 50 }; |
LCD16x2_cfg.h File
1 2 3 4 5 6 7 8 |
#ifndef LCD16X2_CFG_H_ #define LCD16X2_CFG_H_ #include "LCD16x2.h" extern const LCD16x2_CfgType LCD16x2_CfgParam; #endif /* LCD16X2_CFG_H_ */ |
Note that the configuration parameter structure is externed to the header file so that in the LCD16x2.c source code we can include the configuration header file LCD16x2_cfg.h and see that global config parameter and do our pin manipulations on these defined ones. This type of configuration is called linking configuration, as it gets resolved during the compilation time in the linking stage and the user (the programmer) doesn’t have to compile the whole project in order to change the configurations, only compile the configuration source and link it with your application object files. This topic and other types of configurations will be discussed in the next tutorial as well.
LCD16x2.h File
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#ifndef LCD16X2_H_ #define LCD16X2_H_ #include "stm32f1xx_hal.h" typedef struct { GPIO_TypeDef * LCD_GPIO; uint16_t D4_PIN; uint16_t D5_PIN; uint16_t D6_PIN; uint16_t D7_PIN; uint16_t EN_PIN; uint16_t RS_PIN; uint16_t LCD_EN_Delay; }LCD16x2_CfgType; //-----[ Prototypes For All Functions ]----- void LCD_Init(); // Initialize The LCD For 4-Bit Interface void LCD_Clear(); // Clear The LCD Display void LCD_SL(); // Shift The Entire Display To The Left void LCD_SR(); // Shift The Entire Display To The Right void LCD_CMD(unsigned char); // Send Command To LCD void LCD_DATA(unsigned char); // Send 4-Bit Data To LCD void LCD_Set_Cursor(unsigned char, unsigned char); // Set Cursor Position void LCD_Write_Char(char); // Write Character To LCD At Current Position void LCD_Write_String(char*); // Write A String To LCD #endif /* LCD16X2_H_ */ |
LCD16x2.c File
It is a bit long file 150 lines of code, and it’s found in the download link down below as well as the other files. The thing you need to know about this source code file is that it’s an implementation for all the declared functions in the header file above to initialize the LCD, write char, string, and all other stuff. It’s a direct implementation for what is documented in the LCD datasheet and we’ve previously done it in This LCD tutorial. So it should be easily ported to the STM32 ecosystem.
Write The Code Into The Driver Files
After creating the driver directories and files, populate those files with the above code or just download them from the link down below. And let’s get ready to test how it’s going to work!
Download The ECUAL/ LCD16x2 Driver Folder
STM32 LCD Display 16×2 LAB Brief
LAB Number | 16 |
LAB Title | STM32 Timer Encoder Mode Basic LAB |
- Set up a new project as usual with system clock @ 72MHz
- Create The driver directory & files as described above and copy the code into them
- Write a simple application to test the LCD driver code. Print a couple of strings on line1 and line2 and shift the entire display right and left!
STM32 LCD Display 16×2 LAB16
In this LAB, our goal is to build a system that initializes the LCD driver. Which in turn initialized the configuration-defined GPIO pins and therefore send the initialization commands to the LCD as described in its datasheet. After this, we can easily call the LCD driver functions to set the cursor position, print strings, and shift the entire display on the LCD right and left. It’s a very basic and simple LAB.
And now, let’s build this system step-by-step
Step1: Open CubeMX & Create New Project
Step2: Choose The Target MCU & Double-Click Its Name
Step3: Set The RCC External Clock Source
Step4: Go To The Clock Configuration
Step5: Set The System Clock To Be 72MHz
Step6: Name & Generate The Project Initialization Code For CubeIDE or The IDE You’re Using
Step7: Create The ECUAL source code directory and LCD16x2 in it and in the LCD16x2 director create the driver files as described before
Step8: Copy the source code into the driver files. Or even copy the files themselves, or whatever easier for you
Here is The Application Code For This LAB (main.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include "main.h" #include "../ECUAL/LCD16x2/LCD16x2.h" void SystemClock_Config(void); static void MX_GPIO_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); LCD_Init(); LCD_Clear(); LCD_Set_Cursor(1, 1); LCD_Write_String(" DeepBlue "); LCD_Set_Cursor(2, 1); LCD_Write_String("STM32 Course"); while (1) { LCD_SR(); HAL_Delay(450); LCD_SR(); HAL_Delay(450); LCD_SR(); HAL_Delay(450); LCD_SR(); HAL_Delay(450); LCD_SL(); HAL_Delay(450); LCD_SL(); HAL_Delay(450); LCD_SL(); HAL_Delay(450); LCD_SL(); HAL_Delay(450); } } |
Download The STM32 LCD Library Example Project LAB16
The LAB Connections
The Result For LAB Testing (video)
You can also use the free tool down below to generate your own custom characters and symbols for the LCD display. Things like speaker icon, battery level indicator, and so on.
Custom LCD Character Generator Online Tool
Did you find this helpful? If yes, please consider supporting this work and sharing these tutorials!
Stay tuned for the upcoming tutorials and don’t forget to SHARE these tutorials. And consider SUPPORTING this work to keep publishing free content just like this!
![]() |
Previous Tutorial | Tutorial 19 | Next Tutorial | ![]() |
Dear Friend
The file STM32_LCD16x2.rar is broken
Can you please, upload again this file?
Thanks
Claudio
Can you help me, I try to make this circuit in proteus put isn’t work I don’t know why I used STM32F10C6 ..
For my case, RW of LCD must connect to ground.
The LCD is a generic brand.
We hook it to ground to access the LCD in write-only mode. This is totally fine for most applications.
However, if your driver code wanna support read operations from the LCD to the uC, the RW pin shall be connected to an IO pin of the uC.
why the LCD16x2_cfg.h File not include in LCD16x2_cfg.c?
I think must be include or will there is link error!
Yeah, sure. It must be included, i’ll maybe need to check those files
Hi Dear,
thanks a lot for this wonderful tutorial, I’ve follow it step by step but at the end I couldn’t compile but project I got error in the main.c the compilator doesn’t recognize the function LCD_SR() how can I correct it ?
thanks for answers me
Hi and thanks for the amazing library! Just an important note, since your LCD library uses DWT to generate delays, it cannot be used on certain microcontrollers such as STM32F103C4.
Just wanted to point that out so others wouldn’t have to find that after a lot of debugging. Your library worked just fine in STM32F401VC for me.