• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Problem with PIC16F874A I2C communication with a 24LC512 EEPROM

Pommie

Well-Known Member
Most Helpful Member
During development the I²C bus often gets interrupted when you compile. To fix this, I check (at start up) for SDA being held low and send clock pulses until it's released. Once released the next start condition will reset everything.

Mike.
BTW, what language are you using?
 

vero

New Member
Yes, A2,A1,A0 are all zero in code and set to ground in the circuit. Are delays needed between each transmission to the EEPROM? In the SSPSTAT register, I chose for SMP; 1 = Slew rate control disabled for standard speed mode (100 kHz and 1 MHz). I'm not sure if this is correct.

For a write:
1) check if the I2C bus is idle
2) Send the start bit
3) Send the EEPROM control code, slave address, write bit; 0xA0
4) wait for BF to set
5) wait for ACK from slave, ACKSTAT = 0
6) Restart if no ACK from slave and resend data
7) If ACK received, set ACKSTAT = 1
8) Send the MSB address on the EEPROM, repeat steps 5-7
9) Send the LSB address on the EEPROM, repeat steps 5-7
10) Send data to the EEPROM, repeat steps 5-7
11) Send stop bit
12) Wait for SSPIF to set
13) Reset SSPIF

For Random read from EEPROM:
1) check if the I2C bus is idle
2) send start bit
3) Send the EEPROM control code, slave address, write bit; 0xA0
4) wait for BF to set
5) wait for ACK from slave, ACKSTAT = 0
6) Restart if no ACK from slave and resend data
7) If ACK received, set ACKSTAT = 1
8) Send the MSB address on the EEPROM, repeat steps 5-7
9) Send the LSB address on the EEPROM, repeat steps 5-7
10) Send the EEPROM control code, slave address, read bit; 0xA1
11) Read data from SSPBUF, send a dummy value r_I2C(0) wait til BF sets
12) Repeat step 11
13) Send nack to EEPROM; set ACKDT and ACKEN to one
14) wait for SSPIF to set
15) reset SSPIF
16) send stop bit
 

Attachments

Pommie

Well-Known Member
Most Helpful Member
Are you using C or assembly?

Mike.
BTW, I never use the BF flag but check SSPIF instead.
 

Pommie

Well-Known Member
Most Helpful Member
This is my initialization routine.
Code:
void I2cInit(){
    I2cDataTris=1;              //Make data input
    I2cClockTris=1;             //and clock
    while(!I2cData){            //if something holding line low
        I2cClock=0;             //then pulse clock line
        I2cClockTris=0;
        NOP();
        NOP();
        I2cClockTris=1;
    }
    SSPADD=80;      //8000/100;
    SSPSTAT=8;      //I2C Master clock = Fosc/(4*SSPADD)
    SSPCON1=0x28;
    SSP1IF=0;
    SSP1CON1bits.SSPEN=1;
}
Mike.
Edit, Note, the above is not for the chip you're using and may have the wrong register values.
 

vero

New Member
This may be a dumb question or maybe not the right question: what is I2cData? How does it check if the line is being held low?
 

Pommie

