New LiquidCrystal library - LCD library

sixeyes:
That is awesome

Thanks for that.
Try it out and let me know how it behaves with your LCD/s. I've got only a few models to try it on.

Very nice. I like it.
It now compiles and runs using existing Liquidcrystal code on 0022, and 1.0rc2
I've tested this on this shield: Keypad LCD Shield V2.0 - emartee.com
Unfortunately, I can't tell you which LCD it is as it is soldered on the shield.

I think the challenge will be for things like I2c and trying to be compatible with other i2c
implementations and to not break other lcd libraries.
For example, your i2c backpack and backpacks
like this: i2c / SPI character LCD backpack - STEMMA QT / Qwiic : ID 292 : $9.95 : Adafruit Industries, Unique & fun DIY electronics and kits
or this: Error, Electronic & Electronics Components Depot United States
don't use the same pinout for the bits from the i2c register output going into the hd44780 interface.
Many Arduino users are quite hardware ignorant and may really struggle with these types of differences.
I'm not sure how to best support these different hd44780 I2C backpacks and yet make it easy for the Arduino user.
Its a real tough challenge.

Some vendors use a separate lcd library for their board. This is quite simple and avoids any compatibility
or library collisions as it is a separate library and the user simply copies
over the .cpp and .h files into the libraries directory and thats it. They include the vendors "xxx_i2c.h" header
and then it all works just like the LiquidCrystal library.

While adafruit has a seperate custom i2c lcd library for their backpack: https://github.com/adafruit/MCP23008-library
Adafruit also now has released a multi headed LiquidCrystal library that works for both 4 bit parallel and their
own i2c or SPI: GitHub - adafruit/STEMMA_LiquidCrystal: Liquid Crystal Library for Arduino
Their implementation is slower than a real i2c implementation as it uses an "arduino like" interface across the i2c. i.e. they
do pinMode(), DigitalWrite() type operations through the i2c to control the lcd pins.
So it does independent i2c operations for setting every single individual hd44780 signal.
It does have the advantage that if they would update their i2c constructor they could configure which
i2c output register bit controls which lcd signal. Today they have it hard coded to their own i2c backpack
but this could allow it work with any backpack.

I like your implementation as it can be made to support any hd4480 backpack.
I think the challenge is how to deal with all these different interfaces when replacing the LiquidCrystal library.
i.e. Your library cannot coexist with the latest AdaFruit library. Both want to replace the LiquidCrystal library
and both have hardcoded i2c implementations that are incompatible with each other.

From an end users perspective, it gets tricky at best and more than likely frustrating with the current state
of libraries in that if you have different i2c backpacks, it may or may not be possible to control them with just
sketch changes. You may have to replace LiquidCrystal libraries between sketch builds for the different
boards. And that is pretty ugly/painful.

As it is today, the i2c lcd libraries look a bit problematic and painful to use if you have more than a single
vendor's i2c lcd backpack.

I think with your library model, it can be resolved.
It could be through either adding additional supported "boards", or by updating the i2c constructors to support
the ability to configure which hd44780 pin is connected to which i2c register bit. kind of the way the
existing LiquidCrystal constructors work with the 4 bit interface. In the case of i2c, the i2c register bit number
would be specified rather than an arduino pin number.
I think the latter, while not the absolute fastest way to run the interface, offers the greatest flexibility and
ease of use for the users especially those that want/need to support different i2c/spi lcd backpacks
that are wired up differently.


On performance tuning, I would caution you to be careful when using some of the techniques that the code is
currently using. For example, in functions like pulseEnable() some of the delays have been removed.
This is making an assumption about a particular API implementation [ digitalWrite() ] that
offers no such guarantee. ie. digital does not promise to take any particular amount of time.
So it theoretically could take zero time. While 0 really isn't reality, much smaller than 1us or even
the needed 450ns is a potential reality. Consider if this library is to be ported to say the chipKit or the Maple
then you may have issues. Also if the arduino code is compiled differently by compiling all the code together
vs seperately and building core libraries you can get optimizations that can dramatically affect
the timing. If you replace the poor Arduino core code with smarter code, then it could
potentially optimize the digitalWrite() functions down to single cycle instructions which on a standard arduino board
is only 62.5ns

