Thursday, July 29, 2010

Controlling an LCD Display via I2C bus

For the same project I'm working on I mentioned in the last post, I need to control a 2x16 characters LCD Display.
The Arduino software has an official library (LiquidCrystal Library) to control -in a very easy way- that kind of displays. Unfortunately, you need at least 6 pins from the Arduino to control one single display. Once again we'll run out of pins quickly...
So, can we use the same strategy used to control an ICM7218 with only two pins, now with the LCDDisplay? Of course, we can, but this time we'll have to port the original library.
The idea to save (digital) pins is to plug as many PCF8574 chips as you need in a I2C bus, and control it with analog 4 and 5 from Arduino with the Wire library. I recommend to read these two posts (here and here) if you are not familiar with the concept.
The porting was done easily and with good results. I did it without opening the original datasheet, just recoding the Arduino pin working to the use of the PCF8574 connected to the I2C bus. The original library was written in a very clear way.
You can find the new library in my GitHub repository, under Arduino/libraries/PCFCrystal, including some example sketches. Feel free to download and use the library. Remember that the library is provided "as is", without any kind of warranty. (It's been tested only with the two constructors presented below).
Ok, the hard work is done. You only have to use the library! The use of the library is very easy if you know the Liquid Crystal. You only have to use a different syntax for the constructor, include and initialize the Wire library, and the rest of the code is actually the same code you used with Liquid Crystal.

Let's see it in action!


Let's start with an example. I've modified the Hello World example to show the time it takes to print a single character. Below you have a video showing a similar sketch to the one you can find with the code (PCFHelloWorld). It's a bit different as I changed it after the video recording:


As you can see I'm using an Arduino Pro Mini, but this don't affect to the results.
Now lets see the wiring. There are two versions, for a 4 bits interface and a 8 bits one.
This is the schema for the 4 bits interface. In this case I use a single PCF8574 to control the 6 pins needed.



Now the schema for the 8 bits interface. As you can see in this case I need an extra PCF8574 as I need to control 10 pins.



You can note in the schemas, and see in the video too, that there is a blinking led attached to a free pin of one PCF8574. This is to demonstrate that you can use the unused pins of the PCF8574 for whatever you want without interfering the LCDDisplay.

And now the code. Let's focus in the differences with the original library.

First we need to initilize the Wire library to use I2C.
// Inclusion of Wire
#include <Wire.h>

void setup()
{
// Setup for Wire
Wire.begin();

// TODO: more initialization
}


Then we have to create an PCFCrystal object indicating the pins and address of the PCF(s) used.
For the 4 bits interface this is the code:

byte buffer = 0;
// initialize the library with the numbers of the interface pins
// rs, en, d0, d1, d2, d3, address, buffer
// 4 bits
PCFCrystal lcd(B00100000, B00010000, B00000001, B00000010, B00000100, B00001000, 0x20, &buffer);

As you can see, you need a byte to store the information sent to the PCF8574. Then we call the constructor passing the masks used to access the pins (rs, enable and d0 to d3) plus the byte used to store data and the address of the PCF8574 in the I2C bus. Note that you can use whichever pins you prefer from the PCF8574.
In the case of the 8 bits interface the code is similar except because you need two PCF8574 and consequently, two buffers and addresses. Note that in this case, all the data pins must be in the first PCF, and the control pins (enable and rs) must be in the second one. Here you have the code:
byte buffer = 0;
byte data = 0;
// initialize the library with the numbers of the interface pins
// rs, en, d0, d1, d2, d3, d4, d5, d6, d7, data_address, control_address, data_buffer, control_buffer
PCFCrystal lcd(B00100000, B00010000, B10000000, B01000000, B00100000, B00010000, B00000001, B00000010, B00000100, B00001000, 0x21, 0x20, &data, &buffer);


From that point, the rest of the code is the same as using a regular Liquid Crystal library. Here is the whole sample code with comments:

/*
PCFCrystal Library - Hello World

Demonstrates the use a 16x2 LCD display using the I2C capabilities of Arduino.
This library is a porting of the original LiquidCrystal library, prepared to
control the LCD display via one or two PCF8574.
The LiquidCrystal and PCFLiquidCrystal
libraries work with all LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you
can usually tell them by the 16-pin interface.

This sketch prints "Hello World!" to the LCD
and shows the time and the time consumed to print a single character

The circuit (4 bits interface):
* LCD RS pin to PCF8574 output 5 (pin 10)
* LCD Enable pin to PCF8574 output 4 (pin 9)
* LCD D4 pin to PCF8574 output 0 (pin 4)
* LCD D5 pin to PCF8574 output 1 (pin 5)
* LCD D6 pin to PCF8574 output 2 (pin 6)
* LCD D7 pin to PCF8574 output 3 (pin 7)
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)

Library PCFCrystal created on June 2010
http://ardugonic.blogspot.com

Liquid Crystal:
Library originally added 18 Apr 2008
by David A. Mellis
library modified 5 Jul 2009
by Limor Fried (http://www.ladyada.net)
example added 9 Jul 2009
by Tom Igoe
modified 25 July 2009
by David A. Mellis


http://www.arduino.cc/en/Tutorial/LiquidCrystal
*/

#include <wire.h>

// include the library code:
#include <pcfcrystal.h>

byte buffer = 0;
byte data = 0;
// initialize the library with the numbers of the interface pins
// rs, en, d0, d1, d2, d3, address, buffer
// 4 bits
PCFCrystal lcd(B00100000, B00010000, B00000001, B00000010, B00000100, B00001000, 0x20, &buffer);
// 8 bits
//PCFCrystal lcd(B00100000, B00010000, B10000000, B01000000, B00100000, B00010000, B00000001, B00000010, B00000100, B00001000, 0x21, 0x20, &data, &buffer);

void setup() {
  Wire.begin();

  // set up the LCD's number of rows and columns:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("scnds:loop:micrs");
}

long counter = 0;
int led = HIGH;

void loop() {
  counter++;

  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis()/1000);
  // print the number of loops since reset
  lcd.print(":");
  lcd.print(counter);
  // print the time to print 1 char
  long mics = micros();
  lcd.print(":");
  lcd.print(micros() - mics );

  if (counter % 27 == 0) {
    if (led == LOW) {
      buffer = buffer | B01000000;
      led = HIGH;
    }
    else {
      buffer = buffer & ~B01000000;
      led = LOW;
    }
  
    Wire.beginTransmission(0x20);
    Wire.send(buffer);
    Wire.endTransmission();
  }
}


