How to Interface LCD1602 via I2C with STM32
Today we will see how to interface LCD1602 with STM32 using an I2C device (PCF8574). PCF8574 is a port extender, which is used to connect multiple devices in the output while the input has only 2 pins, SCL and SDA. We will connect this PCF8574 with the LCD1602, thus controlling the entire LCD with only 2 pins.
As you can see above PCF8574 has 4 input pins GND, VCC, SDA, SCL and 16 output pins. We will connect our LCD to these 16 pins.
What is the ADDRESS for PCF8574
The higher 4bits of PCF8574 address are 0100 and these are fixed. But lower 4 bits can be modified according to our convenience. The question you must be thinking is why we need to modify lower 4 bits?
Well you generally don’t but as you know that we can connect up to 128 devices on the same I2C line. Let’s say we want to connect two different LCDs on the same I2C line, then we can’t use two PCF8574 with same addresses and we need to modify the address for one of them.
So how do we modify the address?
- The address of the PCF8574 is 0 1 0 0 A2 A1 A0 R/W. To change the address we are provided with A0, A1 and A2 pins.
- By default these three pins are high so the address by default is 01001110 which is 0x4E.
- To change the address of this device, you have to connect any/all of these three pins to ground, which is provided just above them.
- So let’s say you connected A0 to ground, new address will be 01001100 which is 0x4C.
- In this manner, we can connect up to 8 LCDs to the same line.
- I want to point to one more thing here, the last bit of the address is kept 0 intentionally, because this bit is responsible for read(1)/ write(0) operation.
HOW TO Connect LCD to PCF8574
As shown in the figure above, first pin of the device is Vss which is pin 1 of LCD. So all you have to do is connect first pins of the LCD to Vss above and rest will connect accordingly. Starting with Vss as first pin, connection is as follows:-
CubeMX Configuration
Below is the image showing the clock diagram.
The STM32F103 is clocked by the external 8MHz crystal. The system is running at maximum 72MHz clock.
Below is the image showing the I2C configuraion.
I am using the I2C1 to connect the LCD. The I2C is configured in the standard mode, with the clock speed set to 100KHz.
The pins PB6 and PB7 are configured as the SCL and SDA pins. These pins are connected to the respective pins of the PCF8574.
Some Insight into the Code
#define SLAVE_ADDRESS_LCD 0x4E // change this according to ur setup
The default slave address defined in the i2c-lcd.c is 0x4E. This is default for the PCF8574.
Let’s take a detailed look at the pinout of the PCF8574.
As you can see above,
- P0 is connected to the pin RS of the LCD. This RS pin is defines whether the transmitted byte is a command (0) or Data (1).
- P1 is connected to the R/W pin of the LCD. This pin should be LOW when writing the data to the LCD and HIGH when reading the data from the LCD.
- P2 is connected to the Enable pin of the LCD. This pin is used for the strobe (E=1 & E=0).
- P3 is connected to the Backlight of the Display. Setting this pin to 1 will turn the backlight ON.
- P4 – P7 are connected to the data pins D4 – D7. Since only 4 data pins are available in the PCF8574, we need to configure the LCD in the 4bit Mode.
Send Command
Below is the function to send the command byte to the LCD.
void lcd_send_cmd (char cmd)
{
char data_u, data_l;
data_u = (cmd&0xf0);
data_l = ((cmd<<4)&0xf0);
This function takes the command byte as the parameter. Inside this function we will first separate the lower 4bits and upper 4bits of the command byte. This needs to be done because we are using the LCD in 4bit Mode, hence only 4bits should be sent at a time.
Remember that we have 8 pins (P0 – P7) in the PCF8574. You can treat each pin as a separate bit. Therefore we will have 8 bit data to control 8 pins of the PCF8574.
Pin P0 is the RS pin and in command mode it will be 0. P1 is the R/W pin and it will always be 0 while writing the data/command. To send the strobe, we need to send the same data twice, once when the Enable pin(P2) is set to 1, and again when the pin is reset to 0. P3 is connected to the backlight, so we will keep it to 1.
With the above configuration, our lower 4 bits of the modified command byte will be b1100 (E=1) and b1000 (E=0). The higher 4 bits are the 4 bits from the actual command.
We will send the upper 4 bits of the command byte first. This is as per the instructions in the LCD datasheet.
uint8_t data_t[4];
data_t[0] = data_u|0x0C; //en=1, rs=0 -> bxxxx1100
data_t[1] = data_u|0x08; //en=0, rs=0 -> bxxxx1000
Next we will send the lower 4 bits of the command byte, again with the strobe.
data_t[2] = data_l|0x0C; //en=1, rs=0 -> bxxxx1100
data_t[3] = data_l|0x08; //en=0, rs=0 -> bxxxx1000
HAL_I2C_Master_Transmit (&hi2c1, SLAVE_ADDRESS_LCD,(uint8_t *) data_t, 4, 100);
}
Send Data
Sending data is similar to that of sending the command. The only difference is that the RS bit (P0) will be 1. This is to indicate that the transmitted byte is a data byte.
void lcd_send_data (char data)
{
char data_u, data_l;
uint8_t data_t[4];
data_u = (data&0xf0);
data_l = ((data<<4)&0xf0);
data_t[0] = data_u|0x0D; //en=1, rs=1 -> bxxxx1101
data_t[1] = data_u|0x09; //en=0, rs=1 -> bxxxx1001
data_t[2] = data_l|0x0D; //en=1, rs=1 -> bxxxx1101
data_t[3] = data_l|0x09; //en=0, rs=1 -> bxxxx1001
HAL_I2C_Master_Transmit (&hi2c1, SLAVE_ADDRESS_LCD,(uint8_t *) data_t, 4, 100);
}
Initialisation
Below is the function to initialise the LCD in the 4bit mode. The initialisation requires us to send a few set of command in a particular order. These commands and sequence are provided in the LCD1602 Datasheet. The code below is commented properly, so you can understand what is the function of each command.
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);
// display 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(2);
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)
}
Additional Functions
We can send a single data byte using the function lcd_send_data
, but to send the entire string or a character array, we will write a separate function.
void lcd_send_string (char *str)
{
while (*str) lcd_send_data (*str++);
}
The function lcd_send_string
can be used to send an entire string to the display. The parameter of this function is the pointer to the string or array, that you want to send.
Below if the function to set the cursor at any point on the display.
void lcd_put_cur(int row, int col)
{
switch (row)
{
case 0:
col |= 0x80;
break;
case 1:
col |= 0xC0;
break;
}
lcd_send_cmd (col);
}
Before sending a string or a character to the display, we need to set the cursor position where we want to print it. The LCD1602 has 2 rows and 16 columns. The parameters of the function are:
- @row is the Row number. It can be either 0 or 1.
- @col is the Column number. It can be from 0 to 15.
The main function
Send Strings
We will first see how to print the strings on the display.
// Display Strings
lcd_init ();
lcd_put_cur(0, 0);
lcd_send_string ("HELLO WORLD");
lcd_put_cur(1, 0);
lcd_send_string("from CTECH");
In the main function we will
- Initialise the LCD by calling
lcd_init()
function. - Then put the cursor at the beginning of the 1st Row (0,0) and send the string “HELLO WORLD” to this location.
- Then put the cursor at the beginning of the 2nd Row (1,0) and send the string “from CTECH” to this location.
Below is the output of the above code.
Send Number
We can not print the number directly on the display. The display is only capable of printing the Ascii characters by default. Therefore we need to convert the number to the Ascii form and then print it.
Below is the code to convert and print the number.
// Display Number
lcd_init();
int num = 1234;
char numChar[5];
sprintf(numChar, "%d", num);
lcd_put_cur(0, 0);
lcd_send_string (numChar);
In the main function we will
- Initialise the LCD by calling
lcd_init()
function. - Let’s say we want to print the number 1234. It has 4 digits, so define a character buffer to store 1 extra byte, i.e 5 bytes.
- Now we will use sprintf to convert the number to the character for and store it in the array we just defined.
- The format specifier, %d, is used to convert integer values to character form.
- Then put the cursor at the beginning of the 1st Row (0,0) and send the array.
Below is the output of the above code.
Send Floats
Just like numbers, we can not print the floats directly on the display. Therefore we need to convert the float value to the Ascii form and then print it.
Below is the code to convert and print the float.
// Display Float
lcd_init();
float flt = 12.345;
char fltChar[7];
sprintf(fltChar, "%.3f", flt);
lcd_put_cur(0, 0);
lcd_send_string (fltChar);
In the main function we will
- Initialise the LCD by calling
lcd_init()
function. - Let’s say we want to print the number 12.345. It has 6 digits, so define a character buffer to store 1 extra byte, i.e 7 bytes.
- Now we will use sprintf to convert the float to the character for and store it in the array we just defined.
- The format specifier, %.3f, is used to convert float values to character form till 3 decimal places.
- Then put the cursor at the beginning of the 1st Row (0,0) and send the array.
Below is the output of the above code.
RESULT
You can watch the video to see the complete working.