Reading from HD44780-based LCDs

Hey, it's me again, this time with some interesting questions:
I have been working with a Hitachi HD44780-based LCD and as part of my project I am writing an extended library to perform read operations from the screen.

The datasheet for the HD44780 says the following regarding readings:

CMD RS R/W DB7-0
Read Busy Flag & Address 0 1 BF (DB7) Address (DB6-0)
Read data from CG/DD RAM 1 1 Data (DB7-0)

Now, the operation in question is trivial to implement when using 8bit mode, as all data channels are available. The question is, when working in 4bit mode, how is the data read then? I can't find any available documentation on this subject. Any help would be appreciated. Thanks

Basically, the same way it's sent into the HD44780, 4 bits at a time. there will be two operations for every 8 bits, because its passed 4 bits at a time.

Take a look at page 42 of the datasheet http://www.sparkfun.com/datasheets/LCD/HD44780.pdf for more detail on how 4 bit operations work.

The datasheet you linked only says about write operations to the controller, it does not mention read operations.

I tried sending a pulse to the strobe (enable) to get the next bit, but that caused undesired side effects, like text randomly disappearing from the screen, which leads me to believe that approach is wrong.

OrlandoArias:

As you probably know, when you write information to the LCD module in four-bit mode it temporarily stores the first group of four bits that it receives (with the first enable pulse). After it receives the second group of four bits (with the second enable pulse) it reassembles them into an eight bit byte and deals with the reassembled byte.

I assume that when you read information from the LCD module in four-bit mode your program would have to do essentially the same thing. You would send an enable pulse, read the incoming data (the upper four bits of the incoming byte) and stash it away. You would then generate another enable pulse, and read the incoming data (the lower four bits of the incoming byte). You then have to do some swapping, masking, ORing etc. to reassemble the byte.

Don

As I said, I already tried the approach of sending a pulse to the strobe [enable pin], to retrieve the second nibble. That did not work. The screen output is corrupted if that is done.

OrlandoArias:

As I said, I already tried the approach of sending a pulse to the strobe [enable pin], to retrieve the second nibble.

I'm sorry, but that is not what you said. Since you have provided almost no information about your program I did the best with what you did provide.

Retrieving data from the LCD module should not corrupt the display. You have to write data to the LCD module in order to do that. So your program code must be changing the R/W line between the Enable pulses or something along those lines....

Don

As I said, I already tried the approach of sending a pulse to the strobe [enable pin], to retrieve the second nibble.

I'm sorry, but that is not what you said.

Oh, I believe that is what I said, from two or three posts above:

I tried sending a pulse to the strobe (enable) to get the next bits, but that caused undesired side effects, like text randomly disappearing from the screen, which leads me to believe that approach is wrong.

Since you have provided almost no information about your program I did the best with what you did provide.

I need to read from an HD44780-based LCD screen in 4 bit mode. What else do you need to know in order to provide an algorithm?

Excuse the ol' scepticism, but if you have an LCD connected to the arduino that has data on it you need to read, surely that information came from the arduino in the first place. Wouldn't it be a lot easier to fire up another variable to hold the data for the next time rather than pulling it back from the display ?

Or am I missing something ?

pluggy:

Or am I missing something ?

It's kind of obscure, but if you are using a display with less than 80 characters (such as a 16 x 2) it is possible to use some of the DDRAM in the LCD controller for general data storage. I don't know why anyone would want to do this, but it is a possibility.

Don

Orlando:

Oh, I believe that is what I said, from two or three posts above:

I'm not trying to get into a pissing match here but my background is in engineering, not horseshoes or hand grenades. I interpret ther term 'quote' as an exact copy of what was originally said. Reply #2 mentions the next "bit". Reply #4 mentions the next "nibble". Reply #6 mentions the next "bits". I used to tell my students that the implication of not being precise in the explanation of ones work is that perhaps the work itself isn't as precise as it should be.

As I said, I already tried the approach of sending a pulse to the strobe [enable pin], to retrieve the second nibble. That did not work. The screen output is corrupted if that is done.

But you haven't provided any code to show how you are attempting to retrieve and deal with this data so all I can do is guess at what is wrong, which is what I did in the second part of reply #5. The scenario that I guessed at is consistant with the description of the behavior that you gave.

What else do you need to know in order to provide an algorithm?

I thought I provided an algorithm in the second paragraph of Reply #3. The reason that I used the words "I assume" at the beginning of that paragraph is because I have never actually read from the LCD RAM. I have however used this technique to read the busy flag which uses essentially the same techniques.

