Help using Wire.write(string) with I2C LCD

I have a 2x16 I2C character LCD (Newhaven NHD-0216K3Z-FL-GBW-V3) I can write individual characters to using the Wire.write() command. I’d like to write entire strings to it, though, and the Reference suggests Wire.write() should be able to do this without me having to explicitly send a single character at a time, but I’m not having any success. Wire.write(“string”) and Wire.write(x) (where x = “string”) both result in one character, an @, which isn’t one of the ones I sent. What’s the secret? Thanks.

I posted over in Programming but it was suggested that this is a more appropriate forum. Code below:

#include <Wire.h>

char red[] = "RED";
char green[] = "GREEN";
char blue[] = "BLUE";
char yellow[] = "YELLOW";
char orange[] = "ORANGE"; 
char brown[] = "BROWN";
char unknown[] = "UNKNOWN";

void setup() {
  Wire.begin();
  Serial.begin(9600); 
}

void loop() {
  char x;
  char* y = green;
  Serial.print("First character:  ");
  Serial.println(y[0]);   // This prints "G" to the monitor, as it should
  Wire.beginTransmission(0x28);
  Wire.write(0xFE);   // Clear display
  Wire.write(0x51);
  Wire.endTransmission();
  Wire.beginTransmission(0x28);
  Wire.write(y);  // Just prints "@" followed by the alphabet, as 
                  // does Wire.write("green") and Wire.write(green)
  Wire.endTransmission();
  for (x = 0; x < 33; x++) {     // Print the alphabet
    Wire.beginTransmission(0x28);
    if (x == 17) {
      Wire.write(0xFE); // Move cursor to 2nd line
      Wire.write(0x45);
      Wire.write(0x40);
    }
    Wire.write(64 + x);
    Wire.endTransmission();
    delay(500);
  }
}

