16x4 Powertip Display

17 Jan 2013

Thanks Wim I think I know where the problem lies in the TextLCD.cpp file.

int TextLCD::address(int column, int row) {
    switch (_type) {
        //case LCD20x4:
        case LCD16x4:
            switch (row) {
                case 0:
                    return 0x80 + column;
                case 1:
                    return 0xc0 + column;
                case 2:
                    return 0x94 + column;
                case 3:
                    return 0xd4 + column;
            }
        case LCD16x2B:
            return 0x80 + (row * 40) + column;
        //case LCD16x4:
        case LCD20x2:
        default:
            return 0x80 + (row * 0x40) + column;
    }
}

case 2 and case 3 addresses are probably not correct for a 16x4 display as these were originally for a 20x4 display.

18 Jan 2013

Hi Derek, as explained, the LCD driver HD44780 is designed for 2 rows of max 40 chars. Note that it needs some support drivers to control that maximum nr of chars.

  • The address for the first char on the first line is 0x00.
  • The address for the first char on the second line is 0x40.

Four line displays are generally created by splitting the two line display and arranging the two parts above eachother. For example 2x40 is split in 2 parts of 2x20 and arranged as 4x20. The adddresses for the 3rd and 4th line just continue where the split was made:

  • The address for the first char on the first line is 0x00, the last char is 0x13.
  • The address for the first char on the second line is 0x40, the last char is 0x53.
  • The address for the first char on the third line is 0x14, the last char is 0x27.
  • The address for the first char on the fourth line is 0x54, the last char is 0x67.

The example code above uses this 4x20 schematic, but adds 0x80 to the returned addressvalue for some software design reason. You may have to correct the code for a 4x16 display and add this as a new LCD configuration option.

Notes: The HD44780 is pretty flexible and variations of driverhardware and LCD glass layout may result in differences. Some 4 line displays actually use two controllers with separate enable pins. The HD44780 supports left/right shifting of memory locations (address) wrt displayposition. That changes the address of the char that is shown on a displaylocation.

19 Jan 2013

Hi Wim,

Thanks for that I found this webpage which gives a good understanding of what the memory allocations are for different types of display architectures.

http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html

I'll be experimenting in setting different starting and ending addresses and seeing their effect to gain a better understanding of what the code does.

20 Jan 2013

Well for everyone's information I have just spent a good few hours getting my head round registers, memory addressing and LCD's. I used a spreadsheet to plot the memory locations for a 2x40 display and played around with different settings for start and end address in the TextLCD.cpp file and found that for my PowerTip 1604A B type display the address registers are quite strangely:-

80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF

The 3rd and 4th line addresses follow on directly from the previous unlike what some of the literature advises. I suppose this is a quirk of the particular manufacturers design. So I now have all 4 lines starting on the leftmost cell. Thanks to Wim for giving me some great help. Now for the next challenge.

20 Jan 2013

It is common that the 3rd and the 4th line addresses directly follow the last address of line 1 and 2. That was shown also in the above example for 4x20.

This behaviour is the result of how the driver hardware for (matrix) character LCDs works. These are in fact designed to use rather long shiftregisters with all outputs connected to the lcd columns. Each character uses 5 outputs (columns or segments). Each character also has 7 rows (or commons), plus one row for the cursor. All characters share the same rows. Only one row output is activated at any time. The pixels on the intersections between rows and columns will now be switched on or off. The HD44780 has 16 row drivers. That is enough to drive 2 lines of characters. You need to split and stack them for 4 lines. The HD44780 has 40 onboard column drivers (8 characters per line). Longer lines need additional external column drivers.

See below for the layout of a minimum 8x2 row display.

/media/uploads/wim/_scaled_hd44780.jpg

The addresses you give are wrong in the sense that they all have the most significant bit set. That upper bit is in fact part of the HD44780 command to set the address (1xxx xxxx, where the bit0-6 is the address). This behaviour can be seen here:

void TextLCD::character(int column, int row, int c) {
    int a = address(column, row);
    writeCommand(a);
    writeData(c);
}

In my opinion the code should be fixed to avoid this confusion.

So this addressmap:

80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F

C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF

90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F

D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF

when stripping the MSB will get you:

00 .. 0F

40 .. 4F

10 .. 1F

50 .. 5F

That addressmap is consistent with the datasheet and the normal layout for a 16x4 display.

20 Jan 2013

I can definately see the point you are making here Wim and I have wondered why the addressing was starting at 80 in the code and not 00. I'll readjust things and play about again and post my results. I'll also read up on the HD44780 data sheet to understand what the MSB bit does.

Thanks again.

Think I need to get my old notes out and read up online about classes and header files.

21 Jan 2013

Derek Calland wrote:

I have wondered why the addressing was starting at 80 in the code and not 00.

