Go Down

Topic: Enhanced LiquidCrystal (Read 48450 times) previous topic - next topic


Mar 08, 2010, 12:57 am Last Edit: Mar 08, 2010, 12:58 am by jrraines Reason: 1
Appendix: A more common pin configuration for the busy test
The more common pin usage for interfacing an LCD is probably that given at
to use the busy test, you will have to connect RW, in this example, to pin 10. To achieve enough board independence for me to test this, download digitalWriteFast (see http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1267553811/0); it will take care of the pin to port issues without ALL of the speed disadvantages of digitalWrite, etc. I can test this code on my mega, whereas I can't test the equivalent port version.
Use this code, then:
Code: [Select]
#include <digitalWriteFast.h>
pinModeFast(10, OUTPUT);
LiquidCrystal lcd(12,11, 5,4,3,2,&checkBusyFlag); //pins from tutorial

#define set_data_pins_to_read pinModeFast(5,INPUT),pinModeFast(4,INPUT),pinModeFast(3,INPUT),pinModeFast                           (2,INPUT);   //data pins 5,4,3,2
#define set_data_pins_to_write
pinModeFast(5,OUTPUT), pinModeFast(4,OUTPUT), pinModeFast(3,OUTPUT), pinModeFast(2,OUTPUT); // digitalWritefast etc gives some board independence
#define set_EN_high digitalWriteFast(11,HIGH);    //port B3 pin 11
#define set_EN_low digitalWriteFast(11,LOW);
#define set_EN2_high digitalWriteFast(11,HIGH);    //port B3 pin 11
#define set_EN2_low digitalWriteFast(11,LOW);
#define set_RW_high digitalWriteFast(10,HIGH);
#define set_RW_low digitalWriteFast(10,LOW);    //port B bit 2 pin10
#define set_RS_high digitalWriteFast(12,HIGH);    //port B bit 4  pin 12
#define set_RS_low digitalWriteFast(12,LOW);
#define read_busy digitalReadFast(2);        //portD2, pin 2