If you want me to write your program for you I can probably do that, although it would be in assembly language. Please remit $100 in small used bills (or a Gator) to get me started.

Don

Thanks floresta. I think I can live without the extra 48 bytes (presumably) my 16*2 is keeping from me. I have pin 5 tied to ground anyway so read is a non starter.....

Well, this is obviously going nowhere, and I can see how I was, at least partly, responsible for it.

Here's where we stand:

  • The LCD is in 4-bit mode.
  • The LCD is sent the command to read from itself. The reason this is needed is because if you were to switch to CGRAM to write your own custom character and then write something to the LCD, you must point back to a location on DDRAM, otherwise, you can write a much text as you want, some may show depending on how much you write, and how you set up the LCD to begin with. I want to implement my write CGRAM function to return to the place the cursor was in DDRAM, thus need to read from the LCD.
  • I tried sending a pulse to the enable pin in order to get the second nibble. The output on the LCD gets corrupted. Yes, it seemed weird to me as well, as I was writing nothing to it. This leads me to believe that triggering the strobe is the wrong approach.
  • I have searched the web tons of time before posting here, which is akin to saying I've given up on finding a solution myself. I am not the kind of person who likes to ask questions.

The library in question is supposed to be an extension (separate library) to my LCD library found in Google Code. With that being said, I have no immediate use for it, it is something that I'd like to have for the future (food for the winter if you will). The code I use I no longer have, deleted it before I committed it to my subversion repository (big mistake, I know), so I have no reference to show. The code was, however, bug free.

Having this data at hand now, and also, whilst apologizing for any misunderstandings/conflicts/headaches/irateness that may posts have caused, I shall post the question again, what to do?

If you want me to write your program for you I can probably do that, although it would be in assembly language. Please remit $100 in small used bills (or a Gator) to get me started.

You and I, my good sir or madam, think very much alike. This statement made chuckle, as I've always say similar stuff ;D.

Orlando:

The LED controller has 80 bytes of DDRAM with each byte corresponding to a specific location on an 80 character (40 x 2 or 20 x4) display. The Controller continuously scans this memory and for each location it reads the information in DDRAM, uses that information (as an address) to access CGROM or CGRAM and uses the CGROM/RAM data it finds at that address to put the corresponding character on the display. If you change the data at one of the DDRAM locations then the character displayed at the coresponding display location will change. Looking at it the other way - the only way to change what is on the display is to change the information in the DDRAM and the only way to change what is in DDRAM is to write information to the LCD controller.

