Need help with I2C code for a 16x2 LCD - DS-LCDD5

I found a couple of these modules (a PIC18 daughter board on the back of a common 16x2 LCD) It says in the datasheet that it can be controlled via I2C, but my skill level is too low to figure out how to do it.
Can somebody please give me advice on how to proceed?

The "I2C Scanner" sketch finds it, but the usual I2C-LCD libraries don't work on it.

Thank you.

Datasheet is here:

You have tried the "HD44780" library with its I²C test codes, haven't you?

The datasheet shows you how to send text and how to send commands.

Just use the regular Wire library functions. e.g. try each of the examples in the datasheet.

I doubt if there is any specific Arduino library. You will have to write your own. Or perhaps adapt the "hd44780" library. It looks as if the first two bytes of any sequence need to be 0x28,0x00 or 0x28,0x01.
From memory the LCD native I2C sequence is a single byte.

David.

I tried it with the "New Liquidcrystal" library created by Francisco Malpartida.
This one works fine with the PCF8574 I2C adaptor boards.
Where can I find the one you mentions?

I got it to work with the single wire serial connection, but can not figure out how to do it with the I2C connections.

funny adapter.

your datasheet says:

"To write text to the LCD a device write must be undertaken by the Arduino / Raspberry-Pi / I2C Master which consists of a Start condition, device ID (‘D’ bit cleared), Register address set to zero (0), one or more characters to be displayed and a stop condition"

so you might just start writing to register 0 .

Look for examples how to use I2C, and start with a simple sketch and try to write a simple A to the display. Something like that at the end of your setup():

  Wire.beginTransmission(lcdAddress);
  Wire.write(0x00);                          /
  Wire.write('A');                          // EEPROM address of deviation angle MSB
  Wire.endTransmission();

if it works - good luck to expand that to support print.h.
If not, show your wiring, a real picture of your modules/wiring and the final sketch you have tried.

The "datasheet" is a little confusing. What they call "Byte 1" looks like the Slave address. And "Byte 2" is the control byte.

Most display controller chips follow a "control byte" + "data byte..." sequence. With bit#7 meaning "Continuation" and bit #6 meaning "RegisterSelect". e.g. hex bytes

<00> <02> meaning single command <02> i.e. CLRHOM
<40> <H> meaning single text data 'H'
<C0> <H><e><l><l><o> meaning multiple text data "Hello"

Your PIC software implementation seems to use

<01> <02> meaning single command <02> i.e. CLRHOM
<00> <H> meaning single text data 'H'
<00> <H><e><l><l><o> meaning multiple text data "Hello"

Bill Perry's hd44780 library can control hardware native I2C. I suspect that you just adapt to the different "control byte" values.

Note that native I2C is completely different to the "bit-banged 4-bit parallel via I2C port expander" approach used by PCF8574-style libraries.

Oh, you will just need to try for yourself. i.e. does the datasheet mean

<28><00> <H><e><l><l><o> or
<00> <H><e><l><l><o>

David.

That backpack is not like any of the supported devices in any of the i2c libraries I've seen. This includes LiquidCrystal_I2C, newLiquidCrystal LiquidCrystal_I2C, and hd44780.
This is because it is a not using an i2c expander like the PCF8574 chip.
It is an intelligent backpack that supports multiple h/w interfaces like async serial and I2C.

I'm not sure if there is a library already for it.
The command set is similar to some other backpacks like some of the older serial backpacks made by vendors like AdaFruit, SparkFun, or ByVAC.

It would be possible to add support for to the hd44780 library, but it does not exist today.

--- bill

I must be missing something basic, this is my setup and code:
Arduino UNO, SDA(A4), SCL(A5), 10k pullup resistors on each.
As mentioned before, the "I2C scanner" sketch , detects the module at 0x14, and according to the data sheet the address becomes 0x28 when writing to the module, if I understand correct.

