In my previous project, I interfaced a 16x2 LCD display with my STM 32 discovery board, you can read about it here. However, this parallel interfacing had many problems such as the large number of wires that had to be used. The large number of wires often causes a mess and is pretty hard to keep track off. Another issue is that it takes up a lot of GPIO pins which might be an issue when we have limited number of pins, like in an Arduino. So, for this project I am going to interface the LCD module through an I2C bus.
Like most of my projects I will first interface the IC with an Arduino followed by implementing it on a STM32 F4 discovery board. In this blog I will implement it on the Arduino and in the next blog I will implement it on the discovery board.
This is a very common peripheral and can be found everywhere. It is based on PCF8574 IC which converts the I2C data to parallel data. The model I bought is from Tomsons electronics
I2C
Before we start this project, it is important to understand what is I2C and how it works. I had actually written a blog about this, well not exactly. I wrote a blog about data transmission with one wire and the pitfalls of it as well as showing an alternative two wire transmission which was essentially I2C. You can read about it over here.
Essentially what there is to know is that there are two wires –
SDA - which is the serial data and
SCL - which is the serial clock line.
The clock line just sends a synchronous clock which is used to make sure that the data is being read properly and at the right time, this ensures synchronisation between the two devices. The data is read only when the clock signal is high. The data line is used to send the data such as the device address, read and write bit, register address, etc.
What we also need to understand is that many devices can be connected to the I2C bus lines. And we can call each device specifically by giving their address. Each I2C device has a specific address. The address of this device can be found in the datasheet or using the example sketch of the wire library on Arduino. The I2C address of my device is 0x27.
The last thing to understand is the actual I2C protocol. The first thing is the start bit. This is when the clock is high and the data pin is turned low. This is followed by the address of the I2C device you want to send or read data from. This is followed by a read or write bit. If we want to read from the device then this bit will be 0 and if we want write to this device then this bit will be 1. After this we wait for the acknowledge bit which is sent by the device. After this we can send the data, 8 bits at a time followed by another acknowledge bit. When we are done, we can send the stop bit which is when both the clock and data bits are high.
You can read more about the I2C protocol here.
Arduino Implementation
So, the first thing we are going implement is a simple interfacing of the Arduino and the I2C device.
Hardware
People normally solder the I2C board directly to the LCD, in a “backpack” configuration. I did not do this as I might need the LCD later. There were no pin numbers on the I2C board, so I didn’t know which way to connect it, but after referring few pictures I connected to the LCD through a bread board and the I2C board to it. I then connected the 4 pins of the I2C module to the Arduino with male to female jumper wires. I then connect the ground to ground and +5V to VCC. Then I connected SDA to A4 and SCL to A5.
Library Implementation
So, most examples on You Tube and every where I looked on the internet uses some sort of Liquid Crystal I2C library, So I installed a Liquid Crystal I2C library by Frank de Brabander and ran the “Hello world” example sketch with few changes.
#include <Wire.h> //library for I2C communications
#include <LiquidCrystal_I2C.h> //library for LCD communications
LiquidCrystal_I2C lcd(0x27,20,4); // initialise the I2C address to 0x27 for a 16 chars and 2 line display
void setup()
{
lcd.init(); // initialize the lcd
lcd.backlight(); // turn on backlight
lcd.setCursor(0,0); // set cursor to (0,0)
lcd.print("CAR"); // to display "CAR"
}
void loop()
{
}
When we upload the code on to the Arduino, we get the following output:
However, this isn’t very impressive, as we just use the library functions and to send that data. We don’t really learn anything through this code, nor do we have any idea how I2C protocol works. So, this really just tell us that our components are not faulty and that are hardware connections are correct.
Implementation without Liquid Crystal library
This part turned out to be much harder that I thought it would be. I might have spent three or four days just trying to figure this part out. The biggest problem is that our I2C module doesn’t come with a proper datasheet. I couldn’t find anything on the internet, everywhere I looked I just found information about changing its I2C address. I then looked for example codes with this module, but all the example codes use the Liquid Crystal I2C library. Numerous blogs and YouTube videos are made about this device, but everyone used the library. The biggest problem of not having a datasheet, is not have a schematic. I do not know what pins are connected to which bits. For example, if I send 0xFF to the I2C bus, which pins on the device would be high, it was not obvious as the device has 16 pin outs, and I2C can only send 8 bits at a time.
My first assumption was that the data on the I2C bus would appear on the 8 data bits of the LCD. I would then use 3 other GPIO pins to control the “handshake” protocol, i.e., the enable, read/write and register select. This obviously didn’t work.
I needed a way to troubleshoot the circuit. I need a DSO or logic analyser, which I don’t have, so I went about making a testing circuit with an LED.
What I did was connect a LED with its negative terminal connected to ground through a 10k resistor, and the positive terminal connected to a jumper wire. I then took this jumper wire and placed it on the pins of the device. If the LED lit up then the pin was outputting a “1” if it didn’t light up then the pin was low.
I send 0xFF to the I2C bus and then noted which all pins where high, then I sent 0x00 to the I2C bus and noted which all pins were low. I repeated this with many different values like 0x0A, 0xAA, 0x55, etc until I started finding patterns. After a whole day of testing, I was able to figure out which pins where connected to what.
One thing became apparent, the lower four data pins (d0, d1, d2, d3) are not connected to the I2C bus and are always high. The most signification 4 data bits which are send to the I2C bus are connected to the most signification data pins (d4, d5, d6, d7) the least significant 3 bits are connected to the enable, read/write and the register select. The other bit is connected to the backlight.
So, this means that the upper nibble is used to send the data and the lower nibble is used to control the LCD. I was able to find a datasheet for a similar device which had schematic which was very blurry. From which I was able to figure out that the lower 4 data bits are not connected and that the backlight had its own bit. This also meant that we had to use 4-bit addressing mode rather than 8-bit addressing mode to interface with the LCD. This was little bit a bummer because I had experience using the 8-bit addressing mode and using a 4-bit addressing mode would complicate the code quite a bit. Never the less at least now I had an idea of what I had to do. Having said that, this all could have been avoided if I could find a datasheet with schematic for the I2C device.
Now we can start to write the Arduino code. I would still be using the wire library. I went and found the Liquid Crystal I2C library on GitHub and used the code in there as a reference, you can find it here. However, it was quite a complicated code and took quite a while to understand the code. After comprehending the liabry I was able to come up with my own Arduino code
#include <Wire.h>
void setup() {
Wire.begin(); // to send the start bit
delayMicroseconds(100); // small delay for the LCD to reset
// Referred from the library which initialises the LCD
I2C_write4(0b00110000);
delayMicroseconds(4200);
I2C_write4(0b00110000);
delayMicroseconds(150);
I2C_write4(0b00110000);
delayMicroseconds(37);
I2C_write4(0b00100000);
delayMicroseconds(37);
// set the LCD at 4 bit addressing mode with 2 line mode in 5x7 font
I2C_write4(0b00101000);
delayMicroseconds(37);
I2C_write8(0b00101000);
delayMicroseconds(37);
//command to turn display on cursor off
I2C_write4(0b00001100);
delayMicroseconds(37);
I2C_write8(0b00001100);
delayMicroseconds(37);
//command to clear the display
I2C_write4(0b00000001);
delayMicroseconds(37);
I2C_write8(0b00000001);
delayMicroseconds(1600);
// Command to increment cursor and shift the cursor to the left automatically
I2C_write4(0b00000110);
delayMicroseconds(37);
I2C_write8(0b00000110);
delayMicroseconds(37);
// to display C
I2C_write4C(0b01000011);
delayMicroseconds(37);
I2C_write8C(0b01000011);
delayMicroseconds(41);
// to display A
I2C_write4C(0b01000001);
delayMicroseconds(37);
I2C_write8C(0b01000001);
delayMicroseconds(41);
// to display R
I2C_write4C(0b01010010);
delayMicroseconds(37);
I2C_write8C(0b01010010);
delayMicroseconds(41);
}
void loop() {
}
void I2C_write4(uint8_t data){ // to send the upper 4 bits for commands
uint8_t d =0b00001000; // turning backlight on
d |= (data & 0xF0); // to select the upper four bits
d |= 1<<2; // to set the enable bit high
Wire.beginTransmission(39); // 39 is the I2C address
Wire.write(d); // sending the data to the device
delayMicroseconds(1); // to send high to low
d &= ~(1<<2); // turning the enable bit high
Wire.write(d); // sending the data to the device
Wire.endTransmission(); // to send stop bit
}
void I2C_write8(uint8_t data){ // to send the lower 4 bits for commands
uint8_t d =0b00001000;
d |= ((data & 0x0F) << 4); // to select the lower four bits
d |= 1<<2;
Wire.beginTransmission(39);
Wire.write(d);
delayMicroseconds(1);
d &= ~(1<<2);
Wire.write(d);
Wire.endTransmission();
}
void I2C_write4C(uint8_t data){ // to send upper for bits for data
uint8_t d =0b00001000;
d |= (data & 0xF0);
d |= 1<<2;
d |= 1<<0; // setting register select as 1
Wire.beginTransmission(39);
Wire.write(d);
delayMicroseconds(1);
d &= ~(1<<2);
Wire.write(d);
Wire.endTransmission();
}
void I2C_write8C(uint8_t data){ // to send lower for bits for data
uint8_t d =0b00001000;
d |= ((data & 0x0F) << 4);
d |= 1<<2;
d |= 1<<0; // setting register select as 1
Wire.beginTransmission(39);
Wire.write(d);
delayMicroseconds(1);
d &= ~(1<<2);
Wire.write(d);
Wire.endTransmission();
}
Code Explanation.
The first part of the code is sending commands to the LCD. These commands can be found here. I found these commands and order of the commands from the library. The next three paragraphs are sending data. I displayed “CAR” on the LCD display.
After this I have defined 4 function, these 4 functions are quite similar with only small variances. The first two functions are used to send commands to the LCD, the first one is to send the upper 4 bits and the second one is lower 4 bits. The last 2 function are used to send data to the LCD.
Result
I cannot tell you how overwhelmingly happy I was to see my code successfully run. It took me so much effort and so much time to figure it all out. I spend days looking for datasheets and example codes. I spend hours manually testing each pin of the device to come up with a schematic. I was very close to giving up many times. But luckily, I did not give up and in the end, it was all worth it. I can say this was one of the hardest projects I have done so far, even though it seemed simple, it was a lot of effort.
Having successfully interfaced with the device using the Arduino, we can do the same on the STM 32 F407VG discovery board in the next project.