Interface LCD 16×2 with STM32 without I2C


As the topic states, today in this tutorial we are going to interface LCD 16×2 with STM32, and this time we are not going to use the I2C to do so. If you are looking for LCD 16×2 with I2C, goto

Basically we are going to use the parallel connection between STM32, and the LCD itself. LCD 16×2 can be connected in the parallel mode either using 4 data pins (LCD 4 bit MODE) or using all 8 data pins (8 bit MODE). In this tutorial, we are going to use the 4 bit MODE to connect LCD with our microcontroller.


lcd connection
lcd connection

As you can see in the picture above, the higher 4 pins of the LCD ( DB4-DB7) are connected to the microcontroller pins (PA4-PA7).

  • RS is connected to PA1
  • RW is connected to PA2
  • EN is connected to PA3

You can connect the pins anywhere you want. It doesn’t have to be in order or in a single port. We will define these pins later in our code.

CubeMx Setup

Here we need to do 2 things. First we have set all the pins, that we want to use, as output. It is shown in the picture below.

lcd cubemx

Second, we have to setup timer to create delay in microseconds. If you are using HCLK less than 100 MHz, you do not need this. I am setting it up just in case. If you don’t know how to use timer to create delay in microseconds, visit

lcd delay timer

Now the cubemx setup is done, let’s take a look at the functions that we are going to use

Some insight into the CODE

First of all we need to define the Pins and Ports that we are going to use for the LCD. As i mentioned in the beginning, you can use any port and pins for the connection. Just make sure you define it in this LCD1602.c file, as shown below

#define RS_PIN GPIO_PIN_1
#define RW_PIN GPIO_PIN_2
#define EN_PIN GPIO_PIN_3
#define D4_PORT GPIOA
#define D4_PIN GPIO_PIN_4
#define D5_PORT GPIOA
#define D5_PIN GPIO_PIN_5
#define D6_PORT GPIOA
#define D6_PIN GPIO_PIN_6
#define D7_PORT GPIOA
#define D7_PIN GPIO_PIN_7

Next is the Timer definition, to give us the delay in microseconds. You might not need to do this for Lower HCLK. Just replace htim1 with the timer handler that you are using.

#define timer htim1
extern TIM_HandleTypeDef timer;
void delay (uint16_t us)
  __HAL_TIM_SET_COUNTER(&timer, 0);
  while (__HAL_TIM_GET_COUNTER(&timer) < us);

I have created a function to write the data/command to the respective pins of the LCD. This function takes 2 parameters, first one is the data that we are going to write, and second one is the rs, which can be set as 0 in case of command, and 1 if we want to write data.

void send_to_lcd (char data, int rs)
    HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, rs);  // rs = 1 for data, rs=0 for command
    /* write the data to the respective pin */
    HAL_GPIO_WritePin(D7_GPIO_Port, D7_Pin, ((data>>3)&0x01));
    HAL_GPIO_WritePin(D6_GPIO_Port, D6_Pin, ((data>>2)&0x01));
    HAL_GPIO_WritePin(D5_GPIO_Port, D5_Pin, ((data>>1)&0x01));
    HAL_GPIO_WritePin(D4_GPIO_Port, D4_Pin, ((data>>0)&0x01));
    /* Toggle EN PIN to send the data
     * if the HCLK > 100 MHz, use the  20 us delay
     * if the LCD still doesn't work, increase the delay to 50, 80 or 100..
    HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, 1);
//  delay (20);
    HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, 0);
//  delay (20);

All we have to do is, take the useful data, which is 4 bit long, and write the first bit to the DB4, second bit to DB5, third to DB6, and fourth to DB7. Then we need to perform the enable pin strobe, to update this data to the respective pins. Which means that first pull the pin HIGH, and than pull it LOW.

If you are using a controller with HCLK more than 100 MHz, than you need to un-comment that delay line, as shown in the code above. This is because We have to wait for the enable pin to settle. If the clock is very high, increase the delay.

The following is the function to send the command to the LCD.

void lcd_send_cmd (char cmd)
    char datatosend;
    /* send upper nibble first */
    datatosend = ((cmd>>4)&0x0f);
    send_to_lcd(datatosend,0);  // RS must be while sending command
    /* send Lower Nibble */
    datatosend = ((cmd)&0x0f);
    send_to_lcd(datatosend, 0);

LCD is connected in the 4 bit mode, where only 4 data pins of the LCD are being used. The command that we send should also be 4 bit wide. Therefore we have to first send the higher nibble, and than lower nibble. RS pin must be 0, for the command operation.

Below is the function to send data to the LCD

void lcd_send_data (char data)
    char datatosend;
    /* send higher nibble */
    datatosend = ((data>>4)&0x0f);
    send_to_lcd(datatosend, 1);  // rs =1 for sending data
    /* send Lower nibble */
    datatosend = ((data)&0x0f);
    send_to_lcd(datatosend, 1);

It is similar to the command function, except RS pin is HIGH to indicate the data operation.

void lcd_put_cur(int row, int col)
    switch (row)
        case 0:
            col |= 0x80;
        case 1:
            col |= 0xC0;
    lcd_send_cmd (col);

The above function puts the cursor at the desired row and column. The row can be either 0 or 1, and the column can vary from 0 to 15.

Lcd_init function initializes the LCD in the 4 bit mode.

void lcd_init (void)
    // 4 bit initialisation
    HAL_Delay(50);  // wait for >40ms
    lcd_send_cmd (0x30);
    HAL_Delay(5);  // wait for >4.1ms
    lcd_send_cmd (0x30);
    HAL_Delay(1);  // wait for >100us
    lcd_send_cmd (0x30);
    lcd_send_cmd (0x20);  // 4bit mode

  // dislay initialisation
    lcd_send_cmd (0x28); // Function set --> DL=0 (4 bit mode), N = 1 (2 line display) F = 0 (5x8 characters)
    lcd_send_cmd (0x08); //Display on/off control --> D=0,C=0, B=0  ---> display off
    lcd_send_cmd (0x01);  // clear display
    lcd_send_cmd (0x06); //Entry mode set --> I/D = 1 (increment cursor) & S = 0 (no shift)
    lcd_send_cmd (0x0C); //Display on/off control --> D = 1, C and B = 0. (Cursor and blink, last two bits)

The above code is with reference to the pattern given for the initialization in the datasheet of the device.

The following is the code inside the main function. First I have initialized the LCD, and than printing the strings.

  lcd_put_cur(0, 0);
  lcd_send_string("HELLO ");
  lcd_send_string("WORLD ");
  lcd_put_cur(1, 0);


lcd result


You can buy me a coffee sensor 🙂

download the CODE below

5 3 votes
Article Rating
Notify of
Oldest Most Voted
Inline Feedbacks
View all comments
Would love your thoughts, please comment.x