The DDRAM address starts at 00 and "set DDRAM address instruction" starts at 80.
Please refer to following instructiin sets.

        RS  R/W  B7   B6   B5   B4   B3   B2   B1   B0
------------------------------------------------------
Set
CGRAM    0    0   0   1   ACG  ACG  ACG  ACG  ACG  ACG
address
------------------------------------------------------
Set
DDRAM    0    0   1  ADD  ADD  ADD  ADD  ADD  ADD  ADD  
address
------------------------------------------------------

So, the code for 16X4 is as follows;

int TextLCD::address(int column, int row) {
    switch (_type) {
        case LCD16x4:
            switch (row) {
                case 0:
                    return 0x80 + column;
                case 1:
                    return 0xc0 + column;
                case 2:
                    return 0x90 + column;
                case 3:
                    return 0xd0 + column;
            }
        case LCD20x4:
            switch (row) {
                case 0:
                    return 0x80 + column;
                case 1:
                    return 0xc0 + column;
                case 2:
                    return 0x94 + column;
                case 3:
                    return 0xd4 + column;
            }
        case LCD16x2B:
            return 0x80 + (row * 40) + column;
        //case LCD16x4:
        case LCD20x2:
        default:
            return 0x80 + (row * 0x40) + column;
    }
}

Also, I use "locate instruction" to set the display position intentionally, instead of using "\n" on printf function.
the example is...

int main()
{
    while(1) {
        lcd.cls();
        wait(0.50);
        lcd.locate(0,0)
        lcd.printf("Start PowerTip");
        wait(2.0);
        lcd.locate(0,1)
        lcd.printf("Integer %d" ,1959);
        wait(2.0);
        lcd.locate(0,2)
        lcd.printf("Float %f" ,1.732);
        wait(2.0);
        lcd.locate(0,3)
        lcd.printf("Characters %c, %c" ,'a', 65);
        wait(2.0);
        
    }
}
21 Jan 2013

Seems best to avoid confusion and modify the 'address' method and the print 'character' method. The address method should return the actual HD44780 memory address for the (column,row) location of a given displaytype. The print method should construct the correct command based on that address.

int TextLCD::address(int column, int row) {
    switch (_type) {
        case LCD16x4:
            switch (row) {
                case 0:
                    return 0x00 + column;
                case 1:
                    return 0x40 + column;
                case 2:
                    return 0x10 + column;
                case 3:
                    return 0x50 + column;
            }
        case LCD20x4:
            switch (row) {
                case 0:
                    return 0x00 + column;
                case 1:
                    return 0x40 + column;
                case 2:
                    return 0x14 + column;
                case 3:
                    return 0x54 + column;
            }


//Not sure about this one, probably typo and same as default?
        case LCD16x2B:
            return 0x00 + (row * 40) + column;

        //case LCD16x4:
        case LCD20x2:
//Covers these too
//        case LCD16x2:
//        case LCD8x2:
//        case LCD8x1:
        default:
            return 0x00 + (row * 0x40) + column;
    }
}

And for the updated print method

void TextLCD::character(int column, int row, int c) {
    int command = 0x80 | (address(column, row) & 0x7F); // Construct Set DDRAM address command
    writeCommand(command);
    writeData(c);
}
21 Jan 2013

Thanks for the responses guys, however as usual I'm going back to basics to learn/understand what happens. I printed out the HD44780U (LCD-II) manual and read about DDRAM (Display Data RAM) page 10 and CGRAM (Character Generator RAM) page 13 of the manual. I'm no expert by a long shot, but nowhere in the manual does it relate to x80 being a start address for DDRAM throughout it clearly states the first address as x00. My understanding of the CGRAM is this is where characters from the CGROM get loaded to on a clock pulse. Page 21 of the manual describes the Timing Generation Circuit and gives an example of the DDRAM address. I understand this

	

        RS  R/W  B7   B6   B5   B4   B3   B2   B1   B0
------------------------------------------------------
Set
CGRAM    0    0   0   1   ACG  ACG  ACG  ACG  ACG  ACG
address
------------------------------------------------------
Set
DDRAM    0    0   1  ADD  ADD  ADD  ADD  ADD  ADD  ADD  
address
------------------------------------------------------

to mean that when B7 is set to a logic 1 as the manual says:- Sets DDRAM address. DDRAM data is sent and received after this setting. So in my simple understanding it's a method of getting the correct Display Data address. So for consistency I tend to agree with Wim's solution since this performs work on the correct address from the manual. This is the code I will adopt and play around with. Many thanks for all replies this is a great way to learn and progress for me.

21 Jan 2013

Derek, the set DDRAM address command (1xxx xxxx) is indeed the command to set the memory address where the next character will be written to. What the LCD code shown above does is compute the memory address based on the current column and row, while taking into account what type of LCD is being used (eg 4x20). The confusing part was that the address method did not return the 'address' but the combined 'address+command' by adding 0x80.

