My I2C LCD library for the Electric Imp


#1

Hello everyone,

I bought an Arduino compatible I2C LCD.
I wanted to hook it up to the Imp with @mdobbs 's code, but it didn’t work - so I wrote my own library
It’s published under LGPL 2.1 license and available on Github

Note that the LCDs sold on eBay have internal pull-ups which pull the I2C bus to 5v, so don’t power it with 5v and connect it to the Imp - it WILL damage it
If you’re using it with the Imp, make sure you either:

  1. Power up the LCD with 3.3 volts, so the pull-ups will pull it to 3.3v (I don’t recommend doing this, the brightness will be horrible)
  2. Put a zener diode to lower the pull-up to 3.3v

At the bottom of the code there’s an example usage.

Would love your feedback.

Omri.


#2

Works great! Would love to see a version of the library for this: http://learn.adafruit.com/adafruit-led-backpack/0-dot-56-seven-segment-backpack


#3

I’ve been working with this and apparently am not getting something right. I pasted the code in, in it’s entirety. It builds and downloads apparently without error but I get no output on the screen. So I then tried changing the address and pins on line 308 “lcd <- ImpLcd(hardware.i2c12, 0x40, 2, 0);” I changed the i2c port to 12 (as I’m using pins 1 and 2. And my serial address of my backpack is 0x40.

Are these the only lines that need changed to get output? Can I add a line in to output a server log of successful or unsuccessful communication? I’m new at this so I’m sorry if I’ve asked a dumb question.


#4

Hi @sconner,

You are correct. The only line you should edit is 308 to get the example working.
My suspicions:

  1. You mixed the clock/data pins, try swapping them
  2. You have an incompatible LCD screen (have you tried @mdobbs 's version? I’ve linked it above)
  3. You have the wrong I2C base address

What happens if you replace line 111

return this.i2cPort.write((this.lcdAddress << 1) | 0x0, data);

with

server.log(this.i2cPort.write((this.lcdAddress << 1) | 0x0, data));

?

The i2cPort.write() function should return 0 upon successful, after you’ve altered the code you should see the return value printed on the IDE console a few times.
If you see a non-zero value, you probably specified the wrong address. Try to find the datasheet for the I2C chip and find the right address there.

Can you attach an image of the LCD module you have? If you have an Arduino near by, try testing it with Arduino LiquidCrystal_I2C library, see if it works.

Good luck,
Omri.

P.S.: @jackchalkley unfortunately, I don’t have of these at my disposal, so I can’t help you with that :\


#5

Thanks for the input omri. I got quite busy with work and wasn’t able to tinker for a while.

I get a return value of 0 normally. If I change the address to an invalid address it comes back with -2. So I’m pretty confident my address is right.

When I put in that line it says “ERROR: the index ‘data’ does not exist”


#6

@sconner can you paste your entire code? I’ll try to help you.


#7

Here is what I’ve been working with. I have had many iterations, so it may not be 100%. It started as you can see from Matt Dobbs New Haven LCD code. I used it as a base and have been tinkering with it since. I’m pretty sure the first part is working correctly as I get a return value of 0. It must be in what I’m trying to write to it, the format or something. I’ve banged my head on the desk for days on it and am just not getting it. Thanks for any help you can provide omri!

This is the output I get:
2013-11-26 19:12:08 UTC-6: [Status] Downloading new code
2013-11-26 19:12:08 UTC-6: [Status] Device configured to be "NewHaven_LCD test code"
2013-11-26 19:12:08 UTC-6: [Device] initializing LCD with return value: 0(0 means successful I2C communication.)
2013-11-26 19:12:08 UTC-6: [Device] 2013/10/27 1:12:8 UTC, using impee xxxx

`/* Basic code to write text to New Haven LCD display via I2C */
// Matt.Dobbs@mcgill.ca June 2013
// New Haven NHD-C0216CIZ-FSW-FBW-3V3 is I2C LCD display, 3V, 2 lines of 16 characters, $11 @ Digikey
//
// Resources:
// http://www.newhavendisplay.com/specs/NHD-C0216CiZ-FSW-FBW-3V3.pdf (datasheet)
// http://dbindner.freeshell.org/msp430/lcd_i2c.html (example for a similar LCD)
// http://www.newhavendisplay.com/app_notes/ST7032.pdf (app note for LCD controller used by New Haven, see font table on last pages)
// font table: http://akizukidenshi.com/download/ds/sitronix/st7032.pdf (last pages)

//-----------------------------------------------------------------------------------------
class NewHaven_LCD {
// Data Members
i2cPort = null;
// This device has a manufacturer hardwired I2C address of 0b01111100=0x7C for write
// change the last bit to one, 0b01111101=0x7D for read.
i2c_writeAddress = 0x40;
i2c_readAddress = 0x41;

//-------------------
constructor( i2c_port ) {
    // example:   local myLCD = NewHaven_LCD(I2C_12);
    if(i2c_port == I2C_12)
    {
        // Configure I2C bus on pins 1 & 2
        hardware.configure(I2C_12);
        hardware.i2c12.configure(CLOCK_SPEED_100_KHZ);
        i2cPort = hardware.i2c12;
    }
    else if(i2c_port == I2C_89)
    {
        // Configure I2C bus on pins 8 & 9
        hardware.configure(I2C_89);
        hardware.i2c89.configure(CLOCK_SPEED_100_KHZ);
        i2cPort = hardware.i2c89;
    }
    else
    {
        server.log("Invalid I2C port " + i2c_port + " specified in NewHaven_LCD::constructor.");
    }
    // initialize the LCD communications:
    server.log( "Initializing LCD with return value: " + init_LCD() + " (0 means successful I2C communication.)" );

}

function init_LCD() {        
    //    These initialization parameters follow page 11 of datasheet http://www.newhavendisplay.com/specs/NHD-C0216CiZ-FSW-FBW-3V3.pdf
    //    Datasheet first sets the function to 0x38= 8-bit bus, 2-line display, extension instruction mode,
    //    then immediately follows this by the 0x39 function. I can't see any reason to do this, so I removed the first function set.
    imp.sleep(0.01); // Wait 10 milliseconds
    local result = i2cPort.write(i2c_writeAddress, 
                                format("%c%c",0x80,     // Control byte: one instrution word will follow
                                0x39) );  // function: 8-bit bus, 2-line display, normal instruction mode.
    imp.sleep(0.01); // Wait a while,

    // This configuration string taken directly from datasheet, page 11.
	//  0x01=0b0000.0001	Clear Display
    //  0x14=0b0001.0100	Move cursor right one.
    //  0x10=0b0001.0000	Move cursor left one.
    //  0x1C=0b0001.1100	Scroll right
	//  0x18=0b0001.1000	Scroll left
	//  0x0C=0b0000.1100	Turn display on
	//  0x08=0b0000.1000	Turn display off
	//  0x80+=0b1000.0000	Set cursor position

    //  0x71=0b0111.0001  Contrast set (datasheet recommeded 0x78, but the contrast was unreadable. 0x71 works best)
    //  0x5E=0b0101.1110  Icon display on, booster on, contrast set.
    //  0x6D=0b0110.1101  Follower circuit on, amplifier=1?
    //  0x0C=0b0000.1100  Display on, cursor off.
    //  0x01=0b0000.0001  Clear display.
    //  0x06=0b0000.0110  Entry mode set to cursor-moves-right.

/* result = result | i2cPort.write(i2c_writeAddress,
format("%c%c%c%c%c%c%c%c",0x00, // Control byte: indicates many instruction bytes will follow.
0x14,0x71,0x5E,0x6D,0x0C,0x01,0x06) );
*/
result = result | i2cPort.write(i2c_writeAddress,
format("%c%c%c%c",0x00 // Control byte: indicates many instruction bytes will follow.
0x7c157,0x7c4,0x01) ); // Clear display.

    imp.sleep(0.01); // Wait 10 milliseconds
    return result;
    
}

function clear_display() {
    return i2cPort.write(i2c_writeAddress, format("%c%c",0x80,     // Control byte: one instruction word will follow
                                                  0x01) );  // Clear display.
}

function goto_line1start() { // returns home to the start of the 1st line, without clearing.
    return i2cPort.write(i2c_writeAddress, format("%c%c",0x80,     // Control byte: one instruction word will follow
                                                  0x02) );  // set cursor to home position
}

function goto_line2start() { // returns to the start of the 2nd line, without clearing.
    return i2cPort.write(i2c_writeAddress, format("%c%c",0x80, // Control byte: one instruction word will follow
                                           0xc0) );  // set cursor to position 0x40, set command is 0x80. 0x80+0x40 = 0xC0
}

function show( text ) {
// Each line allows for 40 characters, of which only the first 16 show.        
return i2cPort.write(i2c_writeAddress, format("%c",0x40)+     // Control byte: data bytes follow, data is RAM data.
                                           text ); 
}

function test( text ) {
// Each line allows for 40 characters, of which only the first 16 show. 


return i2cPort.write(i2c_writeAddress, format("%c%c",0x0c,0x81));
return i2cPort.write(i2c_writeAddress, format("%c"0x07) "157");     
return i2cPort.write(i2c_writeAddress, format("%c"0x81)+"blah"); 
}
//-------------------
function get_date_string() {
    local d = date(); // the time() function would return seconds since 1970.
    return ( d["year"] + "/" + d["month"] + "/" + d["day"] + "  " +
             d["hour"] + ":" + d["min"] + ":" + d["sec"] + " UTC" );
}

}

imp.configure(“NewHaven_LCD test code”, [], []);

// uses i2c on pins 1, 2
local myLCD = NewHaven_LCD(I2C_12);
local counter = 0;

function bigLoop() {
counter = counter +1;

server.log( myLCD.get_date_string() + ", using impee " + hardware.getimpeeid() );
myLCD.test("blah");

/*
myLCD.clear_display();
myLCD.show( “Hello world” + myLCD.get_date_string() );
myLCD.goto_line2start();
myLCD.show(“go Habs, go”);
*/
imp.wakeup(5, bigLoop); // sleep for 10 seconds
}

bigLoop();`


#8

I also tried yours at https://github.com/omribahumi/imp_i2c_lcd, adjusted the port and address on line 308 and get nothing. I’m really struggling with this thing!


#9

I didn’t have any luck when I tried either, and it’s been sitting in a parts box since. I was able to getting 0’s back on occasion, but never anything to the screen. I think I’ll try to make a better “backpack” will all the caps and try again.


#10

What are you using for pullup resistors on the SDA and SCLK?


#11

I have this running in a loop, and trying to configure the LCD every 5 seconds. Most of the time I get a -2, but every say 6-8 tries, I get a 0.


#12

No pullup resistors on pins 1 or 2. The IMP goes directly to the backpack. I’ve read that before, though I’m not sure why you would need a pullup resistor on an output? Can you educate me? BTW, I did try adding a pullup resistor from pins 1 and 2 to 3.3V. Same thing. Returns 0 but I get nothing on the screen.


#13

The LCD module I have has internal pull-ups on the I2C bus, but because I’m powering it up with 5V (higher brightness), it could damage the Imp.
I use a zener diode to pull it down to 3.3v (the I2C bus, that is).

This is the module I have:
http://www.ebay.com/itm/New-for-Arduino-IIC-I2C-TWI-1602-Serial-LCD-Module-Display-Blue-new-/330810324593?pt=LH_DefaultDomain_0&hash=item4d05d4f671


#14

BTW, do you have an Arduino laying around?
Maybe you can test your module with an Arduino, using this library:


#15

Pin 1 is the active low reset, so 2 and 3 get the pull-ups to 3.3v per the datasheet. I am just as frustrated as you are, but I know I have seen pullup resistors mentioned for I2C communication before.

I think I have a hardware problem with my weird returns.

I do have an arduino laying around… not a bad idea. :slight_smile:


#16

What I2C IO expander chip does your module use?
Mine uses PCF8574


#17

Datasheet says: Built-in ST7032i-oD with I²C interface


#18

Don’t think it’ll work.
The protocols seem different.

Edit: the module I have is actually a parallel LCD with an I2C IO expander:
http://www.ebay.com/itm/New-5V-IIC-I2C-Serial-Interface-Board-Module-For-Arduino-1602-LCD-Display-/360791383879?pt=LH_DefaultDomain_0&hash=item5400d79f47


#19

I got it working. RS needs to be pulled high, if you aren’t. I’ll post the code I am using in a sec…


#20

I have it working this way:

Hardware set up just like the datasheet shows, with 10K pull-up resistors on Pins 2,3, and Pin 1 pulled up to 3.3V with a 10k resistor as well. I have the indicated caps installed, and am using pins 1 and 2 for I2C.

`/* Basic code to write text to New Haven LCD display via I2C */
// Matt.Dobbs@mcgill.ca June 2013
// New Haven NHD-C0216CIZ-FSW-FBW-3V3 is I2C LCD display, 3V, 2 lines of 16 characters, $11 @ Digikey
//
// Resources:
// http://www.newhavendisplay.com/specs/NHD-C0216CiZ-FSW-FBW-3V3.pdf (datasheet)
// http://dbindner.freeshell.org/msp430/lcd_i2c.html (example for a similar LCD)
// http://www.newhavendisplay.com/app_notes/ST7032.pdf (app note for LCD controller used by New Haven, see font table on last pages)
// font table: http://akizukidenshi.com/download/ds/sitronix/st7032.pdf (last pages)

//-----------------------------------------------------------------------------------------
class NewHaven_LCD {
// Data Members
i2cPort = null;
// This device has a manufacturer hardwired I2C address of 0b01111100=0x7C for write
// change the last bit to one, 0b01111101=0x7D for read.
i2c_writeAddress = 0x7C;
i2c_readAddress = 0x7D;

//-------------------
constructor( i2c_port ) {
    // example:   local myLCD = NewHaven_LCD(I2C_12);
    if(i2c_port == I2C_12)
    {
        // Configure I2C bus on pins 1 & 2
        hardware.configure(I2C_12);
        hardware.i2c12.configure(CLOCK_SPEED_100_KHZ);
        i2cPort = hardware.i2c12;
    }
    else if(i2c_port == I2C_89)
    {
        // Configure I2C bus on pins 8 & 9
        hardware.configure(I2C_89);
        hardware.i2c89.configure(CLOCK_SPEED_100_KHZ);
        i2cPort = hardware.i2c89;
    }
    else
    {
        server.log("Invalid I2C port " + i2c_port + " specified in NewHaven_LCD::constructor.");
    }
    // initialize the LCD communications:
    server.log( "Initializing LCD with return value: " + init_LCD() + " (0 means successful I2C communication.)" );

}

function init_LCD() {        
    //    These initialization parameters follow page 11 of datasheet http://www.newhavendisplay.com/specs/NHD-C0216CiZ-FSW-FBW-3V3.pdf
    //    Datasheet first sets the function to 0x38= 8-bit bus, 2-line display, extension instruction mode,
    //    then immediately follows this by the 0x39 function. I can't see any reason to do this, so I removed the first function set.
    imp.sleep(0.01); // Wait 10 milliseconds
    local result = i2cPort.write(i2c_writeAddress, 
                                format("%c%c",0x80,     // Control byte: one instrution word will follow
                                0x39) );  // function: 8-bit bus, 2-line display, normal instruction mode.
    imp.sleep(0.01); // Wait a while,

    // This configuration string taken directly from datasheet, page 11.
	//  0x01=0b0000.0001	Clear Display
    //  0x14=0b0001.0100	Move cursor right one.
    //  0x10=0b0001.0000	Move cursor left one.
    //  0x1C=0b0001.1100	Scroll right
	//  0x18=0b0001.1000	Scroll left
	//  0x0C=0b0000.1100	Turn display on
	//  0x08=0b0000.1000	Turn display off
	//  0x80+=0b1000.0000	Set cursor position

    //  0x71=0b0111.0001  Contrast set (datasheet recommeded 0x78, but the contrast was unreadable. 0x71 works best)
    //  0x5E=0b0101.1110  Icon display on, booster on, contrast set.
    //  0x6D=0b0110.1101  Follower circuit on, amplifier=1?
    //  0x0C=0b0000.1100  Display on, cursor off.
    //  0x01=0b0000.0001  Clear display.
    //  0x06=0b0000.0110  Entry mode set to cursor-moves-right.

    result = result | i2cPort.write(i2c_writeAddress, 
                                    format("%c%c%c%c%c%c%c%c",0x00, // Control byte: indicates many instruction bytes will follow.
                                    0x14,0x71,0x5E,0x6D,0x0C,0x01,0x06) );

                                    
    imp.sleep(0.01); // Wait 10 milliseconds
    return result;
    
}

function clear_display() {
    return i2cPort.write(i2c_writeAddress, format("%c%c",0x80,     // Control byte: one instruction word will follow
                                                  0x01) );  // Clear display.
}

function goto_line1start() { // returns home to the start of the 1st line, without clearing.
    return i2cPort.write(i2c_writeAddress, format("%c%c",0x80,     // Control byte: one instruction word will follow
                                                  0x02) );  // set cursor to home position
}

function goto_line2start() { // returns to the start of the 2nd line, without clearing.
    return i2cPort.write(i2c_writeAddress, format("%c%c",0x80, // Control byte: one instruction word will follow
                                           0xc0) );  // set cursor to position 0x40, set command is 0x80. 0x80+0x40 = 0xC0
}

function show( text ) {
// Each line allows for 40 characters, of which only the first 16 show.        
return i2cPort.write(i2c_writeAddress, format("%c",0x40)+     // Control byte: data bytes follow, data is RAM data.
                                           text ); 
}

function test( text ) {
// Each line allows for 40 characters, of which only the first 16 show. 


return i2cPort.write(i2c_writeAddress, format("%c%c",0x0c,0x81));
return i2cPort.write(i2c_writeAddress, format("%c"0x07) "157");     
return i2cPort.write(i2c_writeAddress, format("%c"0x81)+"blah"); 
}
//-------------------
function get_date_string() {
    local d = date(); // the time() function would return seconds since 1970.
    return ( d["year"] + "/" + d["month"] + "/" + d["day"] + "  " +
             d["hour"] + ":" + d["min"] + ":" + d["sec"] + " UTC" );
}

}

imp.configure(“NewHaven_LCD test code”, [], []);

// uses i2c on pins 1, 2
myLCD <- NewHaven_LCD(I2C_12);
myLCD.clear_display();
myLCD.goto_line1start();
myLCD.show( "Date: " + myLCD.get_date_string() );
myLCD.goto_line2start();
myLCD.show(“This is line 2.”);
`