• AVR Freaks

Hot!Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825)

Author
PStechPaul
Super Member
  • Total Posts : 2354
  • Reward points : 0
  • Joined: 2006/06/27 16:11:32
  • Location: Cockeysville, MD, USA
  • Status: online
2018/03/23 00:18:33 (permalink)
5 (2)

Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825)

After struggling with various libraries and code examples, I have come up with a simplified example that uses macros for I2C interface. I plan to build another project for serial EEPROMs.
 
Here is the test program:

// PIC16F1825 Configuration Bit Settings
// 'C' source line config statements
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
#include    <xc.h>
#include    "I2C_macros.h"
#include    "LCD_I2C.h"
void    main (void){
    OSCCON = 0b01101011;    //4 MHz
    TRISA = 0b00011011;     //RA2, RA5 output
    TRISC = 0b00110011;     //RC2, RC3 output
    WPUA = 0b00001000;      //Pull-up on RA3
    ANSELA = 0b00010000;    //AN3 RA4
    ANSELC = 0;
    LATA = 0b00000100;      // TXEN OFF
    I2C_INIT
           
    while(1) {
    LCD_open();
    LCD_init();
    LCD_goto(1,1);
    LCD_write_string("** LCD test **");
    LCD_close();
    __delay_ms(1000);
    LATA5 ^= 1;
    }         
}

 
The I2C interface:

#ifndef __I2C_MACROS_H
#define __I2C_MACROS_H
#define SDA  RC1   // Pin 9, Data pin for i2c
#define SCK  RC0   // Pin 10, Clock pin for i2c
#define SDA_DIR  TRISC1   // Data pin direction
#define SCK_DIR  TRISC0   // Clock pin direction
#define I2C_SCL TRISCbits.TRISC0
#define I2C_SDA TRISCbits.TRISC1
// Define i2c speed
#define _XTAL_FREQ 4000000
#define I2C_SPEED 100    // kbps
#define I2C_OPEN    SSP1CON1 |= 0x20;              // enable synchronous serial port
#define I2C_CLOSE   SSP1CON1 &= 0xdf;              // disable synchronous serial port
#define I2C_INIT    SDA_DIR=1; SCK_DIR=1; SSP1ADD=((_XTAL_FREQ/4000)/I2C_SPEED) - 1; SSP1STAT=0x80; SSP1CON1=0x28;
#define I2C_START   SSP1CON2bits.SEN = 1; //while(SSP1CON2bits.SEN);
#define I2C_STOP    SSP1CON2bits.PEN = 1; //while(SSP1CON2bits.PEN);
#define I2C_ACK     SSP1CON2bits.ACKDT=0; SSP1CON2bits.ACKEN=1; while(RCEN && SSP1CON2bits.ACKEN);
#define I2C_NACK    SSP1CON2bits.ACKDT=1; SSP1CON2bits.ACKEN=1; while(RCEN && SSP1CON2bits.ACKEN);
#define I2C_READ    RCEN=1; while(SSP1CON2bits.ACKEN); return SSP1BUF;
#define I2C_WRITE(d)    I2C_IDLE SSP1BUF=d; while( SSP1STATbits.BF );
#define I2C_WAIT    while(!PIR1bits.SSP1IF); PIR1bits.SSP1IF=0;
#define I2C_IDLE    while ((SSP1CON2 & 0x1F) || (SSP1STATbits.R_nW));
#endif

 
The header for the LCD module:

#ifndef LCD_I2C_h
#define LCD_I2C_h
#define BYTE    unsigned char
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_CLRSCR 0x01
#define LCD_HOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80
// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00
// flags for backlight control
#define LCD_BACKLIGHT 0x00
#define LCD_NOBACKLIGHT 0x80
/* Original adapter
 * RS --- P5
 * E ---- P4
 * D4 --- P0
 * D5 --- P1
 * D6 --- P2
 * D7 --- P3
*/
#define En B00010000  // Enable bit
#define Rw B00100000  // Read/Write bit
#define Rs B01000000  // Register select bit
#define RS 0x01  // P0 (RS pin was 04)
#define RW  0x02        // P1
#define E 0x04  // P2 (E pin was 08)
#define SET_RS LCDpins |= RS
#define CLR_RS LCDpins &= ~RS
#define SET_RW LCDpins |= RW
#define CLR_RW LCDpins &= ~RW
#define SET_E LCDpins |= E
#define CLR_E LCDpins &= ~E
#define LCD_I2C_ADDR  (BYTE)0x4E
// Line addresses for LCDs which use
// the Hitachi HD44780U controller chip
#define LCD_LINE_1_ADDRESS 0x00
#define LCD_LINE_2_ADDRESS 0x40
#define LCD_LINE_3_ADDRESS 0x14
#define LCD_LINE_4_ADDRESS 0x54
BYTE  LCD_BL_Status = 1;     // 1 for POSITIVE control, 0 for NEGATIVE control
BYTE  pin_E;//   =    I2C_BYTE.2
BYTE  pin_RW;//  =    I2C_BYTE.1
BYTE  pin_RS;//  =    I2C_BYTE.0
BYTE  pin_D4;//  =    I2C_BYTE.4
BYTE  pin_D5;//  =    I2C_BYTE.5
BYTE  pin_D6;//  =    I2C_BYTE.6
BYTE  pin_D7;//  =    I2C_BYTE.7
BYTE  pin_BL;//  =    I2C_BYTE.3
#define  testbit(var, bit)   ((var) & (1 <<(bit)))
#define bit_test    testbit
#define  setbit(var, bit)    ((var) |= (1 << (bit)))
#define  clrbit(var, bit)    ((var) &= ~(1 << (bit)))
#define LCD_sendcmd(a)  CLR_RS; LCD_sendbyte(a);
#define LCD_sendchar(a) SET_RS; LCD_sendbyte(a);
BYTE _LCD_build_byte();
void _LCD_write_lower_nibble(BYTE l);
void _LCD_write_upper_nibble(BYTE u);
void LCD_BL(BYTE status);
void LCD_clear_line(BYTE line);
void LCD_clear();
void LCD_close( void );
void LCD_goto(BYTE x, BYTE y);
//void LCD_home( void );
void LCD_init( void );
void LCD_open( void );
//void LCD_second_row( void );
//void LCD_send_hexbyte ( BYTE data );
//void LCD_sendbyte( BYTE tosend );
//void LCD_send_string( const char *str_ptr );
//void LCD_send_ram_string (char *str_ptr);
void LCD_write_byte(BYTE address, BYTE n);
void LCD_write_string(const char *str);
#endif

 
And the LCD code:

/* Serial LCD support routines */
#define _LCD_I2C_c_
#include    <xc.h>
#include    "LCD_I2C.h"
#include    "I2C_macros.h"
/* local definitions */
BYTE _LCD_build_byte();
void _LCD_write_lower_nibble(BYTE l);
void _LCD_write_upper_nibble(BYTE u);
void LCD_open()
{
    I2C_IDLE
    I2C_OPEN
    I2C_IDLE
    I2C_START
    I2C_IDLE
    I2C_WRITE(LCD_I2C_ADDR)          // Tell all I2C devices you are talking to LCD_PIC_I2C_ADDR
    I2C_ACK
}
void LCD_close()
{
    I2C_IDLE
    I2C_CLOSE   //I2C_close();
}
void LCD_init()
{
    LCD_open();
    // Following bytes are all Command bytes, i.e. address = 0x00
    LCD_write_byte(0x00, 0x03);   // Write Nibble 0x03 three times (per HD44780U initialization spec)
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x03);   //
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x03);   //
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x02);   // Write Nibble 0x02 once (per HD44780U initialization spec)
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x02);   // Write Nibble 0x02 once (per HD44780U initialization spec)
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x01);   // Set mode: 4-bit, 2+lines, 5x8 dots
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x0C);   // Display ON 0x0C
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x01);   // Clear display
    __delay_ms(5);                // (per HD44780U initialization spec)
    LCD_write_byte(0x00, 0x06);   // Set cursor to increment
    __delay_ms(5);                // (per HD44780U initialization spec)
}
void LCD_BL(BYTE status)
{
    LCD_BL_Status = status;
    LCD_write_byte(0x00, 0x00);
}
void LCD_goto(BYTE x, BYTE y)   //x=col, y=row
{
BYTE address;
   switch(y)
     {
      case 1:
        address = LCD_LINE_1_ADDRESS;
        break;
      case 2:
        address = LCD_LINE_2_ADDRESS;
        break;
      case 3:
        address = LCD_LINE_3_ADDRESS;
        break;
      case 4:
        address = LCD_LINE_4_ADDRESS;
        break;
      default:
        address = LCD_LINE_1_ADDRESS;
        break;
     }
   address += x-1;
   LCD_write_byte(0, 0x80 | address);
}
void LCD_write_string(const char *str)
{
   // Writes a string text[] to LCD via I2C
   pin_RS  = 1;
   pin_RW  = 0;
   pin_E   = 0;
   pin_BL  = LCD_BL_Status;
   while (*str)
   {
        // Send upper nibble
        _LCD_write_upper_nibble(*str);
        // Send lower nibble
        _LCD_write_lower_nibble(*str);
        str++;
   }
}
void LCD_write_byte(BYTE address, BYTE n)
{
    if (address)
    {
        pin_RS=1;   // Data
    }
    else
    {
        pin_RS=0;   // Command
    }
    pin_RW  = 0;
    pin_E   = 0;
    pin_BL  = LCD_BL_Status;
    // Send upper nibble
   _LCD_write_upper_nibble(n);
    // Send lower nibble
   _LCD_write_lower_nibble(n);
}
void LCD_clear()
{
    LCD_write_byte(0x00,0x01);
    __delay_ms(5);
}
void LCD_clear_line(BYTE line)
{
    LCD_goto(1,line);
    for (int i = 0; i<20; i++)
    {
        LCD_write_string(" ");
    }
    LCD_goto(1,line);
}
void _LCD_write_upper_nibble(BYTE u)
{
    // Send upper nibble
    if(bit_test(u,7))
        pin_D7=1;
    else
        pin_D7=0;
    if(bit_test(u,6))
        pin_D6=1;
    else
        pin_D6=0;
    if(bit_test(u,5))
        pin_D5=1;
    else
        pin_D5=0;
    if(bit_test(u,4))
        pin_D4=1;
    else
        pin_D4=0;
   pin_E = 0;
   I2C_WRITE(_LCD_build_byte());
   pin_E = 1;
   I2C_WRITE(_LCD_build_byte());
   pin_E = 0;
   I2C_WRITE(_LCD_build_byte());
}
void _LCD_write_lower_nibble(BYTE l)
{
    // Send lower nibble
    if(bit_test(l,3))
        pin_D7=1;
    else
        pin_D7=0;
    if(bit_test(l,2))
        pin_D6=1;
    else
        pin_D6=0;
    if(bit_test(l,1))
        pin_D5=1;
    else
        pin_D5=0;
    if(bit_test(l,0))
        pin_D4=1;
    else
        pin_D4=0;
    pin_E = 0;
    I2C_WRITE(_LCD_build_byte())
    pin_E = 1;
    I2C_WRITE(_LCD_build_byte())
    pin_E = 0;
    I2C_WRITE(_LCD_build_byte())
}
BYTE _LCD_build_byte()
{
    BYTE ret = 0x00;
    ret |= pin_E    << 2;
    ret |= pin_RW   << 1;
    ret |= pin_RS   << 0;
    ret |= pin_D4   << 4;
    ret |= pin_D5   << 5;
    ret |= pin_D6   << 6;
    ret |= pin_D7   << 7;
    ret |= pin_BL   << 3;
    return ret;
}

 
I will also attach the project.

 
#1

