2x20 (ST7066U) lcd not working

Hi everyone;
I have got this exact lcd screen, which uses the ST7066U driver. I want to use it in 4 bit mode with a pcf8574. It si cabled like this:

LCD pcf8574
7 7
6 6
5 5
4 4
EN 1
RS 0

and the address is 0x20.
The datasheet of the driver is this.

this is my non working code:

#include <Wire.h>
#define I2C_ADDR    0x20


void write(byte data) {

  Wire.beginTransmission(I2C_ADDR); // transmit to device #8
  Wire.write(data);
  Wire.endTransmission();
}

void setup()
{

  Serial.begin(9600);

  Wire.begin(); 



  delay(16);
  write(0b00110000);
  delay(5);
  write(0b00110000);
  delayMicroseconds(110);
  write(0b00100000);
  delay(5);


//            0b  7654E<||>RS
  write(0b01100000);
  write(0b00000000);
  delayMicroseconds(50);
//            0b  7654E<||>RS
  write(0b00000000);
  write(0b10000000);
  delayMicroseconds(50);
//            0b  7654E<||>RS
  write(0b00000000);
  write(0b00010000);
  delay(2);
//            0b  7654E<||>RS
  write(0b00000000);
  write(0b01100000);
  delayMicroseconds(50);

}
void loop()
{ }

My questions are these:
1-Why is my code not working? the short example from this tutorial says that you should see the cursosr at the left, but nothing appears.
2-
Is there any good tutorial to learn how to use lcds at this level? I get lost and dont fully understand the datasheet :frowning:
I will apreciate any help or suggestions :slight_smile:
Thanks in advance

Diablillowilly: 1-Why is my code not working?

Because you are not wiggling the PCF8574 pins correctly to control the LCD. You appear to be sending an 8 bit pattern in attempt to set all 8 LCD dbx pins. However you can't do that. The PCF8574 only has an 8 bit output port so when hooked to an LCD, it can only control 4 data pins and up to 4 other signals. You have EN and RS hooked up. You have to send each nibble separately. The tutorial you linked to talks about this. So does the data sheet. But before you can talk to the LCD in 4 bit mode you must put the LCD in to 4 bit mode. There is a very specific sequence of commands that must be sent to the LCD to reliably get the LCD into 4 bit mode that must be done first.

In order to send something/anything to the LCD, you must wiggle the E signal. How that works for a write (which is sending data or commands) is documented in the datasheet on page 7. The summary is you must set up the RS signal and the 4 data bits to what you want, then raise EN, then Lower it. It is the lowering of EN that sends the 4 bits to the LCD.

If you look closely at the signals on page 7 you see that RS must be stable when EN is raised (tAS) is zero, and the data signals can be set at any time but must be stable 40ns (tDSW) before lowering EN and then remain stable for 10ns (tH) after EN is lowered. It takes multiple byte transfers to the PCF8574 to do this.

This chipset is signal & command compatible with the hd44780 chip and I think the information in the hitachi hd44780 datasheet is a bit better and more detailed about 4 bit mode and its initialization than what I see in this datasheet. Although the low level timing for the hd44780 is a bit different. For example it wants 40ns for tAs vs this chip says tAS can be zero. However, from what I've seen zero tAS works on the PCF8574 as well.

Is there any good tutorial to learn how to use lcds at this level?

Not that I know of off the top of my head but the Internet is full of stuff. Try doing some googling.

You may want to look at an Arduino library that actually works doing this. Have a look this library. It is available in the Arduino library manager: - LiquidCrystal_I2C

