1
\$\begingroup\$

I am using Bit-Banging for I2C communication via PIC24FJ128GA010.

The code works fine for writing 16bytes to EEPROM ( I recieved ACK = 0 for every Byte write ).

While reading EEPROM I am able to read only first Byte. Thereafter all bytes received are 0x00.

My EEPROM Device has 3 Pins ( Data_in, Data_out, CLK).

Here is my code.

**** I2CInterface.h ****

#ifndef I2CINTERFACE_H
#define I2CINTERFACE_H

#include <xc.h>
#include <p24FJ128GA010.h>
#include <stdbool.h>

#define HIGH    1
#define LOW     0

#define SCK             PORTFbits.RF6   // Clock Pin for i2c
#define SDA_OUT         PORTFbits.RF7   // Data Input Pin
#define SDA_IN          PORTFbits.RF8   // Data Output Pin

#define SCK_DIR             TRISFbits.TRISF6   // Clock Pin for i2c
#define SDA_OUT_DIR         TRISFbits.TRISF7   // Data Input Pin
#define SDA_IN_DIR          TRISFbits.TRISF8   // Data Output Pin

#define Set_SDA_OUT_Low     ( SDA_OUT = 0 )
#define Set_SDA_OUT_High    ( SDA_OUT = 1 )
#define Set_SCK_Low         ( SCK     = 0 )
#define Set_SCK_High        ( SCK     = 1 )

#define I2C_SPEED_FACTOR    1          // Low Value means low i2c frequency
#define Crystal_Value       8          // MHz
#define HalfBitDelay        (500*Crystal_Value)/(12*I2C_SPEED_FACTOR)

void InitI2C(void);
void I2C_Start(void);
void I2C_ReStart(void);
bool I2C_Write_Byte(unsigned char Byte);
unsigned char I2C_Read_Byte(void);
void I2C_Stop(void);
bool I2C_Get_ACK(void);
void I2C_Send_ACK(void);
void I2C_Send_NACK(void);
unsigned char I2C_Data_Inverter(unsigned char Byte);     // this function is just for     current circuit.
void __delay_us(unsigned int d);


#endif  /* I2CINTERFACE_H */

***** I2CInterface.c *******

#include "I2CInterface.h"


// Function   : Set Initial values of SCK & SDA pins
void InitI2C(void)
{

    SDA_IN_DIR  =   1;              // Configure RF8 pin as Input;

    SCK_DIR     =   0;              // Configure RF6 pin as Output;
    SDA_OUT_DIR =   0;              // Configure RF7 pin as Output;

    SCK = 1;                        // write 1
    SDA_OUT = 1;                    // write 1

}

//Function : I2C_Start sends bit sequence
void I2C_Start(void)
{

    Set_SCK_High;                   // Make SCK pin High
    __delay_us( HalfBitDelay/2 );     // Half bit delay

    Set_SDA_OUT_High;               // Make SDA_OUT pin High
    __delay_us( HalfBitDelay/2 );     // Half bit delay

    Set_SDA_OUT_Low;                // Make SDA_OUT pin Low
    __delay_us( HalfBitDelay/2 );     // Half bit delay

}

// Function Purpose: I2C_ReStart sends start bit sequence
void I2C_ReStart(void)
{

    Set_SCK_Low;                // Make SCK pin low
    __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                            // when it is confirm that SCK is low
    Set_SDA_OUT_High;               // Make SDA pin High
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SCK_High;               // Make SCK pin high
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SDA_OUT_Low;                // Make SDA Low
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

}

void I2C_Stop(void)
{

    Set_SCK_Low;
    __delay_us( HalfBitDelay/2 );

    Set_SDA_OUT_Low;                // Make SDA pin low
    __delay_us( HalfBitDelay/2 );     // 1/4 bit delay

    Set_SCK_High;                   // Make SCK pin low
    __delay_us( HalfBitDelay/2 );     // Data pin should change it's value,when it is confirm that SCK is low

    Set_SDA_OUT_High;               // Make SDA high
    __delay_us( HalfBitDelay/2 );     // 1/4 bit delay

}

bool I2C_Write_Byte(unsigned char Byte)
{
    unsigned char i;        // Variable to be used in for loop

    bool ack =  false;

    for(i=0;i<8;i++)        // Repeat for every bit
    {
        Set_SCK_Low;        // Make SCK pin low

        __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                                // when it is confirm that SCK is low

        if((Byte<<i)&0x80)  // Place data bit value on SDA pin
            Set_SDA_OUT_High;   // If bit is high, make SDA high
        else                // Data is transferred MSB first
            Set_SDA_OUT_Low;    // If bit is low, make SDA low

        __delay_us(HalfBitDelay/2); // Toggle SCK pin
        Set_SCK_High;               // So that slave can
        __delay_us(HalfBitDelay);   // latch data bit

    }
    Set_SCK_Low;
    __delay_us( HalfBitDelay );

    Set_SCK_High;
    __delay_us( HalfBitDelay );

    ack = SDA_IN;

    return ack;

}

