Electrical – I2C Master: How to read with repeated start condition

i2cmplabxpic

  1. I have a PIC16F18875, on a board where it is configured as I2C master.
  2. 7 bit addressing, 100 kHz clock
  3. Several different I2C slave devices are on the bus.
  4. am using MPLAB X IDE with MCC plugin
  5. using the MCC-generated code for interacting with the peripherals
  6. I can read most slave ICs on the board, like some ADCs, and am getting sensible values back
  7. the UCD9090 PMIC, though, is only giving me 0xFF back, no matter which register is attempted to be read
  8. oscilloscope shows good signal integrity
  9. with a RaspberryPi's I²C attached to the same bus, I can read the 9090 just fine
  10. the logic analyzer shows that the slave is transmitting the value to be read back, a nack and the last stop with 36ms (milli) delay after the address-read (see image below)
  11. it is shown that the library code used generates a START+STOP after the address and register# bytes have been sent
    • using i2c_masterOperation(), i2c_setBuffer() etc
  12. whereas the RaspberryPi logic analyzer readout shows it does the REPEATED START
  13. according to this, some slave devices need a REPEATED START, instead of a STOP+START, to be readable
  14. hence it looks to me that that's the problem

Now, from the MCC generated I2C interface header file, I see no way of telling the code to use the method of repeated-start instead of stop-start. Haven't seen anything configurable on the sparsely populated I2C page in MCC, either.
So, before I go and muck about within autp-generated code…

Is there a way to "properly" do that?
Otherwise, I guess I'll have to dig through their library code and find how to modify it to do what's necessary.

An illustration of attempting to read the UCD9090 with MCC I2C lib

Update:
What I was trying, having been made aware of the state machine altering nature of the setCallbackX functions by brhans, is this:

i2c_error_t i2c_readRegRepeatStart(uint8_t reg, void* data, uint8_t size)
{       
    i2c_error_t ret;    

    __i2c_buf.reg = reg;
    i2c_setBuffer(&__i2c_buf.reg, 1);
    i2c_setDataCompleteCallback( i2c_restartRead, 0 );
    ret = i2c_masterOperation(false); // writing the start,addr,reg,restart part
    i2c_setDataCompleteCallback( i2c_returnStop, 0 );
    if (ret == I2C_NOERR)
    {        
        i2c_setBuffer(data, size);
        ret = i2c_masterOperation(true); // reading the data bytes
    }
    return (ret);
}

I call this like: i2c_readRegRepeatStart( registerNum, &someUshortVar, 2 ).
This code does produce a sequence on the logic analyzer screen that starts out good:

  • Start
  • W, Addr, Ack
  • W, Data, Ack
  • Repeat Start — good, that's what I wanted to see, instead of stop/start
  • R, Addr, Ack
  • R, Data, Ack
  • R, Data, Ack

So far, so good, right? Well, almost. Instead of now a STOP, this is what follows:

  • R, Data, Ack (19 times)
  • R, Data, Nack (once)
  • Stop
  • Start
  • R, Addr, Ack
  • R, Data, Ack
  • R, Data, Ack
  • Stop

I haven't figured out yet what's going on. It seems in principle what's supposed to be done, and I traced down to where the buffer pointer and size are set within the MCC library, and it clearly has the value of 2 (size), then the driver is engaged to perform the I2C operation, and it does that weird stuff.

Best Answer

This helped. Gave up on using the MCC stuff for this, other than the clicked-together basic peripheral init stuff. (Note that in the microchip official forum it was said the MCC I2C library is currently changing and not tested a lot) I basically just added a further parameter to indicate whether I want to use stop,start; or restart (aka repeated start condition), making it so that for "restart" the write-block routine omits the STOP at the end, and the read-block routine calls restart() instead of start(). Also note that that code expects left-aligned slave addresses (Lshift 1).

http://picforum.ric323.com/viewtopic.php?f=76&t=755#