I get lost and dont fully understand the datasheet :(

That may be an issue as working at this level requires manipulating actual h/w signals and the description of how the h/w works and the required timing is the datasheet.

If you are just wanting to use the LCD, and not write you own code (which is a lot of work), then I'd recommend wiring it up a bit differently and using an existing working library. And then either using the LiquidCrystal_I2C library or my hd44780 library with the hd44780_I2Cexp i/o class.

To use LiquidCrystal_I2C library you must wired it up like this:

LCD     pcf8574 output port pin
DB7        P7
DB6        P6
DB5        P5
DB4        P4
           P3 (optional backlight circuit control)
 EN        P2
 RW        P1
 RS        P0

The hd44780 library can work with that pin mapping as well.

BTW, you can buy pre-made i2c backpacks for these types of LCDs which include backlight control for less than $1 USD shipped to your door.

--- bill

Thanks you very much for your time :slight_smile:

bperrybap:
Because you are not wiggling the PCF8574 pins correctly to control the LCD.
You appear to be sending an 8 bit pattern in attempt to set all 8 LCD dbx pins. However you can’t do that.
The PCF8574 only has an 8 bit output port so when hooked to an LCD, it can only control 4 data pins and up to 4 other signals. You have EN and RS hooked up.
You have to send each nibble separately. The tutorial you linked to talks about this. So does the data sheet.
But before you can talk to the LCD in 4 bit mode you must put the LCD in to 4 bit mode.
There is a very specific sequence of commands that must be sent to the LCD to reliably get the LCD into 4 bit mode that must be done first.

In order to send something/anything to the LCD, you must wiggle the E signal.
How that works for a write (which is sending data or commands) is documented in the datasheet on page 7.
The summary is you must set up the RS signal and the 4 data bits to what you want, then raise EN, then Lower it. It is the lowering of EN that sends the 4 bits to the LCD.

If you look closely at the signals on page 7 you see that RS must be stable when EN is raised (tAS) is zero, and the data signals can be set at any time but must be stable 40ns (tDSW) before lowering EN and then remain stable for 10ns (tH) after EN is lowered.
It takes multiple byte transfers to the PCF8574 to do this.

taking all of this into account and rereading the datasheet, I have made this new improved code which has in account the E pin, the times, and the nibbles, but it still doesnt work:

void command(byte data) {

  Wire.beginTransmission(I2C_ADDR); // transmit to device #8

  //         0b7654E<||>RS
  Wire.write(0b00000010);//enable at 1 to make the lcd listen the command, and rs at zero because its a command and not data
  delayMicroseconds(40);//Tdsw delay

  byte first_nibble = data>>4;//I make this so it doesnt send anything to the 2nd nibble where the EN and RS are
  //first_nibble = first_nibble >> 4;
  first_nibble = first_nibble << 4;

  first_nibble = first_nibble | 0b00000010;//keep the E pin High while sending the nibble
  //Serial.print("fn_");
  //Serial.println(first_nibble, BIN);
  Wire.write(first_nibble);//first nibble


  delayMicroseconds(10);//Th delay
  Wire.write(0b00000000);//lower E pin so it reads the nibble
  delayMicroseconds(40);//Tdsw delay



  byte second_nible = data << 4;//I make this so it doesnt send anything to the 2nd nibble where the EN and RS are
  //second_nible = second_nible << 4;

  second_nible = second_nible | 0b00000010;//keep the E pin High while sending the nibble
  //Serial.print("sn_");
  //Serial.println(second_nible, BIN);
  Wire.write(second_nible);//second nibble
  //         0b7654E<||>RS
  delayMicroseconds(10);//Th delay
  Wire.write(0b00000000);//lower everything
  Wire.endTransmission();
}
void setup()
{

  Serial.begin(9600);

  Wire.begin(); // join i2c bus (address optional for master)


  delay(1000);//initial delay

  //0x30
  delay(50);//power applied delay
  command(0b00110000);
  delayMicroseconds(160);
  command(0b00110000);
  delayMicroseconds(160);
  command(0b00110000);
  delay(10);

  //0x20
  command(0b00100000);

  //0x28
  command(0b00101000);

  //0x10
  command(0b00010000);

  //0x0f
  command(0b00001111);

  //0x06
  command(0b00000110);
}

void loop(){}

bperrybap:
If you are just wanting to use the LCD, and not write you own code (which is a lot of work),
then I’d recommend wiring it up a bit differently and using an existing working library.
And then either using the LiquidCrystal_I2C library or my hd44780 library with the hd44780_I2Cexp i/o class.

To use LiquidCrystal_I2C library you must wired it up like this:

LCD     pcf8574 output port pin

DB7        P7
DB6        P6
DB5        P5
DB4        P4
          P3 (optional backlight circuit control)
EN        P2
RW        P1
RS        P0




The hd44780 library can work with that pin mapping as well.

I actually have the pin RW jsut soldered to groung because Im not reading anything from the display, and the pins 3 & 4 of the pcf are connected to a Digital Potentiometer to regulate the contrast. Because of this I am not using libraries, and also because I want to learn how do these lcds work. though it seems to be pretty tough.

Here is some feedback. The datasheet timing is in ns (nano-seconds) not us (micro-seconds) while you can delay longer than the h/w requires us are 3 orders of magnitude larger than ns.

Also, the Wire library doesn't send any of the bytes you may be sending using write() calls until endTransmission() is called. Due to the timing of I2C it buffers the data and sends it all back to back over the bus when you call endTransmission(). Any delays between write() calls are not inserting delays between bytes written to the device but are simply delaying data being written to the buffer. You do not want to do this.

Another big thing to note is what I said in post #1

But before you can talk to the LCD in 4 bit mode you must put the LCD in to 4 bit mode. There is a very specific sequence of commands that must be sent to the LCD to reliably get the LCD into 4 bit mode that must be done first.

You have not posted your full code but if command() is the only way you send things to the LCD then you have not yet initialized the LCD into 4 bit mode. And those initial commands to put the device into 4 bit mode does not and cannot use code that sends two nibbles as the LCD is in 8 bit mode so commands are sent using a single EN strobe vs two.

Writing a 4 bit LCD library is a large undertaking. You probably should look closer at the hd44780 datasheet and study some libraries that are actually working. There is also a very large comment in my hd44780 library that has lots of explanation as to how 4bit initialization works.

--- bill

Thanks for answering so fast. At first I didnt know there were libraries that worked with this chip, or that were compatible, or that you could just not use the rw pin. I have installed your library, and it seems great, very well documented, so I was trying the hello_world_i2c example, but it didnt worked, so I search through the files to see the hd44780_I2Cexp constructor, and I saw all the options there were, so I changed the construnctor that comes with the example(hd44780_I2Cexp lcd;) , to :

// Constructor without r/w control without backlight control
hd44780_I2Cexp(uint8_t i2c_addr, I2CexpType type, uint8_t rs, uint8_t en,
			 uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7 )

(full example code):

#include <Wire.h>
#include <hd44780.h>                       // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header


//OLD:
//hd44780_I2Cexp lcd; // declare lcd object: auto locate & config exapander chip

//NEW
hd44780_I2Cexp lcd(0x20, I2Cexp_PCF8574, 0, 1,4,5,6,7);

// LCD geometry
const int LCD_COLS = 20;
const int LCD_ROWS = 2;

void setup()
{
int status;

	// initialize LCD with number of columns and rows: 
	// hd44780 returns a status from begin() that can be used
	// to determine if initalization failed.
	// the actual status codes are defined in <hd44780.h>
	// See the values RV_XXXX
	//
	// looking at the return status from begin() is optional
	// it is being done here to provide feedback should there be an issue
	//
	// note:
	//	begin() will automatically turn on the backlight
	//
	    Serial.begin(9600);
	status = lcd.begin(LCD_COLS, LCD_ROWS);
	if(status) // non zero status means it was unsuccesful
	{
		status = -status; // convert negative status value to positive number

             Serial.println(status);//dont know if 
		// begin() failed so blink error code using the onboard LED if possible
		hd44780::fatalError(status); // does not return
	}

	// initalization was successful, the backlight should be on now

	// Print a message to the LCD
	lcd.print("Hello, World!");
}

void loop() {}

but this still doesnt show anything in the screen.On the serial port it doesnt print any error code. The only clue of what can fail Is the timing that you said it was a bit different on the hd44780 chip

bperrybap:
This chipset is signal & command compatible with the hd44780 chip and I think the information in the hitachi hd44780 datasheet is a bit better and more detailed about 4 bit mode and its initialization than what I see in this datasheet.
Although the low level timing for the hd44780 is a bit different. For example it wants 40ns for tAs vs this chip says tAS can be zero. However, from what I’ve seen zero tAS works on the PCF8574 as well.

Any idea of what could I be doing wrong or could not be working?

Ah I see you found the extended constructor(s). The parameters that you entered should work if you have it wired up the way you said in the first post?

Are you sure the I2C address is 0x20? Is the onboard LED blinking?

Run the diag sketch (I2CexpDiag). It will likely not detect your pin mappings but it will display the I2C address of any devices found or any i2c signal issues with pullups.

--- bill

I actually have the pin RW jsut soldered to groung because Im not reading anything from the display, and the pins 3 & 4 of the pcf are connected to a Digital Potentiometer to regulate the contrast.

So you don't have the PCF8574 wired up like you stated in the original post. (no mention of anything other than LCD wired up to PCF8574) I would not recommend trying to share P4 between the LCD and something else.

I looked closer at the code. The way the code is currently written. If the backlight is not configured, then the backlight bit mask it set to 0 What this means is that any unused PCF8574 output port pins are set to low. In your case with the constructor you used, that means that P2 and P3 will always be low. Not sure that that means for you other circuitry.

I would recommend that you wire in a pot or resistor for Vo contrast control to first get things working before trying to move on to fancier things.

BTW, I'm not sure why I left out a constructor that autolocates without rw and and without backlight control but if you want to autolocate the PCF8574 i2c address you can use an adddress of 0 (zero).

--- bill

The stack of the IC2expDiag:

********************************************************************
Serial Initialized
--------------------------------------------------------------------
I2CexpDiag - i2c LCD i/o expander backpack diagnostic tool
--------------------------------------------------------------------
hd44780 lib version: 0.9.1
--------------------------------------------------------------------
Reported Arduino Revision: 1.8.5
CPU ARCH: AVR - F_CPU: 16000000
--------------------------------------------------------------------
 A4: digital pin: 18
 A5: digital pin: 19
SDA: digital pin: 18
SCL: digital pin: 19
--------------------------------------------------------------------
Checking for required external I2C pull-up on SDA - NO
Checking for required external I2C pull-up on SCL - NO
********************************************************************
WARNING: I2C requires external pullups for proper operation
It may appear to work without them, but may be unreliable and slower
Do not be surprised if it fails to work correctly
Install external pullup resistors to ensure proper I2C operation
********************************************************************
--------------------------------------------------------------------
Scanning i2c bus for devices..
 i2c device found at address 0x20
Total I2C devices found: 1
--------------------------------------------------------------------
Scanning i2c bus for all lcd displays
 LCD at address: 0x20 | config: P01245673L | R/W control: Yes
Total LCD devices found: 1
--------------------------------------------------------------------
LCD Display Memory Test
Display: 0
 LCD stuck BUSY status
--------------------------------------------------------------------
No working LCD devices

Yes, the address was 0x20, and the led blinks in an iregular way.

bperrybap: So you don't have the PCF8574 wired up like you stated in the original post. (no mention of anything other than LCD wired up to PCF8574) I would not recommend trying to share P4 between the LCD and something else.

Im gonna end wiring the whole pcf to the lcd if I keep encountering more errors :sob:

bperrybap: I looked closer at the code. The way the code is currently written. If the backlight is not configured, then the backlight bit mask it set to 0 What this means is that any unused PCF8574 output port pins are set to low. In your case with the constructor you used, that means that P2 and P3 will always be low. Not sure that that means for you other circuitry.

In the 2 open pins(2,3), I have got a digital pot, for the contrast, so I would like to keep them in 0 if posible.

bperrybap: I would recommend that you wire in a pot or resistor for Vo contrast control to first get things working before trying to move on to fancier things.

Done, when moving to one side(the contrast pin receives 5v it shows the first row of black squares), anywhere else It doesnt appear anything

bperrybap: BTW, I'm not sure why I left out a constructor that autolocates without rw and and without backlight control but if you want to autolocate the PCF8574 i2c address you can use an adddress of 0 (zero).

That's cool, if i finaly get the display to work, I will be able to use the digital pot(it just uses 2 pins) without having to add another pcf. I haven't seen any other library that alows that.

I tried again the hello world, and it worked. I had to turn the contrast between 0V and 0.2V,if it is not in that range, the text doesnt appear. that is very weird.

Reading the stack:

Why are those pullups necessary, what do they do?? Why do you think the IC2expDiag doesnt detect the lcd? maybe because of teh RW pin not being connected, so it cant read anything?

So then if I use that constructor, I will be able to use the 2 pins left of the pcf without having the library interfiering right?(thats cool, and the only library that I found that allows you not to use the rw and bl pins)

Thank you very much, you have really helped me here. Also, your library is great, it is pretty well documented, and has lots of settings, and posibilities like the constructors. I will be using it in the future for sure. :)

