Go Down

Topic: New LiquidCrystal library - LCD library (Read 67212 times) previous topic - next topic


Nov 10, 2011, 10:10 pm Last Edit: Nov 10, 2011, 11:01 pm by fm Reason: 1

I never did like using libraries.  Now I remember why!


I understand you!  :) First hand example: we were working on a software defined radio and we purchased a third party IP decoder, from a well know company. It turned out to be ...p and ended up spending a huge amount of time figuring out what on earth was going on. Debugging an FPGA is not very nice, nor a highly productive task. Button line, IP to the bin and rework it from scratch.

However, libraries are necessary, depending on the ambition of the project. If it is too big, it is impossible to assume a do it all approach. I use them and tend to understand what they do and how they do it before hand.


If your application uses a device (IO pin, UART, ...) it is the application that has to arbitrate how to use that shared resource (because your application made it a shared device). You can't assume that a dumb driver takes on that responsibility. This is common good practice in engineering and software.
So in the example that you have made about a LCD sharing IO pins with a keypad; it is the application the one that would have to use something to reconfigure the IOs before using those pins by the other device. You can't assume that in the driver. Assuming such thing in an application is not a very good policy at all. This becomes more obvious when you have an OS in between.


I can see Don's point.
I had not considered that the existing LiquidCrystal library actually reprograms the data pins to be
outputs just before setting their output state, which does allow sharing the data pins with other functions.
This new "functionality" was added when Limor Frieds LiquidCrystal library was pulled into arduino back in July 2009.
The LiquidCrystal library only reprograms the data lines  to outputs prior to pushing out the LCD data line bits.
It does not reset any of the control line's input/output state.
Just speculation on my part but I'm guessing that this functionality occurred by accident rather than by design
since, as it is, only the data lines that are reprogrammed and not the control lines (Although you can't share E with anything).
I'm assuming that at some point the BUSY status was polled and that required the lines be turned around and
when the BUSY status code went away, the code to set the data lines to outputs was left in.
Total speculation on my part but otherwise why not set the state for the control lines as well as the data lines
to allow those to be shared as well (obviously the E line can never be shared with anything)?

All that said, fm, it is possible to be a bit flexible in this area. In fact it is something to really consider as
it may be necessary to maintain full 100% backward compatibility. There may actually be some existing
code floating around out there that takes advantage of being able to use the data lines for other purposes
when not used by the LCD.

And while technically it should be the application that should have to deal with the sharing of pins between libraries,
the issue is that in the general case the application really may not know how the pins are used and what state they need
to be returned to. (This is why sharing resources like pins between libraries (drivers) is usually a bad thing).

So the dilemma becomes that the LiquidCrystal library does allow sharing of the data lines and
to provide 100% backward compatibility, this new library would need to do it too.
But how much code really takes advantage of this?
Don, have you ever used this capability or know of any sketches that use it?

Even without this you can share the data lines and the control lines (with a separate E line for each LCD)
and run multiple LCDs in 4 bit mode.

Maybe just make a note of it and not support it until somebody that uses it complains?
Maybe have an ifdef to allow pin sharing?
(for 4 bit mode to provide 100% backward compatibility with LiquidCrystal)

Not sure what the answer is as pin sharing can get really messy
if pin sharing is to work on all interfaces not just the 4 bit.

If it were up to me, I'd put in an ifdef in the 4 bit code to work like the LiquidCrystal code and
then leave it off by default. Then if somebody complains, tell them how to turn it on.
If many people start to complain then turn it back on by default.

--- bill



Rel. V 1.1.3 of the LCD library available:

  • Documentation improvements - on going.

  • Improved code comments and added some corrections

  • Created new constructors for the I2C interface to allow for full pin mapping on any PCF8574.

  • All project documentation is now in HTML

  • Conditional compilation to introduce delays should faster digitalWrite be used.

Current library performance:
3.25 faster than de LiquidCrystal library.

Project wiki - https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home
Project donwload - https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads

Release 1.1.4 will include a new backlight class, to control and attach to any LCD.

Last weekend I was working on another project (https://bitbucket.org/fmalpartida/vinciduino/wiki/Home), and didn't have time go through some of the improvements I was hoping to do.

@bill - I will meditate your comments, but unfortunately I don't like encouraging bad engineering practices. People that may use this library can be novels and I wouldn't like to leave a door open to introduce bad development habits as the start. If they learn, might as well learn right the first time.

I also don't think that a lot of people are using this "feature" of the library. What is the adoption state of this "feature"? For the very few that use it, they can start their corrections and learn something on the way too. What will this model be when you start using a RTOS (something I use and is surely coming on the the ARM line).

By the way, the continuos change of pin direction was introduced because the original work poled for "operation complete". I am more inclined to think that changes were introduced and then removed and left some forgotten code on the way.

Currently more than 100 people are using it and no complains yet. I also think that the Maple team have a copy of it as I sent them the links.


The latest library does not work with my lcd when using 4bit mode.
I'll try to chase this down but so far it just sends garbled characters to the display.

--- bill


Found it.
In LCD.cpp the delays for clear() and home() were reduced
from 2000 to 1500 us.
This shorter delay does not work on my display.
(It is soldered to a shield so I have no idea the manufacturer)

1600 does work on my display, but I'm not sure how to ever pick a value for this.
I found one 44780 data sheet that showed clear and home commands to take 1.52ms
and every other command or data write to be 38us.
So 1500 for that display would be just a bit too short as well.
The 38us seems to explain the 37us delay after lowering the strobe in
pulseEnable() as there is no need to wait after lowering the enable strobe,
at least not for any type of "settling" anyway.
This delay is actually waiting for the command to complete.
There also is no need for the initial
Code: [Select]
digitalWrite(_enable_pin, LOW);
waitUsec(1) in pulesEnable();

The strobe should already be low.
(It works fine on my lcd with these 2 lines commented out).

A couple of other small things I noticed:
The HelloWorld_4bit.pde sketch includes <Wire.h> which it doesn't need to.
The files LiquidCrystal_I2C.cpp LiquidCrystal_I2C.h and keywords.txt have the execute bit set in
their permissions.
This causes Nautilus on linux to want to run it instead of edit it when you click on it.

This is a bigger issue:

Not sure how to handle this one but the copyright in LiquidCrystal.cpp is
not really correct. The code in that module is largely the same as the original from Arduino
so it is really a derivative work.
Copyrights are mess on Arduino. Their main web page says that the core libraries
are released as "LGPL" : http://www.arduino.cc/en/Main/FAQ
Which is what I believe is their intent.
However, they have failed to meet even the basic the requirements for LGPL by failing to put the LGPL
notices in the source code.

But under the LGPL licensing, since you started with a LGPL library, you don't own the copyright
to the library since this is a derivative work.
See the LGPL 2.1 license for further details: http://www.gnu.org/licenses/lgpl-2.1.html

Overall, I really like it. It's Looking really nice.

--- bill


Hi bill,

I have updated the repo with version 1.1.4 which your comments built in.

I have increased the delay for clear and home to be 1700, so it should cater form most LCDs.

I have also remove the unnecessary setting of the _enable_pin just like the I2C version of the driver. The performance of the library has been bumped up to x3.6. Great!

So, as far as licensing is concerned, if they complain I will change it. There is nothing in their header files that suggests anything. I have maintained the original header of the SR driver, I have no problem in doing the same with the LiquidCrystal.cpp. I may reword a bit the text, though, to highlight the library is derived from the original LiquidCrystal on next release. This one is released following the CC BY-SA.


When I worked on LiquidCrystal I found an LCD that was significantly slower and increased delays to accommodate that. I bought another one just like it out of the same bin at our local Axman store and it had the same timing issues. As I said before, checking the busy flag increases reliability of communication between the display and the Arduino MCU as well as speeding it up. It does not seem reasonable to expect the user to fine tune the various delays. Producing code that worked with 'most' displays was not my goal, and I doubt it is your goal.

I will happily mail you one of the slow displays if you want to try to make your code work reliably with them. PM me if you would like that.

Even one of the displays I bought from Adafruit needed an increase in one of the delays from what was in the initialization code of the standard LiquidCrystal routine. As Don pointed out, increasing the delays at initialization has almost no penalty--it just runs as the code starts up.


Hi hrraines,

thanks for your kind offer.

In the library, I haven't modified the delays during the start-up, as you say, there is no point. That code only executes during initialization and has no time constrains.

The main time constrains come from the operations that are executed on a regular bases such as the pulse to the enable pin. This is based on the fact, that this operation is the one that is used on every write operation to the LCD.

I have maintained the delays for the home and clear commands very close to the original, which I was looking to shorten but ...

I'm still struggling to see how reading the LCD status indication is faster (for all regular write operations) since the direction change and read ops take a fair amount of time. I will give it a try anyway. I can see the usefulness for long operations such as clear and home since a fixed delay is foreseen for the worse case. While the active pole will just wait until the command is complete.

When I say that it should work on most LCDs is that I haven't tested it with all brands of LCDs. I have done testing with 4 different LCD manufacturers, some with the Hitachi chipset, some with a compatible one.


Nov 19, 2011, 03:25 pm Last Edit: Nov 19, 2011, 03:30 pm by floresta Reason: 1
(reply #49) I found one 44780 data sheet that showed clear and home commands to take 1.52ms

(reply #50) I have increased the delay for clear and home to be 1700, so it should cater form most LCDs.

(reply #51) Producing code that worked with 'most' displays was not my goal, and I doubt it is your goal.

The Hitachi HD44780 datasheet specifies 1.52mS -- BUT you must look at the other information in the 'Execution Time' column.

At the top of the column it specifies that the time is specified when the (LCD controller) clock speed is 270 KHz.

At the bottom of the column it shows how to determine the time for other clock speeds.

When you look at the AC Characteristics for the controller, near the end of the datasheet, you will find that the 'Clock oscillation frequency' can legitimately be as low as 190 KHz which is 30% lower than nominal.

This means that the execution time for the clear and home instructions for an LCD controller that is within specifications can legitimately be as long as 2.16mS or 2160uS.

In my opinion you are doing a disservice to the hobby community if you design your software with any delay lower than this value especially since the eBay and Axman type displays that many hobbyists use are likely to be out-of-spec manufacturers rejects.



Hi Don, that is excellent feedback!

The coming release will take those values as inputs for home and clear. The only thing is that the original library is using a 2000us if I recall correctly. Therefore, there is a chance that it will not drive some LCDs.


Hi Don, that is excellent feedback!

The coming release will take those values as inputs for home and clear. The only thing is that the original library is using a 2000us if I recall correctly. Therefore, there is a chance that it will not drive some LCDs.

Their 2000us is really at least 2173us on a 16Mhz AVR because they:
[ Timing for send() and writexbits() to set up the data/control lines]
(assuming current slow Arduino digitalWrite() timing)
- 4us to set RS
- 4us *8 to set the 4 data lines to OUTPUT (they assume data lines can be shared with other things)
- 4us * 8 to set the 4 data lines
So that adds another 68 us.
[then there is the pulseEnable() timing]
- 4us to set enable to LOW (which it already was) using the digitalWrite() function
- 1us blind delay after the above set to LOW
- 100us  inserted an unneeded blind 100us delay after lower the enable strobe.
Their comment said  "commands need > 37us to settle" which makes no sense
but they do need at least 37us here to cover the normal command/data delay since they have
no other delays.

IMHO, this last delay in pulesEnable() should actually be moved to the bottom
of send() since it has nothing to do with the enable strobe and is really
a delay for the command/data writes to the display to complete.

The Arduino delay functions are not very accurate so they all are probably actually slightly longer.

So their "2000us" delay is definitely long enough assuming the current 16mhz Arduino core code environment
and their existing code with all their other delays, some of which were not needed otherwise.
The 2000us command delay may break when the CPU is faster like say in a Maple environment or if the digitalWrite()
is finally re-written in a way that is significantly faster and the lcd is one of the super slow devices.

An interesting test would be to hook up one of these very slow lcds to a Teensy board
and try it since Paul does not use the Arduino supplied digitalWrite() functions so
his are significantly faster.

The code could be updated to actually support polling the BUSY status if a rw pin is handed down.
But my suspicion is that it won't be any faster unless you actually use 8 bit mode
which is a hefty pin requirement.

Maybe the simplest is to have some kind ifdef in the LiquidCrystal header that can be
turned on by those more advanced users to change the timing to a "typical"/"standard" hd44780
timing rather than the default of a worst case lcd.
(I'm assuming that the code will ship with delays set for worst case timing)

On a related performance speed up note:

In 4 bit mode there are 2 pulseEnables() so that adds an additional
105us in their current code.
(I didn't count that in the 2173us) as it may not matter since the command
is probably starting once the first strobe of the 4 bits is sent.
The significance of this is that they are also adding another one of their
goofy "settling" delays between nibbles.
If I remember correctly, there is no need for any delay
after a strobe of the first nibble in 4 bit mode, so if the 37us command/data delay was removed from pulseEnable()
and moved to the bottom of send() as mentioned above, that delay would only happen
once per command/data write vs twice.
This would be a significant speed up.

--- bill


Nov 19, 2011, 06:54 pm Last Edit: Nov 19, 2011, 07:02 pm by bperrybap Reason: 1
So for clarity this is what I'm talking about for send() and pulseEnable()

Code: [Select]
void LiquidCrystal::send(uint8_t value, uint8_t mode)
  digitalWrite( _rs_pin, mode );
  // if there is a RW pin indicated, set it low to Write
  // ---------------------------------------------------
  if (_rw_pin != 255)
     digitalWrite(_rw_pin, LOW);
  if (_displayfunction & LCD_8BITMODE)
     write4bits ( value >> 4 );
     write4bits ( value );
  waitUsec(37);         // commands need > 37us to settle
void LiquidCrystal::pulseEnable(void)
  digitalWrite(_enable_pin, HIGH);
  waitUsec(1);          // enable pulse must be > 450ns
  digitalWrite(_enable_pin, LOW);

I have actually tested this on my lcd so I know it does work.

And if you require that rw cannot be shared, you could move the setting
of the rw line up in begin() rather than do it on every single send()

--- bill

I guess technically this 37us command/write delay should actually be in LCD.cpp
since that is where commands and data writes are really handled.
So maybe the delay should really be down in LCD::comand() and LCD::write() ???


Hi Bill,

it was very clear on your first post, I have already tested it and it works very well. I mist that one completely, great that more people are contributing. Thanks for the suggestion.

Remember that the LiquidCrystal.h has now a FAST_MODE switch for the delay routine. Therefore, if FAST_MODE is defined those waits will have no effect at all.

I will also roll this out on version 1.1.5 with the LCD backlight driver.


Very nice.
One other thing that needs to be looked at for LCD.cpp is the row offset table.
I believe that there are some issues related to this since there is only one table
and I believe that Don has made comments
that the offsets are different when using different geometries and the begin() code does not look
at the geometry.
Maybe he will chime in.

I just noticed that LCD:setcursor() has not been updated to
fix a bug.
It needs to be:
Code: [Select]
    if ( row >= _numlines )
      row = _numlines-1;    // rows start at 0

to prevent walking off the end of the row_offsets[] table.

--- bill


I'll make a note on the issue tracker for this. Nice one!

Go Up