//You DON'T have to modify the subroutine itself:
void checkBusyFlag(int8_t chip) {
uint8_t busy; // = 0x04;
set_RW_high;             //RW to read
if (chip == 0) {  //the if and else can be eliminated if only one hd44780 chip eg 20x4
do {
  busy = read_busy;    // read busy flag
  delayMicroseconds(1);  //pulse the second nibble--discard it;
}while (busy);
} else {
  do {
  busy = read_busy;    // read busy flag
  delayMicroseconds(1);  //pulse the second nibble--discard it;
} while (busy);
set_data_pins_to_write; // data pins to write
set_RW_low;              //RW to write

Finally here is my idea of the equivalent defines for the Mega8 or 168/328 based Arduinos using ports; I expect there are errors here:
Code: [Select]
//LiquidCrystal lcd(12,11, 5,4,3,2,&checkBusyFlag); The is the example configuration from the Arduino tutorial for LiquidCrystal BUT hook RW to pin 10
#define set_data_pins_to_read DDRD&=~0b00111100;   //data pins 5,4,3,2 on Arduino 168,328
#define set_data_pins_to_write DDRD|=0b00111100;    // D5,D4,D3,D2
#define set_EN_high PORTB |= 0b00001000;    //port B3 pin 11
#define set_EN_low PORTB &= ~(1<<3);
#define set_EN2_high PORTB |= (1<<3);    //port B3 pin 11
#define set_EN2_low PORTB &= ~(1<<3);
#define set_RW_high PORTB |= (1<<2);
#define set_RW_low PORTB &= ~(1<<2);    //port B bit 2 pin10
#define set_RS_high PORTB |= (1<<4);    //port B bit 4  pin 12
#define set_RS_low PORTB &= ~(1<<4);
#define read_busy PIND & (1<<2);        //portD2, pin 2


Mar 17, 2010, 09:57 pm Last Edit: Mar 17, 2010, 10:16 pm by gary_white Reason: 1
Okay, I'm confused.  I downloaded the library zip, but I think to the wrong folder.  When I open it, I have two of everything.   I saved it in doc>arduino 18>libraries>liquidcrystal>examples.
What would be the correct folder to put it in and why do I have two two of of everything everything?? Also what do I need to "include" in the program i.e. LiquidCrystal440.h ?


At this point there are 2 possible downloads: if you downloaded LiquidCrystal440.zip, make a new folder for it at doc>arduino 18>libraries>LiquidCrystal440. Then include LiquidCrystal440.h.

LiquidCrystal.zip is just an alias for LiquidCrystal440.zip that I created because of typos earlier in this thread.

LiquidCrystalBusy.zip goes in the doc>arduino 18>libraries>LiquidCrystal folder and replaces the standard library routines there. include LiquidCrystal.h

All versions are called in your code with the name
LiquidCrystal lcd(....

sorry for the confusion


Apr 11, 2010, 02:28 pm Last Edit: Apr 11, 2010, 02:28 pm by bingo2000 Reason: 1
Just wanted to thank jrraines for his libary!!
It works fine on a 27x4 LCD (4Bit mode).

How about putting it to the Playground??? I've started to make modifications by myself before I found your lib.
If you don't know how to do that, I can do it for you ;)


I don't know how to put it on the playground. feel free.. I'm glad to hear that it works on a 27x4; I didn't even know there was such a size but I was pretty sure it worked for all the 2 chip/4 line designs; their addressing will always be 0x0,0x40,0x0,0x40, I think.


My opinion, as of today would be that what would go on the playground would be the older version without the user busy flag stuff. Paul Stoffregen pointed out that just having that complex option available may scare off some users and similar thoughts on my part lead me to keep the older version available for download.



You've got a letter missing on the playground page.
# EnhancedLiquidCrystal for lager LCDs with two Enable (Enable1, Enable2) LiquidCrystal440.zip



I found an LCD that needed longer delays than were in the previous versions of the code. Several of the delays are a lot longer. see:

I have a new version of the code that works with this LCD. It is, of course, slower than the previous version. It is so much slower that it made sense to reinstate the busy flag testing when the rw pin number is specified.



Modifications to LiquidCrystal for the Arduino with callback busy test

I made several modifications to the LiquidCrystal library module from Arduino17:

40x4 LCDs
I added support for an LCD of 4 LInes and 40 characters. It worked with a 27x4 LCD. The 40x4 LCD (and any HD44780 based LCD with between 81 and 160 characters) will have 2 enable lines. To use an LCD with 4 lines and 40 columns you would declare your LiquidCrystal object as:
LiquidCrystal lcd(RS,RW,Enable1,Enable2,  data3,data2,data1,data0); at this time I don't support 8 data lines. (You can pass 255 as the RW item, ground RW and save an Arduino pin.)
Then in the setup function you would call:

When you declare the dimensions of the LCD in your begin call, the LiquidCrystal library remembers how long the lines are. Now when it reaches the end of line 1, text wraps onto line 2 (not line 3 as previously).

Although print has worked properly in the past, println has not. Now the '\r' and '\n' characters are not sent to the screen as though they were visible characters and the '\r' resets the character position to the top of the next line.

16x4 LCDs
The begin statement also correctly positions text at the beginning of the line on 16x4 (and 40x4) LCDs, which were not correctly handled before.

In the past setCursor selected a location in the HD44780's RAM not actually a screen location. If you use any of the commands that shift the display left or right with the previous routines, then setCursor and print, text appears in an unexpected location on the screen. With the new software,  if you call either scrollDisplayLeft() or scrollDisplayRight(), the LiquidCrystal package keeps track of the relationship between RAM and the LCD so that setCursor coordinates are pegged to a specific spot on the screen, rather than a spot in RAM. The sotware does not handle autoScroll, however. Call home() after autoScroll to restore the expected relationship between setCursor and the LCD screen.

Testing the LCD Busy Flag
Previous versions of LiquidCrystal always used timed delays on the Arduino side of the interface to give the LCD module enough time to complete its operation. This version still does that if you tie the RW pin to ground and do not tell LiquidCrystal what that pin number is or pass it the address of a user routine to test the busy flag. If you do specify RW now, however, the software will poll the busy flag on the LCD module. Arduino operations may thus overlap LCD operations and potentially things may go a little faster.
Syntactic Sugar
#include <Streaming.h> from http://arduiniana.org/2009/04/new-streaming-library/
Then you can combine that with an overloading of the () operator in this code. This lets you  specify screen location and chain print commands together by writing: lcd(column,line)<<"a="<<a;
Streaming.h is so efficient you may actually save a few bytes of memory!
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 than LCD work. 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 1491 milliseconds  |  nonAxman 1491
Axman 4 data pins  + RW  774 milliseconds  |  nonAxman  679
Axman 8 data pins no RW 1407 milliseconds  |  nonAxman 1407
Axman 8 data pins  + RW  633 milliseconds  |  nonAxman  620
Axman 4 pins + user busy 510 milliseconds  |  nonAxman  441

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 1289 milliseconds  |  nonAxman 1289
Axman 4 data pins  + RW  369 milliseconds  |  nonAxman  331
Axman 8 data pins no RW 1251 milliseconds  |  nonAxman 1251
Axman 8 data pins  + RW  423 milliseconds  |  nonAxman  394
Axman 4 pins + user busy 361 milliseconds  |  nonAxman  252

Crazy 8 Addressing
16x1 LCDs often have an unusual address layout; these modules often have two 8 character halves and work best with this software if you declare them as lcd.begin(8,2); if you do that, then you can print("abcdefghilklmno"); and have all the characters appear as you would like across the screen. If you use any of the scrolling commands, the bizarre addressing of these modules will manifest itself. For details follow the _LCD Addressing_ link at web.alfredstate.edu/weimandn

User callback busy test
Get LiquidCrystal running without this first; setting up this complicated option is error-prone and this should be left for last, if implemented at all.


Just a quick idea... would rwBusy() be faster if you unrolled the two loops for the pinMode.  For example, instead of this:

Code: [Select]

   for (uint8_t i = 0; i < _data_pinNumber; i++) pinMode(_data_pins[i],INPUT);

maybe something like this could be faster?

Code: [Select]

   if (_data_pinNumber > 4) {

If the _data_pins were static, the compiler would likely just insert direct memory addresses.  But maybe it'll be smart and use the Y pointer with displacement instruction?  Maybe not?


Or if the compiler doesn't generate efficient code for that, this might coax it into using the single LD with post inc instruction for the first arg, and a single instruction for the second, so each line should compile to only 2 instructions plus the call.

Code: [Select]

   const uint8_t *pinptr = _data_pins;
   if (_data_pinNumber > 4) {

It really should allocate "pinptr" to Y (r28 & r29).  If it doesn't, there an asm syntax that can be added to force allocating to Y, which isn't clobbered by the call to pinMode.


I will play with those ideas. I think the problem will be that the library might get any pin number passed to it. It would be great if the optimizer looked across the call from user code to library code and saw what the passed pin numbers were, but I have assumed that was way too much to expect of the optimizer. It would be GREAT if my assumption has been wrong!

You are right that there is something different going on in the case of the Teensy++ in the rw busy test and the issue is almost certainly the time consumed doing 8 pinModes on the way in and 8 pinModes on the way out.
This shows up as surprising relative speeds in the 4 bit and 8 bit versions with rw on the Teensy++.


May 14, 2010, 01:35 am Last Edit: May 14, 2010, 04:02 am by jrraines Reason: 1
both of these resulted in the same time results for the non-Axman LCD on the Teensy++2.0, about 10% faster by unrolling that loop:

316 4 bit
368 8 bit

of course, pinptr can't be const and incremented.

the disassembly for setting the pinmodes back to OUTPUT at the end of rw_busylooks like this for the pinptr version
Code: [Select]
    3e8:      89 89             ldd      r24, Y+17      ; 0x11
    3ea:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
pinptr = _data_pins;
   if (_data_pinNumber > 4) {
    3ee:      8a 89             ldd      r24, Y+18      ; 0x12
    3f0:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
    3f4:      8b 89             ldd      r24, Y+19      ; 0x13
    3f6:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
    3fa:      8c 89             ldd      r24, Y+20      ; 0x14
    3fc:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
    400:      89 8d             ldd      r24, Y+25      ; 0x19
    402:      85 30             cpi      r24, 0x05      ; 5
    404:      a0 f0             brcs      .+40           ; 0x42e <_ZN13LiquidCrystal6rwBusyEv+0xda>
    406:      87 01             movw      r16, r14
    408:      0f 5f             subi      r16, 0xFF      ; 255
    40a:      1f 4f             sbci      r17, 0xFF      ; 255
    40c:      8d 89             ldd      r24, Y+21      ; 0x15
    40e:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
    412:      f7 01             movw      r30, r14
    414:      81 81             ldd      r24, Z+1      ; 0x01
    416:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
    41a:      f8 01             movw      r30, r16
    41c:      81 81             ldd      r24, Z+1      ; 0x01
    41e:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
    422:      0f 5f             subi      r16, 0xFF      ; 255
    424:      1f 4f             sbci      r17, 0xFF      ; 255
    426:      f8 01             movw      r30, r16
    428:      81 81             ldd      r24, Z+1      ; 0x01
    42a:      0e 94 73 0c       call      0x18e6      ; 0x18e6 <_pinMode_output>
                             "call _digitalWrite_LOW"
                             : "+z" (tmp)
                             : "I" (CORE_NUM_TOTAL_PINS)
    42e:      8f 81             ldd      r24, Y+7      ; 0x07
    430:      e8 2f             mov      r30, r24
    432:      0e 94 b6 0c       call      0x196c      ; 0x196c <_digitalWrite_LOW>
                             "call _digitalWrite_HIGH"
                             : "+z" (tmp)
                             : "I" (CORE_NUM_TOTAL_PINS)
    436:      8e 81             ldd      r24, Y+6      ; 0x06
    438:      e8 2f             mov      r30, r24
    43a:      0e 94 af 0c       call      0x195e      ; 0x195e <_digitalWrite_HIGH>
  digitalWrite(_rw_pin,LOW);   //set RW back to 'Write'
    43e:      df 91             pop      r29
    440:      cf 91             pop      r28
    442:      1f 91             pop      r17
    444:      0f 91             pop      r16
    446:      ff 90             pop      r15
    448:      ef 90             pop      r14
    44a:      08 95             ret

0000044c <_ZN13LiquidCrystal16checkLcdBusyFlagEv>:
digitalWrite(en, HIGH);   // enable pulse must be >450ns
// delayMicroseconds(1);    // but this is unnecessary see:
digitalWrite(en, LOW);    //http://billgrundmann.wordpress.com/2009/03/03/to-use-or-not-use-writedigital/
//  delayMicroseconds(DELAYPERCHAR);   // commands need > 37us to settle       not needed if using checkLcdBusyFlag

Go Up