If you pulse the Enable to get the next nibble and the display gets corrupted this means that you are not reading data from the LCD as you intended but you are writing data to the controller instead (which is what I said in reply #5).

In several posts you have stated that you have pulsed the Enable to get the next nibble but you have not mentioned anything about all the other steps that must be taken to read data from the LCD controller. I assume that you know what these steps are since they are also used in the 8-bit implementation which you imply you have done in your original post.

The bottom line is that neither I nor anyone else can help you any further without seeing some code.

(sir) Don

Keeping in mind the fact that I do not use the Arduino sketch editor (too crummy for me to work on) and that I code directly in C using some of the wiring extensions.

This is how the LCD struct is declared:

typedef enum _bmode {
      BIT_MODE_4      =4,
      BIT_MODE_8      =8
} bmode;

typedef struct _LCD {
      int datapin[8];
      int rs_pin;
      int rw_pin;
      int enable_pin;
      bmode bitmode;
} LCD;

This implementation is how I'd get data from the LCD.

Since the LCD has only 8 data channels, an 8 bit integer (char type) is enough to hold the data. This is the same implementation I had written a while back and that fails (corrupts the LCD screen display).

#include <inttypes.h>
#include "WProgram.h"
//some other include that aren't relevant to our issue.
// some interfacing code that has nothing to do with this: lcd_sendRaw()


uint8_t lcd_getRaw(LCD *l, uint8_t rs_mode){
      digitalWrite(l->rs_pin, rs_mode);
      digitalWrite(l->rw_pin, HIGH);

      uint8_t retVal = 0;

      for (int i = 0; i < l->bitmode; i++){
            pinMode(l->datapin[i], INPUT);
            retVal |= (digitalRead(l->datapin[i]) << i);
            pinMode(l->datapin[i], OUTPUT);
      }

      if (l->bitmode == BIT_MODE_4){
            retVal <<= 4; //obtained high nibble, must obtain low.

            digitalWrite(l->enable_pin, HIGH);
            digitalWrite(l->enable_pin, LOW);

            for (int i = 0; i < 4; i++){
                  pinMode(l->datapin[i], INPUT);
                  retVal |= (digitalRead(l->datapin[i]) << i);
                  pinMode(l->datapin[i], OUTPUT);
            }
      }

      digitalWrite(l->enable_pin, HIGH);
      digitalWrite(l->enable_pin, LOW);

      return retVal;
}

This function gets called whenever we need to get the status flag or any address. The status flag is simply obtaining by bitshifting 7 places to the left

busy_flag =  lcd_getRaw(lcd, LOW) >> 7;

The address counter is obtained by just anding with dec127 (b01111111).

address_counter = lcd_getRaw(lcd, LOW) & 127;

Reading data from CGRAM or DDRAM is done by calling lcd_getRaw() with a HIGH value for the rs pin

data = lcd_getRaw(lcd, HIGH);

This, however, fails, and the output is corrupted for some reason. Perhaps I made something overflow somewhere, I don't know. The code looks fine to me, in case anybody has any suggestions, please, let me know.

I seem to remember seeing this somewhere....

// I wish people would document their code as well as I try to do.

Don

I seem to remember seeing this somewhere....

Touche... I wrote the code in a rush from what I had in memory. It pretty much implements what we've spoken about.

I should assume I do not need to comment the enum or the struct as what they define should be evident.

#include <inttypes.h>   //needed for uint8_t datatype
#include "WProgram.h"    //needed for wiring extensions to C.
//some other include that aren't relevant to our issue.
// some interfacing code that has nothing to do with this: lcd_sendRaw()


uint8_t lcd_getRaw(LCD *l, uint8_t rs_mode){
      digitalWrite(l->rs_pin, rs_mode);    // registry select
      digitalWrite(l->rw_pin, HIGH);    //set read mode

      uint8_t retVal = 0;    // initializes return variable

      for (int i = 0; i < l->bitmode; i++){    //if bitmode is 4, it will loop until i < 4, if bitmode is 8, it will loop until i<8
            // set pin to input mode
            pinMode(l->datapin[i], INPUT);
            // reads pin, shift value to proper place and add it to retVal
            retVal |= (digitalRead(l->datapin[i]) << i);
            set pin to output mode
            pinMode(l->datapin[i], OUTPUT);
      }

      if (l->bitmode == BIT_MODE_4){
            // LCD is in 4 bit mode, only one nibble was obtained
            // shift nibble to upper end of byte and loop to obtain low
            // end of byte
            retVal <<= 4; //obtained high nibble, must obtain low.

            // trigger the strobe to signal for the next nibble
            digitalWrite(l->enable_pin, HIGH);
            digitalWrite(l->enable_pin, LOW);

            // same loop as above, only this time we know the upper limit
            for (int i = 0; i < 4; i++){
                  pinMode(l->datapin[i], INPUT);
                  retVal |= (digitalRead(l->datapin[i]) << i);
                  pinMode(l->datapin[i], OUTPUT);
            }
      }

      // end of operation, trigger the strobe
      digitalWrite(l->enable_pin, HIGH);
      digitalWrite(l->enable_pin, LOW);

      // return the complete value
      return retVal;
}

Evaluating bitwise OR:
As per Boolean Algebra:
0 OR 0 = 0
0 OR 1 = 1
1 OR 0 = 1
1 OR 1 = 1
C/C++ Operator for this operation is |

Bitshifts:
Bitshifting is an operation done to, of course, bits. As the name implies, all that is done is shift to the right or left. A left bitshift by n is equivalent to multiplying by 2^n. A right bitshift by n is equivalent to dividing by 2^n.
Examples:
(bitshift two one to the left) dec2 << dec1 = bin10 << dec1 = bin100 = 4
(bitshift two one to the right) dec2 >> dec1 = bin10 >> dec1 = bin1 = 1 (the zero gets dropped)

Algorithm to obtain raw data
We can store all data that is read from the LCD in one byte (8 bits). This will be stored in an 8 bit integer (same as a char type). For ease of access, we use an 8bit unsigned integer as defined in <inttypes.h>. Unsigned 8 bit integers range from dec0 to dec255, or b00000000 to b11111111.

We loop to get data out of the LCD. The first loop is generic and gets executed no matter whether the LCD is in 8 bit mode or 4 bit mode. The process is simple:

  1. Initialize the return value to 0.

  2. Read the first pin (i = 0), this is the least significant digit. In 4 bit mode, this would be data pin 4 on the LCD board, in 8 bit mode, this would be data pin 0. The output is either a HIGH (1) or LOW (0). Let the reading be the bit [D0]. It gets bitshifted by zero places to the left. The operation performed will be:
    0 | [D0] = [D0]
    which is stored back in the return value.

  3. Read the second pin (i = 1). Let the output of this be the bit [D1]. It gets bitshifted one to the left and OR'ed to our return value:
    [D1] << 1 = [D1]0
    [D0] | [D1]0 = [D1][D0]

  4. This process gets repeated until the upper limit is reached. If the display was operating on 8 bit mode, we end up with the full byte by the end of the loop, and it is returned. If the display is in 4 bit mode, we only have one nibble [D3][D2][D1][D0], the if clause executes.

In case of 4 bit mode
5) Bitshift retVal 4 places to the left. This puts the nibble as the upper end of the byte.
[D3][D2][D1][D0] << 4 = [D3][D2][D1][D0]0000
6) Trigger the strobe (This is what this entire argument has been about, and what I am not sure that should be the proper procedure).
7) Repeat process above to get the second nibble. By the end of the loop, the lower end of the byte will be completed and retVal can be returned.

