I2C LCD Interface

The common saying is that “Necessity is the mother of invention”.  In reality, however, there are lots of “mothers” of invention.  My favorite one is “laziness” but sometimes stuff gets invented just because it’s a fun challenge to figure out a new way of doing things.  I’d like to claim that was the reason that I came up with the 3-wire, 8-bit LCD interface but that’s not the complete picture.  I might have never even gotten there if it hadn’t been for my failure to get a common I2C LCD piggyback module to work.  To be fair, I was new to the PIC, new to the 1602 LCD, and clueless about I2C.  But, hey, that was a year ago and now I’m only slightly older but much, much wiser.  The driving force here is that I’ve ordered an I2C real-time clock module so I figured I better get up to speed on I2C.  And what better way to do that than to revisit my earlier failure and get the I2C LCD working?  So here we go.

LCD I2C Module

LCD I2C Module

The I2C module I have is similar to the one shown here.  The pins are self-explanatory but there are a couple of other things to point out on this module.  Normally you would need to have 4.7k pull up resistors on the SDA and SCL lines but those are included on the board.  The blue square thing is a potentiometer that allows you to vary the contrast of the LCD display.  The board usually comes with a default I2C address of 27 hex but I’ve seen online where some may be 3F hex or 20 hex.  There are three sets of solder pads on this particular board labeled A0, A1, and A2.  Those can be used to change the address by shorting across one or more of the pad sets.

I2C Communications

I2C Waveform1

I2C LCD Bit Defs

I2C communications are accomplished by using just two wires: one for data (SDA) and one for a clock (SCL).  The data line is defined as being bidirectional so there needs to be a “master” device on the bus to control things.  Each “slave” device will have its own unique address.  As shown in the diagram above, the basic sequence has the master signaling the start of a data transfer.   After the master signals the transfer start, it then puts out the address of the slave it wants to talk to.  The slave will then momentarily take control of the data line to acknowledge the request.  The master will then send one or more data bytes with the slave acknowledging the receipt of each byte.  When it is done sending data, the master signals the end of the transfer sequence with a stop bit.  The master controls the movement of data bits with the clock line.  The data is deemed to be valid while the clock line is high and should be latched when the clock goes back low.

In our current application with the LCD the master does all of the data sending but that is not a limitation of the I2C protocol.  For instance, the real-time clock module I have on order requires data from the master, but it also has data that the master wants to retrieve.  That will be the topic of a future project but suffice it to say that the master determines the direction of the data transfer by setting or clearing the least significant bit of the address byte to indicate either a read or a write.  As can be seen in the software, the I2C address is seven bits and gets shifted left by one bit to make room for the R/W bit.


LCD I2C Interface

The connections to the PIC are pretty simple with only two I/O pins being used.  That leaves four other pins on our little 12F683 for getting sensor data.  Obviously, this same connection can be used with the 16F688 if we want even more I/O pins.  As stated earlier, the module already has the required pull up resistors on the SDA and SCL lines and a contrast control potentiometer for the LCD.  Usually these modules come with a 16-pin header that exactly matches the 16 lines on the LCD.  In fact, you can buy a combo that includes the I2C board already soldered to the LCD.


The software link is listed below.  While it is targeted for the 12F683, it is easily ported to bigger versions of the PIC.  Mostly it requires changing names like TRISIO to TRISA, and GPIO to PORTA.  You will also need to change the line that identifies the PIC version (LIST=) and the INCLUDE file but those are intuitive changes.  The __CONFIG line may also need tweaking just because one or two of the labels used are spelled differently in some of the INCLUDE files.  Just make sure that the PIC you use has a pin that allows an External Interrupt input (usually labeled EXT).  As we’ve seen before, the 16F688 (14-pin PIC) could be used with the same physical pin connections as the 12F683 if you want more I/O pins.

The software is basically the same code that was written for the 4-bit LCD interface.  The primary changes are in how the data bytes are output for the serial interface and in how the PIC handles the I2C protocol.  The I2C routines were easily found on the internet but, as with many things on the internet, they needed to be inspected to ensure that they followed the specification.  The one error I found was at the end of the “i2cwaitack” routine where the SDA pin was set back to an output before the SCL line was brought low to complete the clock cycle.  That could potentially have both the PIC (master) and the slave device trying to drive the SDA line at the same time.  The routine obviously has worked for other folks but that doesn’t make it correct.

The I2C module has an 8-bit serial to parallel shift register built in for connection to the LCD.  The bit definitions are shown in the diagram above.  The upper four bits connect to the upper four data lines on the LCD, just like in a typical 4-bit LCD interface.  The lower four bits are connected to various control pins on the LCD.  In a normal LCD interface these control pins would either be hard wired (like R/W) or controlled with a GPIO line on the PIC.  The backlight control will not have any effect for many LCD modules because the backlight is usually hard wired to be on.

In normal LCD interfaces the enable line would be toggled high and then back low to cause the LCD to latch the data.  In this application we manage that transition by sending each byte twice.  The first send has the enable bit set high and the second send has the enable bit set low.  For a normal 4-bit interface we have to do two sends for each byte because we send the upper nibble and then the lower nibble.  The LCD then pieces the two nibbles together internally.  For the I2C application we actually end up doing four sends for each byte.  Kind of inefficient but that’s the price we pay for a two wire interface.

As I mentioned earlier, I made a small fix to the “i2cwaitack” routine that I found online.  I also added a little code to the end of the routine.  If the slave fails to acknowledge a transfer from the master then the software will be reset by enabling the Watch Dog Timer (WDT).  Newer PIC chips have a software reset command that can be used instead.  An alternative for some I2C applications would be to simply make a call to “i2cstop” and then continue where the code left off.  That’s it for this post.  Check out my other electronics projects.