# I2C Master: How to read with repeated start condition?

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.

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);
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, Data, Ack
• Repeat Start -- good, that's what I wanted to see, instead of stop/start
• 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, 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.

• Superficially it looks like you need to use I2C_SetDataCompleteCallback to change the FSM sequence from the default I2C_CallbackReturnStop to I2C_CallbackRestartRead before starting the I2C operation. – brhans Feb 5 '20 at 15:17
• Why don't you sniff the bus for the Raspberry Pi instantiation, since that works, and see what's going on? – Scott Seidman Feb 5 '20 at 15:17
• @brhans: Ah, so there is a way to modify this that I overlooked... internal callback, that's funny. Will try! @ Scott: I did, it's in the maybe somewhat wordy list, item 12. – sktpin Feb 5 '20 at 15:26
• It seems we have different libraries, things are named differently, but I am seeing items that are similar. Haven't gotten it to work with that for a quick try. Will investigate further. – sktpin Feb 5 '20 at 16:19
• Not there yet, see updated post – sktpin Feb 6 '20 at 16:13

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)
{
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
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)
{
i2c_stop(); //if address was NAKed, terminate the cycle
return 2;   //and return error code
}