If you still want to go down the path of trying to squeeze out the performance by removing or reducing delays like this,
I'd encourage using some sort of ifdef that can turn them back on to insert the proper delays for environments
that have faster digitalWrite() implementations.
BTW, if you want to step just a bit outside of the Arduino core library functions, there are other delay functions
that can offer shorter than 1us delays with much better accuracy than the Arduino delayMicroseconds() function
which has terrible accuracy.

--- bill

Hi Bill,

thanks again for your feedback. It seams that you are reading my mind... I was just polishing up the I2C class, with new constructors to enable configuration of the control and data pins of the LCD. This is something that I had in mind for the I2C class to enable people to configure the pin mapping of their "backpacks".

Should the backpack, controller chip differ from what is already there, it is a simple question of just extending the class hierarchy.

As far as optimizations are concerned, I am not too concerned about a different digitalWrite coming into the scene, in the sense that people would have to rewrite parts of the SW. However, I do think it is a nice have compilation flag to compile in our out the FAST mode doesn't hurt at all. In those lines, I was also going to write a fast digital write to find the limits of the LCD. How much more will I be able to squeeze out? Perhaps a few usecs per write operation, but it will be very specific to AVRs.

Keep you all posted, stay tuned for version 1.1.3 of the library.

fm:
As far as optimizations are concerned, I am not too concerned about a different digitalWrite coming into the scene, in the sense that people would have to rewrite parts of the SW.

Well, in some cases that re-writing has already been done.
There are already Arduino compatible s/w core libraries for the Maple and the Chipkit
which use 32 bit ARMs running at 60-80+mhz.
I have not looked at their digitalWrite() implementation but the processor is already
5-6 times faster. So even if they used the same crappy Arduino core code digitalWrite()
implementation, they would likely already be sub microsecond for digitalWrite() given
that the current digitalWrite() on a 16Mhz AVR is about 4us.

Paul's Teensy for example is AVR based but does not use the same core code.
It's implementation is much better and much faster that the stock Arduino core

  • in some cases it can reduce to a single clock cycle for digitalWrite().
    The Arduino team refuses to use it.

There is also the digitalwritefast project:
http://code.google.com/p/digitalwritefast/
(not the way I'd do it, and not the way the Paul did it for Teensy) but it does
already exist today.

The method used in the dwf project gives you very fast i/o operations when all the constants
are known at compile time. This method of optimization is quite simple and a little too simple minded.
While it works great for a sketch where all parameters can be known at compile time,
for a library that allows configuration of pins, this would be of no benefit since it would revert
back to the existing digitalWrite().

What really needs to be done is to throw out the current digitalWrite() implementation
and re-write how digitalWrite() works so it not only does the same things that
dwf is doine but also reduces the number of lookups since much
of the information being looked up is known at compile time.
And that is what Paul's teensy core code does.

If the Arduino team hadn't defined its interface using naked constants (i.e used #defines like DPIN_0 for digital 0 etc
instead of allowing naked constants like 0 or 1 etc)
many other things could be done to speed up the implementation even for pins that are not constants.

BTW, you are looking for additional support of your library, I'd bet you'd be greeted fairly well by
the chipKit and Maple guys as they are more open than Arduino and are also looking to beef up their
library support.

--- bill

Hi Bill,

just uploaded the last changes to the LCD library. I haven't released it as such, but uploaded the change set to the main repo. This is what will become V 1.1.3.

Main highlights:

  • Configuration of I2C extender pin mapping (with a very low hit on performance - less than 0,8%).
  • Configurable FAST_MODE, i.e. setting the control line pulse duration to be optimized or not.

I have also seen that the library, even without the FAST_MODE active is 2x more performant than the original LiquidCrystal.

If you are interested is in the regular place, but download it through the repo, i.e. (get source).

After a weeks testing I be releasing it.

I have also posted the library to the Maple team, see what they say/think.

Very cool. I've give it a try.
--- bill

