I2C: невозможно прочитать несколько байтов с использованием метода Bit-Banging

Я использую Bit-Banging для связи I2C через PIC24FJ128GA010.

Код отлично работает для записи 16 байтов в EEPROM (я получил ACK = 0 для каждой записи байта).

При чтении EEPROM я могу прочитать только первый байт. После этого все полученные байты равны 0x00.

Мое устройство EEPROM имеет 3 контакта (Data_in, Data_out, CLK).

Вот мой код.

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

}

**** основной.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++);
    }

}

Я не могу найти причину. Может чего-то не хватает. Пожалуйста, помогите мне найти проблему.

РЕДАКТИРОВАТЬ: я проверил данные, записанные на устройство, вручную изменив байтовые адреса. И я могу видеть все данные, хранящиеся в устройстве. Я предполагаю, что внутренний адрес не увеличивается автоматически. Есть ли способ дать команду устройству на автоматическое увеличение или в моем коде есть какая-то логическая проблема ??

I2C обычно имеет только два контакта, SDA и SCL. Вы уверены, что ваша EEPROM не SPI? Какой номер детали?
@RogerRowland ... Да ... Это устройство I2C в соответствии с его руководством пользователя ... Также я могу успешно прочитать только первый байт. Может быть логическая проблема, вызывающая препятствия при чтении всех байтов.
Хорошо, не могли бы вы предоставить номер детали и / или ссылку на техническое описание?
@RogerRowland..пожалуйста, перейдите по этой ссылке
У вас есть захват осциллографом ваших транзакций чтения/записи? Я бы попытался сопоставить его с осциллограммами, показанными на странице 4 таблицы данных памяти. По моему опыту, наиболее распространенная проблема с I2C заключается в том, какой компонент управляет линиями clk/data в любой момент времени.

Ответы (3)

После чтения одного байта вы вызываете I2C_Send_ACK, который устанавливает SDA в низкий уровень. После этого вы всегда читаете 0 в SDA, поскольку больше никогда не выпускаете его (до вызова I2C_Send_NACK). Вы должны установить SDA на высокий уровень (не управлять им) при вызове вашей I2C_Read_Byteфункции.

Кроме того, вы должны изменить свой Set_SDA_OUT_Lowи Set_SDA_OUT_Highтаким образом, чтобы при установке его на низкий уровень он устанавливал контакт как выход и приводил его в низкий уровень, а при установке на высокий уровень он устанавливал контакт как вход.

@ Тобиас.. Спасибо за ваш ответ. Я попробовал оба ваших предложения 1.) Результат остается прежним 2.) Не работает ACK = 1;
Работает ли запись в вашу EEPROM, и действительно ли первый прочитанный байт соответствует тому, что вы в нее записали?
@ Тобиас .... Да ... Первый байт, я получаю то, что записал на устройство. Мой первый байт - «r» (согласно приведенному выше коду). То же самое считывается устройством. Также во время записи я получил ACK=0 для всех 16 байт. Но при чтении доступен только первый байт, все остальные равны 0x00.

Мне кажется, что ваш микроконтроллер постоянно управляет линиями SDA и SCK. Шина I2C является шиной с открытым стоком, что означает, что вы управляете только 0 на шине, в то время как 1 генерируются подтягиваниями (поэтому она намного медленнее, чем SPI).
Если вы не можете установить тройное состояние своих входов-выходов, способ битбанга заключается в том, чтобы установить вывод PORT в «0» и использовать регистр TRIS для управления шиной. Когда TRIS = '0', линия активируется, а когда TRIS = '1', линия освобождается.
Кроме того, нет необходимости использовать 2 контакта ввода-вывода на линию.

Измените свой код соответствующим образом и посмотрите, что произойдет.

Если вы взглянете на техническое описание модуля, который он использует, вы поймете, почему он использует 2 IO для линии SDA. Однако я все еще с вами в отношении тройного положения штифтов.
@ Алекс ... Спасибо за ценное предложение. Это помогло мне избавиться от проблемы. Выкладываю окончательный ответ.
@TobiasMüller .. Спасибо за ваше предложение. это помогло мне избавиться от проблемы. выкладываю свой окончательный ответ

Краткое описание проблемы.

У меня есть устройство EEPROM с 3 контактами. Детали устройства можно найти здесь

Я использовал концепцию I2C Bit-Banging, как было предложено, чтобы начать связь с устройством.

Наконец я застрял с проблемой. Я смог записать устройство (с получением ACK = 0) для каждого записанного байта.

При чтении устройства я смог прочитать первый байт с устройства, а для остальных байтов я получил 0x00.

Выполнив все приведенные выше предложения, я обнаружил, что мне нужно тянуть вывод SDA_OUT HIGH при чтении каждого бита.

Наконец я смог прочитать все байты с устройства.

Я снова публикую I2CInterface.c со всеми изменениями, которые заставили его работать.

**** Интерфейс 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++);

}