According to the spec sheet (http://www.newhavendisplay.com/specs/NHD-0216K3Z-FL-GBW-V3.pdf) this is a monochrome 16x2 LCD display. It makes no sense tod efine colors.

I don’t know what wire.rite does, but it seems to me that this is the wrong sketch for a 16x2 LCD display.

Please find attached a bare I2C LCD sketch. It should produce ‘Hello World’ also on a 16x2 LCD display.

/* YourDuino.com Example Software Sketch
20 character 4 line I2C Display
Backpack Interface labelled “YwRobot Arduino LCM1602 IIC V1”
Connect Vcc and Ground, SDA to A4, SCL to A5 on Arduino
terry@yourduino.com */

/-----( Import needed libraries )-----/
#include <Wire.h> // Comes with Arduino IDE
// Get the LCD I2C Library here:
// https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads
// Move any other LCD libraries to another folder or delete them
// See Library “Docs” folder for possible commands etc.
#include <LiquidCrystal_I2C.h>

/-----( Declare Constants )-----/
/-----( Declare objects )-----/
// set the LCD address to 0x3Ffor a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
// addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address

/-----( Declare Variables )-----/

void setup() /----( SETUP: RUNS ONCE )----/
{
Serial.begin(9600); // Used to type in characters

lcd.begin(20,4); // initialize the lcd for 20 chars 4 lines, turn on backlight

// ------- Quick 3 blinks of backlight -------------
for(int i = 0; i< 3; i++)
{
lcd.backlight();
delay(250);
lcd.noBacklight();
delay(250);
}
lcd.backlight(); // finish with backlight on

//-------- Write characters on the display ------------------
// NOTE: Cursor Position: Lines and Characters start at 0
lcd.setCursor(3,0); //Start at character 4 on line 0
lcd.print(“Hello, world!”);
delay(1000);
lcd.setCursor(2,1);
lcd.print(“From YourDuino”);
delay(1000);
lcd.setCursor(0,2);
lcd.print(“20 by 4 Line Display”);
lcd.setCursor(0,3);
delay(2000);
lcd.print(“http://YourDuino.com”);
delay(8000);
// Wait and then tell user they can start the Serial Monitor and type in characters to
// Display. (Set Serial Monitor option to “No Line Ending”)
lcd.setCursor(0,0); //Start at character 0 on line 0
lcd.print(“Start Serial Monitor”);
lcd.setCursor(0,1);
lcd.print(“Type chars 2 display”);

}/–(end setup )—/

void loop() /----( LOOP: RUNS CONSTANTLY )----/
{
{
// when characters arrive over the serial port…
if (Serial.available()) {
// wait a bit for the entire message to arrive
delay(100);
// clear the screen
lcd.clear();
// read all the available characters
while (Serial.available() > 0) {
// display each character to the LCD
lcd.write(Serial.read());
}
}
}

}/* --(end main loop )-- */

/* ( THE END ) */

It looks as if the OP's LCD is an intelligent display. i.e. you address its native I2C address, and then simply write text. Any "control" functions are handled with escape sequence. Much like the intelligent 9600 baud Serial backpacks.

Yes. Wire.write(char *msg) should work ok. It is just another overloaded write() method. e.g. from the Wire docs:

Wire.write(value)
Wire.write(string)
Wire.write(data, length)

If you have difficulty, it is easy enough to make your own Helper function:

void lcdmsg(char *s) 
{
      uint8_t c;
      while (c = *s++) {
            Wire.write(c);
      }
}

Now that the OP has told us what the display actually is, the use of Wire now makes sense. This basic information was missing from the original post in programming sub-section.

Upon closer examination of the sketch, why so many tests that run together and overwrite one another? Why in loop so they repeat forever? There should be one and only one test of the display in the setup routine so that it runs only once. With the current code arrangement, I don't see how you know what works and what doesn't.

The ultimate difficulty for the community at large to help is that it is an uncommon display with a custom controller not supported by existing Arduino libraries. You'd be done with this aspect of the project already had you used a common $3 1602 LCD that has build-in library support. You're now left to reinvent the wheel for unknown reasons.

IMO, for what New Haven charges for their products, I'd be asking them for help. Their lack of example code is appalling.

I was fascinated by the original post. So I thought that I would try all three styles of Wire.write()
So I wrote to an AT24Cxxx EEPROM

#include <Wire.h>

uint8_t slave;
const uint16_t loc = 0x0005;

void setup()
{
    uint8_t block[] = { 0x01, 0x02, 0x03, 0x04};
    Serial.begin(9600);
    Wire.begin();
    for (slave = 0x50; slave < 0x58; slave++) {
        Wire.beginTransmission(slave);
        if (Wire.endTransmission() == 0) break;
    }
    Serial.print("24Cxx slave found at 0x");
    Serial.println(slave, HEX);
    Wire.beginTransmission(slave);
    Wire.write((uint8_t)(loc >> 8));     // hi-byte of address
    Wire.write((uint8_t)loc);
    Wire.write(0xAA);                    //(value)
    Wire.write(0x55);
    Wire.write("David Prentice");        //(string)
    Wire.write(block, sizeof(block));    //(block[], size)
    Wire.endTransmission(1);        // starts I2C sequence with STOP
    delay(5);                       // STOP starts actual page-write.
}

void loop()
{
    uint8_t buf[32];
    read_i2c_mem(loc, buf, 32, slave);
    hexdump(loc, buf, 32);
    Serial.println();
    delay(10000);
}

bool read_i2c_mem(uint16_t loc, uint8_t *buf, int n, int slave)
{
    int ask;
    Wire.beginTransmission(slave);
    Wire.write((uint8_t)(loc >> 8));        // hi-byte of address
    Wire.write((uint8_t)loc);
    if (Wire.endTransmission() != 0) return false;
    while (n) {
        ask = (n > 16) ? 16 : n;
        n -= ask;
        Wire.requestFrom(slave, ask);       // request up to WIRESIZE bytes from slave device
        while (Wire.available() && ask--)   // slave may send less than requested
        {
            *buf++ = Wire.read();           // receive a byte as character
        }
    }
    return true;
}

void printhex(uint8_t c)
{
    if (c < 0x10) Serial.print(F("0"));
    Serial.print(c, HEX);        // print the character
}

void hexdump(uint16_t loc, uint8_t *block, int16_t n)
{
    int16_t cnt;
    char ascbuf[17], *p, wid = 16;
    while (n > 0) {
        printhex(loc >> 8);
        printhex(loc);
        Serial.print((":"));
        p = ascbuf;
        cnt = n;
        if (cnt > wid) cnt = wid;
        loc += cnt;
        n -= cnt;
        while (cnt--) {
            uint8_t c = *block++;
            *p++ = (c >= ' ' && c < 128) ? c : '.';
            Serial.print((" "));
            printhex(c);
        }
        *p = 0;
        Serial.print((" *"));
        Serial.print(ascbuf);
        Serial.println(("*"));
    }
}

It behaves as expected.

David.

photoncatcher:
According to the spec sheet (http://www.newhavendisplay.com/specs/NHD-0216K3Z-FL-GBW-V3.pdf) this is a monochrome 16x2 LCD display. It makes no sense tod efine colors.

The application this LCD will be used in involves colors whose names will have to be sent to the display. I define them in the test sketch to have real data to test with. They're unrelated to the capabilities of the display.

photoncatcher:
I don't know what wire.write does

https://www.arduino.cc/en/Reference/WireWrite

photoncatcher:
Please find attached a bare I2C LCD sketch. It should produce 'Hello World' also on a 16x2 LCD display.

That code appears to require a specific I2C bus expander chip which is not part of my system.

david_prentice:
It looks as if the OP's LCD is an intelligent display. i.e. you address its native I2C address, and then simply write text. Any "control" functions are handled with escape sequence.

That is correct.

david_prentice:
Yes. Wire.write(char *msg) should work ok.

Except when I try it I only get some of the sent characters to show in the display. Wire.write ("xyz"), which should display "xyz", displays nothing.

david_prentice:
If you have difficulty, it is easy enough to make your own Helper function:

void lcdmsg(char *s) 

{
     uint8_t c;
     while (c = *s++) {
           Wire.write(c);
     }
}

That was going to be my fallback, but I wasn't able to get that to work with my own code. Yours, of course, works great, though I did modify it slightly below:

void lcdmsg(char *s) 
{
      uint8_t c;
      while (c = *s++) {
            Wire.beginTransmission(LCD_I2C);
            Wire.write(c);
            Wire.endTransmission();
      }
}

I have to confess I don't understand what while (c = *s++) does (I suck at pointers). Can you explain it?

avr_fred:
Upon closer examination of the sketch, why so many tests that run together and overwrite one another?

I’m new to Arduino. Also, I was using the serial monitor to help me keep track of where the program was. Now that the code is more reliable, I’ve pulled out the serial monitor stuff and have this:

#include <Wire.h>

const char  LCD_I2C = 0x28;   // LCD I2C address

char red[] = "RED";
char green[] = "GREEN";
char blue[] = "BLUE";
char yellow[] = "YELLOW";
char orange[] = "ORANGE"; 
char brown[] = "BROWN";
char unknown[] = "UNKNOWN";

void setup() {
  Wire.begin();
  Serial.begin(9600); 
}

void loop() {
  char x;
  char* y = orange;
  Wire.beginTransmission(LCD_I2C);
  Wire.write(0xFE);   // Clear display
  Wire.write(0x51);
  Wire.write(0xFE);   // Cursor home
  Wire.write(0x46);
  Wire.endTransmission();
  for (x = 65; x < 98; x++) {
    if (x == 81) {
      Wire.beginTransmission(LCD_I2C);
      Wire.write(0xFE); // Move cursor to 2nd line
      Wire.write(0x45);
      Wire.write(0x40 );
      Wire.endTransmission();
    }
    if (x == 67) {
      Wire.beginTransmission(LCD_I2C);
      Wire.write(y);    // Only prints part "orne"
      Wire.endTransmission();
    }
    Wire.beginTransmission(LCD_I2C);
    Wire.write(x);
    Wire.endTransmission();
    delay(500);
  }
}

void lcdmsg(char *s) 
{
      uint8_t c;
      while (c = *s++) {
        Wire.beginTransmission(LCD_I2C);
        Wire.write(c);
        Wire.endTransmission();
      }
}

avr_fred:
The ultimate difficulty for the community at large to help is that it is an uncommon display with a custom controller not supported by existing Arduino libraries.

I have other I2C devices in my design and the Wire library seemed like a simple way to let me communicate with all of them. The fewer libraries I have to resort to, the fewer dependencies my code has and the better I can understand and maintain it.

avr_fred:
a common $3 1602 LCD that has build-in library support.

Can you recommend one? The Newhaven just happens to be the one I had lying around, and I’ve had good luck with them in the past via the UART interface. First time I’ve tried the I2C. I’m certainly not wedded to it.

david_prentice:
I was fascinated by the original post. So I thought that I would try all three styles of Wire.write()
So I wrote to an AT24Cxxx EEPROM

I really appreciate you putting in the effort on this, David. I’m afraid it’s a little hard for me to follow your code, but, as I said before, I can’t get things like Wire.write(“David Prentice”) to work on this display, though I can write individual characters fine.

To everyone, I should add I've now confirmed via the I2C decoder built into my scope that

Wire.beginTransmission(LCD_I2C);
Wire.write("ORANGE");    
Wire.endTransmission();

actually does send "O", "R", "A", "N", "G", "E" out on the bus. For some reason, the LCD only displays ORNE. On the other hand, a call to:

void lcdmsg(char *s) 
{
      uint8_t c;
      while (c = *s++) {
        Wire.beginTransmission(LCD_I2C);
        Wire.write(c);
        Wire.endTransmission();
      }
}

(thanks again, David) also sends all the letters AND displays correctly. Not sure why, but I may have to settle for it working without knowing. I'd also like to understand what that while condition is doing when you get a moment, David.

I do not own your display. I have not read any NHD documentation.

I just "guessed" that it would be a Wire version of the legacy intelligent Serial LCD.

The Serial version works without gaps or Break between characters.
So I assumed that a Wire version would also accept consecutive bytes. After all, a single I2C byte takes 90us and a legacy HD44780 LCD takes about 40us per character.

It looks as if you need to send every character individually. i.e. with startTransmission() and endTransmission().
And the "intelligent" controller uses this to decode each printable character or command escape sequence.

Yes, the CLRSCREEN command and the HOME command take longer than regular data or commands.
So those escape sequences might need a delay.

NHD generally supply example code.

David.

From post #7

Can you recommend one?

While you cannot go wrong with buying from Adafruit or Sparkfun, I see their prices for simple I2C 1602's is on par with New Haven. On the upside, they have authored a large number of the Arduino libraries for such parts and so both the vendor and community support is excellent. IMO, it's always good to support them when you can.

For hobby projects where you don't need long term availability of a specific part (New Haven's market) and where delivery time isn't a huge consideration, you cannot beat buying direct from China on either eBay or Aliexpress. I find eBay to be a bit higher price wise but the good sellers tend to ship very quickly and if you actually pay extra for shipping, it's usually the faster "ePacket" service which right now is running 7 to 10 days. YMMV. This typically slows down during the end of the year holiday period here in the US and of course early in the year during the Chinese New Year.

I have had very good experiences with the eBay seller Alice1101983 as well as Czb67211960. Both of these sellers have hundreds of thousands of sales with >99% feedback ratings. Of course there are other excellent ones as well but these two are my goto sources for the more common stuff. Here is a current listing for an I2C LCD at $2.65 plus shipping.

David, in your function:

void lcdmsg(char *s) 
{
      uint8_t c;
      while (c = *s++) {
        Wire.beginTransmission(LCD_I2C);
        Wire.write(c);
        Wire.endTransmission();
      }
}

s Points to a string, so *s++ increments the pointer and assigns the resulting character to c on each pass through the loop, causing each character to be printed. How does the loop stop at the end of the string s points to? Since (c = *s++) is an assignment and not a test, why doesn't s just increment all the way through memory?

As a follow up to David's mention of New Haven's documentation: There is zero mention in the specification sheet of any delays required for any of the escape sequences. Further, there is only one demo program, in C, for an undocumented processor. The program is a hodge-podge combination of all three controller modes (I2C, SPI and serial) and the I2C demo is bit-banged. Pretty useless for the OP's purposes although it does show that the screen clear and cursor position escapes need a 10ms delay.

The only entries in their application notes for Arduino and character LCD's are for the standard hd44780 controller. Lots of demos for graphic TFT's, not so much for character mode LCD's.

I had not read the NHD documentation.

However it says:

Built-in PIC16F690 controller

So any Serial, SPI, I2C interface is implemented by the PIC controller. Depending in how "intelligent" the firmware is, I can believe it could be relatively slow.

Since the underlying LCD controller is ST7066U I guess that the raw operations are 40us.

NHD provide examples. They have never claimed to be fast or efficient.
The example "looks" as if you can send a "text" packet. i.e. consecutive I2C data bytes.

I would just do some experimentation with the Wire.h library.
e.g. a "string"

David.

Ah, but the assignment does return the value to the while(). This behavior is discussed at great length here:

david_prentice:

void lcdmsg(char *s) 

{
      uint8_t c;
      while (c = *s++) {
            Wire.write(c);
      }
}

Yes, I am guilty of bad habits. A "better" way of writing it is:

void lcdmsg(char *s) 
{
      uint8_t c;
      while ((c = *s++) != '\0) {
            Wire.write(c);
      }
}

The assignment is evaluated. And compared with the NUL terminator. All C strings have a NUL terminator. The while loop stops when you get to the NUL. i.e. only the printable characters are written.

This is why there is a Wire.write(block, n) method. This will write n bytes with any value including non-printable bytes. e.g. NUL

David.

david_prentice:
All C strings have a NUL terminator.

Yes, I was aware of that, but I couldn't see how the while loop knew when it reached the NULL. I didn't realize that feature was "built-in." Thanks for straightening that out. I'm going to assume my other problems were related to timing issues with my particular model of LCD and just use your function to send strings instead of trying to get Wire.write(string) to work.

avr_fred:
This behavior is discussed at great length here:

Thanks for that, avr_fred. Perfect.

NUL is ascii character 0. It is used as terminator for a C string.
NULL is a pointer with a zero value address. It is used as a special illegal address. e.g. for a function failure.

Some CPUs will produce a hardware exception if you pass NULL to an address register.

The AVR is a simple CPU. It does not complain if you set an address register to an illegal value.

David.