I2C: PSoC5LP Master is reading before data is ready on PSoC5LP slave

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

I'm having a little bit of confusion with the interaction between an I2C master and an I2C slave.  Both the Master and Slave are PSoC5LP devices under my control.

I'm not worried much about the master, this is code I've successfully used before to interface with a number of I2C slave devices.

Regardless, Here's the function that's performing the entire master Write/Read transaction.  It uses a custom i2c packet type:

typedef struct {

       unsigned char addr;      /*I2C Slave Address*/

       unsigned char cmd;       /*I2C Register Address (command)*/

       unsigned char tx_num;    /*Number of data bytes to be transmitted (not including command byte)*/

       unsigned char rx_num;    /*Number of data bytes to be received*/

    } i2c_pkt_t;

/* I2C_Receive

*  - Performs a full I2C Transmit/Receive in the form:

*    [ADDR(W)][CMD][DATA][SR][ADDR(R)][DATA]

*  RETURNS: bool   true -> I2C Error Occurred

*                  false -> I2C Transmission OK

*/

bool I2C_MasterReceive (i2c_pkt_t pkt, uint8_t* txbuff, uint8_t* rxbuff){

    uint8_t i2c_stat;

    bool i2c_err = false;

   

    /*START->ADDR(W)*/

    i2c_stat = MI2C_MasterSendStart(pkt.addr,0);

   

    if (i2c_stat != MI2C_MSTR_NO_ERROR){

        i2c_err = true;

    }

   

    /*CMD*/

    if (!i2c_err){

        i2c_stat = MI2C_MasterWriteByte(pkt.cmd);

       

        if (i2c_stat != MI2C_MSTR_NO_ERROR){

            i2c_err = true;

        }

       

    }

   

    /*DATA BYTES OUT*/

    if (!i2c_err){

       

        for (uint8_t i = 0; i < pkt.tx_num; i++){

            i2c_stat = MI2C_MasterWriteByte(txbuff);

           

            if (i2c_stat != MI2C_MSTR_NO_ERROR){

                i2c_err = true;

                break;

            }

           

        }

       

    }

   

    /*RESTART->ADDR(R)*/

    if (!i2c_err){

        i2c_stat = MI2C_MasterSendRestart(pkt.addr,1);  

       

        if (i2c_stat != MI2C_MSTR_NO_ERROR){

            i2c_err = true;

        }

    }

   

    /*DATA BYTES IN*/

    if (!i2c_err){

       

        for (uint8_t i = 0; i < pkt.rx_num; i++){

           

            if (i == (pkt.rx_num - 1)){

                rxbuff = MI2C_MasterReadByte(MI2C_NAK_DATA);

            } else {

                rxbuff = MI2C_MasterReadByte(MI2C_ACK_DATA);

            }

           

           

            if (i2c_stat != MI2C_MSTR_NO_ERROR){

                i2c_err = true;

                break;

            }

           

        }

       

    }

   

    /*STOP*/

    i2c_stat = MI2C_MasterSendStop();

   

    if (i2c_stat != MI2C_MSTR_NO_ERROR){

            i2c_err = true;

        }

   

    if (i2c_err){

        I2C_MasterReset(); 

    }

   

    return i2c_err;

}

Like I said above, this code works fine as far as I can determine.

Where I'm running into a problem is getting the PSoC5LP slave to respond properly.  I am expecting a 0xAA byte in response to my command, but I am getting 0x00.  Here is my slave reception code, which is repeatedly called in main:

#define MAX_I2C_PACKET_SIZE 64

uint8_t SI2C_RdBuffer[MAX_I2C_PACKET_SIZE];

uint8_t SI2C_WrBuffer[MAX_I2C_PACKET_SIZE];

void Process_SI2C (void){

    uint8_t status = SI2C_SlaveStatus();

    uint8_t incoming_size = 0;

    uint8_t data_in[MAX_I2C_PACKET_SIZE] = {0};

   

    if (status &= SI2C_SSTAT_WR_CMPLT){

        /*Received Data From Master*/

        SI2C_SlaveClearReadBuf();

        if (status &= SI2C_SSTAT_WR_ERR){

            /*TODO: Handle Error Condition*/

           

        } else {

            incoming_size = SI2C_SlaveGetWriteBufSize();

            memcpy(data_in, SI2C_WrBuffer, incoming_size);

            Process_I2C_Cmd(data_in);   //Processes command, loads Read Buffer with data for the master if necessary

        }

       

        SI2C_SlaveClearWriteBuf();

        SI2C_SlaveClearWriteStatus();

    }

   

    if (status &= SI2C_SSTAT_RD_CMPLT){

        /*Master has finished reading data*/

        SI2C_SlaveClearReadStatus();

    }

}