unsigned char I2C_Read_Byte(void)
{
    unsigned char i, RxData = 0;

    for(i=0;i<8;i++)
    {
        Set_SCK_Low;                    // Make SCK pin low
        __delay_us(HalfBitDelay);       // Half bit delay
        Set_SCK_High;                   // Make SCK pin high
        __delay_us( HalfBitDelay );     // 1/4 bit delay
        RxData = RxData |( SDA_IN << (7-i) );   // Captured received bit
    }

    return RxData;                      // Return received byte
}


//Function : I2C_Send_ACK sends ACK bit sequence
void I2C_Send_ACK(void)
{

    Set_SCK_Low;                // Make SCK pin low
    __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                            // when it is confirm that SCK is low
    Set_SDA_OUT_Low;                // Make SDA High
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SCK_High;               // Make SCK pin high
    __delay_us(HalfBitDelay);   // Half bit delay

}


//Function : I2C_Send_NACK sends NACK bit sequence
void I2C_Send_NACK(void)
{

    Set_SCK_Low;                // Make SCK pin low
    __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                            // when it is confirm that SCK is low
    Set_SDA_OUT_High;               // Make SDA high
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SCK_High;               // Make SCK pin high
    __delay_us(HalfBitDelay);   // Half bit delay

}

// Function Purpose: Produce approximate delay in given uSecs.
void __delay_us(unsigned int d)
{
   unsigned int i, limit;
   limit = d/15;

   for(i=0;i<limit;i++);

}

**** main.c ****

#include <xc.h>
#include <p24FJ128GA010.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <i2c.h>

#include "src/idmodule.h"
#include "src/lcd.h"
#include "src/I2CInterface.h"

_CONFIG1( JTAGEN_OFF & FWDTEN_OFF )
_CONFIG2( FNOSC_FRCPLL & OSCIOFNC_OFF )


void writeData( void );
void readData( void );
void wait();

bool ackWriteOp[16] = {false};
bool ackReadOp[16] = {false};

bool ackWrite_1 = false, ackWrite_2 = false;
bool ackRead_1 = false, ackRead_2 = false, ackRead_3 = false;

unsigned char dataRead[16] = {0};

unsigned char addr_byte = 0x70;
unsigned char data_byte [] = { 'r', 'a', 't', 'n', 'e', 's', 'h', '#', 's', 'u', 'd', 'h', 'e', 'e', 'r', '#' };


int main()
{
    TRISA = 0;
    TRISD = 0;

    LCD_Initialize();

    idmInitI2C();

    writeData();

    wait();
    wait();

    readData();

    return 0;

}

void writeData( void )
{
    unsigned char i;

    I2C_Start();

    ackWrite_1 = I2C_Write_Byte( 0xA0 );

    ackWrite_2 = I2C_Write_Byte( 0x70 );

    for (i = 0; i < 16; i++)
    {
        ackWriteOp[i] = I2C_Write_Byte( data_byte[i]);

    }

    I2C_Stop();
}

void readData( void )
{
    unsigned char i;

    I2C_Start();

    ackRead_1 = I2C_Write_Byte(0xA0);

    ackRead_2 = I2C_Write_Byte(0x70);

    I2C_Restart();
    ackRead_3 = I2C_Write_Byte(0xA1);

    for ( i = 0; i < 16; i++)
    {
        dataRead[i] = I2C_Read_Byte();

        if (i < 15){
            I2C_Send_ACK();
        }

    }

    I2C_Send_NACK();
    I2C_Stop();

    for (i = 0; i < 16; i++)
    {
         LCD_PutChar ( dataRead[i] ) ;
    }
}

void wait()
{
    unsigned int i, j;

    for (i = 0 ; i < 2000; i++)
    {
        for (j = 0; j < 1000; j++);
    }

}

I am unable to find the cause. Might be missing something. Please help me finding the issue.

EDIT: I checked the data writted to the device by manually changing the Byte-addresses. And I can see all the data stored in the device. I guess internally address is not auto-increasing. Is there a way out to command the device for auto increment or is there any logical issue in my code??

