Why connect the rw line when using the standard LiquidCrystal libray ?

Hi,

I am writing a text on how to connect a standard character LCD to an Arduino Uno.

I know and have been reading many documents on this subject. These documents are however not as detailed as I intend to do it. I want to write that document for students coming to my make space to learn about basic electronics, microcontrollers and the Arduino. It is now a 12 page document in Dutch, my native language.

In that context I wanted to explain when to use/connect the rw-line and when it would not make much sense.

If I did my homework correctly I found out that the LiquidCrystal library included with the 1.6.13 release is not using the busy flag. So speeding up things to the maximum is not a valid reason to connect the rw line.
After that the only reason I can see to connect the rw-line would be to enable the user to read from the LCD. But I see no commands that do read from the LCD.

That leads me to the question :

“What would be a good reason to spend an IO-line on connecting the rw-line when using the standard LyquidCrystal library included in the 1.6.13 release of the Arduino IDE ?”

Jan Huygh (Atelier Vilvoorde)

As you have figured out there really is no good reason at the present time, but historically there was at least one that most people wouldn't even think of nowadays.

Judging from your photograph I suspect that you were around when these devices were first produced so you should be able to relate to this.

The LCD controller contains 80 bytes of RAM corresponding to the 80 characters on the largest displays that the controller can deal with (20x4 or 40x2). If a product used a 16x2 display then only 32 of those 80 bytes were needed in most cases. This means there were 48 bytes of valuable RAM that were unused. The presence of the R/W signal facilitated the use of this RAM by the microprocessor.

Don