Diablillowilly: I tried again the hello world, and it worked. I had to turn the contrast between 0V and 0.2V,if it is not in that range, the text doesnt appear. that is very weird.

The needed voltage for a suitable contrast at room temperature and around 5V vcc is close to 0v.

Why are those pullups necessary, what do they do??

Because i2c is an open drain bus. Pullups are required for operation since that is what generates the HIGH signals. It is done this way because I2C is a bus. This allows any master or slave to communicate by yanking a signal low and will not cause a signal short between master(s) and devices. You may want to have a read of some information about I2C.

The i2c h/w inside the AVR allows enabling the internal pullups on the SDA and SCL pins; however, they are too weak and way out of spec. While they can and often do work when short wires with low capacitance are used, there can also be lots of issues because the slew rate of the rising high signals can be too long and so the signals are much more subject to errors and glitches. I'd recommend added the required external pullups rather than depending on the internal AVR pullups which are inadequate. If not now, you will have eventually issues by not using external pullups. I'd use at least 4.7k pullup resistors but not higher than 10k and no lower than 1k.

Why do you think the IC2expDiag doesnt detect the lcd? maybe because of teh RW pin not being connected, so it cant read anything?

It did detect an LCD, but like I mentioned earlier would be the case, it didn't detect your particular pin mappings. Detecting pin mappings is not trivial; actually it is quite complex. The pin mapping you are wanting to use is not used by any other i2c backpack that I've ever seen so the s/w does not look for that pin mapping combination. I'm not even sure if it is possible to detect that pin combination - It probably is, but since no other lcd backpack is using it, supporting it isn't that interesting to me, particularly since it would make the auto detection code larger and more complex to support it. Like I said, I'd recommend that you change your pin mapping. I suggested the most common pin mapping used by LCD backpacks which will offer the maximum amount of compatibility. You don't have to hook up r/w to P1 but you should hook up RS and EN to the pins I showed vs the pins you have selected if you want to be compatible with some of the existing s/w out there like LiquidCrystal_I2C and hd44780_I2Cexp auto detection.