Here an excerpt of the most relevant stuff, incl. those of my changes I mentioned - you might have to change things to qualify the correct register structs before the register names in this code, if you also use the MCC generated init code, despite then not using MCC for the actual business. If you use the read-block routine and pass repeatStart=true, it will do repeated-start condition intead of stop,start. Otherwise, it will do stop,start. With this, I was able to read that device which would refuse to be read before, so the question is answered - just without using the MCC I2C state machine, instead rather bypassing it with direct register access.

// Send an I2C START
// Return 0 if all ok, 1 if bus collision
__bit i2c_start(void)
{
    BCLIF = 0;  //Clear 'Bus collision" flag
    SEN = 1;    //initiate a START cycle
    while (SEN);    //wait until it has been sent
    return BCLIF;   //return value of BCLIF flag
}

// Send an I2C STOP
void i2c_stop(void)
{
    PEN = 1;    //initiate a STOP cycle
    while (PEN);    //wait until it has been sent
}

// Send an I2C REPEATED START
void i2c_restart(void)
{
    RSEN = 1;    //initiate a REPEATED START cycle
    while (RSEN);    //wait until it has been sent
}

//Send one byte. Return 0 if ACK received, or 1 if NAK received
__bit i2c_sendbyte(unsigned char dat)
{
    SSPBUF = dat;
    while (R_W);    //wait until byte sent and ACK/NAK received
    return ACKSTAT;
}

//Receive one byte. ackflag=0 to send ACK, or 1 to send NAK in reply
unsigned char i2c_recvbyte(unsigned char ackflag)
{
    RCEN = 1;   // initiate a RECEIVE cycle
    ACKDT = ackflag;    //specify if we should send ACK or NAK after receiving
    while (RCEN);   //wait until RECEIVE has completed
    ACKEN = 1;  //initiate an ACK cycle
    while (ACKEN);  //wait until it has completed
    return SSPBUF;
}

//Send an array of data to an I2C device.
//Return 0 if all OK, 1 if bus error, 2 if slave address NAK, 3 if slave register NAK, 4 if slave data NAK
unsigned char i2c_writeblock(unsigned char slave_address, unsigned char start_reg, unsigned char buflen, const unsigned char * bufptr, bool stop)
{
    if (i2c_start() )   //send a start, and check if it succeeded
        return 1;   //abort if bus collision
    //send the I2C slave address (force R/W bit low)
    if (i2c_sendbyte(slave_address & 0xFE))
    {
        i2c_stop(); //if address was NAKed, terminate the cycle
        return 2;   //and return error code
    }
    //send the device register index
    if (i2c_sendbyte(start_reg))
    {
        i2c_stop(); //if register was NAKed, terminate the cycle
        return 3;   //and return error code
    }
    //send the data. buflen might be zero!
    for (; buflen>0; --buflen)
    {
        if (i2c_sendbyte(*bufptr++))
        {
            i2c_stop(); //if register was NAKed, terminate the cycle
            return 4;   //and return error code
        }

    }
    if (stop)
      i2c_stop();
    return 0;   //no error
}

//Receive an array of data from an I2C device.
//Return 0 if all OK, 1 if bus error, 2 if slave address NAK, 3 if slave register NAK
unsigned char i2c_readblock(unsigned char slave_address, unsigned char start_reg, unsigned char buflen, unsigned char * bufptr, bool repeatStart)
{
    //do a dummy zero length write cycle to set the register address
    unsigned char retval = i2c_writeblock(slave_address, start_reg,0,0, !repeatStart);
    if (retval)
    {
        return retval;  //abort if there was an error
    }

    if (repeatStart)
      i2c_restart(); // restart can't fail - if bus remains asserted, there can be no collision
    else
    //now start the READ cycle
      if (i2c_start() )   //send a start, and check if it succeeded
        return 1;   //abort if bus collision

    //send the I2C slave address (force the R/W bit high)
    if (i2c_sendbyte(slave_address | 0x01))
    {
        i2c_stop(); //if address was NAKed, terminate the cycle
        return 2;   //and return error code
    }
    //receive the data.
    for (; buflen>0; --buflen)
    {
        unsigned char ackflag = (buflen == 1);   //1 if this is the last byte to receive => send NAK
        *bufptr++ = i2c_recvbyte(ackflag);
    }
    i2c_stop();
    return 0;   //no error
}