Andrew Schelb
Andrew Schelb

Reputation: 13

I2C LCD1602 interfacing with PIC16F877A

My partner and I are currently building out an interface code set for a PIC16F877A microcontroller and a LCD1602 I2C screen

We can onboard everything perfectly fine however nothing shows on the screen. We have double checked the PIN mapping and connected up the proper ports on the I2C module of the screen. The code compiles with no issues and runs into the microcontroller but there is no display.

/* File: config.h */
// [ PIC16F877A ] Configuration Bit Settings
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG
#pragma config FOSC = HS
#pragma config WDTE = OFF
#pragma config PWRTE = OFF
#pragma config BOREN = OFF
#pragma config LVP = ON
#pragma config CPD = OFF
#pragma config WRT = OFF
#pragma config CP = OFF

#include <xc.h>



/* File: I2C_LCD.h */
#define _XTAL_FREQ            16000000

#define I2C_BaudRate          100000
#define SCL_D                 TRISC3
#define SDA_D                 TRISC4

#define LCD_BACKLIGHT         0x08
#define LCD_NOBACKLIGHT       0x00
#define LCD_FIRST_ROW         0x80
#define LCD_SECOND_ROW        0xC0
#define LCD_THIRD_ROW         0x94
#define LCD_FOURTH_ROW        0xD4
#define LCD_CLEAR             0x01
#define LCD_RETURN_HOME       0x02
#define LCD_ENTRY_MODE_SET    0x06
#define LCD_CURSOR_OFF        0x0C
#define LCD_UNDERLINE_ON      0x0E
#define LCD_BLINK_CURSOR_ON   0x0F
#define LCD_MOVE_CURSOR_LEFT  0x10
#define LCD_MOVE_CURSOR_RIGHT 0x14
#define LCD_TURN_ON           0x0C
#define LCD_TURN_OFF          0x08
#define LCD_SHIFT_LEFT        0x18
#define LCD_SHIFT_RIGHT       0x1C
#define LCD_TYPE              2 // 0 -> 5x7 | 1 -> 5x10 | 2 -> 2 lines

//-----------[ Functions' Prototypes ]--------------

//---[ I2C Routines ]---

void I2C_Master_Init();
void I2C_Master_Wait();
void I2C_Master_Start();
void I2C_Master_RepeatedStart();
void I2C_Master_Stop();
void I2C_ACK();
void I2C_NACK();
unsigned char I2C_Master_Write(unsigned char data);
unsigned char I2C_Read_Byte(void);

//---[ LCD Routines ]---

void LCD_Init(unsigned char I2C_Add);
void IO_Expander_Write(unsigned char Data);
void LCD_Write_4Bit(unsigned char Nibble);
void LCD_CMD(unsigned char CMD);
void LCD_Set_Cursor(unsigned char ROW, unsigned char COL);
void LCD_Write_Char(char);
void LCD_Write_String(char*);
void Backlight();
void noBacklight();
void LCD_SR();
void LCD_SL();
void LCD_Clear();



/* File: I2C_LCD.c */
#include <xc.h>
#include "I2C_LCD.h"

unsigned char RS, i2c_add, BackLight_State = LCD_BACKLIGHT;

//---------------[ I2C Routines ]-------------------
//--------------------------------------------------
void I2C_Master_Init()
{
  SSPCON = 0x28;
  SSPCON2 = 0x00;
  SSPSTAT = 0x00;
  SSPADD = ((_XTAL_FREQ/4)/I2C_BaudRate) - 1;
  SCL_D = 1;
  SDA_D = 1;
}

void I2C_Master_Wait()
{
  while ((SSPSTAT & 0x04) || (SSPCON2 & 0x1F));
}

void I2C_Master_Start()
{
  I2C_Master_Wait();
  SEN = 1;
}

void I2C_Master_RepeatedStart()
{
  I2C_Master_Wait();
  RSEN = 1;
}

void I2C_Master_Stop()
{
  I2C_Master_Wait();
  PEN = 1;
}

void I2C_ACK(void)
{
  ACKDT = 0; // 0 -> ACK
  I2C_Master_Wait();
  ACKEN = 1; // Send ACK
}

void I2C_NACK(void)
{
  ACKDT = 1; // 1 -> NACK
  I2C_Master_Wait();
  ACKEN = 1; // Send NACK
}

unsigned char I2C_Master_Write(unsigned char data)
{
  I2C_Master_Wait();
  SSPBUF = data;
  while(!SSPIF); // Wait Until Completion
  SSPIF = 0;
  return ACKSTAT;
}

unsigned char I2C_Read_Byte(void)
{
  //---[ Receive & Return A Byte ]---
  I2C_Master_Wait();
  RCEN = 1; // Enable & Start Reception
  while(!SSPIF); // Wait Until Completion
  SSPIF = 0; // Clear The Interrupt Flag Bit
  I2C_Master_Wait();
  return SSPBUF; // Return The Received Byte
}
//======================================================

//---------------[ LCD Routines ]----------------
//-----------------------------------------------

void LCD_Init(unsigned char I2C_Add)
{
  i2c_add = I2C_Add;
  IO_Expander_Write(0x00);
  __delay_ms(30);
  LCD_CMD(0x03);
  __delay_ms(5);
  LCD_CMD(0x03);
  __delay_ms(5);
  LCD_CMD(0x03);
  __delay_ms(5);
  LCD_CMD(LCD_RETURN_HOME);
  __delay_ms(5);
  LCD_CMD(0x20 | (LCD_TYPE << 2));
  __delay_ms(50);
  LCD_CMD(LCD_TURN_ON);
  __delay_ms(50);
  LCD_CMD(LCD_CLEAR);
  __delay_ms(50);
  LCD_CMD(LCD_ENTRY_MODE_SET | LCD_RETURN_HOME);
  __delay_ms(50);
}

