I2C: no se pueden leer varios bytes con el método Bit-Banging

Estoy usando Bit-Banging para la comunicación I2C a través de PIC24FJ128GA010.

El código funciona bien para escribir 16 bytes en EEPROM (recibí ACK = 0 por cada byte escrito).

Mientras leo la EEPROM, solo puedo leer el primer byte. A partir de entonces, todos los bytes recibidos son 0x00.

Mi dispositivo EEPROM tiene 3 pines (entrada de datos, salida de datos, CLK).

Aquí está mi código.

**** Interfaz I2C.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 */

***** Interfaz I2C.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++);

}

**** C Principal ****

#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++);
    }

}

No puedo encontrar la causa. Puede que le falte algo. Por favor, ayúdame a encontrar el problema.

EDITAR: Verifiqué los datos escritos en el dispositivo cambiando manualmente las direcciones de Byte. Y puedo ver todos los datos almacenados en el dispositivo. Supongo que la dirección interna no aumenta automáticamente. ¿Hay alguna forma de comandar el dispositivo para el incremento automático o hay algún problema lógico en mi código?

I2C normalmente solo tiene dos pines, SDA y SCL. ¿Estás seguro de que tu EEPROM no es SPI? ¿Cuál es el número de pieza?
@RogerRowland... Sí... Es un dispositivo I2C según su Manual de usuario... También puedo leer con éxito solo el primer byte. Podría ser un problema lógico que obstruya la lectura de todos los bytes.
Bien, ¿puede proporcionar el número de pieza y/o un enlace a la hoja de datos?
@RogerRowland... consulte este enlace
¿Tiene una captura de osciloscopio de sus transacciones de lectura/escritura? Intentaría hacerlo coincidir con las formas de onda que se muestran en la página 4 de la hoja de datos de la memoria. En mi experiencia, el problema más común con I2C es qué componente está impulsando las líneas clk/data en un momento dado.

Respuestas (3)

Después de leer un byte, llama, I2C_Send_ACKlo que establece SDA en bajo. Luego, siempre lee 0 en SDA ya que nunca lo vuelve a liberar (hasta que llama I2C_Send_NACK). Debe configurar SDA en alto (no conducirlo) cuando llame a su I2C_Read_Bytefunción.

Además, debe cambiar su Set_SDA_OUT_Lowy Set_SDA_OUT_Highde tal manera que, cuando se establece en bajo, establece el pin como salida y lo conduce a bajo, y cuando se establece en alto, establece el pin como entrada.

@Tobias..Gracias por tu respuesta. Intenté ambas sugerencias 1.) El resultado sigue siendo el mismo 2.) No funciona ACK = 1;
¿Funciona escribir en su EEPROM y es el primer byte leído realmente lo que ha escrito en él?
@Tobias... Sí... El primer byte obtengo lo que he escrito en el dispositivo. Mi primer byte es 'r' (según el código anterior) Y el dispositivo lee lo mismo. Además, mientras escribía, recibí ACK = 0 para los 16 bytes. Pero mientras se lee, solo el primer byte es legible, todos son 0x00.

Para mí, parece que su micro maneja las líneas SDA y SCK todo el tiempo. El bus I2C es un bus de drenaje abierto, lo que significa que solo conduce 0 en el bus mientras que los pull-ups generan 1 (por lo tanto, es mucho más lento que SPI).
La forma de explotar eso, si no puede hacer tri-state de sus IO, es configurar el pin PORT en '0' y usar el registro TRIS para conducir el bus. Cuando TRIS = '0', la línea se activa y cuando TRIS = '1', la línea se libera.
Además, no es necesario usar 2 pines IO por línea.

Modifique su código en consecuencia y vea qué sucede.

Si observa la hoja de datos del módulo que usa, verá por qué usa 2 IO para la línea SDA. Sin embargo, todavía estoy contigo con respecto a la tri-estado de los pines.
@Alex... Gracias por tu valiosa sugerencia. Me ayudó a deshacerme del problema. Estoy publicando la respuesta final.
@TobiasMüller.. Gracias por tu sugerencia. me ayudó a deshacerme del problema. Estoy publicando mi respuesta final.

Breve descripción sobre el problema.

Tengo un dispositivo EEPROM con 3 pines. El detalle del dispositivo se puede encontrar aquí

Utilicé el concepto Bit-Banging de I2C como se sugirió para iniciar la comunicación con el dispositivo.

Finalmente me quedé atascado con el problema. Pude escribir el dispositivo (recibiendo ACK = 0) para cada byte escrito.

Mientras leía el dispositivo, pude leer el primer byte del dispositivo mientras que para el byte de descanso obtenía 0x00.

Después de seguir todas las sugerencias anteriores, descubrí que necesito tirar del pin SDA_OUT ALTO mientras leo cada bit.

Finalmente pude leer todos los bytes del dispositivo.

Estoy publicando I2CInterface.c nuevamente con todos los cambios que hicieron que funcionara.

**** Interfaz I2C ****

#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++);

}