Electronic – 8051 to I2C backpack/4 bit LCD Byte Construct Probelms

8051assemblyi2clcd

Been feverishly writing bit bang code in assembly for this I2C backpack/4 bit LCD and dont quite understand the binary constructs I need to display text. Im trying to learn this the real way, not the Arduino way where everything is handled for you in the background by library's. Please correct me where I am wrong. Thank you.

I start out in 8-bit mode initializing the 20×4 LCD because it only really recognizes the high order 0010 and disregards the low order 0000 because it is wired for 4 bit anyway:

Send 00110000 (30h) 3 times to LCD in 8 bit mode because thats what the Hitachi controller says to do before 4-bit setup.

Then Hitachi says to send 0010000 (20H) in an 8 bit send and this really puts it in 4 bit mode.

This all works fine. The problem is when in 4 bit mode Im getting confused as to how the nibbles should be constructed for sending commands and text versus interfering with the "RS" and "EN" pins.

So I know the LCD control pins are in the low byte. "EN" is tied to bit 2 so to turn it on I would need to send 00000100 and "RS" is tied to bit 0 so to turn on RS I would send 00000001

Lets use the letter "H" 01110010 (72H) as letter to send to screen.
In 4 bit mode I need to break this up into high nibble 0111 and low nibble 0010.

So knowing that in 4 bit wiring mode the LCD only recognizes the high order for data and expects the upper order of the letter "H" first for data, then the lower order of the letter "H" for data.

So I would need to send out in 8 bit format 01110000 and then 00100000 for my letter "H" and the LCD would take the high order only of these 2 bytes sent separately and reassemble them correctly for letter display.

Now I know for the LCD to actually know this is data I need to make "RS" high so I would first need to send 00000001 in 8 bit mode, because the "RS" is in the lower order no matter what LCD mode I am in? Or would I need to send it twice because I am in 4 bit mode and that would complete a 4 bit transaction and still keep "RS" high?

Then for the letter "H" I would send my upper 01110000 part. Now the high order of letter "H" is in the LCD RAM, waiting for the low order of letter "H" to appear in the next incoming 8 bit high order.

But by sending this haven't I just wiped out the "RS" from being high? Would I need to send 01110001 to keep "RS" high? Wouldn't the 4 bit LCD only see my high order and the low order would still go to the control pins?

Now I need to make "EN" high 00000100 but here is my problem. If I send this I have now just made "RS" low as well. So Wouldn't I need to send 00000101 ? To make "EN" high and KEEP "RS" high?

Then I would need to make "EN" low 00000000, but that would make "RS" low now, so wouldn't I need to send 00000001 to make "EN" low and keep "RS" high?

Now I need to repeat this using the low order nibble of the letter "H" but in the high order position of the 8 bit send 00100000.

When sending data to 4 bit LCD via I2C, would I still have to include the "RS" bit in the low order of both 8 bit nibble sends just to maintain the state of "RS" should it need to be kept at 1 high?

This is SUPER CONFUSING and so far has demanded a lot of assembly code just to get this to try to work.

Best Answer

It looks like you have a 1602 LCD with a 4-bit interface. I recently wrote an interface to the 1602 for the MSP430. It is in 'C', but you should be able to follow it.

You need to 'OR' in the control bits for every I2C access. You probably don't need 4 accesses like I used. I like to end with all signals at default (looks nice on a scope). The 'backlight' bit is vendor dependent, your module may not use it. gbBackLight is a global boolean set/cleared elsewhere.

#define LCD_I2C_SLAVE_ADDR   0x27

// HW Port Bits
#define LCD_D7   0b10000000    // Most significant data bit in 4-bit mode
#define LCD_D4   0b00010000    // Least significant data bit in 4-bit mode
#define LCD_BACK 0b00001000    // Back-light bit
#define LCD_EN   0b00000100    // Enable bit
#define LCD_RW   0b00000010    // Read/Write bit
#define LCD_RS   0b00000001    // Register select bit, 0 = Instr, 1 = Data

#define LCD_DDDD 0b11110000    // All data bits in 4-bit mode


