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 controllerstech.com/i2c-lcd-in-stm32/

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.

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.


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 https://controllerstech.com/create-1-microsecond-delay-stm32/

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_PORT GPIOA
#define RS_PIN GPIO_PIN_1
#define RW_PORT GPIOA
#define RW_PIN GPIO_PIN_2
#define EN_PORT GPIOA
#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;
            break;
        case 1:
            col |= 0xC0;
            break;
    }
    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.


Initialize 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);
    HAL_Delay(10);
    lcd_send_cmd (0x20);  // 4bit mode
    HAL_Delay(10);

  // dislay initialisation
    lcd_send_cmd (0x28); // Function set --> DL=0 (4 bit mode), N = 1 (2 line display) F = 0 (5x8 characters)
    HAL_Delay(1);
    lcd_send_cmd (0x08); //Display on/off control --> D=0,C=0, B=0  ---> display off
    HAL_Delay(1);
    lcd_send_cmd (0x01);  // clear display
    HAL_Delay(1);
    HAL_Delay(1);
    lcd_send_cmd (0x06); //Entry mode set --> I/D = 1 (increment cursor) & S = 0 (no shift)
    HAL_Delay(1);
    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_init();
  lcd_put_cur(0, 0);
  lcd_send_string("HELLO ");
  lcd_send_string("WORLD ");
  lcd_send_string("FROM");
  HAL_Delay(1000);
  lcd_put_cur(1, 0);
  lcd_send_string("CONTROLLERSTECH");
  HAL_Delay(2000);
  lcd_clear();


RESULT

Check out the Video Below




Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Subscribe
Notify of

45 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Brody
1 year ago

Hi, I hope you’re well. I walked through your tutorial, and I can only get the cursor to blink with no data. I was wondering if I could have your help?
Thanks

vandung
1 year ago

please, why I can’t load the the download website

Thamanoon Kedwiriyakarn
2 years ago

Hello, thank you very much, Sir.
Would you mind, can I translate your tutorial to Thai language?

Bao
3 years ago

Hi,

When I tried this with my stm32 and the maximum clock is 32MHz, it is showing weird character. It seems I have problems with the LCD initialize. I tried with the board that can be set to 72MHz, the LCD works like a charm.
Do I need to adjust the delay?

Bao
Reply to  admin
3 years ago

Thank you for the help. If I understand correctly that you said with HCLK below 100MHz, then we don’t need to use microsecond delay?
But I did set the timer as your setting but just change the PSC = 32-1 instead of 72-1 and the rest is the same as yours. Is there any factor leading to this problem?

Dener
Reply to  admin
1 year ago

Hello, I’m using STM32G030K6T6, its maximum clock is 64MHz and I’m facing the same problem, could you help me?

Manuel
Reply to  Bao
3 years ago

hey, you managed to fix it? my clock can’t exceed 32MHz too and I cant find the right delay to make it work well

Adam
3 years ago

Hi, I’d like to ask, what are those logic ands with 0x0f in sending commands for?

Last edited 3 years ago by Adam
EMBIEN CRACK
3 years ago

My code run on STM32F100RB With

void LCDInit(void)
{
//4 bit initialisation
//display initialisation
HAL_Delay(500);
LCDSendCommand(0x02); // Return Home
LCDSendCommand(0x28); //4 bit mode
LCDSendCommand(0x0C); //Display on/cursor off
LCDSendCommand(0x01); // clear display
HAL_Delay(100);
}

rest is same
now lets enjoy the coffee

Aadhavan
3 years ago

i am using 16*4 display, can i use this code for my implementaion, what are all things i have to do? please anyone answer me

sakkigoni
3 years ago

Can you make the tutorial with the f303k8 as well? I followed the same tutorial but had some errors in the end saying failed to start GDB server, other errors were: Target Held under Reset

Error in initializing ST-LINK device.
Reason: Target held under reset.

Aadhavan
Reply to  sakkigoni
3 years ago

hi bro, can i go for 16*4 display,could i take this code for my implementation?

ooriatias
3 years ago

hey admin , i have a nucleo board G474 and i burn the program and i try to change the delay , and still not work , can you help me please?

Karthick Reddy
3 years ago

Surely Coffee my friend

sam
4 years ago

hi, how do i use variables inside the print function?

Ahmed
4 years ago

Hello, for me everything fine, the debuging is completed, but the lcd doesn’t work.
the stm program go directly to the sentence : HAL_Init();
can u maybe help me?
Thanks