The set CGRAM command is used to create one of the user defined characters that is supported by the HD44780. Meaning that you can create a small set of your own symbols by writing a pixel pattern (7x5) to the CGRAM. You can then show those special symbols on the display. You could use that for small logo's or other stuff like a battery full/empty indicator.

21 Jan 2013

Thanks again Wim, I wont be posting for a while you'll be glad to hear! I really feel I need to relearn about classes again and the c++ code syntax, I'm getting confused reading and understanding what's happening in code where I should not and it will help in the future when I ask questions. I'll continue to use this example though the TextLCD.cpp and TextLCD.h it's given me a lot of background understanding already.

Many thanks for your patience and advice.

28 Jan 2013

I am also using a 16x4 LCD and found that the 3rd and 4th lines were not being displayed correctly - they were displaced by 4 characters. My solution is shown below which uses lcd.locate(-4,2) and lcd.locate(-4,3) for lines 3 and 4. It would be much better if TextLCD could be enhanced to include the appropriate code for a 16x4 LCD.

#include "mbed.h"
#include "TextLCD.h"
TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD20x4); // rs, e, d4-d7
int main() {
    lcd.printf("4 * 16 = 64\n");
    lcd.locate(0,1); 
    lcd.printf("Backlit LCD\n");
    lcd.locate(-4,2); 
    lcd.printf("display\n");
    lcd.locate(-4,3);
    lcd.printf("0123456789ABCDEF\n");
}
29 Jan 2013

I think personally Wim's solution above is more robust rather than using locate. Setting the four lcd display lines to the correct starting address as Wim points out avoids confusion. I implemented it in my program and it works fine on my Powertip 1604A B display.

05 Feb 2013

I fixed the address issues with different LCD types by updating the TextLCD lib.

There is now support for

  • LCD8x1, /< 8x1 LCD panel */
  • LCD8x2, /< 8x2 LCD panel */
  • LCD16x2, /< 16x2 LCD panel (default) */
  • LCD16x2B, /< 16x2 LCD panel alternate addressing */
  • LCD16x4, /< 16x4 LCD panel */
  • LCD20x2, /< 20x2 LCD panel */
  • LCD20x4, /< 20x4 LCD panel */
  • LCD24x2, /< 24x2 LCD panel */
  • LCD24x4, /< 24x4 LCD panel (for KS0078 controller) */
  • LCD40x2 /< 40x2 LCD panel */

Also added is a new method getAddress(column,row) that will return the correct memoryaddress.

You can find the lib here: http://mbed.org/users/wim/code/TextLCD/

The code may be tested using this example:

// Hello World! for the TextLCD

#include "mbed.h"
#include "TextLCD.h"

// Host PC Communication channels
Serial pc(USBTX, USBRX); // tx, rx

//TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD16x4); // rs, e, d4-d7 ok
//TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD20x2); // rs, e, d4-d7 ok
TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD20x4); // rs, e, d4-d7 ok
//TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD24x2); // rs, e, d4-d7 ok
//TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD40x2); // rs, e, d4-d7 ok

int main() {
    pc.printf("LCD Test. Columns=%d, Rows=%d\n\r", lcd.columns(), lcd.rows());
    
    for (int row=0; row<lcd.rows(); row++) {
      int col=0;
      
      pc.printf("MemAddr(Col=%d, Row=%d)=0x%02X\n\r", col, row, lcd.getAddress(col, row));      
      lcd.putc('-');
//      lcd.putc('0' + row);
      
      for (col=1; col<lcd.columns()-1; col++) {    
        lcd.putc('*');
      }

      pc.printf("MemAddr(Col=%d, Row=%d)=0x%02X\n\r", col, row, lcd.getAddress(col, row));      
      lcd.putc('+');
        
    }       
}

Some results are shown here:

/media/uploads/wim/_scaled_img_2636.jpg

/media/uploads/wim/_scaled_img_2638.jpg

/media/uploads/wim/_scaled_img_2637.jpg

/media/uploads/wim/_scaled_img_2640.jpg

/media/uploads/wim/_scaled_img_2642.jpg

The terminal will print the actual memory address like this:

LCD Test. Columns=20, Rows=4

MemAddr(Col=0, Row=0)=0x00

MemAddr(Col=19, Row=0)=0x13

MemAddr(Col=0, Row=1)=0x40

MemAddr(Col=19, Row=1)=0x53

MemAddr(Col=0, Row=2)=0x14

MemAddr(Col=19, Row=2)=0x27

MemAddr(Col=0, Row=3)=0x54

MemAddr(Col=19, Row=3)=0x67

Edit: Added support for LCD24x4 (KS0078 controller only) and method to show or hide cursor.

05 Feb 2013

An updated forked version of the original Text LCD is here