// Instruction control bits
#define LCD_INSTR_CLEAR_DISP       0x01
#define LCD_INSTR_RETURN_HOME      0x02
#define LCD_INSTR_ENTRY_MODE_SET   0x04
#define LCD_INSTR_DISP_ON_OFF      0x08
#define LCD_INSTR_CURS_DISP_SHIFT  0x10
#define LCD_INSTR_SET_FUNCT        0x20
#define LCD_INSTR_SET_CGRAPH_ADDR  0x40
#define LCD_INSTR_SET_DATA_ADDR    0x80

// Bit definitions for Display on/off
#define LCD_DISPLAY_ON             0x04
#define LCD_DISPLAY_CURS           0x02
#define LCD_DISPLAY_BLINK          0x01

// Bit definitions for Cursor shift
#define LCD_CURS_SHIFT_RIGHT       0x04

// Bit definitions for Set Function
#define LCD_FUNCTION_8BIT          0x10
#define LCD_FUNCTION_2LINE         0x08
#define LCD_FUNCTION_5x11          0x04

// Write an instruction to the LCD display
// Assumes that LCD is already in 4-bit mode
boolean_t WriteLcdInstr( uint8_t u8Data )
{
   uint8_t au8TxData[4];


   // Write upper LCD nibble (always from upper DIO nibble)
   au8TxData[0] = ( gbBackLight << 3 );
   au8TxData[1] = ( u8Data & 0xF0 ) | ( gbBackLight << 3 ) | LCD_EN;
   au8TxData[2] = ( u8Data & 0xF0 ) | ( gbBackLight << 3 );           // Write occurs when EN goes low
   au8TxData[3] = ( gbBackLight << 3 );

   I2C_Master_Write( LCD_I2C_SLAVE_ADDR, au8TxData, 4 );

   __delay_cycles( LCD_EXEC_CYC );       // Don't really need a delay here, added to make scope interpretation easier


   // Write lower LCD nibble (always from upper DIO nibble)
   au8TxData[0] = ( gbBackLight << 3 );
   au8TxData[1] = ( u8Data << 4 ) | ( gbBackLight << 3 ) | LCD_EN;
   au8TxData[2] = ( u8Data << 4 ) | ( gbBackLight << 3 );              // Write occurs when EN goes low
   au8TxData[3] = ( gbBackLight << 3 );

   I2C_Master_Write( LCD_I2C_SLAVE_ADDR, au8TxData, 4 );

   __delay_cycles( LCD_EXEC_CYC );       // This delay might be important


   // 'Clear' and 'Home' take a long time
   if ( u8Data <= LCD_INSTR_RETURN_HOME )
   {
      __delay_cycles( LCD_HOME_CYC );
   }

   return ( TRUE );
}



// Write a character to the LCD display at the current cursor position (or to the custom graphic RAM if CG addr was set last)
// Assumes that LCD is already in 4-bit mode
// Cursor will increment afterwards
boolean_t WriteLcdData( uint8_t u8Data )
{
   uint8_t au8TxData[4];


   // Write upper LCD nibble (always from upper DIO nibble)
   au8TxData[0] = ( gbBackLight << 3 ) | LCD_RS;
   au8TxData[1] = ( u8Data & 0xF0 ) | ( gbBackLight << 3 ) | LCD_EN | LCD_RS;
   au8TxData[2] = ( u8Data & 0xF0 ) | ( gbBackLight << 3 ) | LCD_RS;           // Write occurs when EN goes low
   au8TxData[3] = ( gbBackLight << 3 );

   I2C_Master_Write( LCD_I2C_SLAVE_ADDR, au8TxData, 4 );

   __delay_cycles( LCD_EXEC_CYC );       // Don't really need a delay here, added to make scope interpretation easier


   // Write lower LCD nibble (always from upper DIO nibble)
   au8TxData[0] = ( gbBackLight << 3 ) | LCD_RS;
   au8TxData[1] = ( u8Data << 4 ) | ( gbBackLight << 3 ) | LCD_EN | LCD_RS;
   au8TxData[2] = ( u8Data << 4 ) | ( gbBackLight << 3 ) | LCD_RS;             // Write occurs when EN goes low
   au8TxData[3] = ( gbBackLight << 3 );

   I2C_Master_Write( LCD_I2C_SLAVE_ADDR, au8TxData, 4 );

   __delay_cycles( LCD_EXEC_CYC );        // This delay might be important

   return ( TRUE );
}