jak
Reply to  Ahmed
4 years ago

I have the same problem, probably u dont put the V0 pin (LCD) to GND, is not in the diagram, but u have to put it

shruti
4 years ago

Hi, The lcd shows a blank screen.

jihhhh
4 years ago
I want to get a data sheet
Sam
4 years ago

hi, where is the function lcd_send_string described?

Charles
4 years ago

Hmm! I’m wondering if the 1602A is different from the one mentioned in this tutorial. While going over the init from Arduino I noticed the RS and E pins were set to 12 and 11, and I don’t see those in the init code here. Meh, I’ll have to go through it all thoroughly and then disassemble the arduino code.

My mistake. Still going through the code, though. As it’s still not running on this.

Last edited 4 years ago by Charles
Charles
Reply to  admin
4 years ago

I know. Should have mentioned I was attempting to translate from a cpp library file that is called from a c file so kind of got confused.

Anyway, other than removing some redundant code and renaming/re-assigning the routines and pins from the sources here, the only thing that is really different is I don’t quite understand how to handle the timers as of yet (if needed for this example on my stm32l152c board). So I used HAL_Delay (which I’ve read isn’t the best choice), uwTick, and for loops.

Well, once I can get around to it. I’ll go through everything again and see what happens. Might even hook it up to the arduino and use the example code with it just to be sure the device works.

I’m still learning.

<Edit> AHA! I forgot to ground RW. That was the problem.

Last edited 4 years ago by Charles
Ahmed
Reply to  Charles
4 years ago

ground RW?
What does mean?

4 years ago

Really I have not seen you like your usual .. it is a very good professional explanation … Thanks for the effort you made

SHOAIB KHAN
5 years ago

LCD.c(30): warning: #188-D: enumerated type mixed with another type
HAL_GPIO_WritePin(RS_GPIO_Port, RS_Pin, rs); // rs = 1 for data, rs=0 for command
LCD.c(33): warning: #188-D: enumerated type mixed with another type
HAL_GPIO_WritePin(D7_GPIO_Port, D7_Pin, ((data>>3)&0x01));
LCD.c(34): warning: #188-D: enumerated type mixed with another type
HAL_GPIO_WritePin(D6_GPIO_Port, D6_Pin, ((data>>2)&0x01));
LCD.c(35): warning: #188-D: enumerated type mixed with another type
HAL_GPIO_WritePin(D5_GPIO_Port, D5_Pin, ((data>>1)&0x01));
LCD.c(36): warning: #188-D: enumerated type mixed with another type
HAL_GPIO_WritePin(D4_GPIO_Port, D4_Pin, ((data>>0)&0x01));
LCD.c(42): warning: #188-D: enumerated type mixed with another type
HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, 1);
LCD.c(44): warning: #188-D: enumerated type mixed with another type
HAL_GPIO_WritePin(EN_GPIO_Port, EN_Pin, 0);

HOW TO FIX IT ???

Batuhan
Reply to  SHOAIB KHAN
4 years ago

Yeah i have the same problem and i try to run program, I have hardfault interrupt.

Batuhan
Reply to  admin
4 years ago

 /* Check whether or not the timer instance supports internal trigger input */
   assert_param(IS_TIM_CLOCKSOURCE_ITRX_INSTANCE(htim->Instance));

   TIM_ITRx_SetConfig(htim->Instance, sClockSourceConfig->ClockSource);
   break;
  }

after here (this was from stm32f1xx_hal_tim.c (also disassembly table 4606 column)) itgoes into hardfault interrupt w1_hardfault_ırqn 0 .

Also my lcd just glow i tried to change delay but no change.

Shivam Arya
5 years ago

I have programmed the same to my STM32F103 Nucleo Board. Everything is fine. However, no character is being displayed at the LCD. It just glow, but nothing to display.

5 years ago

Awesome post! Keep up the great work! 🙂

Hari
5 years ago

hello admin……
thank you for your information…….
can you please explain the audio player using sd card

thank you

Pavel
5 years ago

Hello ! I have already repeated your interface lcd 1602 project without I2C. Everything works great. Now I will add ds18b20 to it. Thank you very much.

vikas
Reply to  Pavel
5 years ago

please send the code

Pavel
Reply to  Pavel
5 years ago

Good afternoon!
I’m very interested in polling buttons in projects. Eliminating chatter of contacts. Do you have an example with button processing?