1
\$\begingroup\$

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 20x4 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.

\$\endgroup\$
1
  • \$\begingroup\$ You need to set and maintain all the data bits & control bits at the same time. So to write an 'H' in 4-bit mode: send 01110001 (setup the high nibble & RS), send 01110101 (set EN while maintaining the previous bits), send 01110001 (release EN while still maintaining the others), then do the same for the low nibble: 00100001, 00100101, 00100001. \$\endgroup\$
    – brhans
    Commented Mar 31, 2020 at 22:45

1 Answer 1

1
\$\begingroup\$

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 );
}
\$\endgroup\$
7
  • \$\begingroup\$ I see exactly what both of you are doing. Makes sense now. Yes, my back-light is tied to bit 3 so I must include that as well in every send to keep it on. What a mess of code. Its really the only way to learn it. Thank you both! \$\endgroup\$
    – Nanosynth
    Commented Mar 31, 2020 at 23:41
  • \$\begingroup\$ The whole "OR" thing with the control bits, that is a very nice operation. In 8051 asm code, I split the original LCD data byte which makes the high order 0000 and low order the nibbles, then I swap the high and low in the accumulator, then I OR the low with the control bits, then send it out the wire. A ridiculous amount of code but well worth the learning experience for I2C implementation. \$\endgroup\$
    – Nanosynth
    Commented Apr 1, 2020 at 17:50
  • \$\begingroup\$ @Nanosynth - congratulations on learning assembly. 40 years ago, assembly was the only option for small MCUs. I was proficient in 6502 and 68000, and some familiarity with several others. \$\endgroup\$
    – Mattman944
    Commented Apr 1, 2020 at 20:21
  • \$\begingroup\$ 6502? As in Atari 400/800... I remember when I got my first Atari 400 in 1982, paid $350.00 for it. A lot of cash for an 8th grader. I bought the assemble/editor cart for it but was just learning Basic then, so asm was way out of my league. Came back to the 8031 asm about year 2000, but really started to get serious 3 years ago. With all these new backpack boards, centered around Arduino and the 8051 being so old, sometimes I need to reach out for help. Arduino is cool, but you really dont learn much by just dumping peoples sketches in with downloaded library's. I like to know how it works. \$\endgroup\$
    – Nanosynth
    Commented Apr 12, 2020 at 4:08
  • \$\begingroup\$ @Nanosynth - The Apple II had a 6502 also. I wrote games in assembly, action games had to be in assembly. In 1980 had a contract with a software company for one of my games to get a $1 royalty per copy sold. Never made a cent, they went bankrupt. Still, exciting for a college kid. Around the same time, a 14 year-old kid made $100k for his Apple II "Sneakers" game. Admittedly, his game was better than mine, also right place, right time. \$\endgroup\$
    – Mattman944
    Commented Apr 12, 2020 at 8:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.