I have several questions on the backlight stuff.
It appears that your board uses I2C for the data and control lines but uses a dedicated pin for the backlight control.
Other boards use a pin off the i2c i/o expander to control the backlight.
I see some i2c backlight control code but it looks like it doesn't do anything yet.
It also looked like the i2c backlight masks in LiquidCrystal_I2C.h were assuming an active low signal
from the expander to the backlight would turn it on.
Is this is mid progress?
Is there a solder pad on your board to enable backlight control from the i2c output register instead
of having to use a dedicated pin? (yes no pwm but it saves an AVR pin since on/off controlcould be done through i2c)

The backlight control is challenging, since it looks like it can potentially be
done in multiple ways and can potentially be an active high or active low signal.
Michael and I punted on backlight control so far in the glcd library so I'm very interested
in seeing how this works out.

I think it would be really nice if the backlight control were a function in the base lcd class
so that it could work across all the interfaces rather than just the devices that add it in
as a user expansion.

The tricky part is how to configure it.
What makes it particularly difficult is all the combinations particularly on i2c.
Since the constructors must be able to distinguish between using an AVR pin and a bit
in the i/o expander.
For full flexibility the code also has to know whether the backlight control is active high or active low.
And then there is PWM control.

I'm not sure about the initialization but what about using an interface that is slightly different from
the display() nodisplay() interface for backlight control?
What about using backlight(pwmvalue) as well as backlight() and nobacklight()
The interface can be used for pwm as well as non pwm.
That way people can turn it on and off and try to use pwm on all devices and those
devices that don't support pwm simply turn it on if the pwm value is non zero.
Just an idea....

The magic is figuring out how to configure the pin.
It seems pretty easy to handle the active low stuff.
One possibility is If the constructor is configured to take integers (int or int8_t vs uint8_t) then
you could use a negative value to represent negative logic (active low) signal
at least on the backlight pin.

I would think that when using 4 bit mode that the backlight pin (if specified) is always an Arduino pin.
The tricky part is how to select between an Arduino Pin and a i/o expander bit when i2c is used.
When using i2c I would think that the default would be that the backlight pin is an i2c expander bit
rather than a Arduino pin.

Maybe it could be handled with a macro so that the user simply specifies his intension in the constructor
and the macro ORs in some magic value to help determine which is which.

Examples:

LiquidCrystal_I2C lcd(lcd_Addr, En, Rw, Rs, d0,  d1, d2, d3,  iobit);
LiquidCrystal_I2C lcd(lcd_Addr, En, Rw, Rs, d0,  d1, d2, d3,  BLARDUINO(arduino_blpin));
LiquidCrystal_I2C lcd(lcd_Addr, En, Rw, Rs, d0,  d1, d2, d3,  -iobit); // negative logic
LiquidCrystal_I2C lcd(lcd_Addr, En, Rw, Rs, d0,  d1, d2, d3,  -BLARDUINO(arduino_blpin)); // negative logic

LiquidCrystal lcd(12, 11, 5, 4, 3, 2, arduino_blpin);
LiquidCrystal lcd(12, 11, 5, 4, 3, 2, BLADRUINO(arduino_blpin)); // maybe either or both for consistency?

Where BLARDUINO() is something like (avoid using integer sign bit)

#define BLARDUINO(x) (x|_BV(14))

Maybe even specify the backlight pin type in all cases for consistency.

just a thought.


Another area that may need some attention is the RW line in i2c mode.
Some backpacks like the adafruit don't use the RW line.

--- bill

Hi Bill,

I need to give it a thought. For the I2C driver the intention was to have it in the constructor, considering it as an integral part of the LCD. However, only for those that are part of the backpack. This would be like a specialized method of that class.

Trying to control the backlight is a source of a lot of discussion, in the sense that: do you consider the backlight as part of the LCD or not? When you control them using a 4 bit interface, part of is it just another pin?

Anyway, I will go through your comments and give it a thought. I am sure that you chaps have given it a lot more thought than I have.

As per the RW I did give it a though, this being the main reason for not publishing the library as such. In the previous version you just defined the RW to 0x0 and bingo. Here, I need to overload the value so it is not used as a pin.

In any case, right now is not a big deal since in the library it is not used (only in the constructor) and would work.

nice work!

