You don’t “connect it in CubeMX” so much as:
- Wire the LCD to the STM32 pins
- Tell CubeMX which pins/peripherals you’re using
- Let CubeMX generate init code
- Add a small LCD driver (C code) using HAL
I’ll show you the most common case: an HD44780-style 16×2 character LCD, first with direct GPIO (4-bit mode) and then briefly with I²C backpack.
1. Hardware: connect LCD → STM32 (4-bit GPIO)
Typical HD44780 16×2 LCD pins:
- VSS – GND
- VDD – +5 V (many also work at 3.3 V; if not, use level shifting for signals)
- VO – contrast (middle pin of a 10–20 kΩ pot between VDD and GND)
- RS – Register Select
- RW – Read/Write
- E – Enable
- D0 – not used in 4-bit
- D1 – not used
- D2 – not used
- D3 – not used
- D4 – Data bit 4
- D5 – Data bit 5
- D6 – Data bit 6
- D7 – Data bit 7
- LED+ – backlight + (via resistor)
- LED- – backlight –
Example wiring to STM32 (you can choose any free GPIOs):
- RS → PB0
- RW → GND (write-only)
- E → PB1
- D4 → PB5
- D5 → PB6
- D6 → PB7
- D7 → PB8
Plus:
- LCD VSS → GND, VDD → 5 V or 3.3 V (check your LCD),
- VO via pot for contrast,
- Backlight pins via resistor and GND/VCC as needed.
2. Configure STM32 in STM32CubeMX
- Create a new project
Select your MCU / Nucleo board (e.g. STM32F103C8Tx, NUCLEO-F401RE, etc.).
- Enable GPIO for the LCD pins
- In the Pinout & Configuration tab, click on PB0, PB1, PB5, PB6, PB7, PB8 and set them as GPIO_Output.
- No pull-up/down is usually fine (or default).
- Clock config
In Clock Configuration, set system clock (e.g. 72 MHz or whatever your board supports).
- Project settings
-
Project → Settings:
- Toolchain: e.g. STM32CubeIDE or “Makefile”.
Generate code (“Generate Code” button).
CubeMX will create:
- main.c,
- gpio.c/gpio.h,
- main.h with macros for your pins (LCD_RS_Pin, etc., if you renamed them or you can rename later).
3. Add a simple LCD driver (HAL + GPIO)
Open main.c and add a few static functions under the includes.
3.1. Make sure you have pin macros
In main.h, after CubeMX generated code you’ll typically have something like:
#define LCD_RS_Pin GPIO_PIN_0
#define LCD_RS_GPIO_Port GPIOB
#define LCD_E_Pin GPIO_PIN_1
#define LCD_E_GPIO_Port GPIOB
#define LCD_D4_Pin GPIO_PIN_5
#define LCD_D4_GPIO_Port GPIOB
#define LCD_D5_Pin GPIO_PIN_6
#define LCD_D5_GPIO_Port GPIOB
#define LCD_D6_Pin GPIO_PIN_7
#define LCD_D6_GPIO_Port GPIOB
#define LCD_D7_Pin GPIO_PIN_8
#define LCD_D7_GPIO_Port GPIOB
If not, add them and match your wiring.
3.2. LCD helper functions
In main.c (above int main(void)), add:
#include "main.h"
/* Small delay helper (in microseconds-ish). For simplicity, use HAL_Delay(ms)
for long delays and this busy-wait for short ones. */
static void lcd_delay_us(volatile uint32_t us)
{
// crude delay; for real projects use a timer-based microsecond delay
while (us--) {
__NOP();
}
}
static void lcd_pulse_enable(void)
{
HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_SET);
lcd_delay_us(50); // > 450 ns
HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_RESET);
lcd_delay_us(50);
}
static void lcd_write_nibble(uint8_t nibble)
{
HAL_GPIO_WritePin(LCD_D4_GPIO_Port, LCD_D4_Pin, (nibble & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_D5_GPIO_Port, LCD_D5_Pin, (nibble & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_D6_GPIO_Port, LCD_D6_Pin, (nibble & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_D7_GPIO_Port, LCD_D7_Pin, (nibble & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET);
lcd_pulse_enable();
}
static void lcd_send(uint8_t value, uint8_t rs)
{
// RS: 0 = command, 1 = data
HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, rs ? GPIO_PIN_SET : GPIO_PIN_RESET);
// high nibble
lcd_write_nibble(value >> 4);
// low nibble
lcd_write_nibble(value & 0x0F);
HAL_Delay(2); // most commands need < 1.6 ms
}
static void lcd_command(uint8_t cmd)
{
lcd_send(cmd, 0);
}
static void lcd_data(uint8_t data)
{
lcd_send(data, 1);
}
static void lcd_clear(void)
{
lcd_command(0x01); // clear display
HAL_Delay(2);
}
static void lcd_home(void)
{
lcd_command(0x02);
HAL_Delay(2);
}
static void lcd_init(void)
{
HAL_Delay(50); // wait > 40 ms after power-up
// 4-bit init sequence (per HD44780 datasheet)
HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_RESET);
// send 0x3 three times to force 8-bit mode
lcd_write_nibble(0x03);
HAL_Delay(5);
lcd_write_nibble(0x03);
HAL_Delay(5);
lcd_write_nibble(0x03);
HAL_Delay(5);
// switch to 4-bit
lcd_write_nibble(0x02);
HAL_Delay(1);
// function set: 4-bit, 2 lines, 5x8 font
lcd_command(0x28);
// display off
lcd_command(0x08);
// clear
lcd_clear();
// entry mode set: increment, no shift
lcd_command(0x06);
// display on, cursor off, blink off
lcd_command(0x0C);
}
static void lcd_set_cursor(uint8_t col, uint8_t row)
{
// 16x2 typical: line 0: 0x00, line 1: 0x40
static const uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
if (row > 1) row = 1; // limit to 2 lines
lcd_command(0x80 | (col + row_offsets[row]));
}
static void lcd_print(const char *s)
{
while (*s) {
lcd_data((uint8_t)*s++);
}
}
3.3. Use the LCD in main()
Still in main.c, inside main():
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init(); // generated by CubeMX
lcd_init(); // our function
lcd_set_cursor(0, 0);
lcd_print("Hello STM32");
lcd_set_cursor(0, 1);
lcd_print("LCD 16x2 Demo");
while (1)
{
// Your main loop
}
}
Build & flash. If wiring + supply are OK, you should see your text.
4. Using an I²C LCD (PCF8574 backpack)
If your 16×2 LCD has a little backpack with 4 pins (VCC, GND, SDA, SCL):
Hardware
- LCD backpack VCC → 5 V (or 3.3 V if compatible)
- GND → GND
- SDA → STM32 I2C SDA (e.g. PB7)
- SCL → STM32 I2C SCL (e.g. PB6)
- Pull-up resistors: usually already on the module; if not, add 4.7 kΩ from SDA/SCL to VCC.
CubeMX
- Enable I2C1 (or another I2C) in I2C mode.
- Select PB6 (SCL), PB7 (SDA) or your board’s I2C pins.
- Set speed (e.g. 100 kHz).
- Generate code — you’ll get MX_I2C1_Init() and a hi2c1 handle.
Code idea
The PCF8574 exposes 8 bits that map to RS/E/D4–D7/backlight. You then:
- Use HAL_I2C_Master_Transmit(&hi2c1, address<<1, &data, 1, 10) to send each nibble.
- The rest of the logic (init commands, set cursor, print) is similar; only the lcd_write_nibble() function becomes “write byte via I2C to PCF8574”.
If you want, I can write a ready-to-paste I²C LCD driver for STM32 HAL using CubeMX’s I2C_HandleTypeDef and show exactly how to call it.
5. Common gotchas
No characters / only blocks:
– Contrast (VO) too high/low → adjust pot.
– Init timing wrong → double-check lcd_init() sequence and delays.
Garbled text:
– Wrong D4–D7 wiring or pins not matching what you defined in main.h.
Nothing at all:
– VCC/GND swapped, backlight off, or LCD not actually powered.
– Wrong power voltage for LCD (some need 5 V).

Top comments (0)