Using returned data
The data returned will have the form [B7][B6][B5][B4][B3][B2][B1][B0]. What the data represents depends on value fed to register select (rs_pin). If rs_pin is given a LOW, then [B7] is the busy flag status and [B6]-[B0] will contain the Address Counter. If rs_pin is given a HIGH, then the return value will be data from DDRAM or CGRAM.

To filter out the busy flag, all that needs to be done is bitshift the return value 7 places to the right, which leaves [B7] by itself. To filter out the Address Counter a bitwise AND can be performed with 127dec (01111111), leaving [B6]-[B0].

Observations
This is obviously a backend to be used by other functions which will filter the data for the end user. At some point in time in the execution, the LCD's output gets corrupted, some things disappear.

A question if I may, and please take no offence on it, was my code that obfuscated that it needed documentation? It is not my intention to write unreadable code. I consider obfuscated code to be bad code.

Orlando:

Now I have something I can work with. I don't do much C programming but I am pretty sure I can follow what you have given me.

So far I have not really studied your code but from what I have said before it appears that somewhere when you think you are 'reading' from the LCD you are actually 'writing' to it. For that reason it may be beneficial for me to look at lcd_sendRaw() so that I can compare it to lcd_getRaw().

please take no offence on it

If you can put up with my barbs I certainly can deal with yours...

was my code that obfuscated that it needed documentation?

It's no worse than most other C code that I try to read. Some programmers seem to believe that C is 'self commenting' which is a real joke. Let them try to figure out a program that someone else wrote or one that themselves they wrote ten or fifteen years ago and then see what they say about comments.

Check out the 'Comments on Commenting' at Comments on Commenting. In the 'Commenting Suggestions' at the end he states "My standard for commenting is that someone versed in the functionality of the product - but not the software - should be able to follow the program flow by reading the comments without reference to the code itself."

This agrees with my criteria which maintains that the comments should almost always be language independent. You should be able to use the same comments in an assembly language program as in a C program (and in a flowchart as well).

Don

p.s. I find the term 'obfuscated' to be about as obfuscated as they come.

Here is lcd_sendRaw(). The algorithm is pretty much the same concept, except that instead of reading from the LCD, we write to the LCD.

void lcd_sendRaw(LCD *l, uint8_t val, uint8_t rs_mode){
      digitalWrite(l->rs_pin, rs_mode);
      digitalWrite(l->rw_pin, LOW);    // write to LCD

      if (l->bitmode == BIT_MODE_4){
            for (int i = 0; i<4; i++)
                  digitalWrite(l->datapin[i], (val >> (i + 4)) & 0x01);

            // trigger strobe
            digitalWrite(l->enable_pin, HIGH);
            digitalWrite(l->enable_pin, LOW);
      }

      for (int i = 0; i < l->bitmode; i++)
            digitalWrite(l->datapin[i], (val >> i) & 0x01);

      // trigger strobe
      digitalWrite(l->enable_pin, HIGH);
      digitalWrite(l->enable_pin, LOW);
}