Perfomance


Obviously the drawback of using this approach is that you spend more time to make the operations. But how much more? I don't know, but as an estimation, this sample sketch prints in the display the time spent to write a single character. I've tested the sample with both LiquidCrystal and PCFCrystal libraries in 4 and 8 bits interface and this is the result:
  • LiquidCrystal, 8 bits: 208 microseconds.
  • LiquidCrystal, 4 bits: 324 microseconds.
  • PCFCrystal, 8 bits: 1196 microseconds.
  • PCFCrystal, 4 bits: 1664 microseconds.

This is only an orientation. When you write strings it's supposed to take comparatively less time difference. Anyway it's clear that the saving in pins is payed with a decrease of performance. Is up to you to decide witch solution is better for your project.

30 comments:

  1. Great job! I was looking for something like this. Thanks.

    ReplyDelete
  2. Thanks. Let me know if you have any problem.

    ReplyDelete
  3. i am having problems converting to the liquidcrystal library would you be able to help me out a bit on how to convert the code to this library?

    ReplyDelete
  4. Hi nickcook,
    sure, let me know what's your problem and I'll do my best to solve it.

    ReplyDelete
  5. i just read this whole page again and understand how to do it now i didnt notice the pcfcrystal library wasnt included in the arduino install. i will try it again and let you know if i need help. thanks for the quick response.

    ReplyDelete
  6. Thanks a lot to share this...
    I try to use 2 outputs not used by your code ! (VERY GREAT CODE !) and it's work... I created 2 functions in order to do this.

    But, i saw in cpp file two functions : writePCF and setBit ? possible to switch on/off an output only with this 2 functions ?

    ReplyDelete
  7. Hi bop,
    yes, you can use the pair of functions you mentioned to control outputs not used by the display. Or, alternatively, you can use the same schema I use in my sample to control the "external" led.

    ReplyDelete
  8. Is there a trick to getting this to work with arduino 1.0? works fine with 022 but not 1.0? thanks in advance

    ReplyDelete
  9. Hi,
    I haven't it tested with arduino 1.0 yet, but there shoudn't have any problem. What problem have you found?

    ReplyDelete
  10. In file included from PCFHelloWorld.cpp:45:
    D:\arduino\arduino-1.0\libraries\PCFCrystal/PCFCrystal.h:6:22: error: WProgram.h: No such file or directory
    In file included from PCFHelloWorld.cpp:45:
    D:\arduino\arduino-1.0\libraries\PCFCrystal/PCFCrystal.h:88: error: conflicting return type specified for 'virtual void PCFCrystal::write(uint8_t)'
    D:\arduino\arduino-1.0\hardware\arduino\cores\arduino/Print.h:48: error: overriding 'virtual size_t Print::write(uint8_t)'
    PCFHelloWorld.cpp: In function 'void loop()':
    PCFHelloWorld.pde:-1: error: 'class TwoWire' has no member named 'send'

    As of Arduino 1.0, the Wire.send() function was renamed to Wire.write() for consistency with other libraries.









    In file included from PCFHelloWorld.cpp:45:
    D:\arduino\arduino-1.0\libraries\PCFCrystal/PCFCrystal.h:6:22: error: WProgram.h: No such file or directory
    In file included from PCFHelloWorld.cpp:45:
    D:\arduino\arduino-1.0\libraries\PCFCrystal/PCFCrystal.h:88: error: conflicting return type specified for 'virtual void PCFCrystal::write(uint8_t)'
    D:\arduino\arduino-1.0\hardware\arduino\cores\arduino/Print.h:48: error: overriding 'virtual size_t Print::write(uint8_t)'
    PCFHelloWorld.cpp: In function 'void loop()':
    PCFHelloWorld.pde:-1: error: 'class TwoWire' has no member named 'send'

    As of Arduino 1.0, the Wire.send() function was renamed to Wire.write() for consistency with other libraries.

    ReplyDelete
    Replies
    1. Ok, I'll have to port my library...
      When I have the chance, I'll let you know.
      Thanks.

      Delete
  11. For Arduino 1.0 compliant is necessary change send() by write() and receive() by read(). Also is necessary replace WProgram.h by Arduino.h. Read the Release Notes for Arduino 1.0.

    ReplyDelete
  12. Hello Ardugo,
    I cannot run PCFHelloWorld on Arduino 1.0
    Please help

    In file included from PCFHelloWorld.cpp:45:
    D:\arduino\arduino-1.0\libraries\PCFCrystal/PCFCrystal.h:88: error: conflicting return type specified for 'virtual void PCFCrystal::write(uint8_t)'
    D:\arduino\arduino-1.0\hardware\arduino\cores\arduino/Print.h:48: error: overriding 'virtual size_t Print::write(uint8_t)'

    Best regards,
    SVA

    ReplyDelete
    Replies
    1. Hello,
      I haven't had the chance to test the library on Arduino 1.0. Maybe the previous comment from Rafaro can help you.
      Regards.

      Delete
    2. Hello,
      Unfortunately,no.
      I did not find neither send()nor receive(). WProgram.h was replaced by Arduino.h earlier.
      Soorry.

      Best regards

      Delete
  13. Hello,

    I have made changes to your library so as to work with Arduino 1.0. How do I go about sending it to you?

    ReplyDelete
    Replies
    1. Hi,
      you can send me your work to ardugoblog@gmail.com and I'll publish it in my Git repository, or, if you have already published it somewhere, just send me the url and I'll post a link to it here.
      Thanks.

      Delete
  14. Would it be difficult to convert this project to a different chip with more I/O?
    Example PIC18F25J10 $2.25 mouser

    ReplyDelete
  15. Thanks for the example.

    I am using the Liquid Crystal Library with an ethernet arduino board - http://arduino.cc/en/Main/ArduinoBoardEthernet

    I initialize the LCD first and then turn towards initializing the ethernet with begin() but it never works

    LCd works on I2c

    Code-------
    lcd.init(); // initialize the lcd
    // Print a message to the LCD.
    lcd.backlight();

    lcd.setCursor(0, 1);
    lcd.print(" LCD Init OK! ");

    //Initi the ethernet
    Ethernet.begin(); -It never works?

    Should i use Wire functions?

    ReplyDelete
    Replies
    1. Sorry, I don't have the time currently to analyse your problem.
      Regards.

      Delete
  16. This is a great example! I am trying to figure out how to use a MC23017 port expander. I'm extremely new to all of this - so a couple of questions:

    1) Will your library work with the MC23017 port expander?

    2) Without reading the data sheet - how did you know how to address each pin individually - when you were instantiating the LCD object?

    ReplyDelete
    Replies
    1. Hi,
      about the firs question, I'm sorry but I don't know this chip and currently I don't have the time to look at it.
      About the second one, to port the library I mapped every pin used in the original library to one pin in the PCF8574. When the original library sets or resets a pin, I set or reset the corresponding in the PCF. So I don't need the original datasheet.
      Regards.

      Delete
  17. Will this work for the Arduino Mega? The SCL and SDA pins are digital 21 and 20 respectively.

    ReplyDelete
    Replies
    1. I haven't tested but it should work ...

      Delete
  18. Hi Ardugo

    I swung by github but it appears not to have been updated in 3 years.

    Any luck on an update for your PCF8574 library ?
    yours seems the only library i can get to work on this chip.

    Thanks for your time

    Rupert

    ReplyDelete
    Replies
    1. Hi Rupert,
      sorry for this long silence, but latelly I don't have the chance to continue my electronic projects...
      Anyway, I have a version of the library that Raj Goutam send me wich is compatible with the newer versions of the IDE.
      If you're still interested I can send you if you send me your email.
      Regards.

      Delete
  19. This comment has been removed by a blog administrator.

    ReplyDelete
  20. Hi Ardugo,
    I have been trying to get the library working, but stil fails, so would you like the working version you have
    Thanks in advance

    ReplyDelete
    Replies
    1. Hi,
      what's your email address? (If you are the same user that sent a comment on February 4, I've just sent you the last libraries I have).
      Regards.

      Delete