19 Replies Related Threads

    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/23 00:38:12 (permalink)
    0
    Here are a couple pictures of the hardware:
     

     

     
    The board has a regulator so it will work on a nominal 12-24 VDC supply. But I am powering it with the PICkit3 here.

     
    #2
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/24 13:36:48 (permalink)
    0
    I tried adding an I2C EEPROM test using these macros, and ran into some problems. It seems that the I2C_IDLE macro gets hung up. Here are the macros:

    #ifndef __I2C_MACROS_H
    #define __I2C_MACROS_H
    #define SDA  RC1   // Pin 9, Data pin for i2c
    #define SCK  RC0   // Pin 10, Clock pin for i2c
    #define SDA_DIR  TRISC1   // Data pin direction
    #define SCK_DIR  TRISC0   // Clock pin direction
    #define I2C_SCL TRISCbits.TRISC0
    #define I2C_SDA TRISCbits.TRISC1
    // Define i2c speed
    #define _XTAL_FREQ 4000000
    #define I2C_SPEED 100    // kbps
    #define I2C_OPEN    SSP1CON1 |= 0x20;              // enable synchronous serial port
    #define I2C_CLOSE   SSP1CON1 &= 0xdf;              // disable synchronous serial port
    #define I2C_INIT    SDA_DIR=1; SCK_DIR=1; SSP1ADD=((_XTAL_FREQ/4000)/I2C_SPEED) - 1; SSP1STAT=0x80; SSP1CON1=0x28;
    #define I2C_START   SSP1CON2bits.SEN = 1; //while(SSP1CON2bits.SEN);
    #define I2C_STOP    SSP1CON2bits.PEN = 1; //while(SSP1CON2bits.PEN);
    #define I2C_ACK     SSP1CON2bits.ACKDT=0; SSP1CON2bits.ACKEN=1; while(RCEN && SSP1CON2bits.ACKEN);
    #define I2C_NACK    SSP1CON2bits.ACKDT=1; SSP1CON2bits.ACKEN=1; while(RCEN && SSP1CON2bits.ACKEN);
    #define I2C_READ    SSP1CON2bits.RCEN=1; while(!SSP1STATbits.BF); //SSP1BUF;
    #define I2C_WRITE(d)    I2C_IDLE; SSP1BUF=d; while( SSP1STATbits.BF );
    #define I2C_WAIT    while(!PIR1bits.SSP1IF); PIR1bits.SSP1IF=0;
    #define I2C_IDLE    while ((SSP1CON2 & 0x1F) || (SSP1STATbits.R_nW));
    #endif

    It's difficult to debug because it acts differently using the PICkit3 debugger, and programming production code. The most common problem seems to be that the ACKEN bit is not getting cleared, which it should be by hardware.
     
    I have another project using the same hardware, and function calls instead of macros, and it tests the EEPROM and operates the display reliably. I have made the macros from the functions and I'm using the same sequence of commands. I think it must be a problem with the EEPROM_test() routine, but the hang-up seems to occur in the LCD_write operation.
     
    Here is EEPROM_test:

    void    EEPROM_test()
    {
        const char data[] = "EEPROM Test\n";
        int ptr,slen;
        slen = strlen(eeprom_data);
        I2C_IDLE
        I2C_OPEN
        I2C_IDLE
        I2C_START
        I2C_IDLE
        I2C_WRITE(EEPROM_WR);          // Tell all I2C devices you are talking to EEPROM_WR
        I2C_ACK
        I2C_WRITE(EEPROM_ADDR_HI);     // Address high byte
        I2C_ACK
    //    I2C_IDLE
        I2C_WRITE(0);   
        I2C_ACK
        NOP();
        for(ptr=0; ptr<slen; ptr++) {
    //        I2C_write(data[ptr]);   // this causes hangup
            I2C_WRITE(data[ptr]);
            I2C_ACK
            STAT_ON;
            STAT_ERR;
            __delay_ms(100);
            STAT_OFF;
            __delay_ms(100);
            }
        I2C_STOP
        I2C_IDLE
        I2C_OPEN
        I2C_IDLE
        I2C_START
        I2C_WRITE(EEPROM_WR);          // Tell all I2C devices you are talking to EEPROM_WR
        I2C_ACK
        I2C_WRITE(EEPROM_ADDR_HI);     // Write Address High Byte
        I2C_ACK
        I2C_WRITE(0);   
        I2C_ACK
        I2C_START
        I2C_WRITE(EEPROM_RD);          // Tell all I2C devices you are talking to EEPROM_WR
        I2C_ACK
        NOP();
        for(ptr=0; ptr<slen-1; ptr++) {
            SSP1CON2bits.RCEN=1;
            while(!SSP1STATbits.BF);
    //        I2C_READ;
            eeprom_data[ptr] = SSP1BUF;
    //        eeprom_data[ptr] = I2C_read_byte();
            I2C_ACK //I2C_idle();
            STAT_ON;
            STAT_OK;
            __delay_ms(100);
            STAT_OFF;
            __delay_ms(100);
        }
    //    I2C_READ;
    //    eeprom_data[ptr] = SSP1BUF; // read last byte
        eeprom_data[ptr] = I2C_read_byte();
        I2C_STOP
    }

    It seems that RCEN is getting reset before the SP1BUF can be read:
            SSP1CON2bits.RCEN=1;   //RCEN starts as 0
            while(!SSP1STATbits.BF);          //RCEN is set to 1
    //        I2C_READ;
            eeprom_data[ptr] = SSP1BUF;   //RCEN has been reset to 0


     
    #3
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/24 14:05:49 (permalink)
    0
    I am trying to follow the execution of the project that DOES work. Here is the code for I2C_write with comments showing the registers:

     
    signed char I2C_write( unsigned char data_out )
    {
      SSP1BUF = data_out;           // write single byte to SSPBUF, data_out = 'P'
      if ( SSP1CON1bits.WCOL )      // test if write collision occurred
       return ( -1 );              // if WCOL bit is set return negative #
      else
      {
     if( ((SSP1CON1&0x0F)!=0x08) && ((SSP1CON1&0x0F)!=0x0B) ) //Slave mode only
     {
           SSP1CON1bits.CKP = 1;        // release clock line
           while ( !PIR1bits.SSP1IF );  // wait until ninth clock pulse received
     
           if ( ( !SSP1STATbits.R_nW ) && ( !SSP1STATbits.BF ) )// if R/W=0 and BF=0, NOT ACK was received
           {
             return ( -2 );           //return NACK
           }
        else
        {
       return ( 0 );    //return ACK
        }
     }
     else if( ((SSP1CON1&0x0F)==0x08) || ((SSP1CON1&0x0F)==0x0B) ) //master mode only
     {
         while( SSP1STATbits.BF );   // wait until write cycle is complete, SSP1BUF = '?'
    //     I2C_idle();                 // ensure module is idle
         if ( SSP1CON2bits.ACKSTAT ) // test for ACK condition received, SSP1BUF = '?'
           return ( -2 );   // return NACK
      else return ( 0 );              //return ACK
     }
     
      } // SSP1BUF = '?'
    }

    The I2C_read_byte function:
    unsigned char I2C_read_byte( void )
    {
      if( ((SSP1CON1&0x0F)==0x08) || ((SSP1CON1&0x0F)==0x0B) ) //master mode only, SSP1BUF='?'
        SSP1CON2bits.RCEN = 1;           // enable master for 1 byte reception
      while(!SSP1STATbits.BF);      // wait until byte received, SSP1BUF='?'
      return(SSP1BUF);              // return with read byte, SSP1BUF='E'
    }

    For the code that does NOT work, the write (which seems to work) and subsequent read (which does not):
            I2C_WRITE(data[ptr]);       // data[ptr] = 'E', SSP1BUF = 0
            I2C_ACK                     // SSP1BUF = 'E'
     
            SSP1CON2bits.RCEN=1;                // SSP1BUF = '?'
            while(!SSP1STATbits.BF);            // SSP1BUF = '?'
    //        I2C_READ;
            eeprom_data[ptr] = SSP1BUF;         // SSP1BUF = 0xff

    post edited by PStechPaul - 2018/03/24 14:19:18

     
    #4
    Gort2015
    Klaatu Barada Nikto
    • Total Posts : 3155
    • Reward points : 0
    • Joined: 2015/04/30 10:49:57
    • Location: 0
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/24 15:41:46 (permalink)
    0
    Test if busy before setting any bits.
     
    The I2C_read_byte function does not have a restart.
    Here's a chain of events of what should happen.
     
    i2c getc
    [start]
    [write device ID in bits 7..1, bit 0 clear]
    [write address]
    [restart]
    [write device ID in bits 7..1, bit 0 set]
    [read(1) NACK]
    [stop]
     
    READ (bool NACK)
    [busy]
    [set RCEN]
    [wait for RCEN to clear]
    [READ from I2C1RCV]
    [if(NACK is false, clear I2C1CON,#ACKDT]
    [else set I2C1CON,#ACKDT]
    [set I2C1CON,#ACKEN]


     
    i2c gets

    [start]
    [write device ID in bits 7..1, bit 0 clear]
    [write address]
    [restart]
    [write device ID in bits 7..1, bit 0 set]
    loop n:
    if(last, x=1 (NACK) else x = 0 (ACK)
    [data = read(x)]
    end loop:
    [stop]

    MPLab X playing up, bug in your code? Nevermind, Star Trek:Discovery will be with us soon.
    https://www.youtube.com/watch?v=Iu1qa8N2ID0
    + ST:Continues, "What Ships are Made for", Q's back.
    #5
    Mysil
    Super Member
    • Total Posts : 3327
    • Reward points : 0
    • Joined: 2012/07/01 04:19:50
    • Location: Norway
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/24 18:27:42 (permalink)
    0
    Hi,
    There is a lot of if's and but's to make reliable I2C code.
     
    Program should test state of the MSSP peripheral before and after each operation.
    Master code should test that S bit is Clear before trying to send Start signal.
        if (SSPxSTATbits.S == 0)
            SSPxCON2bits.SEN = 1;
        else {   Error }
    then, should test that S bit is Set before all other operations.
     
    Bus Collision should be tested for each operation,
    but is especially important after Start signal and after I2C address write.
    In MSSP peripheral Bus Collision flag is not available in SSPx control or status register, 
    it is Interrupt Flag register BCLxIF
     
    Note: Phillips/NXP I2C Specification document, state that Bus Collision in Start signal or Address transfer,
    is Not an error. This do not mean that it cannot happen. What it means, is that it is a condition that should be expected, and must be handled by Master hardware and software. In the specification this condition is called 'Lost Arbitration', it is still detected by the same BCLxIF bit in MSSP hardware.
     
    Do Not use the I2C_ACK; macro after I2C_WRITE(EEPROM_WR);
    Acknowledge signals always go in the opposite direction of Data signals,
    so when Master is writing data to slave, it should test that Acknowledge is received from Slave.
    This is done by testing ACKSTAT bit:     if (SSPxCON2bits.ACKSTAT == 1) { error, slave not responding. }
     
    Yes, RCEN bit,  receive enable is cleared by hardware when 8 bits have been received.
    Master shall then send ACK or NACK, this is controlled separately by ACKEN bit.
     
    This is wrong: #define I2C_READ    RCEN=1; while(SSP1CON2bits.ACKEN); return SSP1BUF;
    should be:    #define I2C_READ    RCEN=1; while(SSP1CON2bits.RCEN); return SSP1BUF;
     
    Do Not test:  while(RCEN && SSP1CON2bits.ACKEN);  these two bits are newer set at the same time.
    May just as well test:  while (SSP1CON2 & 0x1F); 
     
    One of the pitfalls with writing and reading I2C EEPROM,
    is that EEPROM write cycle take some time.
    In the time EEPROM is busy with internal operations,
    it will Not respond to I2C address, so NACK will be observed by Master.
    If this happen, master must send Stop signal, wait for Stop sequence to complete, and go back to Start.
     
       Mysil
    #6
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/25 00:53:59 (permalink)
    0
    Thanks for the information. I tried to implement the suggestions, but still no joy. I even copied the code from one project, where the EEPROM read works, but not the write, and in the new project it still fails. I will attach both projects as they are, in the hope that someone might see where I have gone wrong. The LCD_I2C_test project writes the EEPROM OK, but the read is not working.

     
    #7
    Gerald1
    Super Member
    • Total Posts : 338
    • Reward points : 0
    • Joined: 2009/05/12 06:50:37
    • Location: Wien, Austria
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/25 02:55:22 (permalink)
    0
    PStechPaul
    Here are a couple pictures of the hardware:
     

     

     
    The board has a regulator so it will work on a nominal 12-24 VDC supply. But I am powering it with the PICkit3 here.


    Hi,
    Its a fine project.
    Do you use the PCF8574 or do you emulate it with the PIC?
    Deed you read the PCF8574 datasheet?
    The port of the PCF have open collector outputs, is low active. If the port are in high state an internal weak resistor make a constant current source out_high with about 100µA.
    Can this drive an LCD, also an EEPROM?
    Have your circuit also external the necessary pull_ups?
    How is the circuit under your display (your photo shows just the top side), its pinning?
     
    Best Regards
    Gerald
     
     
     

    SG
    ---
    Daily the PC greets:
    "Press Enter!"; "Coward!"
    ---

    #8
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/25 03:04:31 (permalink)
    0
    I have another project where I used routines from the MLA to write and read the same EEPROM 24AA128, but the processor is a PIC18F242. It correctly reads the data that was written to the EEPROM using the LCD_I2C_test project with a PIC16F1825. Here is the code:

    // PIC18F242 Configuration Bit Settings
    // 'C' source line config statements
    // CONFIG1H
    #pragma config OSC = HS         // Oscillator Selection bits (HS oscillator)
    #pragma config OSCS = OFF       // Oscillator System Clock Switch Enable bit (Oscillator system clock switch option is disabled (main oscillator is source))
    // CONFIG2L
    #pragma config PWRT = ON        // Power-up Timer Enable bit (PWRT enabled)
    #pragma config BOR = ON         // Brown-out Reset Enable bit (Brown-out Reset enabled)
    #pragma config BORV = 27        // Brown-out Reset Voltage bits (VBOR set to 2.7V)
    // CONFIG2H
    #pragma config WDT = OFF        // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit))
    #pragma config WDTPS = 128      // Watchdog Timer Postscale Select bits (1:128)
    // CONFIG3H
    #pragma config CCP2MUX = ON     // CCP2 Mux bit (CCP2 input/output is multiplexed with RC1)
    // CONFIG4L
    #pragma config STVR = ON        // Stack Full/Underflow Reset Enable bit (Stack Full/Underflow will cause RESET)
    #pragma config LVP = ON         // Low Voltage ICSP Enable bit (Low Voltage ICSP enabled)
    // CONFIG5L
    #pragma config CP0 = OFF        // Code Protection bit (Block 0 (000200-001FFFh) not code protected)
    #pragma config CP1 = OFF        // Code Protection bit (Block 1 (002000-003FFFh) not code protected)
    // CONFIG5H
    #pragma config CPB = OFF        // Boot Block Code Protection bit (Boot Block (000000-0001FFh) not code protected)
    #pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code protected)
    // CONFIG6L
    #pragma config WRT0 = OFF       // Write Protection bit (Block 0 (000200-001FFFh) not write protected)
    #pragma config WRT1 = OFF       // Write Protection bit (Block 1 (002000-003FFFh) not write protected)
    // CONFIG6H
    #pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write protected)
    #pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0001FFh) not write protected)
    #pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write protected)
    // CONFIG7L
    #pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 (000200-001FFFh) not protected from Table Reads executed in other blocks)
    #pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 (002000-003FFFh) not protected from Table Reads executed in other blocks)
    // CONFIG7H
    #pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0001FFh) not protected from Table Reads executed in other blocks)
    // #pragma config statements should precede project file includes.
    // Use project enums instead of #define for ON and OFF.
    #include <xc.h>
    #include    "..\i2c_plib.h"
    #define     _XTAL_FREQ  14745600
    #define I2CAck       SSPCON2bits.ACKDT = 0; SSPCON2bits.ACKEN = 1; // start the ACK/NACK
    #define I2CNak     SSPCON2bits.ACKDT = 1; SSPCON2bits.ACKEN = 1; // start the ACK/NACK
    #define LED10       LATAbits.LATA1
    #define LED14       LATAbits.LATA4
    #define LEDON       0
    char    array[65];
    void    EEPROM_Page_Write();
    void    EEPROM_Read_Data();
    void i2c_Init(void){
     // Initialise I2C MSSP
     // Master 100KHz
     TRISC3=0;            // set SCL and SDA pins as inputs
     TRISC4=0;
     SSPCON1 = 0b00101000;  // I2C enabled, Master mode
     SSPCON2 = 0x00;
        // I2C Master mode, clock = FOSC/(4 * (SSPADD + 1))
        SSPADD = 35;      // 100Khz @ 4Mhz Fosc
     SSPSTAT = 0b11000000;  // Slew rate disabled
        PIE1bits.SSPIE = 1;
    }
    main (void){
    //        OSCCON = 0b01101011;    //4 MHz
        TRISA = 0b00001001;     //
        TRISB = 0b11110000;
        TRISC = 0b00111111;     //RC2, RC3 output
    //    WPUA = 0b00001000;      //Pull-up on RA3
        ADCON0 = 0b01000000;
        ADCON1 = 0b10100101;
    //    ANSELA = 0b00010000;    //AN3 RA4
    //    ANSELC = 0;
        LATA = 0b00000000;      //
        LATB = 0b00000000;
        LATC = 0b00000000;
        __delay_ms(500);
        i2c_Init();
        LED10 = !LEDON;
        LED14 = !LEDON;
    //    EEPROM_Page_Write();
        EEPROM_Read_Data();
        while(1);
       
    }
    /******************************************
     * I2C EEPROM (AT24C512) Byte Write
     *****************************************/
    void    EEPROM_Byte_Write() {
        StartI2C();
     /* Slave address */
     WriteI2C(0xA0);
     /* 2-Byte Sub Address */
     WriteI2C(0x00);
     WriteI2C(0x00);
     /* Data */
     WriteI2C(0x55);
     StopI2C();
    }
    /******************************************
    15. * I2C EEPROM (AT24C512) 64-Byte Page Write
    16. *****************************************/
    void    EEPROM_Page_Write() {
        int i;
     
        LED14 = LEDON;
     StartI2C();
     /* Slave address */
     WriteI2C(0xA0);
     /* 2-Byte Sub Address */
     WriteI2C(0x00);
     WriteI2C(0x00);
     /* Data 64 bytes */
     for (i = 0x20; i < 0x20 + 64; i++) {
      WriteI2C(i);
            LED14 ^= 1;
            __delay_ms(200);
        }
        StopI2C();
        __delay_ms(1000);
        LED14 = !LEDON;
    }
    /******************************************
     * I2C EEPROM (AT24C512) Read Data
     *****************************************/
    void    EEPROM_Read_Data() {
        int i;
       
        LED10 = LEDON;
     StartI2C();
     /* Slave address */
     WriteI2C(0xA0);
     /* 2-Byte Sub Address */
     WriteI2C(0x00);
     WriteI2C(0x00);
     RestartI2C();
     /* Slave address writh Read bit */
     WriteI2C(0xA1);
     /* Read 10 Byte from EEPROM */
     for (i = 0; i < 14; i++) {
      array[i] = ReadI2C();
            if( array[i] != i ) {
                LED14 = LEDON;
                __delay_ms(500);
                LED14 = !LEDON; }
      I2CAck
            LED10 ^= 1;
            __delay_ms(500);
        }
     array[i] = ReadI2C(); //last byte
     I2CNak
     StopI2C();
        __delay_ms(1000); 
        LED10 = !LEDON; }
    /*****************************************
     * I2C EEPROM (AT24C512) ACK Polling
     *****************************************/
    unsigned char eeprom_not_ready()
    {
     unsigned char ack;
     
     StartI2C();
     /* Slave address */
     ack = WriteI2C(0xA0);
     StopI2C();
     return ack;
    }

     
    Other functions used:

    /********************************************************************
    *     Function Name:    WriteI2C                                    *
    *     Return Value:     Status byte for WCOL detection.             *
    *     Parameters:       Single data byte for I2C bus.               *
    *     Description:      This routine writes a single byte to the    *
    *                       I2C bus.                                    *
    ********************************************************************/
    #if defined (I2C_V1)
    signed char WriteI2C( unsigned char data_out )
    {
      SSPBUF = data_out;           // write single byte to SSPBUF
      if ( SSPCON1bits.WCOL )      // test if write collision occurred
       return ( -1 );              // if WCOL bit is set return negative #
      else
      {
     if( ((SSPCON1&0x0F)!=0x08) && ((SSPCON1&0x0F)!=0x0B) ) //Slave mode only
     {
           SSPCON1bits.CKP = 1;        // release clock line
           while ( !PIR1bits.SSPIF );  // wait until ninth clock pulse received
           if ( ( !SSPSTATbits.R_W ) && ( !SSPSTATbits.BF ) )// if R/W=0 and BF=0, NOT ACK was received
           {
             return ( -2 );           //return NACK
           }
        else
        {
       return ( 0 );    //return ACK
        } 
     }
     else if( ((SSPCON1&0x0F)==0x08) || ((SSPCON1&0x0F)==0x0B) ) //master mode only
     {
         while( SSPSTATbits.BF );   // wait until write cycle is complete  
         IdleI2C();                 // ensure module is idle
         if ( SSPCON2bits.ACKSTAT ) // test for ACK condition received
           return ( -2 );   // return NACK
      else return ( 0 );              //return ACK
     }
     
      }
    }
     
    /********************************************************************
    *     Function Name:    ReadI2C                                     *
    *     Return Value:     contents of SSPBUF register                 *
    *     Parameters:       void                                        *
    *     Description:      Read single byte from I2C bus.              *
    ********************************************************************/
    #if defined (I2C_V1)
    unsigned char ReadI2C( void )
    {
    if( ((SSPCON1&0x0F)==0x08) || ((SSPCON1&0x0F)==0x0B) ) //master mode only
      SSPCON2bits.RCEN = 1;           // enable master for 1 byte reception
      while ( !SSPSTATbits.BF );      // wait until byte received 
      return ( SSPBUF );              // return with read byte
    }
    #endif
     
    /********************************************************************
    *     Function Name:    StartI2C                                    *
    *     Return Value:     void                                        *
    *     Parameters:       void                                        *
    *     Description:      Send I2C bus start condition.               *
    ********************************************************************/
    #if defined (I2C_V1)
    #undef StartI2C
    void StartI2C( void )
    {
      SSPCON2bits.SEN = 1;            // initiate bus start condition
    }
    #endif
     
    /********************************************************************
    *     Function Name:    StopI2C                                     *
    *     Return Value:     void                                        *
    *     Parameters:       void                                        *
    *     Description:      Send I2C bus stop condition.                *
    ********************************************************************/
    #if defined (I2C_V1)
    #undef StopI2C
    void StopI2C( void )
    {
      SSPCON2bits.PEN = 1;            // initiate bus stop condition
    }
    #endif
     
    /*******************************************************************
    Macro       : RestartI2C1()
    Include     : i2c.h
    Description : Macro to initiate Restart condition
    Arguments   : None
    Remarks     : This macro initiates Restart condition and waits till the Restart signal
      sequence is terminated. This macro is applicable only to master
    *******************************************************************/
    #define RestartI2C1()  SSP1CON2bits.RSEN=1;while(SSP1CON2bits.RSEN)
    #define RestartI2C RestartI2C1

    Maybe the I2C functions for the old PIC18F242 are different from the PIC16F1825. This certainly seems simple enough...

     
    #9
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/25 03:27:06 (permalink)
    0
    Here is an approximate schematic of the project:

     
    The LCD display module is one of these, 3 pieces for $11:
    https://www.banggood.com/...h&cur_warehouse=CN

     
    #10
    Mysil
    Super Member
    • Total Posts : 3327
    • Reward points : 0
    • Joined: 2012/07/01 04:19:50
    • Location: Norway
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/25 09:14:09 (permalink)
    0
    Hi,
    Sloppy code working in one example is no proof that it will work in all cases.
     
    Functions in Plib are mostly macros and very simple functions for register access.
    They may be treacherous in making the simple actions look simple,
    but leaving the difficult parts up to code calling the library.
     
    Function:
        signed char WriteI2C( unsigned char data_out )
    return a function value indicating status of the transfer, including NACK or ACK.
    This have some effects:
    Since ACK/NACK status is to be reported, function have to wait for transfer to complete,
    so function isn't suitable is someone intend to write a non-blocking driver.
    Then, there are additional conditions that indicate problems that are not tested, in particular Bus Collision flag.
    In other functions in Plib library, there are no testing, and no status returned, even if the operation is just as likely to fail.
     
    Calling  WriteI2C(...) in snippet shown in message #9 and not testing the return value, nor testing control and status register flags, is pushing any problem under the carpet. 
     
    I2C peripheral in PIC18F242 and PIC16F1825 are very similar in Master mode.
    Both these devices have only one I2C peripheral, but _1825 is part of a family where some products have 2 SSP channels. This have effect on preferred register naming in datasheet and device support file.
    PIC18F242 and siblings, do not have I2C Address masking in Slave mode,
    PIC16F1825 have extra SSPxCON3 and SSPxMSK registers.
    These are for additional features, mostly in I2C Address detection, clock stretching, and Acknowledge control in I2C Slave Mode.
     
    Most differences in Plib between devices, are between devices with 1 or 2 instances of the I2C peripheral,
    and for devices with different pin assignments.
    Some differences also come from differences in terminology in datasheets,
    and corresponding differences in device support header files.
     
       Mysil
    #11
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/25 16:18:12 (permalink)
    0
    I tried using the test for ACK/NACK from the slave EEPROM and I could never detect it. I added timeouts for the blocking functions, but could not get the reads to work. I think perhaps the BMS project reads correctly has something to do with also talking to the LCD and maybe the serial port. I had also tried using the MCC but it used delays with a timer interrupt as well as for I2C, I think. It was rather confusing. Here is its I2C_driver.c module (with some modifications I tried to get it working):

     
    /*
        (c) 2016 Microchip Technology Inc. and its subsidiaries. You may use this
        software and any derivatives exclusively with Microchip products.
     
        THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
        EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
        WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
        PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION
        WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION.
     
        IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
        INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
        WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
        BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE
        FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN
        ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
        THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
     
        MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE
        TERMS.
    */
     

    #include <stdio.h>
    #include <stdint.h>
    #include <stdbool.h>
    #include "mcc.h"
    #include "i2c_driver.h"
     
    #pragma warning disable 520       
     
    inline void i2c_driver_close(void)
    {
        SSP1CON1bits.SSPEN = 0;
    }
     
    /* Interrupt Control */
    inline void mssp_enableIRQ(void)
    {
        PIE1bits.SSP1IE = 1;
    }
     
    inline bit mssp_IRQisEnabled(void)
    {
        return PIE1bits.SSP1IE;
    }
     
    inline void mssp_disableIRQ(void)
    {
        PIE1bits.SSP1IE = 0;
    }
     
    inline void mssp_clearIRQ(void)
    {
        PIR1bits.SSP1IF = 0;
    }
     
    inline void mssp_setIRQ(void)
    {
        PIR1bits.SSP1IF = 1;
    }
     
    inline bit mssp_IRQisSet(void)
    {
        return PIR1bits.SSP1IF;
    }
     
    inline void mssp_waitForEvent(uint16_t *timeout)
    {
        //uint16_t to = (timeout!=NULL)?*timeout:100;
        //to <<= 8;
     
        if(PIR1bits.SSP1IF == 0)
        {
            while(1)// to--)
            {
                if(PIR1bits.SSP1IF) break;
                __delay_us(100);
            }
        }
    }
     
    bit i2c_driver_open(void)
    {
        if(!SSP1CON1bits.SSPEN) //was !
        {
            SSP1STAT = 0x00;
            SSP1CON1 = 0x28;
            SSP1CON2 = 0x00;
            SSP1ADD = 0x9;
            return true;
        }
        else
            return false;
    }
     
    bit i2c_driver_initSlaveHardware(void)
    {
        if(!SSP1CON1bits.SSPEN)
        {
    /* NOTE on AHEN:
     * If multiple slaves are to be emulated, then AHEN must be set.  It must be set
     * because the driver needs to selectively ACK/NACK the address depending on its
     * ability to handle the address.
    */
     
    /* NOTE on DHEN:
     * DHEN must be set so that the data is not automatically NACK'ed if it is not read
     * from the SSPBUF.  This driver will ALWAYS read the SSPBUF so that it can pass
     * the value to the appropriate slave handler.  Because the data is ALWAYS read
     * the data will always be ACK'd if DHEN is cleared.  If the slave does not want
     * the data byte from the master then it will return false and a NACK will be returned.
     */
     
    /* NOTE on SEN:
     * SEN will be set enabling clock stretching.  This is because we don't know how
     * long the user will take to process data bytes in their callbacks.  If they are fast,
     * we may not need to stretch the clock.  If they are slow, we need to stretch the clock.
     * If we ALWAYS stretch the clock, we will release the clock when the ISR is complete.
     */
     
    /* NOTE on PCIE:
     * PCIE will be set to enable interrupts on STOP.  This will allow us know when
     * the master is finished
     */
           
    /* NOTE on SCIE:
     * SCIE will be set to enable interrupts on START.  This will allow us to detect
     * both a START and a RESTART event and prepare to restart communications.
     */
            SSP1CON1 |= 0x06; //setup I2C Slave (7-bit Addressing)
            SSP1STAT = 0x00;
            SSP1CON2 = 0x00;
           
            SSP1CON1bits.SSPEN = 1;
            return true;
        }
        return false;
    }
     
    inline void i2c_driver_resetBus(void)
    {
       
    }
     
    inline void i2c_driver_start(void)
    {
        SSP1CON2bits.SEN = 1;
    }
     

    inline void i2C_driver_start0(void)
    {
        if(SSP1CON1bits.SSPEN == 0) SSP1CON1 = 0x28;SSP1CON2bits.SEN = 1; while(SSP1CON2bits.SEN);
    }
     
    inline void i2c_driver_restart(void)
    {
        SSP1CON2bits.RSEN = 1;
    }
     
    inline void i2c_driver_stop(void)
    {
        SSP1CON2bits.PEN = 1;
    }
     
    inline bit i2c_driver_isNACK(void)
    {
        return SSP1CON2bits.ACKSTAT = 1;
    }
     
    inline void i2c_driver_startRX(void)
    {
        SSP1CON2bits.RCEN = 1;
    }
     
    inline char i2c_driver_getRXData(void)
    {
        return SSP1BUF;
    }
     
    inline void i2c_driver_setAddr(char addr)
    {
        SSP1ADD = addr;
    }
     
    inline void i2c_driver_setMask(char mask)
    {
        SSP1MSK = mask;
    }
     
    inline void i2c_driver_TXData(char d)
    {
        SSP1BUF = d;
    }
     
    inline char i2c_driver_getAddr(void)
    {
        return SSP1ADD;
    }
     
    inline void i2c_driver_sendACK(void)
    {
        SSP1CON2bits.ACKDT = 0;
        SSP1CON2bits.ACKEN = 1; // start the ACK/NACK
    }
     
    inline void i2c_driver_sendNACK(void)
    {
        SSP1CON2bits.ACKDT = 1;
        SSP1CON2bits.ACKEN = 1; // start the ACK/NACK
    }
     
    inline void i2c_driver_releaseClock(void)
    {
        SSP1CON1bits.CKP = 1;
    }
     
    inline bit i2c_driver_isBufferFull(void)
    {
        return SSP1STATbits.BF;
    }
     
    inline bit i2c_driver_isStart(void)
    {
        return SSP1STATbits.S;
    }
     
    inline bit i2c_driver_isAddress(void)
    {
        return !SSP1STATbits.D_nA;
    }
     
    inline bit i2c_driver_isStop(void)
    {
        return SSP1STATbits.P;
    }
     
    inline bit i2c_driver_isData(void)
    {
        return SSP1STATbits.D_nA;
    }
     
    inline bit i2c_driver_isRead(void)
    {
        return SSP1STATbits.R_nW;
    }
     
    inline bit i2c_driver_isWriteCollision(void)
    {
        return SSP1CON1bits.WCOL;
    }
     
    inline bit i2c_driver_isReceiveOverflow(void)
    {
        return SSP1CON1bits.SSPOV;
    }
     
    inline void i2c_driver_clearBusCollision(void)
    {
        PIR2bits.BCL1IF = 0; // clear the bus collision.
    }
     
    inline void i2c_driver_setBusCollisionISR(void *f){
        i2c_driver_busCollisionISR = f;
    }
     
    inline void i2c_driver_setI2cISR(void *f){
        i2c_driver_i2cISR = f;
    }

    post edited by PStechPaul - 2018/03/25 16:39:50

     
    #12
    Mysil
    Super Member
    • Total Posts : 3327
    • Reward points : 0
    • Joined: 2012/07/01 04:19:50
    • Location: Norway
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/03/25 17:39:07 (permalink)
    0
    In MCC, there are now 2 different alternatives for I2C code:
    A:
    Non-blocking interrupt driver with a queue of tasks, in PIC device libraries.
    This code, I have extensively rewritten and published in other thread in this forum.
    B:
    Foundation Services Library, which was introduced about a year ago,
    together with examples for Click plugin boards.
    As far as I understand, this was developed as an exercise in layered design driver development,
    and consist of several files, not I2C_driver.c  only.
    I have not really used it.
     
    To Gerald1, re. message #8:
    LCD controller, at least HD44780U,
    have (weak) pull-up on RS, R/W and DB7-DB0 lines when working as inputs, 
    but Not for pin E input.
    Depending on signal timing and sequence, external pull-up may be needed on pin E only, or not at all.
     
       Mysil
    #13
    eagle1
    Super Member
    • Total Posts : 341
    • Reward points : 0
    • Joined: 2014/11/02 03:04:06
    • Location: Saudi Arabia
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/07/24 20:57:22 (permalink)
    0
    Here's my code, my MCU is PIC18F4550.
     
    /*
     * File: main_sketch.c
     * Author: R1S8k
     *
     * Created on July 17, 2018, 11:43 AM
     */

    // PIC18F4550 Configuration Bit Settings

    // 'C' source line config statements

    // CONFIG1L
    #pragma config PLLDIV = 1 // PLL Prescaler Selection bits (No prescale (4 MHz oscillator input drives PLL directly))
    #pragma config CPUDIV = OSC1_PLL2// System Clock Postscaler Selection bits ([Primary Oscillator Src: /1][96 MHz PLL Src: /2])
    #pragma config USBDIV = 1 // USB Clock Selection bit (used in Full-Speed USB mode only; UCFG:FSEN = 1) (USB clock source comes directly from the primary oscillator block with no postscale)

    // CONFIG1H
    #pragma config FOSC = INTOSC_XT // Oscillator Selection bits (Internal oscillator, XT used by USB (INTXT))
    #pragma config FCMEN = OFF // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
    #pragma config IESO = OFF // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

    // CONFIG2L
    #pragma config PWRT = OFF // Power-up Timer Enable bit (PWRT disabled)
    #pragma config BOR = ON // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
    #pragma config BORV = 3 // Brown-out Reset Voltage bits (Minimum setting 2.05V)
    #pragma config VREGEN = OFF // USB Voltage Regulator Enable bit (USB voltage regulator disabled)

    // CONFIG2H
    #pragma config WDT = OFF // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit))
    #pragma config WDTPS = 32768 // Watchdog Timer Postscale Select bits (1:32768)

    // CONFIG3H
    #pragma config CCP2MX = ON // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
    #pragma config PBADEN = ON // PORTB A/D Enable bit (PORTB<4:0> pins are configured as analog input channels on Reset)
    #pragma config LPT1OSC = OFF // Low-Power Timer 1 Oscillator Enable bit (Timer1 configured for higher power operation)
    #pragma config MCLRE = OFF // MCLR Pin Enable bit (RE3 input pin enabled; MCLR pin disabled)

    // CONFIG4L
    #pragma config STVREN = OFF // Stack Full/Underflow Reset Enable bit (Stack full/underflow will not cause Reset)
    #pragma config LVP = OFF // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
    #pragma config ICPRT = OFF // Dedicated In-Circuit Debug/Programming Port (ICPORT) Enable bit (ICPORT disabled)
    #pragma config XINST = OFF // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

    // CONFIG5L
    #pragma config CP0 = OFF // Code Protection bit (Block 0 (000800-001FFFh) is not code-protected)
    #pragma config CP1 = OFF // Code Protection bit (Block 1 (002000-003FFFh) is not code-protected)
    #pragma config CP2 = OFF // Code Protection bit (Block 2 (004000-005FFFh) is not code-protected)
    #pragma config CP3 = OFF // Code Protection bit (Block 3 (006000-007FFFh) is not code-protected)

    // CONFIG5H
    #pragma config CPB = OFF // Boot Block Code Protection bit (Boot block (000000-0007FFh) is not code-protected)
    #pragma config CPD = OFF // Data EEPROM Code Protection bit (Data EEPROM is not code-protected)

    // CONFIG6L
    #pragma config WRT0 = OFF // Write Protection bit (Block 0 (000800-001FFFh) is not write-protected)
    #pragma config WRT1 = OFF // Write Protection bit (Block 1 (002000-003FFFh) is not write-protected)
    #pragma config WRT2 = OFF // Write Protection bit (Block 2 (004000-005FFFh) is not write-protected)
    #pragma config WRT3 = OFF // Write Protection bit (Block 3 (006000-007FFFh) is not write-protected)

    // CONFIG6H
    #pragma config WRTC = OFF // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) are not write-protected)
    #pragma config WRTB = OFF // Boot Block Write Protection bit (Boot block (000000-0007FFh) is not write-protected)
    #pragma config WRTD = OFF // Data EEPROM Write Protection bit (Data EEPROM is not write-protected)

    // CONFIG7L
    #pragma config EBTR0 = OFF // Table Read Protection bit (Block 0 (000800-001FFFh) is not protected from table reads executed in other blocks)
    #pragma config EBTR1 = OFF // Table Read Protection bit (Block 1 (002000-003FFFh) is not protected from table reads executed in other blocks)
    #pragma config EBTR2 = OFF // Table Read Protection bit (Block 2 (004000-005FFFh) is not protected from table reads executed in other blocks)
    #pragma config EBTR3 = OFF // Table Read Protection bit (Block 3 (006000-007FFFh) is not protected from table reads executed in other blocks)

    // CONFIG7H
    #pragma config EBTRB = OFF // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) is not protected from table reads executed in other blocks)

    // #pragma config statements should precede project file includes.
    // Use project enums instead of #define for ON and OFF.
    #include <xc.h>
    #include <stdint.h>
    #include <stdio.h>
    #define _XTAL_FREQ 4000000
    #define blink_RC2() (LATCbits.LATC2=1,__delay_ms(100),LATCbits.LATC2=0,__delay_ms(100))
    #define E 2 // E bit
    #define RW 1 // RW bit
    #define RS 0 // RS bit
     
    #define function_set1 0x02
    #define function_set2 0x28
    #define function_set3 0x01
    #define function_set4 0x0C

    #define display_control 0x0F
    #define shift_cursor 0x06
    #define clear_display 0x01
    #define first_row 0x80
    #define second_row 0xBF

    #define ddram_before_char1 0xD0
    #define ddram_after_char16 0x11
    #define ddram_before_char17 0xBF
    #define ddram_after_char32 0x22

    #define PCF8574_WR 0x4E
    #define PCF8574_RD 0x4F

    uint8_t i, bitmask;
    uint8_t lcd_str[]={"Hello"};
    extern uint8_t arr[];

    // I2C prototypes
    void i2c_init(void);
    void i2c_idle(void);
    void i2c_start(void);
    void i2c_write(uint8_t data);
    uint8_t i2c_read(void);
    void i2c_ack(void);
    void i2c_nack(void);
    void i2c_stop(void);
    void i2c_restart(void);
    void ck_clr_SSPIF(void);
    // LCD prototypes
    void LCD_Init(void);
    void sendCMD(uint8_t CMD);
    void sendData(uint8_t data);
    void LCD_string(uint8_t arr1[]);
    void move_cursor (uint8_t row, uint8_t col);
    void lcdCMD_read (void);

    void main(void) {
        ADCON0=0x00;ADCON1=0x0f;CMCON=0x07; // disable ADC, comparators
        OSCCON=0x66; // internal oscillator 4MHz
        TRISC=0x00; // port settings most important CCP1 at port C as output
        INTCON=0x00; // compare settings & disable all interrupts
        i2c_init();
        i2c_start();
        LCD_Init();
        
        while(1){
            sendCMD(0x60);
            __delay_ms(500);
            /*LCD_string(lcd_str);
            move_cursor(2,0);
            sendData('a');
            __delay_ms(200);
            move_cursor(2,4);
            sendData('b');
            __delay_ms(500);
            sendCMD(clear_display);*/
        }
    }

    void i2c_init(void)
    {
        TRISB = 0x03; //Configuring SDA and SCL as input pins
        SSPCON1 = 0x28; //Configuring as I2C Master mode
        SSPADD = 0x27; //Setting SCL frequency with value in clock_freq
        SSPSTAT = 0x00; //Resetting SSPSTAT register
        PIR1bits.SSPIF = 0; //Resetting MSSP interrupt flag
    }

    void ck_clr_SSPIF(void) {while(!PIR1bits.SSPIF);PIR1bits.SSPIF=0;}

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

    void i2c_start(void)
    {
        SSPCON2bits.SEN=1; //Initiate start condition
        ck_clr_SSPIF();
    }

    void i2c_write(uint8_t data)
    {
        SSPBUF=data; //Input data to buffer
        ck_clr_SSPIF();
        if(SSPCON2bits.ACKSTAT){ //check if acknowledgement signal received
        SSPCON2bits.RSEN=1;
        ck_clr_SSPIF();
        SSPBUF=data;
        }
    }

    uint8_t i2c_read(void)
    {
        SSPCON2bits.RCEN=1; //Enable reception in Master device
        while(!SSPSTATbits.BF); //Wait till buffer is full
        SSPCON2bits.RCEN=0; //Disable reception in Master device
        ck_clr_SSPIF();
        return SSPBUF; //return received data
    }

    void i2c_ack(void)
    {
        SSPCON2bits.ACKDT=0; //Set as acknowledgement
        SSPCON2bits.ACKEN=1; //Initiate acknowledgement signal
        ck_clr_SSPIF();
    }
     
    void i2c_nack(void)
    {
        SSPCON2bits.ACKDT=1; //Set as negative acknowledgement
        SSPCON2bits.ACKEN=1; //Initiate negative acknowledgement signal
        ck_clr_SSPIF();
    }

    void i2c_stop(void)
    {
        SSPCON2bits.PEN=1; //Initiate Stop condition
        ck_clr_SSPIF();
        SSPCON1bits.SSPEN=0; //Disable I2C operation
    }

    ///////////////////////////////////////////////////
    ///////////////////////////////////////////////////

    void LCD_Init(void)
    {
        __delay_ms(15);
        sendCMD(function_set1);
     sendCMD(function_set2);
     sendCMD(function_set3);
     sendCMD(function_set4);
     sendCMD(display_control);
     sendCMD(clear_display);
    }

    void sendCMD(uint8_t CMD)
    {
        bitmask=0;
     i2c_write(PCF8574_WR);
     i2c_write(bitmask = (CMD & 0xF0) |0x08| (bitmask & 0x0F));
     i2c_write(bitmask &= ~(1<<RS));
        i2c_write(bitmask |= (1<<E));
        __delay_us(100);
     i2c_write(bitmask &= ~(1<<E));
        __delay_ms(1);
     i2c_write(bitmask = (CMD<<4) |0x08| (bitmask & 0x0F));
     i2c_write(bitmask |= (1<<E));
        __delay_us(10);
     i2c_write(bitmask &= ~(1<<E));
        __delay_ms(3);
     i2c_stop();
    }

    void sendData(uint8_t data)
    {
        bitmask=0;
     i2c_write(PCF8574_WR);
     i2c_write(bitmask = (data & 0xF0) |0x08| (bitmask & 0x0F));
     i2c_write(bitmask |= (1<<RS));
        i2c_write(bitmask |= (1<<E));
        __delay_us(10);
     i2c_write(bitmask &= ~(1<<E));
        __delay_ms(1);
     i2c_write(bitmask = (data<<4) |0x08| (bitmask & 0x0F));
     i2c_write(bitmask |= (1<<E));
        __delay_us(10);
     i2c_write(bitmask &= ~(1<<E));
        __delay_ms(3);
     i2c_stop();
    }

    /*uint8_t BusyFlag_check(void)
    {
     i2c_write(PCF8574_WR);
     i2c_write(bitmask &= ~(1<<RS));
     i2c_write(bitmask |= (1<<RW));
     i2c_stop();
     i2c_write(PCF8574_RD);
     i2c_read();
     while (!(SSPBUF == (bitmask & (bitmask<<3))));
     i2c_stop();
     i2c_write(PCF8574_WR);
     i2c_write(bitmask |= (1<<RS));
     i2c_write(bitmask &= ~(1<<RS));
     i2c_stop();
     return bitmask;
    }*/

    void lcdCMD_read (void)
    {
     i2c_write(PCF8574_WR); // write PCF8574 address
     i2c_write(bitmask = 0x0B); // RS, RW = 1 read data
     i2c_write(bitmask |= (1<<E));
     i2c_write(bitmask &= ~(1<<E));
     i2c_stop();
     i2c_write(PCF8574_RD); // read PCF8574 address
     i2c_read(); // receive I2C and get SSPBUF
     bitmask=SSPBUF;
     //while (!(SSPBUF == (bitmask & (bitmask<<7)))); // This function never ends
     i2c_stop();
    }

    void move_cursor (uint8_t row, uint8_t col)
    {
     if (row==1)
     sendCMD(first_row+col);
     if (row==2)
     sendCMD(0xC0+col);
    }

    void LCD_string(uint8_t arr1[])
    {
     sendCMD(clear_display);
     sendCMD(ddram_before_char1);
     sendCMD(first_row);
        uint8_t cnt=1;
     while (arr1[i]!='\0')
     {
      sendData(arr1[i]);
      __delay_ms(200);
      cnt++;
      if (cnt==ddram_after_char16)
      sendCMD(second_row);
      if (cnt==ddram_after_char32)
      sendCMD(clear_display);
     }
    }

    #14
    eagle1
    Super Member
    • Total Posts : 341
    • Reward points : 0
    • Joined: 2014/11/02 03:04:06
    • Location: Saudi Arabia
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2018/07/25 06:35:50 (permalink)
    0
    OK, I fixed my library it's on Github:
    https://github.com/eagl1/PIC18_LCD1602_I2C
    #15
    HENRYTAN
    Starting Member
    • Total Posts : 43
    • Reward points : 0
    • Joined: 2018/08/01 14:47:19
    • Location: 0
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2019/04/02 22:35:02 (permalink)
    0
    PStechPaul
    After struggling with various libraries and code examples, I have come up with a simplified example that uses macros for I2C interface. I plan to build another project for serial EEPROMs.
     
    Here is the test program:

     
    // PIC16F1825 Configuration Bit Settings
     
    // 'C' source line config statements
     
    // CONFIG1
    #pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
    #pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
    #pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
    #pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
    #pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
    #pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
    #pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
    #pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
    #pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
    #pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
     
    // CONFIG2
    #pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
    #pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
    #pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
    #pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
    #pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)
     
    // #pragma config statements should precede project file includes.
    // Use project enums instead of #define for ON and OFF.
     
    #include    <xc.h>
    #include    "I2C_macros.h"
    #include    "LCD_I2C.h"
     
    void    main (void){
        OSCCON = 0b01101011;    //4 MHz
        TRISA = 0b00011011;     //RA2, RA5 output
        TRISC = 0b00110011;     //RC2, RC3 output
        WPUA = 0b00001000;      //Pull-up on RA3
        ANSELA = 0b00010000;    //AN3 RA4
        ANSELC = 0;
        LATA = 0b00000100;      // TXEN OFF
     
        I2C_INIT
               
        while(1) {
        LCD_open();
        LCD_init();
     
        LCD_goto(1,1);
        LCD_write_string("** LCD test **");
        LCD_close();
        __delay_ms(1000);
        LATA5 ^= 1;
        }         
    }

     
    The I2C interface:

     
    #ifndef __I2C_MACROS_H
    #define __I2C_MACROS_H
     
    #define SDA  RC1   // Pin 9, Data pin for i2c
    #define SCK  RC0   // Pin 10, Clock pin for i2c
    #define SDA_DIR  TRISC1   // Data pin direction
    #define SCK_DIR  TRISC0   // Clock pin direction
     
    #define I2C_SCL TRISCbits.TRISC0
    #define I2C_SDA TRISCbits.TRISC1
    // Define i2c speed
    #define _XTAL_FREQ 4000000
    #define I2C_SPEED 100    // kbps
     
    #define I2C_OPEN    SSP1CON1 |= 0x20;              // enable synchronous serial port
    #define I2C_CLOSE   SSP1CON1 &= 0xdf;              // disable synchronous serial port
    #define I2C_INIT    SDA_DIR=1; SCK_DIR=1; SSP1ADD=((_XTAL_FREQ/4000)/I2C_SPEED) - 1; SSP1STAT=0x80; SSP1CON1=0x28;
    #define I2C_START   SSP1CON2bits.SEN = 1; //while(SSP1CON2bits.SEN);
    #define I2C_STOP    SSP1CON2bits.PEN = 1; //while(SSP1CON2bits.PEN);
    #define I2C_ACK     SSP1CON2bits.ACKDT=0; SSP1CON2bits.ACKEN=1; while(RCEN && SSP1CON2bits.ACKEN);
    #define I2C_NACK    SSP1CON2bits.ACKDT=1; SSP1CON2bits.ACKEN=1; while(RCEN && SSP1CON2bits.ACKEN);
    #define I2C_READ    RCEN=1; while(SSP1CON2bits.ACKEN); return SSP1BUF;
    #define I2C_WRITE(d)    I2C_IDLE SSP1BUF=d; while( SSP1STATbits.BF );
    #define I2C_WAIT    while(!PIR1bits.SSP1IF); PIR1bits.SSP1IF=0;
    #define I2C_IDLE    while ((SSP1CON2 & 0x1F) || (SSP1STATbits.R_nW));
     
    #endif

     
    The header for the LCD module:

     
    #ifndef LCD_I2C_h
    #define LCD_I2C_h
     
    #define BYTE    unsigned char
    // commands
    #define LCD_CLEARDISPLAY 0x01
    #define LCD_CLRSCR 0x01
    #define LCD_HOME 0x02
    #define LCD_ENTRYMODESET 0x04
    #define LCD_DISPLAYCONTROL 0x08
    #define LCD_CURSORSHIFT 0x10
    #define LCD_FUNCTIONSET 0x20
    #define LCD_SETCGRAMADDR 0x40
    #define LCD_SETDDRAMADDR 0x80
     
    // flags for display entry mode
    #define LCD_ENTRYRIGHT 0x00
    #define LCD_ENTRYLEFT 0x02
    #define LCD_ENTRYSHIFTINCREMENT 0x01
    #define LCD_ENTRYSHIFTDECREMENT 0x00
     
    // flags for display on/off control
    #define LCD_DISPLAYON 0x04
    #define LCD_DISPLAYOFF 0x00
    #define LCD_CURSORON 0x02
    #define LCD_CURSOROFF 0x00
    #define LCD_BLINKON 0x01
    #define LCD_BLINKOFF 0x00
     
    // flags for display/cursor shift
    #define LCD_DISPLAYMOVE 0x08
    #define LCD_CURSORMOVE 0x00
    #define LCD_MOVERIGHT 0x04
    #define LCD_MOVELEFT 0x00
     
    // flags for function set
    #define LCD_8BITMODE 0x10
    #define LCD_4BITMODE 0x00
    #define LCD_2LINE 0x08
    #define LCD_1LINE 0x00
    #define LCD_5x10DOTS 0x04
    #define LCD_5x8DOTS 0x00
     
    // flags for backlight control
    #define LCD_BACKLIGHT 0x00
    #define LCD_NOBACKLIGHT 0x80
     
    /* Original adapter
     * RS --- P5
     * E ---- P4
     * D4 --- P0
     * D5 --- P1
     * D6 --- P2
     * D7 --- P3
    */
    #define En B00010000  // Enable bit
    #define Rw B00100000  // Read/Write bit
    #define Rs B01000000  // Register select bit
     
    #define RS 0x01  // P0 (RS pin was 04)
    #define RW  0x02        // P1
    #define E 0x04  // P2 (E pin was 08)
     
    #define SET_RS LCDpins |= RS
    #define CLR_RS LCDpins &= ~RS
    #define SET_RW LCDpins |= RW
    #define CLR_RW LCDpins &= ~RW
    #define SET_E LCDpins |= E
    #define CLR_E LCDpins &= ~E
     
    #define LCD_I2C_ADDR  (BYTE)0x4E
     
    // Line addresses for LCDs which use
    // the Hitachi HD44780U controller chip
    #define LCD_LINE_1_ADDRESS 0x00
    #define LCD_LINE_2_ADDRESS 0x40
    #define LCD_LINE_3_ADDRESS 0x14
    #define LCD_LINE_4_ADDRESS 0x54
     
    BYTE  LCD_BL_Status = 1;     // 1 for POSITIVE control, 0 for NEGATIVE control
     
    BYTE  pin_E;//   =    I2C_BYTE.2
    BYTE  pin_RW;//  =    I2C_BYTE.1
    BYTE  pin_RS;//  =    I2C_BYTE.0
    BYTE  pin_D4;//  =    I2C_BYTE.4
    BYTE  pin_D5;//  =    I2C_BYTE.5
    BYTE  pin_D6;//  =    I2C_BYTE.6
    BYTE  pin_D7;//  =    I2C_BYTE.7
    BYTE  pin_BL;//  =    I2C_BYTE.3
     
    #define  testbit(var, bit)   ((var) & (1 <<(bit)))
    #define bit_test    testbit
    #define  setbit(var, bit)    ((var) |= (1 << (bit)))
    #define  clrbit(var, bit)    ((var) &= ~(1 << (bit)))
     
    #define LCD_sendcmd(a)  CLR_RS; LCD_sendbyte(a);
     
    #define LCD_sendchar(a) SET_RS; LCD_sendbyte(a);
     
    BYTE _LCD_build_byte();
    void _LCD_write_lower_nibble(BYTE l);
    void _LCD_write_upper_nibble(BYTE u);
    void LCD_BL(BYTE status);
    void LCD_clear_line(BYTE line);
    void LCD_clear();
    void LCD_close( void );
    void LCD_goto(BYTE x, BYTE y);
    //void LCD_home( void );
    void LCD_init( void );
    void LCD_open( void );
    //void LCD_second_row( void );
    //void LCD_send_hexbyte ( BYTE data );
    //void LCD_sendbyte( BYTE tosend );
    //void LCD_send_string( const char *str_ptr );
    //void LCD_send_ram_string (char *str_ptr);
    void LCD_write_byte(BYTE address, BYTE n);
    void LCD_write_string(const char *str);
     
    #endif

     
    And the LCD code:

     
    /* Serial LCD support routines */
     
    #define _LCD_I2C_c_
     
    #include    <xc.h>
    #include    "LCD_I2C.h"
    #include    "I2C_macros.h"
    /* local definitions */
     
    BYTE _LCD_build_byte();
    void _LCD_write_lower_nibble(BYTE l);
    void _LCD_write_upper_nibble(BYTE u);
     
    void LCD_open()
    {
        I2C_IDLE
        I2C_OPEN
        I2C_IDLE
        I2C_START
        I2C_IDLE
        I2C_WRITE(LCD_I2C_ADDR)          // Tell all I2C devices you are talking to LCD_PIC_I2C_ADDR
        I2C_ACK
    }
     
    void LCD_close()
    {
        I2C_IDLE
        I2C_CLOSE   //I2C_close();
    }
     
    void LCD_init()
    {
        LCD_open();
     
        // Following bytes are all Command bytes, i.e. address = 0x00
        LCD_write_byte(0x00, 0x03);   // Write Nibble 0x03 three times (per HD44780U initialization spec)
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x03);   //
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x03);   //
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x02);   // Write Nibble 0x02 once (per HD44780U initialization spec)
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x02);   // Write Nibble 0x02 once (per HD44780U initialization spec)
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x01);   // Set mode: 4-bit, 2+lines, 5x8 dots
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x0C);   // Display ON 0x0C
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x01);   // Clear display
        __delay_ms(5);                // (per HD44780U initialization spec)
        LCD_write_byte(0x00, 0x06);   // Set cursor to increment
        __delay_ms(5);                // (per HD44780U initialization spec)
     
    }
     
    void LCD_BL(BYTE status)
    {
        LCD_BL_Status = status;
        LCD_write_byte(0x00, 0x00);
    }
     
    void LCD_goto(BYTE x, BYTE y)   //x=col, y=row
    {
    BYTE address;
     
       switch(y)
         {
          case 1:
            address = LCD_LINE_1_ADDRESS;
            break;
     
          case 2:
            address = LCD_LINE_2_ADDRESS;
            break;
     
          case 3:
            address = LCD_LINE_3_ADDRESS;
            break;
     
          case 4:
            address = LCD_LINE_4_ADDRESS;
            break;
     
          default:
            address = LCD_LINE_1_ADDRESS;
            break;
         }
     
       address += x-1;
       LCD_write_byte(0, 0x80 | address);
    }
     
    void LCD_write_string(const char *str)
    {
       // Writes a string text[] to LCD via I2C
       pin_RS  = 1;
       pin_RW  = 0;
       pin_E   = 0;
       pin_BL  = LCD_BL_Status;
     
       while (*str)
       {
            // Send upper nibble
            _LCD_write_upper_nibble(*str);
     
            // Send lower nibble
            _LCD_write_lower_nibble(*str);
     
            str++;
       }
    }
    void LCD_write_byte(BYTE address, BYTE n)
    {
        if (address)
        {
            pin_RS=1;   // Data
        }
        else
        {
            pin_RS=0;   // Command
        }
     
        pin_RW  = 0;
        pin_E   = 0;
        pin_BL  = LCD_BL_Status;
     
        // Send upper nibble
       _LCD_write_upper_nibble(n);
     
        // Send lower nibble
       _LCD_write_lower_nibble(n);
    }
     
    void LCD_clear()
    {
        LCD_write_byte(0x00,0x01);
        __delay_ms(5);
    }
     
    void LCD_clear_line(BYTE line)
    {
        LCD_goto(1,line);
        for (int i = 0; i<20; i++)
        {
            LCD_write_string(" ");
        }
        LCD_goto(1,line);
    }
     
    void _LCD_write_upper_nibble(BYTE u)
    {
        // Send upper nibble
        if(bit_test(u,7))
            pin_D7=1;
        else
            pin_D7=0;
     
        if(bit_test(u,6))
            pin_D6=1;
        else
            pin_D6=0;
     
        if(bit_test(u,5))
            pin_D5=1;
        else
            pin_D5=0;
     
        if(bit_test(u,4))
            pin_D4=1;
        else
            pin_D4=0;
     
       pin_E = 0;
       I2C_WRITE(_LCD_build_byte());
       pin_E = 1;
       I2C_WRITE(_LCD_build_byte());
       pin_E = 0;
       I2C_WRITE(_LCD_build_byte());
    }
     
    void _LCD_write_lower_nibble(BYTE l)
    {
        // Send lower nibble
        if(bit_test(l,3))
            pin_D7=1;
        else
            pin_D7=0;
     
        if(bit_test(l,2))
            pin_D6=1;
        else
            pin_D6=0;
     
        if(bit_test(l,1))
            pin_D5=1;
        else
            pin_D5=0;
     
        if(bit_test(l,0))
            pin_D4=1;
        else
            pin_D4=0;
     
        pin_E = 0;
        I2C_WRITE(_LCD_build_byte())
        pin_E = 1;
        I2C_WRITE(_LCD_build_byte())
        pin_E = 0;
        I2C_WRITE(_LCD_build_byte())
    }
     
    BYTE _LCD_build_byte()
    {
        BYTE ret = 0x00;
     
        ret |= pin_E    << 2;
        ret |= pin_RW   << 1;
        ret |= pin_RS   << 0;
        ret |= pin_D4   << 4;
        ret |= pin_D5   << 5;
        ret |= pin_D6   << 6;
        ret |= pin_D7   << 7;
        ret |= pin_BL   << 3;
     
        return ret;
    }

     
    I will also attach the project.


    Hi: Paul; I downloaded your I2C LCD code and try them on my hardware, MCU pic16f18446 and I2C LCD1602 with PCF8574T chip, the tool is same as yours: pickit3; I just changed some configurations to fit with my MCU, like SDA & SCL pins changed to RB6 & RB4, but unfortunately, there is nothing being displayed on my LCD, so ask for your   suggestions, attached is for my project, thanks!
    #16
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2019/04/03 20:21:43 (permalink)
    0
    It's been a long time since I played with this, and I've moved on to other projects for now. I don't even know if the attached project is operational. I had this and other projects on a Win10 computer that suffered a disk failure and I didn't realize that the MPLABXProjects folder was not backed up because it was not under Documents. I might be able to check it out in a few days, but I can't test your code. Can you provide a schematic, and specifications for your I2C interface to the LCD display?

     
    #17
    HENRYTAN
    Starting Member
    • Total Posts : 43
    • Reward points : 0
    • Joined: 2018/08/01 14:47:19
    • Location: 0
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2019/04/05 00:31:03 (permalink)
    +1 (1)
    PStechPaul
    It's been a long time since I played with this, and I've moved on to other projects for now. I don't even know if the attached project is operational. I had this and other projects on a Win10 computer that suffered a disk failure and I didn't realize that the MPLABXProjects folder was not backed up because it was not under Documents. I might be able to check it out in a few days, but I can't test your code. Can you provide a schematic, and specifications for your I2C interface to the LCD display?


    Hi: Paul; 
     Many thanks for your reply! I just figured out what I have missed: the RB4(SDA) and RB6 related PPS registers setting ! since I added them in the code, the expected display came out,  please find the attached pic! thanks again!
     
    Best Regards,
    Henry

    Attached Image(s)

    #18
    PStechPaul
    Super Member
    • Total Posts : 2354
    • Reward points : 0
    • Joined: 2006/06/27 16:11:32
    • Location: Cockeysville, MD, USA
    • Status: online
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2019/04/05 12:30:00 (permalink)
    0
    I was thinking it might be something like that. The 16F1825 does not have the PPS feature. Glad it worked for you :)

     
    #19
    vleroylr
    New Member
    • Total Posts : 9
    • Reward points : 0
    • Joined: 2018/08/09 20:12:20
    • Location: 0
    • Status: offline
    Re: Simplified I2C Interface to LCD Character Display with PCF8574 Adapter (PIC16F1825) 2019/07/24 15:29:09 (permalink)
    +2 (2)
    Grettings,
     
    Thanks for sharing your project, I was searching for some info about the initialization of the LCD display because I was very confused with what stated in the datasheet, thanks to your info I managed correcting the sequence and making my LCD display work.
     
    Thanks again! (A LOT)
     
    Víctor.
    #20
    Jump to:
    © 2019 APG vNext Commercial Version 4.5