Electronics > Microcontrollers

Initialising a 4x20 LCD with I2C Interface

(1/2) > >>

PerArdua:
Hi, in order to learn about LCDs and PICs at a lower abstraction level, I'm trying to write my own library for controlling a 4x20 LCD. However, I am falling at the first hurdle - initialising the LCD and printing a single character. My hardware consists of a PIC24FJ256GA412, which is connected through level shifters to a PCF8574 which is connected to my LCD in 4 bit configuration. As discussed later, I am fairly content that the hardware works.

For the software, I have used MCC to create an I2C library, and written a header and implementation file called lcd.h/lcd.c. These, along with the rest of the project files, I have attached as a zip. The main file should simply initialise the LCD, and print a letter A on the LCD. The initialisation procedure, I have tried to follow from page 46 of the datasheet (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf). Forgive me for the less than polished code, I am quite new and have sprinkled a few Nops() and generous delays to aid my previous debugging.

However, on running the setup, the LCD fails to show a character - just a blank screen. I have played with the contrast, but it is not that at fault. I have probed the I2C lines at the input of the PCF8574 board, and the data transfer looks as I'd expect. I've written out the PCF8574 connections to I2C bits in the file titled LCD Plan.ods, and shared the measurements in the file LCD debug data 4.ods. This leads me to suspect that the issue lies in how I think the LCD should be initialised/used, rather than my implementation. However, after trying a variety of potential solutions (clearing the LCD after init, tunring LCD display on after init, etc), I still don't see a character.

Would anyone be able to review my code/LCD plan and suggest where I may be going wrong? If you'd like to give feedback on my more general programming, so i can make code more legible/easier to understand, that would be appreciated too.

pqass:
Since I see your lower nibble from bits 0 to 3 are attached to LCD pins RS, RW, EN, BACKLIGHT, I take it that you're going to use 4 bit LCD mode.  But you haven't switched the LCD to that mode in your LCD_init() function!  There is a three byte incantation that needs to be performed first.  Oh, that's the "LCD_CURSORSHIFT | LCD_FUNCTIONSET" three times call.  However, there is a difference with my init sequence (see below) since I send two separate LCD_FUNCTIONSET commands (after the first three) before the "LCD_2LINE | LCD_5x8DOTS" command.  Oh, nevermind.  After the fourth call (sending just LCD_FUNCTIONSET), then you switch to using LCD_send_cmd() which send two bytes for every command.