EDIT: I suppose I ought to remind our readers that 48 bytes of ram was not an insignificant amount when you consider that some of the early microprocessor systems had only 2K bytes (that's bytes, with a 'B') of RAM available for the user's entire program.

1 Like

But I see no commands that do read from the LCD.

You are correct as far as the library that comes with the IDE is concerned.

One of our active forum members has a new library, currently in 'alpha', called HD44780. This library can handle displays with the easily available PCF8574 based I2C interface as well as the standard parallel interface.

Since those adapters have the R/W line implemented Bill has taken advantage of that fact and does use the busy flag. I don't know whether he has implemented reading of the display CGRAM or not but he will probably pop in soon and let us know. A more useful application would be to read the present location of the cursor which is almost trivial since that information comes back when you read the busy flag.

Don

Thank you very much Floresta for confirming my conclusion.

I had to be very sure about my conclusion since I want to include my findings in workshops that I give to students. I sure wanted to avoid by all means that I would be telling/writing incorrect things. Judging from your activity here… you are a trustworthy source :wink:

And .. yes (as you concluded correctly from my picture) I understand that 80 bytes was more than nice to have as extra RAM in the early days. I did use some spare bytes in microcontroller projects when assembles was still a common language. I also checked the busy flag.

Not that it is important here but I would think it makes sense to place a note in the LiquidCrystal library documentation that using the rw -line is not useful if you use the standard LiquidCrystal library. I would add that the versions of the LiquidCrystal commands that do include the usage of the rw-line are here for backward compatibility.

floresta:
Since those adapters have the R/W line implemented Bill has taken advantage of that fact and does use the busy flag. I don't know whether he has implemented reading of the display CGRAM or not but he will probably pop in soon and let us know.

Actually, while hd44780 does support using the r/w signal to the LCD, hd44780 does not use the busy flag to determine command completion.
The reason being that the digital i/o routines on Arduino pinMode(), digitalRead(), digitalWrite() were poorly designed and are so slow, particularly on the AVR, that using the busy flag is actually slower than doing smart delays.
While it sounds counter intuitive, this is because to read the busy flag you have to re program all the data pins as well as the control pins to read mode to do the read then put the lines back to write mode, and the amount of time it takes to do that, all the LCD instructions other than clear and home are already done and that is when using direct arduino pin control.
Obvious on a remote interface over something like i2c it would be even worse.
The hd44780 library does allow setting the LCD instruction execution times if the defaults happen to be too short.
Also, unlike all the other libraries, hd44780 also does very smart LCD instruction execution delay timing that allows the LCD to run its command/data instructions in parallel with the Arduino processor. This allows most, and some cases all of the execution time be hidden which makes it faster.

In terms of using the r/w line, the hd44780 library supports reading data or status from the LCD if the r/w line is configured and the h/w supports it.
This is for any i/o class interface.
So if you have a PCF8574 backpack or use an additional pin for r/w control, you can use the read() and status() functions to read data or status from the LCD.
In terms of reading DDRAM vs CGRAM. Currently, this is a sticky/tricky issue.
This is because a read from the LCD will read whatever RAM is currently in use and the hd44780 library tries to keep the display into DDRAM mode so that normal character writes using write() works.
The sticky/tricky part is that createChar() puts the LCD in CGRAM mode so that it can write to CGRAM and most libraries leave it in CGRAM mode. However, that creates an issue that many users find frustrating in that after they do a createChar() their custom characters get corrupted by any data written to the display using write() until a clear(), home(), or setCursor() is done as those commands put the display back into DDRAM mode so write() works properly again.
hd44780 puts the display back into DDRAM mode after the custom character is created to avoid this.
So currently while the code and API can technically read from CGRAM, in order to do it you have to send a raw hd44780 set CGRAM address to get the display into CGRAM mode before you call read() to read the data.
I may look at providing an API update to make it simpler to read CGRAM in the future.

hd44780 can be installed using the IDE library manager and you can read more about it here:

--- bill

1 Like

jan_huygh:
I would add that the versions of the LiquidCrystal commands that do include the usage of the rw-line are here for backward compatibility.

That would be a good assumption. However no version of the LiquidCrystal library bundled with the Arduino IDE ever supported using the r/w signal to control the LCD all the way back to the very first version of the IDE that included it, all the way to the present time.
While the r/w pin can be controlled by the LiquidCrystal library, all it has ever done has been to set it to low.
So it is essentially, the r/w pin is a resource expensive ground signal.

--- bill

Thank you Bill (and Don again) for your extensive answers.

Purely by curiosity (I try to learn/understand)... Why do the libraries stick to the digitalWrite and digitalRead commands that are "not super efficient" or as you put it "poorly designed".
As a novice to the Arduino IDE I would understand/expect that we all want to keep the sketches simple. On the other hand I would consider the libraries a way to deliver user-friendliness to the user by hiding complexity in the library code. By consequence I would think it is OK that in the library one would opt for the most efficient approach one could think of. In this LCD-support-library-context that would open the road to manipulating IO lines using DDRn and PORTn,...
I expect there will be an obvious reason because hardly anyone does that (all widely used libraries I’ve investigated stick to using the user friendly Arduino specific commands).

Some background:
I once did develop an I2C LCD board (in AVRGCC). On that board I had an AVR using it’s full capabilities to use everything a standard character LCD (my definition would be a character LCD controlled by an HD44780 or compatible controller) had to offer.
It used 8-bit mode and checking the busy-flag to deliver the highest possible throughput.
Of course I had the luxury that my AVR was dedicated to do just the LCD manipulation.
It offered some extra’s like blinking the Backlight to get the user’s attention and play a music on a piezo-speaker to get the user’s attention.
So I was tempted to rework that code into an Arduino library and offer it for free as a contribution to the community. The advantage over the standard LiquidCrystal library would be a higher throughput. The disadvantage would be that it only works with boards using the 8-bit AVR chips.
I would do the effort to rework the code to enable users to use the IO-pins (except Enable and probably DB7, the busy flag) to do something else.

... but I guess the answer I will get here will make me understand why that is not such a smart thing to do.

Jan Huygh

1 word: Portability.
If you want a library or sketch code to run across many different platforms or even different boards, then the easiest way to do that is to use an abstraction layer.
Abstraction layers are not necessarily bad, it is just that the way the digital i/o routines were done on arduino force a very sub optimal implementation.

Consider what happens if you use raw port i/o on a AVR as you mentioned.
Some libraries have done that and typically libraries that do this are not portable and VERY difficult to change which pins are used because the code didn't use any sort abstraction even internally.
So even moving from one AVR based board to another can be extremely painful since the specific PORT and bit assigned to an Arduino pin # like digital pin 10, is not the same on different boards.
This makes things really messy for shields that need to use a specific digital pin #

But say you solve that, through some some extensive macros, or defines then there is the issue of moving between processors.
If you hard code things for AVR it obviously immediately fails on any other processor.

One thing that would have really helped is if Arduino didn't use naked constants for its digital pin numbers.
If it had used names/defines like D10, D13, etc... then those values could have encoded information that could have made things MUCH faster since there would be no need to do a lookup to get from the naked constant to the i/o port/bit information.

There are things that can be done, to make the i/o much faster and still be portable, but it is complicated.
From my working with Arduino over the years, it is obvious to me that the original Wiring & Arduino team was not very technical and familiar with gcc and embedded development techniques.
There are many things that would and should have been done differently. And many of them would not affect the end user API.

I have a library called openGLCD that does do raw port i/o so that it can do bit flipping 100s of times faster than using the standard Arduino digital i/o routines.
But is is MANY thousands of lines of code and difficult to maintain for all the board and processors.
The avrio header is pretty magical. It creates a similar dgitalWrite() type interface for the application but generates the fewest possible instructions for doing multi bit pin i/o.
If the user uses appropriate pins, it can even do 8 bit port i/o.
avrio is 1700 lines of code that often ends up generating a single AVR instruction.
It also has to have a fall back mode so that if a board or processor that does not have specific support that the code can still function (albeit slower).
Creating a system that supports this type of i/o that allows users to still be able to configure their pins is very complex and difficult to implement.

What makes doing raw port i/o so complex and painful on the AVR is that I don't think that the chip designers really understood that C compatibility is pretty critical.
They flubbed big time in two areas:

  • a harvard architecture that doesn't have direct access to flash on the data side
    (no other modern RISC processor does this, not ARM, and not pic32, etc...)
  • registers require using bit set/clear instructions for atomic updates
    (Many other processors and i/o devices have bit set/clear registers)

It is that last one that really kills things when combined with the way the Wiring/Arduino guys defined their API.
The combination of the the way the AVR does atomic i/o and the way Wiring/Arduino defined its digital i/o API is what creates the perfect storm of crappy i/o performance.

When doing things like digitalWrite() a bit gets set/cleared in a register.
Its is VERY important that this operation be atomic to avoid register corruption.
The way the AVR was designed, the only way to do that is to use the special bit set/clr instructions.
C has no knowledge of such low level processor specific instructions.
To work around this the avr-gcc developers hacked the gcc compiler to make some optimizations under certain conditions:

  • memory address is known at compile time
  • bit is known at compile time.
    If those conditions are met, then the compiler will emit sbi/cbi instructions.

So if you do something like
PORTD |= 0x20;

The compiler will notice two things:

  • the memory address of PORTD is known
  • the bit is know

So it will generate a sbi instruction.

The problem is that arduino supports runtime pin configuration.
This means that neither are guaranteed to be known at compile time.
i.e. you could do digitalWrite() using variables for pin and value.
Even worse, since naked constants are allowed for pin numbers, you have to convert that number into the needed information.
and, in their implementation the lookup table is in flash which cannot be directly accessed on the AVR so you have to call functions just to get to the table data.
AND even worse.... when cbi/sbi instructions are not used, it is painful to ensure atomicity.
You have to mask interrupts to ensure atomicity.
So now instead of a single cbi/sbi, you have to:

  • save the ISR state,
  • mask interrupts
  • read the i/o register in a tmp register
  • or/and bit mask into tmp register
  • recover ISR state

There are ways to make it better, like detect if the parameters are compile time constants and do all the lookups at compile time. But the Arduino team has rejected it.
The Teensy versions of the digital i/o routines do this and that is why they are 50x faster than the standard AVR versions.
You could even do some special declarations to allow the compiler to use the data table at compile time so that if the pin numbers are constants the code would be looked up by the compiler at compile time vs at runtime using the flash access routines to get to the data.
But again, the arduino team has been unwilling to look at these types of enhancements.

Interesting side story, the very first day I started looking at Arduino (8 years ago) the first thing I looked at was the digitalWrite() code as I was about to convert a glcd library to use Arduino. I immediately saw an atomicity issue.
They were doing |= and &= operations on registers with runtime data which meant that the operation was not atomic.
It took over a year to convince them that this was an issue and to fix it.
It wasn't an issue of not having the code for the fix, as that was provided.
They simply did not understand the issue. They didn't understand that |= is interruptible on a RISC processor.
Even when showed the exact assembler instructions generated by the compiler and explaining the exact time sequence of events that can create the register corruption, it still was not sinking in.
The only way it got fixed was that several people put the code fix in their version of the AVR core code and it "magically" fixed a long term issue when using the servo library.

It does manifest itself in a odd way in that the foreground code is causing corruption because of an atomic update operation being done in an ISR.

To this day, I still don't think they ever understood the issue.

The net result was that having to add the ISR blocking/restoration around the port updates added additional overhead to i/o operations that were already not that great.

--- bill

Whow ! What a fantastic teacher you are Bill.

Your answer made me see things and understand reasons why... that would have taken me lots of time to figure out myself.

Maybe later today I'll just look up other answers you have written... just to learn more.

But I first need to finish my "Connecting a standard LCD to an Arduino" application note for my training.

Jan Huygh

When the Arduino first appeared, it was only a mega168/mega328 board.

There are many different official Arduinos now and they use several different microcontroller chips.

There are lots of "wannabe" Arduinos e.g. Teensy, STM32, ...
If the "common" libraries use digitalWrite() they will work immediately on the new hardware.

Yes, the portability means that you will not get the best performance. But hey-ho, a 16x2 LCD is not going to notice.

The performance is very obvious with something like a 240x320 TFT. Library authors might optimise the code for Uno, Teensy, Due, ... with dramatic results.
A good example is Marek's "ILI9341_due" library for the ILI9341 displays with SPI interface. The bad news is that it only runs on Uno or Due.

David.

david_prentice:
Yes, the portability means that you will not get the best performance. But hey-ho, a 16x2 LCD is not going to notice.

Having portability does not necessarily mean a loss of performance.
The main issue here is the semantics of the interface, combined with the way the Arduino developers have chosen to implement the code.
There are other ways to write the code, even using the existing API semantics, that will give MUCH better performance even on the AVR, but the Arduino team (mainly the very early founders) chose not to accept them.

i.e. on an AVR based Teensy if you do digitalWrite(10, HIGH); it turn into a single AVR instruction.
Using the standard AVR core library it is many hundreds of instructions involving flash table lookups that takes around 6us at 16Mhz

My observation was that the early Arduino team simply lacked the skills to do some of the types of coding that would have allowed things to be much smaller and faster. Even worse, the early team very much had a not-invented-here attitude that pretty much rejected things from outsiders even when those outsiders were much more experienced.
Luckily, things are a bit different now that there are some great new developers on the Arduino team.
So it might be possible to revisit some of the long abandoned code optimizations and actually get them into the IDE core code.

--- bill

david_prentice:
When the Arduino first appeared, it was only a mega168/mega328 board.

Actually when Arduino first appeared it was atmega8.
Resources are VERY limited when using the atmega8 and so you sometimes have to make some tough choices as to what features can exist in your code.

--- bill

It would have been nice if they had 'bitten the bullet' and abandoned the improperly laid out pins as soon as they discovered the problem.

Don

floresta:
It would have been nice if they had 'bitten the bullet' and abandoned the improperly laid out pins as soon as they discovered the problem.

Don

Are you referring to the use of r/w pin in the LiquidCrystal constructor or to the screwed pin spacing on the physical board?
The latter being a total F-up since it precludes being able to use standard strip boards for shields.
But careless mistakes caused by last minute "fixes" like this seemed pretty common in the earlier days of arduino, not just on the h/w side.
On the positive side, because of the non standard spacing on one side it does prevent accidentally inserting a shield backwards.

--- bill

I'm referring to the latter.

I just assumed that they left the R/W pin in the constructor so that the library could easily be extended if desired.

Don