I am sure it must work with the wire library, but don't know where I make the mistake. That data sheet is very confusing for me, re. I2C. I got the 1 wire serial method working, but would like to play with the I2C method also.

Any other advice?

/*
I2C scanner. Scanning ...
Found address: 20 (0x14)
Done.
Found 1 device(s).


Example: (from data sheet)
To write ‘Hello’ to the display:
Byte 1 - 00101000 binary , 28 hex       // I2C address
Byte 2 - 00000000 binary , 00 hex       // Register address
Byte 3 - ‘H’ ASCII
Byte 4 - ‘e’ ASCII
Byte 5 - ‘l’ ASCII
Byte 6 - ‘l’ ASCII
Byte 7 - ‘o’ ASCII
*/


//************************************************//

// Include Arduino Wire library for I2C
#include <Wire.h>
 
// Define Slave I2C Address
#define SLAVE_ADDR 0x14                      // 7bit address = 0010100D, 0x14 hex



void setup () {
  
  Wire.begin();                                            // Initialize I2C communications as Master

 }

 

void loop () {
  
  // Write something to the Slave
  Wire.beginTransmission(0x14);                //  0x14  (SLAVE_ADDR)  0010100
  Wire.write(0x28);                                      //  (7bit SLAVE_ADDR + another 0 at LSB to enable write = 0x28    00101000
  Wire.write(0x00);                                      //  Register address  "0"
  Wire.write("1");                                         //  some text
  Wire.endTransmission();

  delay(500);
}

Use the address reported by the scanner. The Wire uses 7 bit addresses. The lsb is used to indicate read or write so in use the address is shifted one time, 0x28 becomes 0x14.

Should the loop then look like this?

void loop () {
  
  // Write something to the Slave
  Wire.beginTransmission(0x14);              //  0x14  (SLAVE_ADDR)  0010100
  Wire.write(0x14);                                      //  (7bit SLAVE_ADDR + another 0 at LSB to enable write = 0x28    00101000
  Wire.write(0x00);                                      //  Register address  "0"
  Wire.write("1");                                          // some text
  Wire.endTransmission();

}

I thought the address 0x14 (0010100) becomes 0x28 (00101000) at the 1st Wire.write to tell the module I am now writing to it.

The first write is the address of the register that you want to write to, not the device I2C address.

My take on the datasheet is that there is no address byte in the communication protocol.
The address byte is part of the i2c protocol which handled by the Wire library.
The message protocol is to send the control byte and then the data.
i.e. leave off the extra write of the address.

  Wire.write(0x14);   

as it is not part of the message protocol.

--- bill

Just try

void setup () {

    Wire.begin();                                            // Initialize I2C communications as Master
    // Write something to the Slave
    Wire.beginTransmission(0x14);                //  0x14  (SLAVE_ADDR)  0010100
    Wire.write(0x00);                            //  Register address  "0"
    Wire.write("Hello World");                   //  some text
    Wire.endTransmission();

}

That should determine whether it needs the 0x28 or not.

If successful, you just write a lcd_command(uint8_t c) function, a lcd_data(char c) function and a lcd_string(char *s) function.

void lcd_command(uint8_t cmd)
{
    Wire.beginTransmission(0x14);                //  0x14  (SLAVE_ADDR)  0010100
    Wire.write(0x01);                            //  command mode
    Wire.write(cmd);                             //  the actual command
    Wire.endTransmission();
}

void lcd_data(char c)
{
    Wire.beginTransmission(0x14);                //  0x14  (SLAVE_ADDR)  0010100
    Wire.write(0x00);                            //  data mode
    Wire.write(c);                               //  the actual data
    Wire.endTransmission();
}

void lcd_string(char *s)
{
    Wire.beginTransmission(0x14);                //  0x14  (SLAVE_ADDR)  0010100
    Wire.write(0x00);                            //  data mode
    Wire.write(s);                               //  the actual string
    Wire.endTransmission();
}

Then you can build anything you want from those primitives.