I have only quickly looked at the code. The standard LiquidCrystal initializes the display when you declare the LiquidCrystal object; you can dispense with the begin() statement if you have the size display it assumes. I don't see that in your code.

When Paul Stoffregen was helping me with optimizing the LiquidCrystal440 line of this we eventually eliminated the 8 bit mode internally (we kept the API but ignored 4 of the pins). We did that because when we were testing the LCD's busy flag, we needed to set pinMode on all the data pins twice. So the fastest modes of writing to the display (using the busy flag) were 4 bit rather than 8 bit. The 8 bit mode is a little faster if you don't check the busy flag, but it isn't a big difference. see http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1264823411

you can dispense with the begin() statement if you have the size display it assumes.

That's a big IF. For some unknown reason the default is for a true 16x1 display, the kind with an auxiliary driver, probably one of the rarest configurations currently available.

Don

Hi chaps,

thanks for your kind comments.

I was more inline with Don's comments when refactoring the code. It is a big "if" assuming that when you create the object the LCD you have in place is a 16x1, they are very rare and I have a big question mark about how people a currently using the library. It could be argued that it would be more appropriate then to have the default configuration to a 16x2 LCD and initialize the LCD with that configuration.

However, when looking at all the "standard" libraries, they all have a two phase initialization: creation and initialization. Could it be done? Well yes. They only pending item would be for those libraries that don't use the regular 4 bit/8 bit interface such as the I2C or even Serial interface. Because I2C and Serial need to be explicitly initialized after the runtime is up. Therefore, for consistency with the entire LCD library hierarchy, all LCDs behave the same way when being created and initialized.

As for speed improvements, one of the main improvements for the 4bit (LiquidCrystal class) is to initialize all the IOs to outputs and assume that is the state of the pins. So, when writing to the LCD there is no need to change the direction every time. If you want to read back from the LCD, only the read operations would change the port direction, read and restore the default IO pin configuration. These operations would incur in a time penalty, but they are hardly every used. Even in the standard library doesn't read back. Just this change gives almost a x2 speed improvement to the LiquidCrystal library.

fm