What I'm finding is that it almost seems like the master is attempting to read from the slave before Process_I2C_Cmd has been called, which is what loads SI2C_Rdbuffer with data.  When I set a breakpoint at line 8 above at SI2C_SlaveClearReadBuf() the master looks like it's already read a byte from the slave.

see logic analyzer capture below.  The long NAK is expected because the slave has his the line 8 breakpoint:

i2c.jpg

It looks like my call of Process_I2C_Cmd(), which is meant to load SI2C_RdBuffer with the data the master is requesting, hasn't fired yet when the master is reading the data out (I should be receiving an 0xAA byte instead of 0x00).  The code for this is below:

static void Process_I2C_Cmd(uint8_t * incoming){

    uint8_t cmd = incoming[0];

   

    switch (incoming[0]){

       

        case I2C_ALIVE_CMD:     //0x00

            SI2C_RdBuffer[0] = I2C_ALIVE_BYTE;     //0xAA

        break;

           

    }

   

}

How can I resolve this issue?  I looked a bit into enabling clock stretching, but I can't seem to find any way to do so according to the I2C datasheet.  Is there a way to enable clock stretching on the slave so I can hold up the master until data has been loaded into the Read Buffer?  Is there another solution I should be employing?

Thoughts are greatly appreciated.

Thanks!

0 Likes
1 Solution
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

So I think I've got a solution:

I found the interrupt callback SI2C_HwPrepareReadBuf_Callback() that looks like it's made for exactly this situation.

I put my processing of I2C commands into this callback like so:

void SI2C_HwPrepareReadBuf_Callback(void){

    uint8_t incoming_size = 0;

    uint8_t data_in[MAX_I2C_PACKET_SIZE] = {0};

  

    SI2C_SlaveClearReadBuf();

  

    incoming_size = SI2C_SlaveGetWriteBufSize();

    memcpy(data_in, (uint8_t *)SI2C_WrBuffer, incoming_size);

    Process_I2C_Cmd(data_in);  //Processes command, loads Read Buffer with data for the master if necessary

  

    SI2C_SlaveClearWriteBuf();

    SI2C_SlaveClearWriteStatus();

}

This seems to get the data byte to the read buffer in time to be read by the master.  This seems to be the preferred solution judging by the callback name.

I'd still be curious to know if clock stretching is an option for Slave I2C on the PSoC5LP if anyone has that information.  Witch clock stretching I would be able to use a polling solution rather than interrupt based if need be.

View solution in original post

0 Likes
3 Replies
KyTr_1955226
Level 6
Level 6
250 sign-ins 10 likes given 50 solutions authored

So I think I've got a solution:

I found the interrupt callback SI2C_HwPrepareReadBuf_Callback() that looks like it's made for exactly this situation.

I put my processing of I2C commands into this callback like so:

void SI2C_HwPrepareReadBuf_Callback(void){

    uint8_t incoming_size = 0;

    uint8_t data_in[MAX_I2C_PACKET_SIZE] = {0};

  

    SI2C_SlaveClearReadBuf();

  

    incoming_size = SI2C_SlaveGetWriteBufSize();

    memcpy(data_in, (uint8_t *)SI2C_WrBuffer, incoming_size);

    Process_I2C_Cmd(data_in);  //Processes command, loads Read Buffer with data for the master if necessary

  

    SI2C_SlaveClearWriteBuf();

    SI2C_SlaveClearWriteStatus();

}

This seems to get the data byte to the read buffer in time to be read by the master.  This seems to be the preferred solution judging by the callback name.

I'd still be curious to know if clock stretching is an option for Slave I2C on the PSoC5LP if anyone has that information.  Witch clock stretching I would be able to use a polling solution rather than interrupt based if need be.

0 Likes

Hi KyTr_1955226​,

With clock stretching I would be able to use a polling solution rather than interrupt based if need be.

Even if you poll for the status flags in the I2C code, the underlying I2C implementation is based on interrupts. So the method that you have suggested is the best method to implement processing of commands.

But if you do not want to use the callback function then you can disable the interrupts before processing the commands and enabling the interrupts after processing the commands. As I2C is interrupt based, when interrupts are disabled the clock will be stretched and the data will be loaded into the buffer.

Hope this helps,

Thanks and Regards,

Rakshith M B

Thanks and Regards,
Rakshith M B

Rakshith,

Thank you very much for the info, that makes sense.

0 Likes