\$\endgroup\$
5
  • \$\begingroup\$ I2C normally only has two pins, SDA and SCL. Are you sure youre EEPROM is not SPI? What is the part number? \$\endgroup\$ Aug 11, 2015 at 7:25
  • \$\begingroup\$ @RogerRowland...Yes...Its an I2C device as per its User Manual...Also I can successfully read first Byte only. Might be a logical issue causing obstruction in reading all bytes. \$\endgroup\$
    – skg
    Aug 11, 2015 at 7:52
  • \$\begingroup\$ Ok, please can you provide the part number and/or a link to the datasheet? \$\endgroup\$ Aug 11, 2015 at 8:17
  • \$\begingroup\$ @RogerRowland..please refer this link \$\endgroup\$
    – skg
    Aug 11, 2015 at 8:18
  • \$\begingroup\$ Do you have an oscilloscope capture of your read/write transactions? I'd try to match it to the waveforms shown on page 4 of the memory datasheet. In my experience, the most common issue with I2C is which component is driving the clk/data lines at any given time. \$\endgroup\$ Aug 11, 2015 at 12:46

3 Answers 3

2
\$\begingroup\$

After reading one byte you call I2C_Send_ACK which sets SDA to low. Afterwards, you always read 0 on SDA as you never release it again (until calling I2C_Send_NACK). You should set SDA to high (no drive it) when calling your I2C_Read_Byte function.

In addition you should change your Set_SDA_OUT_Low and Set_SDA_OUT_High in such a way, that when setting it to low it sets the pin as an output and drives it low and when setting it to high it sets the pin as an input.

\$\endgroup\$
3
  • \$\begingroup\$ @Tobias..Thanks for your response. I tried your both suggestion 1.) Result remains the same 2.) Not working ACK = 1; \$\endgroup\$
    – skg
    Aug 11, 2015 at 9:33
  • 1
    \$\begingroup\$ Does writing to your EEPROM work and is the first read byte actually what you've written into it? \$\endgroup\$ Aug 11, 2015 at 9:49
  • \$\begingroup\$ @Tobias....Yes...First byte i am getting what I have written to the device. My First Byte is 'r' (as per above code) And the same is read by the device. Also while writing I recieved ACK=0 for all 16 Bytes. But while reading only First Byte is readable rest all are 0x00. \$\endgroup\$
    – skg
    Aug 11, 2015 at 9:57
1
\$\begingroup\$