See below for my minimalist LCD functions (I use SPI to a '595 then LCD). 

I wait 75ms after reset/powerup. You have just 20ms.  Confirm that your delays are consistant with what I've programmed below.

In LCD_I2C_send(), there should be a delay (50us) after the second byte sent.  2ms delay after the first byte is a bit overkill.  In LCD_send_cmd() and LCD_send_data(), why aren't you reusing LCD_I2C_send() vs. calling I2C routines directly?  Only the RS bit is different between the two, right? This is unnecessary duplication.

Why the Nop() calls? Alignment? Surely not for delay.


--- Code: ---void lcdSend(uint8_t data) {
  // HD44780 display is attached to a 74HC595
  // bit0=backlight(1=on; 0=off), bit1=RS, bit2=RW, bit3=EN, bits4-7=DB4-7
  // below, we override the lower nibble except for the RS bit

  data |=  _BV(0);  // backlight always on
  data &= ~_BV(2);  // RW in write mode
  data &= ~_BV(3);  // keep EN low while DB4-7, and control bits settle

  spi_transfer(data);

  // toggle EN pin; high, then low
  data |=  _BV(3);  spi_transfer(data);  _delay_us(1);
  data &= ~_BV(3);  spi_transfer(data);  _delay_us(50);
}

void lcdClear() {
  lcdSend(0b00000000); //RS=0;
  lcdSend(0b00010000); //RS=0; clear display
  _delay_ms(5);
}

void lcdHome() {
  lcdSend(0b00000000); //RS=0;
  lcdSend(0b00100000); //RS=0; return home
  _delay_ms(5);
}

void lcdInit() {
  spi_setup();         _delay_ms(75); //min. after start before sending commands
  lcdSend(0b00110000); _delay_ms(5);  //RS=0; initialize
  lcdSend(0b00110000); _delay_ms(5);  //RS=0; initialize
  lcdSend(0b00110000); _delay_ms(5);  //RS=0; initialize; "Thrice the brinded cat hath mewed."
  lcdSend(0b00100000); _delay_ms(5);  //RS=0; switch to 4-bit mode
  lcdSend(0b00100000);                //RS=0;
  lcdSend(0b10000000); _delay_ms(5);  //RS=0; 5x8 chars, 2 lines/display
  lcdSend(0b00000000);                //RS=0;
  lcdSend(0b11000000); _delay_ms(5);  //RS=0; display on, no cursor
  lcdClear();
  lcdHome();
}

void setCursor(uint8_t col, uint8_t row) {
  uint8_t row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
  uint8_t data = 0b10000000|((col&0x0f)+row_offsets[row&0x03]);
  lcdSend((data&0xf0)|0b0000); //RS=0;
  data <<= 4;
  lcdSend((data&0xf0)|0b0000); //RS=0; setddramaddr
}

void lcdChar(uint8_t data) {
  lcdSend((data&0xf0)|0b0010); //RS=1; high nibble
  data <<= 4;
  lcdSend((data&0xf0)|0b0010); //RS=1; low nibble
}

void lcdString(char *datap) {
  while (*datap != '\0')
    lcdChar(*datap++);
}


// somewhere in your main():

  lcdInit();
  lcdString("FOOBAR v1.0");
  _delay_ms(1000);


--- End code ---

Andy Watson:
I'm not seeing the code where you initialise the display! Could you just post the display initialisation code?

Also, Can I draw your attention to page 46 of the Hitiachi document that you linked. It gives the initialisation sequence. Note that (particularly in four bit mode) you do not know which phase of the byte-writing cycle you are in when you start the reset sequence, therefore you must follow the sequence given on page 46.  In particular note that the BF flag cannot be checked until the display has performed its internal initialisation - i.e. those delays between writing commands must be present in your software - I usually doulbe the delay times to account for clones that do not meet Hitachi's spec.

PerArdua:
Thanks both.

Andy, I've attached three files (main.c, LCD.h and LCD.c) for review - the init code is from line 13 of LCD.c. Thanks for your comments - hopefully you will be able to see I've given plenty of delay between instructions/bytes - perhaps even overkill.

pqass, many thanks for your comments too. Noted on the delays (I do have a 2s delay in main.c before init, but have changed the first delay in init to 75ms). I will optimise the delays after bytes when I (hopefully!) can get this to work. I will also condense the send_cmd/data functions as you point out - thanks! Nops() are mostly only there to give a convenient line for me to place a breakpoint for the debugger. I will also remove those at a later stage, but they shouldn't affect the function/non-functionality of the LCD in this regard?

Forgive me, but I think I am performing the 3 byte incantation as per pg 46 - the 000011 (RS, RW, 7,6,5,4) byte is given by the three "LCD_CURSORSHIFT | LCD_FUNCTIONSET | LCD_NOBACKLIGHT" lines - or have I misused these? I have tried illustrating how I think the figure on pg 46 lines up with my plan.ods and the measured data on the I2C lines. Apologies for the messy red lines, but I hope it illustrates how I think they corroborate.

pqass:

--- Quote from: PerArdua on June 27, 2022, 02:58:39 pm ---Forgive me, but I think I am performing the 3 byte incantation as per pg 46 - the 000011 (RS, RW, 7,6,5,4) byte is given by the three "LCD_CURSORSHIFT | LCD_FUNCTIONSET | LCD_NOBACKLIGHT" lines - or have I misused these? I have tried illustrating how I think the figure on pg 46 lines up with my plan.ods and the measured data on the I2C lines. Apologies for the messy red lines, but I hope it illustrates how I think they corroborate.

--- End quote ---

Yeah, I now see you are doing the 3 byte incantation.  Then I suspected that after going into 4bit mode, you weren't sending two bytes per command. But then I noticed that you switched to using the LCD_send_cmd() call which does breakout lower and upper nibbles and sends them as two bytes.

However, your init code differs from mine in that I send a LCD_DISPLAYON command whereas you send LCD_DISPLAYOFF.  And therefore, nothing is displayed? 

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version