Search code examples
arduinoi2c

Protocol for writing data to 16x2 LCD via I2C


I am new to electronics and has completed a tutorial on how to operate a 16x2 Character LCD via I2C in Arduino using liquidCrystal_I2C. Everything works fine but I have a question about the low level interaction between the I2C and the LCD. Looking at the library's source code, I notice that when writing a 4 bits nibble (LiquidCrystal_I2C::write4bits), the code writes the nibble to the I2C expander first (LiquidCrystal_I2C::expanderWrite), and then writes again when pulsing the Enable bit. Why is the first expanderWrite necessary? Why can't write4bits just call pulseEnable (with the blacklight bit set)?

I am sure there is a reason as I checked other library like RPLCD and see a similar pattern. Can anyone enlighten me? Thank you.


Solution

  • From the datasheet I found the LCD requires specific timing in the communication protocol. On the rising edge of the enable line the Register Select and Read/Write lines must have already settled for tsu1 (100ns). On the falling edge of the enable line the data must have already settled for tsu2 (60ns). By writing _data they are also writing the RS and R/W lines as they are the lower nibble of _data.

    This article covers the topic very thoroughly.

    TimingDiagram

    //**** From LiquidCrystal_I2C.h
    // flags for backlight control
    #define LCD_BACKLIGHT 0x08
    #define LCD_NOBACKLIGHT 0x00
    
    #define En B00000100  // Enable bit
    #define Rw B00000010  // Read/Write bit
    #define Rs B00000001  // Register select bit
    //              ^--------Backlight bit defined above
    //          ^^^^---------Data bits
    
    //**** From LiquidCrystal_I2C.cpp
    void LiquidCrystal_I2C::write4bits(uint8_t value) {
        expanderWrite(value);
        pulseEnable(value);
    }
    
    void LiquidCrystal_I2C::expanderWrite(uint8_t _data){
        Wire.beginTransmission(_addr);
        Wire.write((int)(_data) | _backlightval);
        Wire.endTransmission();
    }
    
    void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
        expanderWrite(_data | En);  // En high
        delayMicroseconds(1);       // enable pulse must be >450ns
    
        expanderWrite(_data & ~En); // En low
        delayMicroseconds(50);      // commands need > 37us to settle
    }