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.

Please post technical questions on the forum, not by personal message. Thanks!

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 makesa huge difference for typical usage cases.For a 128x64 display it will consume 1k of RAM which granted is a half of what ison a 328 but for some applications it is worth it and other AVRs have additionalmemory 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 itinto the glcd library. It wouldn't be that difficult to add.

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).

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.

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.

Please post technical questions on the forum, not by personal message. Thanks!

The observed time was a bit longer as I didn't measure all the gaps between the actions above, plus there are loops and computations in the C code.

The fact is that commanding display hardware to do things, one pixel at a time, is notoriously slow. The display has 128 * 64 pixels (8192), so anything that has to be done 8192 times, plus stuff like setting up address registers, is going to be slow.

However if you make efficiency compromises, like writing a byte at a time destructively, you can make major speed improvements. As I said before, drawing a bar where you send a byte at a time reduces the time by a factor of 8, and not caring what was there before saves the time taken to query the display for the previous value.

Like I said above, if you stick to rendering headings once, and then smallish amounts of text (like temperature, humidity etc.) more often, the speed is perfectly acceptable.

If you want to have a display that shows 8 lines of text, perhaps some lines between them to divide sections, and a few bar graphs, it will work fine.

For speed, probably the ultimate efficiency would be to pre-render everything into processor RAM, detect which bytes have changed (a second buffer perhaps?) and then just push through changes, a byte at a time, in the optimal order to reduce the need to re-setup the LCD's address registers. But apart from the cost of RAM that would entail, I was aiming for a simple system that anyone could plug in, with minimal overheads both in wiring connections the the Arduino, and memory used by the library.

Please post technical questions on the forum, not by personal message. Thanks!

Some of those timings do seem a bit lengthy.Like setting control lines. 300 uS seems like a long timeto get control lines set.

There are several optimizations that can be done to reducethe overheads by eliminating many of the i/o accesses to the glcd.(I did all the low level code for the ks0108 now glcd library).

You actually don't need to fully render everything in ram to move todoing full byte/page accesses when possible.All you have to do is have some clevercode (that is actually quite complex).Turns out that you only need about 3 bytes and several localstatus variables to ensure full byte accesses when possible.It does get quite hairy/tricky to do any sized font rendering onany boundary and paint the glyphs horizontally rather thanvertically.Things like fills are not too bad.

Another thing that really helps is to keep track of the pageand address for each chip. That way you can take advangeof the auto increment to advance the address for you avoidingthat command when doing multiple horizontal operations (writes) suchas fill operations. Also if you track the page you only have to set itwhen changing rows.

The code can also be made smart enough to detectthe boundaries of things like a fill operation such thatit can combine multiple pixels to build up a page/byte before itis tossed to the physical display. It can also detect page boundariesand full page fills and then do full byte writes (with no reads) when possible rather than doing 8 individual pixel operations.

So you only have to do reads when the updating data is not a full page.

The rule of thumb is always push pages to the lcd horizontally.First handle the fraction pages at the top of a fillthen do full pages, then do the fractional pages along the bottom of the fill.

So in your example oflcd.fillRect(20,20,50,50,1);

You would read the page 3 (pixels 16-23) then update/set pixels20-23 and do that starting at address 20 out to address 50.You would only set the page and address once and auto increment wouldtake care of the rest for that row.Yes you have to read each page 3 as you go across butit turns into a read operation followed by updating the byte locally in ram and then a write. (if caching, then the read is eliminated)Each byte written is updated with the same mask value.Then for the next 3 rows below that, it would reduceto nothing but full writes of the mask value (0xff or 00 depending on color)No reads, no set pages, no set addresses just writes.Yes there is an initial set page and set address to start the rowbut then the next 30 pages (from pixel 20 to 50) would be written as full pages.Then the pixels from 48-50 have to handled just like pixels at 16-23 sincethey are not a full page.

So now instead of 900 set pixel operations,which is set page, set address, read, set address, write

you have 60 reads, (30 at top and 30 at bottom) and 150 writes.And the number of set page and set address operations would alsobe dramatically reduced as you would only need one per rowto start off the row.(actually there is 1 set address for every read as well to back up the addressafter a read to prepare for the write)

Turn on caching and now the operations to the glcd would be:(repeat 5 times) set page, set address, 30 writes

---This is how the glcd library works. It optimizes the operationsto full pages as much as it can.Now I'm curious, how well the glcd library would worksitting on top of a port expander because it already has all the logicto strip out all the unneeded set page/address commands and collapseseverything down to full page accesses when possible to absolutelyminimize the traffic to the glcd module.

My font drawing does in fact make use of the auto increment of the hardware by noting the new X address, so it only has to set the page/address when it crosses the 64-pixel boundary. Similarly for clearing in blocks of 8 pixels.

And you are right that filled rectangles could be optimized into the parts that fall onto 8-pixel vertical boundaries, and the edge cases. Although as you note it gets quite complex.

An interesting compromise would be to keep the library simple, but for the implementer, if they really really needed filled rectangles, and couldn't design them to fit onto 8-pixel boundaries, to combine the operations themselves. For example, do the fast fill and then do a couple of line draws to extend the rectangle vertically as required.

My only concern was that, without knowing the end application, to have a library that was quite complex, when it might not be needed. Although if the linker optimizes that out perhaps no real harm is done.

Anyway, it has been a good learning exercise - I find that if I am forced to find how the hardware works I use it more efficiently.