void IO_Expander_Write(unsigned char Data)
{
  I2C_Master_Start();
  I2C_Master_Write(i2c_add);
  I2C_Master_Write(Data | BackLight_State);
  I2C_Master_Stop();
}

void LCD_Write_4Bit(unsigned char Nibble)
{
  // Get The RS Value To LSB OF Data
  Nibble |= RS;
  IO_Expander_Write(Nibble | 0x04);
  IO_Expander_Write(Nibble & 0xFB);
  __delay_us(50);
}

void LCD_CMD(unsigned char CMD)
{
  RS = 0; // Command Register Select
  LCD_Write_4Bit(CMD & 0xF0);
  LCD_Write_4Bit((CMD << 4) & 0xF0);
}

void LCD_Write_Char(char Data)
{
  RS = 1; // Data Register Select
  LCD_Write_4Bit(Data & 0xF0);
  LCD_Write_4Bit((Data << 4) & 0xF0);
}

void LCD_Write_String(char* Str)
{
  for(int i=0; Str[i]!='\0'; i++)
    LCD_Write_Char(Str[i]);
}

void LCD_Set_Cursor(unsigned char ROW, unsigned char COL)
{
  switch(ROW)
  {
    case 2:
      LCD_CMD(0xC0 + COL-1);
      break;
    case 3:
      LCD_CMD(0x94 + COL-1);
      break;
    case 4:
      LCD_CMD(0xD4 + COL-1);
      break;
    // Case 1
    default:
      LCD_CMD(0x80 + COL-1);
  }
}

void Backlight()
{
  BackLight_State = LCD_BACKLIGHT;
  IO_Expander_Write(0);
}

void noBacklight()
{
  BackLight_State = LCD_NOBACKLIGHT;
  IO_Expander_Write(0);
}

void LCD_SL()
{
  LCD_CMD(0x18);
  __delay_us(40);
}

void LCD_SR()
{
  LCD_CMD(0x1C);
  __delay_us(40);
}

void LCD_Clear()
{
  LCD_CMD(0x01);
  __delay_us(40);
}



/* File: main.c */
#include <xc.h>
#include "config.h"
#include "I2C_LCD.h"

void main(void) {

  I2C_Master_Init();
  LCD_Init(0x27); // Initialize LCD module with I2C address = 0x4E

  LCD_Set_Cursor(1, 1);
  LCD_Write_String(" Khaled Magdy");
  LCD_Set_Cursor(2, 1);
  LCD_Write_String(" DeepBlue");

  while(1)
  {
    LCD_SR();
    __delay_ms(350);
    LCD_SR();
    __delay_ms(350);
    LCD_SL();
    __delay_ms(350);
    LCD_SL();
    __delay_ms(350);
  }
  return;
}

Would appreciate getting some eyes on this from a different perspective as we have worked tirelessly and are unsure why there is no communication.

Upvotes: 0

Views: 525

Answers (1)

Kozmotronik
Kozmotronik

Reputation: 2520

Character LCD modules are very slow devices compared to the PIC microcontrollers. So the required time for them to processing the commands and data may vary depending on the specific product.

These modules should be set up properly by respecting their processing time in order to get them functioning. That's why the init procedure is very important for these devices and should be handled carefully.

For a reference, here is an example init function code snippet taken from a working project.

// Init the LCD module for 4-bit mode
void lcd4bitInit(void)
{
    __delay_ms(100);    // Character LCD modules are relatively slow, the required time may vary depending on the product
                        // So give it sufficient time especially when setting up
    lcdPort = 0x30;     // Send function set command 3 times with 10ms intervals
    lcdRW_low();        // Set LCD RW low for write mode
    lcdRS_low();        // Set LCD RS low for command mode
    lcdEN_high();       // Set EN low so that it latch the data in, should keep high at least 140ns
    lcdEN_low();        // Set EN low again
    __delay_ms(10);     // Let it process the data
    lcdEN_high();       // Latch the function set command for 2nd time (still 8-bit mode).
    lcdEN_low();
    __delay_ms(10);     // Let it process the data
    lcdEN_high();       // Latch the function set command for 3d time (still 8-bit mode).
    lcdEN_low();
    __delay_ms(10);     // Let it process the data
    
    lcdPort = 0x28;     // Set DL 0: 4-bits; N 1: 2 lines; F 0: 5x7 dots
    lcdEN_high();
    lcdEN_low();
    __delay_ms(10);
    /* From now on, the LCD module should function in 4-bit mode. So we can write it in 4-bit mode as of now */
    
    lcdCmd_write(0x08); // Here goes the rest of the setup which can be sent in 4-bit mode
    lcd_clear();
    lcdCmd_write(0x06);
    lcdCmd_write(0x0C);
}

The pin setting functions in the snippet are function-like macros. For example the definition of the lcdEN_high() and the `lcdEN_low would be as following respectively:

#define lcdEN_high()      do {lcdENPort |= 1 << lcdEN; } while(0)
#define lcdEN_low()       do {lcdENPort &= ~(1 << lcdEN); } while(0)

where lcdENPort is mapped to the port of the pin where I control the EN pin, and the lcdEN is mapped to the bit number through which the EN of the LCD is connected.

Another important factor for a proper setup is respecting the module's processing time. As I said before, this time may vary slightly among different products or manufacturers. But I see no problem in keeping these times a bit more than required since the init function is generally executed once on every system init.

Upvotes: 1

Related Questions