Go Down

Topic: Connecting a graphical LCD via a I2C using a 16-bit port expander (Read 14 times) previous topic - next topic

Nick Gammon

I recently purchased a 128x64 bit graphical LCD from Adafruit, and decided to avoid using up most of the free ports on the Arduino, by connecting it via I2C, using a MCP23017 16-bit I/O expander chip.

This is a photo of the results:



Note that only four wires go from the LCD screen to the Arduino.

The cost is pretty minor - the expander chip sells for around $US 1.20 to $US 2.

More details, how to wire it up, and library code is available here:

http://www.gammon.com.au/forum/?id=10940

The library has fairly basic features, however you can draw text, clear rectangles to black or white, draw boxes, and "blit" in predefined images.

The nice thing about using I2C is that almost all of your pinouts on the Arduino are still free for connecting up to whatever it is you want to show on the LCD screen. And indeed since I2C can be shared, you could also use the I2C for some other device as well.

I'm no expert on making circuit boards, but it seems to me that this would be a useful project for a "backpack" board. It would basically only need the expander chip, a row of 20 holes for connecting to the LCD, and the contrast pot on it somewhere. I used a couple of resistors to pull the reset lines high, but they could probably just be straight wires. Any advice on how to go about getting such a board made would be welcome.

pocketscience

Nice! I bought the same screen but with the serial backpack. The library for that is a bag of nails, so I think I might just remove the backpack and use your setup instead!


Cheers,
G.
Is life really that serious...??!

bperrybap

One thing you might want to look at for dramatically enhancing the performance.
is to create a write-through cache.
I have experimented with this in the latest ks0108 glcd library and it makes
a huge difference for typical usage cases.
For a 128x64 display it will consume 1k of RAM which granted is a half of what is
on a 328 but for some applications it is worth it and other AVRs have additional
memory so it isn't that bad.

In the glcd library it was less than 10 lines of code to do it
(around 13 lines including ifdefs to turn it on/off)

To do this, you create a memory buffer in AVR memory that mirrors the glcd display memory.
So every time you write to the glcd you also write to the AVR memory buffer.
For reads you never read the glcd hardware but instead read the data from the AVR memory buffer.

The code to do this is quite small (just a few lines).

I really like that chip, so I may also look at adding support for it
into the glcd library. It wouldn't be that difficult to add.

--- bill


Nick Gammon

Thanks for the comments! Much appreciated.

I tried adding the write-through cache, which I had initially resisted as it was gobbling up a lot of RAM, but admittedly you might have it free.

My measured figures certainly showed a big performance improvement, particularly for large pixel-based operations (eg. filling a large box). It was approximately twice as fast (I'm not sure I would call it "huge" but maybe that's a matter of opinion).

For example, executing this line:

Code: [Select]
  lcd.fillRect (20, 20, 50, 50, 1);



  • Without cache: 4.686 seconds

  • With cache: 2.586 seconds



Maybe I didn't do it as efficiently as possible. I understand caching, but since I was allowing for multiple displays the cache was a member variable of the lcd class, so accessing it was a couple of dereferences.

I would be more excited if the time went from 4 seconds to 0.4 seconds. :-)

In terms of speed, the original is really quite fast for something like showing a bar graph of volume, temperature, etc.

For example, this test code here:

Code: [Select]
int sensorPin = A0;    // select the input pin for the potentiometer
char buf [20];

void loop ()
{
  // read the value from the sensor:
  int sensorValue = analogRead(sensorPin);   

  // draw bar
  lcd.clear (0, 16, sensorValue / 10, 23, 0xFF); 
  lcd.clear (sensorValue / 10 + 1, 16, 127, 23, 0); 
  lcd.gotoxy (0, 32);
  lcd.clear (0, 32, 127, 39);
  sprintf (buf, "Value: %i", sensorValue);
  lcd.string (buf); 
 
  delay (100);
}  // end of loop


This read (random noise) from A0 and displayed a bar using the (fast) clear routine. It also showed the value as a number. This ran so fast it flickered annoyingly, hence the 100 ms loop to slow it down a bit.

So I think a bit of careful screen layout, allowing for the more efficient use of boxes aligned on vertical 8-pixel boundaries, is what really speeds things up. Basically you reduce having to do 8 writes to the LCD screen down to one write, which is the big time-saver.

I didn't really emphasise it before, but with the I2C approach you could easily enough have multiple LCD screens, all connected to the same 2 pins on the Arduino. So for a project that needed to show a lot of data, that could be ideal. Of course, they are sharing the same data bus so throughput would be down a bit, but if the important thing to you is to show a lot of data, rather than updating it really quickly, that could be a nice solution.

robtillaart

Quote

lcd.fillRect (20, 20, 50, 50, 1);  <== thats 900 pixels
    * Without cache: 4.686 seconds
    * With cache: 2.586 seconds


Are those really seconds? or should that be millis?
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Go Up