Quote

Now I'm curious, how well the glcd library would work sitting on top of a port expander ...

Well that would be an interesting exercise. One of the reasons I wrote my own code was because the existing libraries seemed to be heavily reliant on toggling individual pins, whereas using the port expander you had to take bit more of a "batch" approach. Plus I learnt more about port expanders. And LCD modules. And graphics.

Even without much in the way of fancy graphics I am leaning towards upgrading my mini-Adventure game to use the graphics LCD because I get 8 lines of text rather than 4. To see the earlier version:

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

With 8 lines of text, and maybe a mini-map in the corner, it could be quite cool. Well, as a teaching exercise if nothing else.

Just as a quick hardware question - I initially connected the backlight straight to the +5V, but then read somewhere that maybe a series resistor was warranted. Adding in a 220 ohm resistor cuts the backlight down from "quite bright" to "nice and subtle". Do you know if the backlight is intended to be directly connected to the +5V, or do they assume you put a resistor in, like you usually do for LEDs?

Please post technical questions on the forum, not by personal message. Thanks!

With respect to the resistors on the backlight. It depends on the module.Some do need them and some don't. Some will actually burn out instantly withno resistor. So I use a resistor all the time just to be safe.Measure the current and see if falls within the spec(if you can really believe the spec).For several of mine I inserted a larger than necessary resistor because I power my circuits from USB and to keep the power under the USB specthe display needs to be limited more than usual.(most USB ports can supply way more than the spec - which is howall these small external USB hard drives work - they use more thandouble the spec when powering up)

In my experiments for using limiting resistors, I learned that the humandoes not see brightness linearly. What that means is that it sees a changein brightness (light energy level) easier when the level is low rather the brightand that once you achieve a certain level of brightness, the eye reallycan't detect it any more. So at least for my eyes, I can limit the currenta bit and not tell any noticeable difference.

Also some of my circuits are battery powered which benefit from the reducedcurrent. I noticed that often you can power it with half or less than the specand still see things quite well when indoors. - Outdoors, well that isanother matter.

And I'll agree with you that the only real way to learn about this stuff is to reallydig down into it by writing a real project.

I'm impressed with your i/o expander. Neat project.

------------------------------

On the graphics and speed optimization yet keeping things simple,one thing that I've also thought about doing is to make a very slimmeddown "lean and mean" library that uses the CP437 font.(if you haven't seen this google it).With CP437, you could do many graphics like capabilities (horizontal and vertical lines) without actually doing graphics.There are even characters/glyphs that allow doing horizontal and vertical barsfor bar graphs on pixel boundaries.And if you force the font to be 8x8 and alwaysland on row boundaries, you can slam the characters out and removethe reads, even when doing graphic like line operations.With CP437 and a 8x8 font you will not insert any pixel paddingbetween characters (it isn't needed for this type of font).Sure the graphics are limited to some straight line functions, butyou can do quite a bit using only the CP437 font.And it is much faster than plotting lines.Also, it is easy to support a "wide/bold" mode and a "tall" mode. Wide and tall modes simple double each pixel of the font.Wide is really easy as you simply double each page/byte as you go.Tall is a little bit more complicated as you have to stretch the byteinto two bytes and interleave the bits.Its kind of like was don't back in the late 70's and early 80's beforewe really had the ability to set individual pixels.

What would be great would be a library that sat on top of thecp437 font to provide the "graphic" functions.Things to draw rectangles/boarders, horizontal and vertical lines,and bar graphs etc...

For many applications cp437 would work fine.I'm surprised that it isn't more widelyavailable on the character only lcds.

On the graphics and speed optimization yet keeping things simple, one thing that I've also thought about doing is to make a very slimmed down "lean and mean" library that uses the CP437 font. (if you haven't seen this google it).

Ah, *that* font! That brings back fond memories of when I used to work with Turbo Pascal. We used to make quite nice menus with boxes, bars, double lines, tick marks, etc. And the gray boxes could be put to good use too.

Indeed there is absolutely nothing stopping you adding that in, because in the case of my library I had a "bit blit" function that copies characters in groups of 8 pixels to the screen, so each glyph in an 8x8 pixel font would simply be 8 bytes. To build that into the library would take 1024 bytes (8 * 128), which is memory you may or may not want to spare. Perhaps with a define around it?

I wonder if anyone has the CP437 with the pixels written down as bytes? That way at least it could be offered as an option, and it *is* tedious doing it yourself. So far most of the links I have found have shown what the font looks like, or the Unicode equivalent, but not the pixels as a group of 8 bytes.

Please post technical questions on the forum, not by personal message. Thanks!

I've got it if you want it.(Couple of different sizes but 8x8 is easiest to work with)And the format of the data matches the bit order of the glcd pages.I played around with it on ks0108s for a while.PM me if interested.--- bill

Today I received some MCP23S17 chips, which are the SPI equivalent of the MCP23017. With some fairly minor circuit changes (the pins are almost identical), and some code changes, the library is now much faster (at the expense of having to run two more wires):

Clear screen: 62 ms

Draw 96 characters of text: 54 ms

Frame a rectangle: 58 ms

Fill a rectangle: 622 ms

That is without write-through caching enabled. With it enabled the relevant times dropped to:

Frame a rectangle: 48 ms

Fill a rectangle: 432 ms

With the exception of big block fills, it now fills the screen very quickly indeed.

Please post technical questions on the forum, not by personal message. Thanks!