part of the advantage of testing the busy flag is reliability. When I've worked on displays that weren't able to keep up with the fixed delays in the standard LIquidCrystal, it became clear that waiting for the busy flag greatly increased the reliability--once you get initialization right (initialization can't use the busy flag). I still puzzle over how much faster the code runs with the busy flag testing than I can make it run without that; I can't just try to slowly reduce the delays until it doesn't run and come anywhere close to the speed of testing the busy flag. Here's a quote from the thread I pointed to before:

Speed testing
All of the interface modes go faster than the eye can follow. This version of the software is significantly slower than previous versions when using timed delays. I found an LCD (Axman) that needed longer delays and in the interests of making the code foolproof, I lengthened the delays to make that LCD work. However Paul Stoffregen has significantly speeded up the code when testing the busy flag and so those options run significantly faster than before. I compared the speeds of the different interfaces--writing 80 characters to the screen then 80 blanks and looping through that 20 times. The results on a Mega are:
Axman 4 data pins no RW 1349 milliseconds | nonAxman 1349
Axman 4 data pins + RW 565 milliseconds | nonAxman 468
Axman 8 data pins no RW 1314 milliseconds | nonAxman 1314
Axman 8 data pins + RW 520 milliseconds | nonAxman 500
Axman 4 pins + user busy 369 milliseconds | nonAxman 316

I also have a Teensy++2.0 board. One of the interesting things about that board is that the software that comes with it includes considerable optimization of digitalRead, digitalWrite etc. The board runs at 16 megaHz, just like the Mega, but speeding up those commands results in an impressive change in the benchmarks:
Axman 4 data pins no RW 1207 milliseconds | nonAxman 1207
Axman 4 data pins + RW 327 milliseconds | nonAxman 219
Axman 8 data pins no RW 1212 milliseconds | nonAxman 1212
Axman 8 data pins + RW 361 milliseconds | nonAxman 296
Axman 4 pins + user busy 241 milliseconds | nonAxman 189

I realized as I was bicycling to work that the code base you started from has a long delay between nibbles when it uses the 4 bit mode. Don Weiman (floresta) pointed out that that is unnecessary. 1 usec is adequate there. I don't have your code here but that change is worth 30-40% improvement in speed with the 4 bit mode, if I recall.

1 usec is adequate there.

Correction - 0 usec is adequate there.

The reason for the time delays (or reading the busy flag) is to allow the LCD controller time to complete acting on a command before you send it a new command. In the four bit mode after it has received the first nibble it does not start doing anything, it just sits there waiting for the second nibble. There's no need for any kind of delay because there is nothing going on that needs time to be completed.

Don

The objective of the library was NOT to make it faster than the current LiquidCrystal library.
The main objective was to have an extendable library that could: be compatible with what there is, be extendable, be the same library independently of the way you "talk" to the LCD.

This is achieved by having a "two layer" design: the first layer deals with the LCD control (LCD commands), in essence everything common to the particular LCD family, where as the second layer deals with how you talk to the LCD (the specifics of each connection).

By just having a LCD library class hierarchy, with one abstract common class, developers are able to extend it and users are able to control the LCDs with just one common library . If you come up with a new communication mechanism (for example SPI, which is currently not supported), then simply extend the library. If users now use a pointer to the base LCD abstract class, their code would be able to support any type of LCD assembled into their project just by changing the constructor. The rest of the code would be exactly the same.

There are two main areas that I have modified to get the speed enhancemnets:

  1. Remove the IO remaping from imput to output on every single 4/8 bit write transaction.
    Since there is no part in the code that reads from the LCD, you simply configure the IOs during initialization and then simply write.

  2. I have removed the 1us delay + 100us delay (in fast mode) from the pulse enable toggling.
    This is based on the fact that the slow digitalWrites take longer than what the display needs. Since this could potentially be a problem for faster IO routines, I have added a compilation flag that enables it or disables it, i.e. active wait or nothing. Thanks to Bob for sharing his thoughts here.

So as a summary, the LCD library (including virtualization overhead) is 3.25 times faster
than the stock LiquidCrystal library.
The main contributor to these figures is 1, where the speed improvement is 2x.

Can it be optimised even further, yes by using a faster digitalWrite operation and correctly
adjusting the times. Will it be substantially faster? Surely a bit more.

As a conclusion I would like to highlight the LCD library architecture/design more than its performance. To have just one library to control LCDs as opposed to having libraries cropping up all over the place like mashrooms (to control and do exactly the same thing),
that is the main highlight. Extention not replacement nor reinvention. Performance is good, its great since, the MCU can can do more than just control the LCD but not the main KPI of this library concept. Anyway, this is just my thought when I designed this driver.

Current lines of activity:

  • backlight support - under consideration.
  • making the I2C be more generic by having a similar design as the LCD, i.e. a control "layer"
    and an "access" layer, i.e. the IO expansion chip.
  • improve documentation
  • SPI LiquidCrystal support
  • Serial support - under consideration since they tend to be more
  • make it usable to other platforms.

Sorry for the length of the post.

jrraines:
nice work!

I have only quickly looked at the code. The standard LiquidCrystal initializes the display when you declare the LiquidCrystal object; you can dispense with the begin() statement if you have the size display it assumes. I don't see that in your code.

Well, it is interesting that the LIquidCrystal library did not follow the API recommendations in the LCD API:

http://www.arduino.cc/playground/Code/LCDAPI

If that were followed, then there would be no begin() and there would be an init() function
which does not take any parameters.
This would have pushed the LCD dimension information back into the constructor, which is where it
belongs IMHO.

I mean its not like the dimensions of an LCD are going to change so in my mind it would have made
much more sense to put it in with the constructor.
But the issue now is that is not backward compatible.
The new constructor with additional dimension parameters could be added to dispense with the begin()
call for those that use the new constructor. However to remain compatible with the existing LiquidCrystal
sketches, you still have to support begin().

-- bill

fm,
I totally agree with your approach, which is one I often use.
First design it, then make it work. Then, if needed, make it work fast.

fm:
Current lines of activity:

  • backlight support - under consideration.
  • making the I2C be more generic by having a similar design as the LCD, i.e. a control "layer"
    and an "access" layer, i.e. the IO expansion chip.
  • improve documentation
  • SPI LiquidCrystal support
  • Serial support - under consideration since they tend to be more
  • make it usable to other platforms.

For backlight support, I kind of like the API definition in the LCD API:
http://www.arduino.cc/playground/Code/LCDAPI

I think as far as serial support goes, I think that will depend on what this library is meant to be.
Is is an intelligent LCD library or is a hd44780 library?

Today it is really more of a hd44780 library with a very thin LCD API on top of it
(as defined by the LCD API mentioned above)
I think that a more generic smarter API LCD library will require more layers that would sit on top
of this type of library. So if this library is a lower level lcd library for "dumb" devices,
it would require some layers above it to translate from the higher level interface to this interface.

--- bill

Well, it is interesting that the LIquidCrystal library did not follow the API recommendations in the LCD API:

The LCDAPI in the playground is a proposal brought forth by one user around the time of Arduino v0014 or so. The LiquidCrystal library was started long before that time and the likelyhood of getting any major change made is probably next to impossible. As far as I know the API recommendations are not 'official' cast in stone Arduino policy. As a matter of fact they actually fall in the category of NIH and may never be recognized no matter how logical they are.

Don

  1. Remove the IO remaping from imput to output on every single 4/8 bit write transaction.
    Since there is no part in the code that reads from the LCD, you simply configure the IOs during initialization and then simply write.

Maybe I am misinterpreting this but it appears to me that:

(1) You are assuming that the pins used by the LCD will not be used for anything else. That is really not a good assumption since there are applications involving a keypad and LCD for example that share the data lines.

(2) You are assuming that every other routine that may use these pins is going to return them to the configuration that they found the pins in.

(3) You are assuming that every other routine that may use these pins will not make the same assumption.

Don

[edit]
bill,
[/edit]

For backlight support I was more thinking in the lines of "adding" or "attaching" a backlight control method. This would allow catering for LCDs that don't have a backlight at all. In any case, it is still under the thinking process.

Yes, I have seen the LCD API, as a matter of fact the last contribution is from me:

ST7036 Lib	Generic ST7036 LCD controller	Y	i2c

Again, it is a generic ST7036 library following the LCD API 1.0. and a subclass for a particular NHD LCD. The problem I find with this is that it is an API, i.e. a contract you must comply with when writing a compliant library LCD API library. More than a real driver class hierarchy.

The LCD Library or (New LiquidCrystal library), is meant to be similar to what there is with the current LiquidCrystal library: an LCD access driver to control LCDs based on the hd44780. Extendable, at least as performant as what there is, independent of the interface mechanism, ... The LCD library, nor the LiquidCrystal Library pretend to be an intelligent Display package. This would be something built on top, but with a common LCD driver hierarchy.

So for example, if you wanted to add a character based MMI, you would only have to use a reference to the LCD base class and use it throughout your code. During MMI construction you would simply pass on a reference to the particular LCD you are using. Therefore, any LCD with HD44780 would just use it. Another example could be a scroll widget, bargraph widget, etc. More or less some of the "gadgets" that I use on my projects. Another example is a remote LCD control.

From my point of view and IMHO, ideally there should be a base abstract LCD class that supports any type of character based LCD regardless of their driver, for HD44780 and compatible LCDs, ST7036, etc. Each LCD controller driver, would simply be a specialised subclasses of the LCD with different access mechanism (or maybe not, access could be aggregated). This would be the only way to have a real generic MMI, a pool of generic LCD widgets, a pool of generic LCD controllers, etc. Kind of a visual SDK or a generic building block.

An alternative is to use the poor man's inheritance: API contract that everybody adopts and you just pool in the particular library that your project uses.

The problem is that all the methods in this hypothetical LCD class would have to be virtual and hence would consume a lot of memory and a complete hierarchy would end up eating all the available memory. The library I posted in the LCD API uses this concept where the LCD is a virtual class. I left the class definition there without having the ST7036 inherit from it because of the amount of memory used.

As far as serial is concerned I have it in the cooler because I haven't seen a single module that acts as a "proxy" between the serial port and the LCD. They all have their own proprietary protocol on top.

Just some thoughts.