To me it looks like your micro drives the SDA and SCK lines all the time. The I2C bus is an open-drain bus which means that you only drive 0's on the bus while 1's are generated by the pull-ups (therefore it's so much slower than SPI).
The way to bitbang that, if you can't tri-state your IOs is to set the PORT pin to '0' and use the TRIS register to drive the bus. When the TRIS = '0' the line is driven and when TRIS = '1' the line is released.
Also, it's not necessary to use 2 IO pins per line.

Modify your code accordingly and see what happens.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ If you have a look at the datasheet of the module he uses, you'll see why he uses 2 IOs for the SDA line. However I'm still with you regarding the tri-stating of the pins. \$\endgroup\$ Aug 12, 2015 at 5:18
  • \$\begingroup\$ @Alex... Thanks for your valuable suggestion. It helped me get rid of the issue. I am posting the final answer. \$\endgroup\$
    – skg
    Aug 12, 2015 at 8:02
  • \$\begingroup\$ @TobiasMüller.. Thanks for your suggestion. it helped me get rid of the issue. I am posting my final answer \$\endgroup\$
    – skg
    Aug 12, 2015 at 8:03
1
\$\begingroup\$

Brief description about the issue.

I have an EEPROM device with 3 pins. Detail of the device can be found Here

I used I2C Bit-Banging concept as suggested to start communication with the device.

Finally I got stuck with the issue. I was able to write the device ( with recieving ACK = 0) for each byte written.

While reading the device I was able to Read the First Byte from the device while for rest Byte I was getting 0x00.

After following all the suggestions above I found that I need to pull SDA_OUT pin HIGH while reading each bit.

Finally I was able to Read all the Bytes from the device.

I am posting I2CInterface.c again with all the changes that made it working.

**** I2CInterface ****

#include "I2CInterface.h"

unsigned tempData[16] = {0};

// Function   : Set Initial values of SCK & SDA pins
void InitI2C(void)
{
    SDA_IN_DIR  =   1;              // Configure RF8 pin as Input;

    SCK_DIR     =   0;              // Configure RF6 pin as Output;
    SDA_OUT_DIR =   0;              // Configure RF7 pin as Output;

    SCK = 1;                        // write 1
    SDA_OUT = 1;                    // write 1

}

//Function : I2C_Start sends bit sequence
void I2C_Start(void)
{
    Set_SCK_High;                   // Make SCK pin High
    __delay_us( HalfBitDelay/2 );     // Half bit delay

    Set_SDA_OUT_High;               // Make SDA_OUT pin High
    __delay_us( HalfBitDelay/2 );     // Half bit delay

    Set_SDA_OUT_Low;                // Make SDA_OUT pin Low
    __delay_us( HalfBitDelay/2 );     // Half bit delay

}

// Function Purpose: I2C_ReStart sends start bit sequence
void I2C_ReStart(void)
{
    Set_SCK_Low;                // Make SCK pin low
    __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                            // when it is confirm that SCK is low
    Set_SDA_OUT_High;               // Make SDA pin High
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SCK_High;               // Make SCK pin high
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SDA_OUT_Low;                // Make SDA Low
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

}

// Function : I2C_Stop to generate Stop Sequence
void I2C_Stop(void)
{
    Set_SDA_OUT_Low;                // Make SDA pin low
    __delay_us( HalfBitDelay/2 );     // 1/4 bit delay

    Set_SCK_Low;
    __delay_us( HalfBitDelay/2 );

    Set_SCK_High;                   // Make SCK pin low
    __delay_us( HalfBitDelay );     // Data pin should change it's value,when it is confirm that SCK is low

    Set_SDA_OUT_High;               // Make SDA high
    __delay_us( HalfBitDelay );     // 1/4 bit delay

}

// Function : I2C_Write_Byte to write Bytes
bool I2C_Write_Byte(unsigned char Byte)
{
    unsigned char i;        // Variable to be used in for loop

    bool ack =  false;

    for(i=0;i<8;i++)        // Repeat for every bit
    {
        Set_SCK_Low;        // Make SCK pin low

        __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                                // when it is confirm that SCK is low

        if((Byte<<i)&0x80)  // Place data bit value on SDA pin
            Set_SDA_OUT_High;   // If bit is high, make SDA high
        else                // Data is transferred MSB first
            Set_SDA_OUT_Low;    // If bit is low, make SDA low

        __delay_us(HalfBitDelay/2); // Toggle SCK pin
        Set_SCK_High;               // So that slave can
        __delay_us(HalfBitDelay);   // latch data bit

    }
    Set_SCK_Low;
    __delay_us( HalfBitDelay );

    Set_SCK_High;
    __delay_us( HalfBitDelay );

    ack = SDA_IN;

    return ack;  
}

// Function : I2C_Read_Byte reads Byte 
unsigned char I2C_Read_Byte(void)
{
    unsigned char i, RxData = 0;

    for(i=0;i<8;i++)
    {
        Set_SCK_Low;                    // Make SCK pin low
        __delay_us(HalfBitDelay);       // Half bit delay

        Set_SCK_High;                   // Make SCK pin high
        __delay_us( HalfBitDelay );     // 1/4 bit delay

        RxData = RxData |( SDA_IN << (7-i) );   // Captured received bit
        __delay_us( HalfBitDelay/2 );       // 1/4 bit delay

        Set_SDA_OUT_High;
        __delay_us(HalfBitDelay/2); // 1/4 bit delay

    }

    return RxData;                      // Return received byte
}

//Function : I2C_Send_ACK sends ACK bit sequence
void I2C_Send_ACK(void)
{

    Set_SCK_Low;                // Make SCK pin low
    __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                            // when it is confirm that SCK is low
    Set_SDA_OUT_Low;                // Make SDA High
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SCK_High;               // Make SCK pin high
    __delay_us( HalfBitDelay ); // Half bit delay

}

//Function : I2C_Send_NACK sends NACK bit sequence
void I2C_Send_NACK(void)
{
    SDA_OUT_DIR = 0;

    Set_SDA_OUT_Low;
    __delay_us(HalfBitDelay/2);

    Set_SCK_Low;                // Make SCK pin low
    __delay_us(HalfBitDelay/2); // Data pin should change it's value,
                            // when it is confirm that SCK is low
    Set_SDA_OUT_High;               // Make SDA high
    __delay_us(HalfBitDelay/2); // 1/4 bit delay

    Set_SCK_High;               // Make SCK pin high
    __delay_us(HalfBitDelay);   // Half bit delay

}

// Function Purpose: Produce approximate delay in given uSecs.
void __delay_us(unsigned int d)
{
   unsigned int i, limit;
   limit = d/15;

   for(i=0;i<limit;i++);

}
\$\endgroup\$

Your Answer

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

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