So then if I use that constructor, I will be able to use the 2 pins left of the pcf without having the library interfiering right?(thats cool, and the only library that I found that allows you not to use the rw and bl pins)

Well kind of. The PCF8574 has a very primitive output port. They call it "quasi" bi-direction. How it really works is not very well documented in the datasheet. I'd say it is actually confusing and a bit misleading. This is how it really works. There are no direction bits. You can write 8 bits, and you can read 8 bits. When you write, it updates all 8 bits and sets the corresponding Px output state, when you read, it reads the state of the actual pins. (what you wrote to the port and what you read are not necessarily the same) The outputs are not driven high, there is a weak pullup inside that gets enabled when the pin is set to 1/high. The outputs are driven low when the Px pin is set to 0/low. So to read a Px pin hooked to an external signal, you set the Px pin to 1/high then the external signal can drive it low. But if you set the Px pin to low and the external signal tries to drive the pin high, you have a short and bad things can happen.

Keep all that in mind. This means when using the pins for outputs you can't source any current (ok 20 ua - but that is effectively nothing) but you can sink 10ma.

Now in terms of using the 2 unused Px pins for something else, that can be tricky and would require modifications to the hd44780 code. This is because all writes to the pcf8574 output port are 8 bits. There is no way to write only 6 of the bits. Currently the unused Px pins normally used for r/w and bl are set to low. hd44780_I2Cexp would have to know the desired state of those pins each time it wrote to the output port and then whatever other code was controlling those two pins would have to make sure it wasn't interfering with the LCD pin signals.

--- bill

I just had a read of the datasheet for your digital pot. If you didn't want to muck with the library s/w and you are willing to give up backlight control, and auto pin detection, here is an alternative. Hook the /CS signal up to the backlight control pin. Hook the UD an INC pins to the same pins as DB4 and DB5 This will allow you to address the digital pot using normal writes whenever the "backlight" is turned on.' You would use the larger constructor that includes backlight pin and level. The level would be LOW if you wanted the CS pin low when you turned the "backlight" on. When sending a byte, the pot would see bit 4 and bit 5 then a few microseconds later it would see bit 0 and bit 1. Because each nibble is sent separately. You could take advantage of this to do an increment or decrement with a single lcd.write().

The side effect of controlling the pot this way would be that garbage characters would also be potentially be written to display. However, you could hide this by setting the cursor to a location that is off the end of a line before doing talking to the pot chip.

just an idea.

--- bill

BTW, the library does have support for a setContrast() function that could be used, but it would require updates to the hd44780_I2Cexp code. --- bill