The data to be written is stored in an unsigned 8 bit integer (char type). For ease of reading, we use an unsigned 8 bit integer as defined in <inttypes.h>. The data has the form [B7][B6][B5][B4][B3][B2][B1][B0].

If the LCD is in 8 bit mode, the second loop is the only one that executes. The operation in question is extracting the least significant bit not sent. This is done by bitshifting the data to the right n places and bitwise anding with dec1 (b00000001).

Assuming the LCD is in 8 bit mode, the following is executed:
i=0) [B7][B6][B5][B4][B3][B2][B1][B0] >> 0 = [B7][B6][B5][B4][B3][B2][B1][B0]
[B7][B6][B5][B4][B3][B2][B1][B0] & 0x01 = [B0]
[B0] is then written to datapin[0]
i=1) [B7][B6][B5][B4][B3][B2][B1][B0] >> 1 = [B7][B6][B5][B4][B3][B2][B1]
[B7][B6][B5][B4][B3][B2][B1] & 0x01 = [B1]
[B1] is then written to datapin[1]
and so on, until all data is written.

If the LCD is in 4 bit mode, the byte must be sent in nibbles. The second loop is generic and can send the low end of the nibble (loops from i = 0 to 3, yielding B0 to B3). The first loop is executed before in this case.
i=0) [B7][B6][B5][B4][B3][B2][B1][B0] >> (0 + 4) = [B7][B6][B5][B4]
[B7][B6][B5][B4] & 0x01 = [B4]
[B4] is then written to the LCD.
i=1) [B7][B6][B5][B4][B3][B2][B1][B0] >> (0 + 1) = [B7][B6][B5]
[B7][B6][B5] & 0x01 = [B5]
and so on until i = 3, where all data has been sent.

This should be the same algorithm that is used in the current LiquidCrystal library that comes with the Arduino IDE, only the way it is implemented there is too bloated for my taste in programming.

Thanks and cheers, Orlando.

Edit: I just committed the entire code I have for this library to my subversion repo in google code. The code is available here. It is all GPLv3. Since the lcd_getRaw() function is suspected to be broken, the lcd_getState() function is not made to use the lcd_getRaw() function.

Orlando:

You have finally provided enough information for me to make some more educated guesses. From the information in your last two posts it appears to me that you do indeed know the signal flow involved in reading and writing in both 4 and 8 bit modes. Since I am not much more than a rookie C programmer I will assume that your C code is correct as well. I still stand by my analysis that in order for the display to become corrupted the DDRAM has to be getting messed up somehow.

What is missing from your code, as far as I can tell, is proper implementation of at least some of the timing requirements. For example look at the Enable pulse width which must be at least 150 nS wide according to some datasheets but must be as long as 450 nS on others. In assembly language (and maybe in very efficient C) pulsing the enable pin high then low can be done with two 2-cycle instructions which takes 270 nS at 16 MHz. Therefore this may work all of the time on some displays, some of the time on all displays, etc. but not necessarily all of the time on all displays.

There are also some setup times, some hold times and also the Enable cycle time that should be considered. I don't know the ramifications of not meeting some of these requirements. I would suspect that the device would just not work, but in your case you could be right on the borderline with even more spetacular results.

I would start by liberally sprinkling your program with nice long time delays and see what happens. If that doesn't work I have some more troubleshooting ideas in mind.

By the way, keep in mind that these timing problems just get worse as processor speeds increase and LCD controller speeds stay essentially the same. To make your code more repairable in the future I recommend that for each time delay specified in the LCD datasheet you include at least a comment to that effect at the point in your program where that time delay would occur.

Don

The timing part I know is something I am missing. If you look over lcd_init() over at my subversion repo at Google Code, you will find that initialization is not done properly either. I planned to add this at some point, it looks however, that it may be the root of the issue. The lcd_sendCommand() and lcd_sendChar() functions however rely on reading the busy flag and waiting for the LCD to become available again before exiting.

Yeah, DDRAM must be getting corrupted somewhere. What I don't understand is how is something that was written to the LCD disappear and the rest of the stuff still be around. It's not like random characters came up or anything...
The LCD was supposed to show "Distance: ##cm", with ## being a two or three digit number. If distance was less than 25 cm, it would show "PROXIMITY WARNING" on the second line. Instead, it showed "##cm" and would display the warning whenever the case. Reverting the library to its previous state fixed the problem. In case you are wondering, the distance was obtained from a PING sensor.
I'll add proper timing and initialization to the library and we'll see what happens afterwards.