The "command set" looks pretty similar to regular HD44780.

David.

No, still not working.
I noticed that they say in the manual that the serial baud rate must be1200-9600bps.
How do I set it in this case, or does it not matter with I2C?

The baud does not matter in I2C. You must have real-life pullup resistors. Measure resistance between SDA and 5V pin on the PIC backpack.

I would expect my example to work. And would expect the lcd_command() and lcd_data() functions to work too.

But first of all, I would run the display in UART mode. And write some simple programs that use the UART versions of lcd_command() and lcd_data().
You only need one UART TXD pin. Either digital#1 with hardware Serial or a Software Serial on a GPIO pin.

When UART programs are running nicely you can try the I2C version.
If I2C is problematic, stick with the UART.

Quite honestly the PCF8574 backpacks work fine. And an LCD + backpack is very cheap.
Your PIC backpack is only attractive if the LCD has RGB backlighting.

David.

Your board has two entirely alternate interfaces. It uses one or the other. One is serial, with a given baudrate (and some mechanism of it detecting the baudrate you will actually use).

The other is I²C, which uses a synchronous clocking - thus it uses two interface lines.

Try this and see if anything is displayed:
< edit > added commands for backlight control

#include <Wire.h>


void setup() {
  Wire.begin();
  //Home (abort scroll)
  Wire.beginTransmission(0x14);
  Wire.write(0x01);
  Wire.write(0x00);
  Wire.endTransmission();
  //Clear display
  Wire.beginTransmission(0x14);
  Wire.write(0x01);
  Wire.write(0x01);
  Wire.endTransmission();
  //Cursor OFF or restore after blanking
  Wire.beginTransmission(0x14);
  Wire.write(0x01);
  Wire.write(0x0C);
  Wire.endTransmission();
  //Turn ON backlight
  Wire.beginTransmission(0x14);
  Wire.write(0x02);
  Wire.write(0x0F);
  Wire.endTransmission();
  //Backlight RED level
  Wire.beginTransmission(0x14);
  Wire.write(0x03);
  Wire.write(0xFF);
  Wire.endTransmission();
  //Backlight GREEN level
  Wire.beginTransmission(0x14);
  Wire.write(0x04);
  Wire.write(0xFF);
  Wire.endTransmission();
  //Backlight BLUE level
  Wire.beginTransmission(0x14);
  Wire.write(0x05);
  Wire.write(0xFF);
  Wire.endTransmission();
  //Set cursor to first line, first character position
  Wire.beginTransmission(0x14);
  Wire.write(0x01);
  Wire.write(128 + 0);
  Wire.endTransmission();
  //Display text
  Wire.beginTransmission(0x14);
  Wire.write(0x00);
  Wire.write("Hello World");
  Wire.endTransmission();
}

void loop() {
}

The Slave is 0x14 and not decimal 14. But the general idea looks fine. You could do the same thing with my primitives e.g.

void setup() {
  Wire.begin();
  lcd_command(0x00); //Home (abort scroll)
  lcd_command(0x01); //Clear display
  delay(2);  //just to be safe
  lcd_command(0x0C); //Cursor OFF or restore after blanking
  //lcd_helper(0x02, 0x0F); //Turn ON backlight
  lcd_command(128 + 0); //Set cursor to first line, first character position
  lcd_string("Hello World"); //Display text
}

void loop() {
}

I have omitted the backlight commands. I suggest that you write a separate helper function. Or just leave as longhand.

void lcd_helper(uint8_t cmdtype, uint8_t value)
{
    Wire.beginTransmission(0x14);                //  0x14  (SLAVE_ADDR)  0010100
    Wire.write(cmdtype);                            //  specify mode
    Wire.write(value);                             //  the actual command
    Wire.endTransmission();
}

David.

Ok, I edited the post to correct for the 0x14 device address.

I was writing everything out sequentially just for a test, so that the code did not get too complicated, putting in all the commands that might possibly be needed to setup the display.