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??
-
\$\begingroup\$ I2C normally only has two pins, SDA and SCL. Are you sure youre EEPROM is not SPI? What is the part number? \$\endgroup\$Roger Rowland– Roger Rowland2015年08月11日 07:25:31 +00:00Commented 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– skg2015年08月11日 07:52:07 +00:00Commented Aug 11, 2015 at 7:52
-
\$\begingroup\$ Ok, please can you provide the part number and/or a link to the datasheet? \$\endgroup\$Roger Rowland– Roger Rowland2015年08月11日 08:17:02 +00:00Commented Aug 11, 2015 at 8:17
-
\$\begingroup\$ @RogerRowland..please refer this link \$\endgroup\$skg– skg2015年08月11日 08:18:55 +00:00Commented 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\$bjornruffians– bjornruffians2015年08月11日 12:46:38 +00:00Commented Aug 11, 2015 at 12:46
3 Answers 3
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.
-
\$\begingroup\$ @Tobias..Thanks for your response. I tried your both suggestion 1.) Result remains the same 2.) Not working ACK = 1; \$\endgroup\$skg– skg2015年08月11日 09:33:27 +00:00Commented 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\$Tobias Müller– Tobias Müller2015年08月11日 09:49:04 +00:00Commented 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– skg2015年08月11日 09:57:34 +00:00Commented Aug 11, 2015 at 9:57
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.
-
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\$Tobias Müller– Tobias Müller2015年08月12日 05:18:41 +00:00Commented 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– skg2015年08月12日 08:02:53 +00:00Commented 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– skg2015年08月12日 08:03:32 +00:00Commented Aug 12, 2015 at 8:03
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++);
}