Showing posts with label PCF8574. Show all posts
Showing posts with label PCF8574. Show all posts

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.

Thursday, June 10, 2010

Can we control more than 7,600 LEDs with just two pins?

In the last two posts (here and here), we saw how to drive 64 LEDs (in the form of a 4 digits 7-segments display) with two Arduino wires controlling an I2C bus.
But how many LEDs can we control with this architecture? Let's find out.
First what we learned. We made groups of 64 LEDs controlled each by an ICM7218a. Then, we used a PCF8574 bus expander to drive the ICM with only two pins. As we saw, we can plug 8 PCF8574 chips in the same bus and 8 additional PCF8574a. So we can have (8 + 8) * 8 = 128 outputs controlled with Arduino's analog 4 and 5.
Now, let's see how can we use all this to drive as many LED's as we can.
The ICM only checks its inputs when the WRITE pin goes from high to low. So, we can connect the 8 outputs from a PCF to the input pins of the ICM ID0 to ID7. I mean, I can take the outputs of 1 PCF and connect them to the inputs of as many ICM's as I need. With the MODE line, I do the same.
As the ICM's only take in account their inputs when WRITE goes low, the only thing we have to do is to connect a different output to each of the ICMs WRITE.
To control an ICM I use the common data and MODE lines and it's WRITE pin.
If you're lost with the explanation here is a schema of the wiring (for only 3 ICMs).



Conclusion: I only need 1 additional pin to control a single ICM. How many pins do I have left after the use of data and MODE lines? 16 * 8 - 9 = 119 pins. That is, I can control 119 ICMs. As I control 64 LEDs with each ICM, I can drive 119 * 8 = 7,616 independent LED's with two pins!
I haven't tested this "invention", but it looks feasible. Doesn't it?
And what can we do with the rest of the analog and digital pins?... Maybe we could control some leds!! ;)

Monday, June 7, 2010

ICM7218a combined with a PCF8574 to reduce the number of pins needed

In my last post we saw how to use an ICM7218a to control 64 leds. The ICM was driven directly by an Arduino. The problem with this approach is that you need 10 pins. That's a lot.
In this post we'll cover how to reduce the amount of pins needed to just two. With the help of the I2C bus this will be easy.


I2C


I2C is a protocol invented by Phillips that needs the use of only two wires. It allows to communicate all kind of devices that implement the protocol like accelerometers, distance sensors, memory modules, digital potentiometers and many more.
One of the devices is the master and is responsible to control the high level communication protocol and the others are slaves that respond to master commands (readings and writings).
Arduino implements I2C via the Wire core library. It can act as master or slave, being the coding process very easy. There are several official samples included in the Arduino IDE installation.
The two pins used in the Arduino's implementation are analog 4 (SDA) and analog 5 (SCL).

PCF8574


This chip (datasheet) implements I2C in slave mode and offers 8 independent input/output pins. So, you can easily use it to add 8 digital pins to your Arduino.
The IC has 3 pins to indicate its slave address. So you can plug 8 units in the same bus to obtain 64 pins. And, what's more, there is another version, the PCF8574A with exactly the same specification except that the address generated are in a different range. So you can add 64 more digital pins. Can you imagine an Arduino with 128 digital pins?

Driving our 7 segments display


The idea here to reduce the amount of pins needed to control the ICM7218 is to use two PCF8574 that I'll control with two pins. Actually I'm going to use only a PCF8574 in my sample because, as in my previous post, I'm using CODEb decoding. That means that I need at most 7 simultaneous pins. Here you can see a picture of the complete system.



As you can hardly see in the yellow breadboard are still placed the displays and the ICM. But now the wires don't go to the Arduino but to the white one, where the PCF is located. From there, two wires go to analog 4 and 5 at the Arduino. And here a video of the whole "invention" working.



Let's see in detail the wiring used from the PFC8574 to the ICM7218.
First I've used two pins for Write (P5) and Mode (P6).
Second, 4 pins for SHUTDOWN (P0), DECODE (P1), HEXA/CODE B (P2) and DATA COMING (P4).
Finally, 5 pins for the 4 datalines (P0 to P3) plus the digital poins (P4). Note that 4 datalines share the pins with the control pins, as these groups are never used simultaneously.
With this wiring the way to control the ICM is simple. As all the data lines are checked when WRITE goes from high to low, to send data you have to set WRITE high in the PCF and then send to this chip the bits you need with WRITE low. At this moment the ICM will respond.
At the end of the post you can see the sketch of the video above.
In conclusion, adding the I2C capabilities of Arduino with a PCF8574, you can reduce from 10 to 2 the amount of pins needed to drive the ICM7218. And what's more, you can add more PCF modules to drive additional ICM without the need to use any additional pin from Arduino. What's the drawback, apart from having to use an additional chip? Obviuosly, the time. If with the direct driving of the ICM it took 880 microseconds to write the full eight digits, now I need 4788 to do the same. This is 5.5 times more... but I can still make 208 complete writings in a second!

// Pin definition
// Actually are pins (P0 to P7) from the PCF8574
#define ID0_PIN B00000001
#define ID1_PIN B00000010
#define ID2_PIN B00000100
#define ID3_PIN B00001000
#define ID7_PIN B00010000

#define NOT_WRITE_PIN B00100000
#define MODE_PIN B01000000

#define NOT_SHUTDOWN_PIN B00000001
#define NOT_DECODE_PIN B00000010
#define NOT_CODE_B_PIN B00000100
#define DATA_COMING_PIN B00010000

// The 3 address lines are grounded.
// Looking at the datasheet this is the address 0x40.
// But Wire shifts the address one bit to left in write and read operations, so I have to provide the address shifted to right
#define ADDRESS 0x20

// Inclusion of Wire
#include <Wire.h>

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

// Set write to high for the first time
sendI2C((byte)NOT_WRITE_PIN);

// A couple of tests
// 1- Fill with 00000000 to 99999999
for (int i = 0; i < 10; i++) {
write8Digits(i * 11111111);
delay(500);
}

// 2- Make a full refresh and display the time it takes
unsigned long time = micros();
write8Digits((unsigned long)0); // Number to test
write8Digits(micros() - time); // Displaying microseconds
delay(2000);

}

unsigned long counter = 0;
void loop()
{
// Display an infinite counter
write8Digits(counter++);
// If you don't wait at least 2 microseconds, the display doesn't have the time to refresh the 8 digits
delay(2);
}

void sendI2C(byte b)
{
// Using of I2C in master mode with the device at ADDRESS
Wire.beginTransmission(ADDRESS);
// Data to send
Wire.send(b);
// End of communication
Wire.endTransmission();
}

void write8Digits(unsigned long num)
{

// Control word
byte data = MODE_PIN | NOT_SHUTDOWN_PIN | DATA_COMING_PIN;
sendI2C(data);

// Write high
sendI2C((byte)NOT_WRITE_PIN);

// Sending a digit (will send Write to low)
unsigned long digit = num;
for (byte i = 0; i < 8; i++){
writeDigit(digit % 10);
digit /= 10;
}
}

void writeDigit(byte b)
{
// Using CODEB
// The digital point allways off (it's inverted)
byte data = ID7_PIN;
// ID0 to ID3 with the number to display
if (B00000001 & b)
data |= ID0_PIN;
if (B00000010 & b)
data |= ID1_PIN;
if (B00000100 & b)
data |= ID2_PIN;
if (B00001000 & b)
data |= ID3_PIN;

sendI2C(data);

// Leave Write HIGH for the next writing
sendI2C((byte)NOT_WRITE_PIN);

}