Well-Known Member
Most Helpful Member
I2cData is the port pin and I2cDataTriss is the TRISS register. For the 16F874 it's
Code:
#define I2cClock        PORTCbits.RC3
#define I2cData         PORTCbits.RC4
#define I2cClockTris    TRISCbits.TRISC3
#define I2cDataTris     TRISCbits.TRISC4
Mike.
edit, the line
while(!I2cData){
is looping while I2cData is low.
 

vero

New Member
My program gets stuck in a loop in the write function waiting for SSPIF to set after a byte is written to SSPBUF.
 

vero

New Member
Code:
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "Configuration.h"

#define _XTAL_FREQ 16000000

#define I2C_clock PORTCbits.RC3
#define I2C_data PORTCbits.RC4
#define scl TRISCbits.TRISC3
#define sda TRISCbits.TRISC4

void init_I2C();
void init_uart1(const long int baudRate);
bit w_I2C(unsigned char d);
unsigned char r_I2C(unsigned char d);
void I2C_idle();
void strt_I2C();
void i2c_restart(void);
void i2c_ack(void);
void i2c_nack(void);
void stp_I2C();
void write_uart(unsigned char *data);
unsigned char read_uart();

void main(void) {
    unsigned char data = 'A';
    
    init_uart1(9600);
    init_I2C();
    
    write_uart("hello\r\n");
    __delay_ms(100);
    write_uart("start...\r\n");
    __delay_ms(100);
    I2C_idle();      //Check if I2C bus is busy
    strt_I2C();      //Send start bit
    __delay_ms(100);
    w_I2C(0xA0);     //Send address of slave and r/w bit
    
    __delay_ms(100);
    w_I2C(0x00);     //Send MSB memory address of slave to write to
    
    __delay_ms(100);
    w_I2C(0x01);     //Send LSB memory address of slave to write to
    __delay_ms(100);
    w_I2C(0x41);     //Send data to memory of slave, wait for Ack 
    __delay_ms(100);
    stp_I2C();
    __delay_ms(100);
    //Read Randomly from slave EEPROM
    I2C_idle();     //Check if I2C bus is busy
    strt_I2C();     //Send Start bit to initiate read from slave EEPROM
    __delay_ms(100);
    w_I2C(0xA0);    //Send slave address(control byte) =
                    //'control code' + 'slave address:A2+A1+A0' + 'R/W' = 
                    //1010' + '000' + '0'
    __delay_ms(100);
    w_I2C(0x00);    //LSB of slave memory address  
    __delay_ms(100);
    w_I2C(0x01);    //MSB of slave memory address
    __delay_ms(100);
    i2c_restart();  //Resend start bit     
    __delay_ms(100);
    w_I2C(0xA1);    ////Send slave address =
                    //'control code' + 'slave address:A2+A1+A0' + 'R/W' = 
                    //1010' + '000' + '1'
    __delay_ms(50);
    data = r_I2C(0);    //read data from slave with a dummy value to SSPBUF
    __delay_ms(50);
    data = r_I2C(0);    //read data from slave with a dummy value to SSPBUF
    __delay_ms(50);    
    
    i2c_nack();         //Send NACK after a read cycle
    __delay_ms(50);
    stp_I2C();          //Send stop bit to slave for end of read cycle
    __delay_ms(50);
    write_uart(data);
    return;
}
void init_I2C()
{
    TRISCbits.TRISC3 = 1;       //Set SCL as input
    TRISCbits.TRISC4 = 1;       //Set SDA as input
    while(!I2C_data)
    {
        I2C_clock = 0;
        scl = 0;
        NOP();
        NOP();
        scl = 1;        
    }
    SSPSTATbits.SMP = 1;        //Slew rate disabled
    SSPSTATbits.CKE = 1;        //Enable SMbus
    SSPCONbits.SSPM3 = 1;       //Set PIC for master mode
    SSPCONbits.SSPM2 = 0;
    SSPCONbits.SSPM1 = 0;
    SSPCONbits.SSPM0 = 0;
    SSPCON2bits.RCEN = 1;
    SSPEN = 1;                  //Enable SDA and SCL pins
    
    SSPADD = 0x09;              //400khz for SCL FOSC/(4*(SSPADD+1))   
}
void init_uart1(const long int baudRate)
{
    unsigned int x=0;
    
    RCSTAbits.SPEN = 1;         //Serial port enabled
    RCSTAbits.RX9 = 0;          //Eight bit reception
    RCSTAbits.CREN = 1;         //Continuous receive enable bit
    RCSTAbits.ADDEN = 0;        //Disable address detection
    
    TXSTAbits.TXEN = 1;         //Transmit enable
    TXSTAbits.TX9 = 0;          //Eight bit transmission
    TXSTAbits.BRGH = 1;         //High speed transfer rate baud rate >= 9600
    TXSTAbits.SYNC = 0;         //Asynchronous mode   
    
    TRISCbits.TRISC6 = 1;       //TX set to output
    TRISCbits.TRISC7 = 1;       //RX set to input
    
    RCIE = 1; //enable receive interrupt
    GIE = 1;
    PEIE = 1;
    
    x = (_XTAL_FREQ/(16*baudRate))-1;
    SPBRG = x;    
}
void strt_I2C()
{
   SSPCON2bits.SEN = 1;            //Send start bit
   while(!SSPIF)                  //Wait for start condition to finish
        write_uart("SSPIF not setting after STRT\r\n");
   SSPIF = 0;                      //Clear FLAG 
}
void i2c_restart(void)
{
    RSEN=1;                       //Initiate restart condition
    while(!SSPIF)                 //Wait till completion of event
        write_uart("Wait for SSPIF after RSTRT \r\n");
    SSPIF = 0;
}
void I2C_idle()
{
    while ( ( SSPCON2 & 0x1F ) || ( SSPSTAT & 0x04 ) )
        write_uart("I2C NOT IDLE \r\n");    
}
bit w_I2C(unsigned char d)
{   
    SSPBUF;
    L1:      
     SSPBUF = d;                    //Write data to send to slave
    while(!SSPIF)
        write_uart("Wait for write SSPIF \r\n");
    while(SSPCON2bits.ACKSTAT)         //Wait for data to send
    {
       write_uart("Wait for slave ACK \r\n");
       i2c_restart();
       __delay_ms(100);
       goto L1;
    }
     SSPCON2bits.ACKSTAT = 1;
   
    SSPIF = 0;                         //Clear Flag    
    return SSPCON2bits.ACKSTAT;   
}
unsigned char r_I2C(unsigned char d)
{
    SSPBUF = d;
    RCEN = 1;                     //Enable master to receive
    while(!BF)                   //Wait for byte to be received
        write_uart("Wait for BF \r\n");
    SSPIF = 0;                      //Clear Flag
    RCEN = 0;
      
    return SSPBUF;   
}
void i2c_ack(void)
{
    ACKDT=0;                      //Set as acknowledgment
    ACKEN=1;                      //Initiate acknowledgment signal
    while(!SSPIF)                 //Wait till completion of event
        write_uart("Wait for SSPIF after ACK \r\n");
    SSPIF = 0;
}
 
void i2c_nack(void)
{
    ACKDT=1;                         //Set as negative acknowledgment
    ACKEN=1;                        //Initiate negative acknowledgment signal
    while(!SSPIF)                   //Wait till completion of event
        write_uart("Wait for SSPIF after NACK \r\n");
    SSPIF = 0;
}
void stp_I2C()
{
    PEN = 1;
    while(!SSPIF)
        write_uart("Wait for SSPIF after STP \r\n");
    SSPIF = 0;
}

void write_uart(unsigned char *data)
{
    int len=0,i = 0;
    
    len = strlen(data);
   
        while(i < (len)){
            while(!TXIF);
            TXREG = data[i];
            i++;
        }       
}
unsigned char read_uart()
{
    if(RCSTAbits.OERR || RCSTAbits.FERR)    //Check for overrun or frame error
    {
        RCSTAbits.CREN = 0;                 //Reset CREN if either error occurs
        RCSTAbits.CREN = 1;
    }
    while(!RCIF);                           //Stay here till a byte is received
    return RCREG;    
}
 

Pommie

Well-Known Member
Most Helpful Member
I²C does not need a dummy write to receive a byte.

Somethings to try,
clear the SSPIF in your init routine.
Remove the write to SSPBUF in your read routine.

Here are read and write routines that I use that work,
Code:
uint8 I2cWriteByte(int Address,unsigned char Dat){
    if(!I2cSendId((uint8)(0xa0)))            //EEPROM ID and address 0
        return(0);
    I2cSendByte((uint8)(Address>>8));
    I2cSendByte((uint8)(Address&255));
    I2cSendByte(Dat);
    I2cStop();
    return(1);
}

uint8 I2cReadByte(long Address){
uint8 temp;
    if(!I2cSendId((uint8)(0xa0)))            //EEPROM ID
        return(0);
    I2cSendByte((uint8)(Address>>8));
    I2cSendByte((uint8)(Address&255));
    I2cReStart();
    I2cSendByte((uint8)(0xa1));
    temp=I2cReceiveByte();
    I2cNack();
    I2cStop();
    return(temp);
}
The sendID routine will try 50 times and then return zero if it failed. It is,
Code:
unsigned char I2cSendId(unsigned char ID){
unsigned char count;
    I2cStart();
    if(I2cSendByte(ID))
        return(1);
    count=50;
    do{
        I2cReStart();
        if(I2cSendByte(ID))
            return(1);
    }while(--count!=0);
    return(0);
}
The various "helper" routines are,
Code:
void WaitSSP(){
    while(SSP1IF==0);
    SSP1IF=0;
}

void I2cStart(){
    SSP1CON2bits.SEN=1;
    WaitSSP();
}

void I2cStop(){
    SSP1CON2bits.PEN=1;
    WaitSSP();
}

void I2cReStart(){
    SSP1CON2bits.RSEN=1;
    WaitSSP();
}

void I2cNack(){
    SSP1CON2bits.ACKDT=1;
    SSP1CON2bits.ACKEN=1;
    WaitSSP();
}

void I2cAck(){
    SSP1CON2bits.ACKDT=0;
    SSP1CON2bits.ACKEN=1;
    WaitSSP();
}

uint8 I2cSendByte(unsigned char data){
    SSPBUF=data;
    WaitSSP();
    return((uint8)!SSP1CON2bits.ACKSTAT);
}

uint8 I2cReceiveByte(){
    SSP1CON2bits.RCEN=1;
    WaitSSP();
    return(SSPBUF);
}
HTH,

Mike.
Edit, change all the uint8 to unsigned char.
Edit2, you seem to be defining SSPBUF as a local variablein
Code:
bit w_I2C(unsigned char d)
{  
    SSPBUF;
    L1:
Edit3, Actually, I've no idea what that is doing.
 

vero

New Member
That is very helpful. So, It may take multiple writes to get an acknowledge from the slave. I will fix my code according to your edits and fix my read and write functions. In your I2cSendByte(unsigned char data) function, why did you return SSP1CON2bits.ACKSTAT with a not(!) logical operator?
 

Pommie

Well-Known Member
Most Helpful Member
If the (slave) device is there it will return an ack so the sendID knows if the write was successful.

When you write to the EEPROM it can take time to complete the write, during this time it ignores any writes. Hence why I try writing ID 50 times.

Mike.
Edit, Note ACKSTAT is high is no ACK was received.
 
Last edited:

vero

New Member
I re-did some of my code following your suggestions and the reads and writes are working. The program doesn't get stuck in a loop anymore. The only problem is when I read back what I wrote to the EEPROM, I get garbage. I put short delays between each write, could this be causing the problem? Thanks for all your help.

Code:
void main(void) {
    uint8_t data = 'A',d = '0';
    uint16_t addr = 0x0001;
    init_uart1(9600);
    init_I2C();
    
    write_uart("hello\r\n");
    __delay_ms(100);
    write_uart("start...\r\n");
    __delay_ms(100);
    
    if(!write_eeprom(data,addr))
        write_uart("write failed..\r\n");
    else 
    {
        write_uart("write successful.. \r\n");
        d = read_eeprom(addr);
    }
    if(!d)
        write_uart("read failed..\r\n");
    else{
        write_uart("read succeeded.. \r\n");
        write_uart("data is: ");
        write_uart(d);
        write_uart("\r\n");
    }
               
    
    return;
}
void init_I2C()
{
    TRISCbits.TRISC3 = 1;       //Set SCL as input
    TRISCbits.TRISC4 = 1;       //Set SDA as input
    while(!I2C_data)
    {
        I2C_clock = 0;
        scl = 0;
        NOP();
        NOP();
        scl = 1;        
    }
    SSPSTATbits.SMP = 1;        //Slew rate disabled
    SSPSTATbits.CKE = 0;        //Enable SMbus
    SSPCONbits.SSPM3 = 1;       //Set PIC for master mode
    SSPCONbits.SSPM2 = 0;
    SSPCONbits.SSPM1 = 0;
    SSPCONbits.SSPM0 = 0;
    SSPCON2bits.RCEN = 1;
    SSPEN = 1;                  //Enable SDA and SCL pins
    SSPIF = 0;
    SSPADD = 0x09;              //400khz for SCL FOSC/(4*(SSPADD+1))   
}
void init_uart1(const long int baudRate)
{
    unsigned int x=0;
    
    RCSTAbits.SPEN = 1;         //Serial port enabled
    RCSTAbits.RX9 = 0;          //Eight bit reception
    RCSTAbits.CREN = 1;         //Continuous receive enable bit
    RCSTAbits.ADDEN = 0;        //Disable address detection
    
    TXSTAbits.TXEN = 1;         //Transmit enable
    TXSTAbits.TX9 = 0;          //Eight bit transmission
    TXSTAbits.BRGH = 1;         //High speed transfer rate baud rate >= 9600
    TXSTAbits.SYNC = 0;         //Asynchronous mode   
    
    TRISCbits.TRISC6 = 1;       //TX set to output
    TRISCbits.TRISC7 = 1;       //RX set to input
    
    RCIE = 1; //enable receive interrupt
    GIE = 1;
    PEIE = 1;
    
    x = (_XTAL_FREQ/(16*baudRate))-1;
    SPBRG = x;    
}
void strt_I2C()
{
   SSPCON2bits.SEN = 1;            //Send start bit
   while(!SSPIF)                  //Wait for start condition to finish
        write_uart("SSPIF not setting after STRT\r\n");
   SSPIF = 0;                      //Clear FLAG 
}
void i2c_restart(void)
{
    RSEN=1;                       //Initiate restart condition
    while(!SSPIF)                 //Wait till completion of event
        write_uart("Wait for SSPIF after RSTRT \r\n");
    SSPIF = 0;
}
void I2C_idle()
{
    while ( ( SSPCON2 & 0x1F ) || ( SSPSTAT & 0x04 ) )
        write_uart("I2C NOT IDLE \r\n");    
}

unsigned char r_I2C()
{    
    RCEN = 1;                     //Enable master to receive
    while(!SSPIF)                   //Wait for byte to be received
        write_uart("Wait for read SSPIF \r\n");
    SSPIF = 0;                      //Clear Flag
    RCEN = 0;
        
    return SSPBUF;   
}
void i2c_ack(void)
{
    ACKDT=0;                      //Set as acknowledgment
    ACKEN=1;                      //Initiate acknowledgment signal
    while(!SSPIF)                 //Wait till completion of event
        write_uart("Wait for SSPIF after ACK \r\n");
    SSPIF = 0;
}
 
void i2c_nack(void)
{
    ACKDT=1;                         //Set as negative acknowledgment
    ACKEN=1;                        //Initiate negative acknowledgment signal
    while(!SSPIF)                   //Wait till completion of event
        write_uart("Wait for SSPIF after NACK \r\n");
    SSPIF = 0;
}
void stp_I2C()
{
    PEN = 1;
    while(!SSPIF)
        write_uart("Wait for SSPIF after STP \r\n");
    SSPIF = 0;
}
unsigned char write_eeprom(unsigned char data,uint16_t address)
{
    uint8_t MSB=0,LSB=0;
    MSB = address>>8;
    LSB = 0x00FF & address;
    
    I2C_idle();         //Check if I2C bus is busy
    //i2c_restart();      //Send start bit
    __delay_ms(100);
    if(!sendCode(0xA0)) //Send address of slave and r/w bit    
        return 0;
    sendByte(MSB);     //Send MSB memory address of slave to write to    
    __delay_ms(100);
    sendByte(LSB);     //Send LSB memory address of slave to write to
    __delay_ms(100);
    sendByte(data);     //Send data to memory of slave, wait for Ack 
    __delay_ms(100);
    stp_I2C();
    return 1;
}
unsigned char read_eeprom(uint16_t address)
{
    unsigned char data = ' ';
    uint8_t MSB = 0,LSB = 0;
    
    MSB = address>>8;
    LSB = address & 0x00FF;
    //Read Randomly from slave EEPROM
    I2C_idle();         //Check if I2C bus is busy    
    __delay_ms(20);
    if(!sendCode(0xA0)) //Send slave address(control byte) =
                        //'control code' + 'slave address:A2+A1+A0' + 'R/W' = 
                        //1010' + '000' + '0'
        return 0;
    sendByte(MSB);      //LSB of slave memory address  
    __delay_ms(10);
    sendByte(LSB);      //MSB of slave memory address
    __delay_ms(10);
    i2c_restart();      //Resend start bit     
    __delay_ms(10);
    sendByte(0xA1);     //Send slave address =
                        //'control code' + 'slave address:A2+A1+A0' + 'R/W' = 
                        //1010' + '000' + '1'
    
    data = r_I2C();     //read data from slave to SSPBUF
     
   
    i2c_nack();        //Send NACK after a read cycle
    __delay_ms(20);
    stp_I2C();         //Send stop bit to slave for end of read cycle
    __delay_ms(10);
   return(data);    
}
unsigned char sendCode(unsigned char code)
{
    unsigned char n = 50;
    strt_I2C();
    if(sendByte(code))
    {
        return 1;
    }
    while(n != 0)
    {
        i2c_restart();
        if(sendByte(code))
        {
            return 1;
        }
        n--;
    }
    return 0;    
}
unsigned char sendByte(unsigned char data)
{
    SSPBUF = data;
    while(!SSPIF);
    SSPIF = 0;
    return(!SSPCON2bits.ACKSTAT);
}
void write_uart(unsigned char *data)
{
    int len=0,i = 0;
    
    len = strlen(data);
    //if(TXIF)                //Check TX interrupt, if set TXREG is empty
    //{
        while(i < (len)){
            while(!TXIF);
            TXREG = data[i];
            i++;
        }    
    //}
}
unsigned char read_uart()
{
    if(RCSTAbits.OERR || RCSTAbits.FERR)    //Check for overrun or frame error
    {
        RCSTAbits.CREN = 0;                 //Reset CREN if either error occurs
        RCSTAbits.CREN = 1;
    }
    while(!RCIF);                           //Stay here till a byte is received
    return RCREG;    
}
 

Pommie

Well-Known Member
Most Helpful Member
You shouldn't need any delays at all. Plus, there is no need to check if the bus is idle - if it isn't then something is very wrong.

I see you enable interrupts but don't show an ISR. Can you post the ISR?

Apart from that I can't see anything wrong.

What happens if you put a half second delay between writing and reading?

Mike.
Edit, can you write a variable to the uart or is it expecting a string? Can you set a breakpoint on the write and see what d contains?
Edit2, just noticed the write Uart routine which expects a pointer to a string - you're sending 65 ('A') so it'll print what is at that location.
 
Last edited:

vero

New Member
That was it! The write_uart_() function is expecting a string, just like you said. I made another function, write_uart_byte() to write only one byte to the uart and I see the data written to the EEPROM.

Also to answer your question, I don't have an ISR in this program. I had enabled the interrupts in a different program and copied the init_ uart function into this program.

Thank you so much for all your help!

Code:
void write_uart_byte(unsigned char data)
{
    int len=0,i = 0;
    
    while(!TXIF);
    TXREG = data;                 
}
 

Pommie

Well-Known Member
Most Helpful Member
Good to hear it's all working. Good luck with the rest of your project.

Mike.
 

Latest threads

EE World Online Articles

Loading

 
Top