# Problems communicating with I²C RTC

I'm using PIC18F4680 to work with PCF8583P RTC using C18 compiler (and compiler's library functions for hardware $I^2C$) and I can't figure out how to read data from it or to determine if written data is actually there.

I think that my hardware setup is correct because it works fine using Parallax Propeller to write and read to the RTC. I'm using $4.7 \mbox{ } k\Omega$ pull-up resistors, 5 V Vcc, have decoupling capacitor of 100 nF at the RTC and am using a 32768 Hz quartz crystal for the oscillator. Also I get the 1 second interrupt from the RTC correctly. The A0 pin is connected to ground, so the RTC's write address should be 0xA0 and its read address should be 0xA1;

On the PIC side, I connected the RTC's SCL pin to PIC's RC3 pin and RTC's SDA pin to PIC's RC4 pin, as indicated in PIC's datasheet.

Here are relevant pieces of my code (NOTE: The code has been updated and should now work):

char i;//just a simple counter
char data[6];//array which will hold the data
char result;//result of the write function
char temp;  //Here junk data from buffers will be stored

/*we're in main now*/

TRISCbits.RC4=1; //For some not clearly explained reason
TRISCbits.RC3=1; //it is said by many that these bits need to be inputs

CloseI2C();//Make sure it didn't remain open for some reason
OpenI2C (MASTER, SLEW_OFF); //PIC is master, SLEW_OFF for 100 kHz communication

IdleI2C();//make sure that the bus is idle, in case there are other masters
StartI2C();
while(SSPCON2bits.SEN);//wait until start condition passes
Delay10TCYx (4);//This delay is here because the RTC requires is

temp=SSPBUF;//we're clearing the write buffer, just in case something remained
do
{
if (-1 == result) //write collision handler
{
temp=SSPBUF;       //Clearing the junk in buffer
SSPCON1bits.WCOL=0;//Clearing the write collision flag
}
}
while (0!=result);//we're repeating transmission until ACK is received

//same procedure is used for every write
//another option is to put the required data into '\0' terminated array
//and use while(0!= putsI2C(array) );
//to do the transmission. It will automatically handle problems
IdleI2C();//make sure the bus is idle
StopI2C();
while(SSPCON2bits.PEN);
Delay10TCYx (2);
CloseI2C ();


Then I try to read the values using following code:

  putrsUSART ("I2C read start \r\n");
putrsUSART ("\r\n");

CloseI2C();//make sure I2C is actually closed

OpenI2C (MASTER, SLEW_OFF);
Delay10KTCYx (2000);

IdleI2C();//Checks if the bus is free in case there are multiple masters
StartI2C();
while(SSPCON2bits.SEN);
Delay10TCYx (2);

temp=SSPBUF;

do
{
if (-1 == result) //write collision handler
{
temp=SSPBUF;       //Clearing the junk in buffer
SSPCON1bits.WCOL=0;//Clearing the write collision flag
}
}
while (0!=result);//we're repeating transmission until ACK is received

do
{
result=WriteI2C (0x02);//sets the register on the RTC
if (-1 == result) //write collision handler
{
temp=SSPBUF;       //Clearing the junk in buffer
SSPCON1bits.WCOL=0;//Clearing the write collision flag
}
}
while (0!=result);//we're repeating transmission until ACK is received

RestartI2C();
while(SSPCON2bits.SEN);
Delay10TCYx (2);
do
{
if (-1 == result) //write collision handler
{
temp=SSPBUF;       //Clearing the junk in buffer
SSPCON1bits.WCOL=0;//Clearing the write collision flag
}
}
while (0!=result);//we're repeating transmission until ACK is received

temp=SSPBUF;

for (i=0; i<4;i++)// I'm reading first 4 values here
{               //the fifth will be read manually, since NotAckI2C() is needed
// to terminate the communication

AckI2C();
}
NotAckI2C();
StopI2C();
while(SSPCON2bits.REN);
Delay10TCYx (2)
CloseI2C();


So it seems that the main problem is with this part:

while(1)
{
putrsUSART ("It gets stuck in this loop!\r\n");
if(DataRdyI2C())
{
AckI2C();
break;
}
}


The if is never true and program waits here forever. The compiler documentation says that the reason for such behavior is that the receive buffer never has any data available.

The procedure for writing data to and reading from $I^2C$ I got from the RTC's user manual. Same procedure is also described in the datasheet.

I'm aware that there is a possibility that writing to USART can cause problems with $I^2C$ timings, but turning the debug messages off doesn't seem to do anything.

So how do I troubleshoot this?

Also is there a way to see if the RTC is sending ACKs after receiving data in the write part?

UPDATE 1:

I used while(SSPCON2bits.ACKSTAT); to check for ACKs from RTC and it seems that the RTC is in fact sending them, so one side of communication seems fine.

UPDATE 2:

Well I just remembered that PICkit 2 has 3 channel logic analyzer capability and here are the results:

writing to RTC:

I removed the rest of the results because they are just flat lines. The logic analyzer was running in 1 MHz sample rate mode and was set to trigger on high to low transition for channels one and two. If I remember correctly, one screen should be 1024 samples long.

On the other hand reading from the RTC using Parallax Propeller and same circuit running on 3.3 V looks like this:

So the PIC sends the start condition on the bus and then it stops or at least waits long enough that the rest of the communication can't fit inside analyzer's capture interval even when set to 250 kHz sample rate. Any ideas what could be causing this?

UPDATE 3

Right now I'm reading the source files for the library functions and from them it looks like the fact that I get zero as a result of the write function means that the PIC actually received the ACK from the RTC (or at least that the ACKSTAT bit in SSPCON2 register is high). On the other hand, the PICkit 2 shows quite a long flat line, a bit longer than one second which is the maximum $I^2C$ transfer time for the RTC. So someone here is showing wrong results.

The write routine is not too complex. It simply sends the byte to the transmit buffer,checks is there was a collision and returns -1 if there was, waits for the buffer to empty and the bus to become available, checks if ACK was received and then reports ACK as zero, which is what I get and NACK as -2, which isn't a documented feature in library documentation for the compiler.

UPDATE 4
I just switched to compiler's software $I^2C$ library and the output looks fine.

So did anyone here actually did manage to start the MSSP module with C18 at all? If yes, what's the general procedure for that? Are there any special configuration bit settings needed? What about pin directions? Are there any extra settings needed for control registers?

-
See this question of mine, maybe it will help. – abdullah kahraman Apr 10 '12 at 13:02
Ah, that was my second question here, reminds me some memories :) – abdullah kahraman Apr 10 '12 at 13:04
Are these I2C routines written by you, or are they C18's? – abdullah kahraman Apr 11 '12 at 8:21
@abdullah kahraman They're C18's routines. The descriptions are [here](ww1.microchip.com/downloads/en/devicedoc/c18_lib_51297d.pdf). – AndrejaKo Apr 11 '12 at 8:30
Ah, man, I really hate digital :) So will these libraries work with XC8? I am trying to port from "mikroC PRO for PIC" to XC8. – abdullah kahraman Apr 11 '12 at 8:34

I have found over the years that except in speed-critical or multi-master applications, it's actually easier to bit-bang an I2C master than to try to use the I2C facilities built into many chips.

Note that if a device uses clock stretching, any time you release SCK, you must wait for it to actually go high. For simplicity, such delays are omitted from the following descriptions, but should be included if appropriate in your "release_SCK()" routine.

To start an I2C transaction, release SCK (if it isn't already) and, if the data line is low, assert SCK (drive it low), release SDA (if it isn't already), and release the SCK. Repeat this process up to nine times until SDA is high. If SDA is still low after nine repetitions, the bus is unusable.

To output each byte (including the address byte), assert SDA, and then for each bit repeat the sequence (assert SCK; set SDA high or low to match next bit of data; release SCK) eight times. After the last bit, assert SCK, release SDA, and release SCK. If SDA is low, a slave is acknowledging; if SDA is high, no slave is acknowledging and the transaction should be aborted.

When all output is complete, assert SCK, then SDA, and then release SCK, then SDA.

To input each byte, assert SDA, then release SCK if it isn't already (it will be for the first byte, but not others). Then reassert SCK, release SDA, and repeat the sequence (release SCK, read data bit, assert SCK) eight times. Note that at the end of this sequence, unlike when outputting a byte, SCK will be left asserted.

When all input is complete, release SDA (it should already already be released) and SCK.

Note that because the clock is left asserted after inputting each byte, it's not necessary to specify whether the byte should be ack'ed or nak'ed. If you read another byte, the last byte read will be nak'ed. If you terminate the read, it will be nak'ed.

Start; send address; write one byte, finish
SCK    - -__-__-__-__-__-__-__-__-__-- -__-__-__-__-__-__-__-__-__--- -__--
SDA(M) - __777666555444333222111___--- --777666555444333222111000---- --__-
SDA(S) - -------------------------??AA A------------------------??AAA A----

SCK    - -__-__-__-__-__-__-__-__-__--- -__--_--_--_--_--_--_--_--__ -__--_--_--_--_--_--_--_--__ _-
SDA(M) - __777666555444333222111------- __-------------------------- __-------------------------- --
SDA(S) - -------------------------??AAA A??77?66?55?44?33?22?11?00?? -??77?66?55?44?33?22?11?00?? ?-

-
+1 for the firmware IIC master. I do that a lot too, especially since there were a couple bugs in some of the PIC MSSP module on how IIC was implemented. It's safer to do in firmware where you can guarantee a minimum time between any two things happening. Then you can use any pins, not just the ones connected to the IIC hardware, which often collide with UART or other peripherals you don't want to do in firmware. – Olin Lathrop Apr 10 '12 at 21:22

For some reason, I2C seems to be very confusing and poorly explained in datasheets. Plus the master tends to be very picky about you using it correctly, and the slave is likewise picky. Rightly so, I guess. This leads to I2C being a lot more annoying to use than SPI.

The way to solve this, I often find is to capture the waveform on an oscilloscope or logic analyser (I recommend the Saleae Logic. It will save days of your life, and pay for itself in no time). Zoom in, and check every edge and every bit to make sure it's what you expect. Often I have found that for some reason a start or stop or restart condition isn't happening properly, and this tells me where to look in my code.

Debugging I2C without a 'scope or logic analyser is the road to dispair.

-
Yes, I2C is really confusing in the datasheets. – abdullah kahraman Apr 10 '12 at 13:14
I have to agree with the road to despair statement. – AndrejaKo Apr 10 '12 at 13:20
IIC is actually a very simple and elegant protocol. But, you have to actually read the manual. That means learning how IIC works and then learning how your particular device communicates using it. – Olin Lathrop Apr 10 '12 at 21:19
@abdullah - The I2C spec (UM10204) is 50 pages long, and explains I2C very well in every detail. Don't expect datasheets to include all those pages. If you want to use a protocol, go to the source (NXP) for your information. – stevenvh Apr 11 '12 at 5:52
Forgot the link to the I2C spec – stevenvh Apr 11 '12 at 7:31

To debug IIC, start with seeing if the slave device ACKs the address byte. Fortunately, IIC is only two lines, so it's easy to capture the address byte on a scope. Make sure it is preceeded by a proper start sequence. A start sequency is both lines starting high, then SDA goes low before SCL goes low. Most likely the slave doesn't think it's being addressed, in which case it won't ACK the address byte. I didn't look at your code, but aren't you checking for that? If not, you should.

-
I'm aware of the 7+1 bit way of addressing, but I'm not sure how to actually implement that. I'm trying to do this in C and the logical way to me seems to be to open the I2C port and then send the whole byte which consists of the high seven bits and the one low bit. I don't see how to fit the seven bit system into this. Also is there any way to check if the PIC received ACK other than to read the MSP Control register 2? – AndrejaKo Apr 10 '12 at 12:35
@Andrej: You want to use a high level wrapper but you want to see the details. Make up your mind. Of course you assemble the address byte in your code from the 7 bit address and the 1 bit read/write fields. If you want to have constants for addressing this device for read and for write, that's fine too, but only if you document it correctly. Again, what have you found by looking at the start sequence and address byte on a scope or logic analyzer? – Olin Lathrop Apr 10 '12 at 13:07
I don't have a scope or a logic analyzer. Yes, I know that I really need to get one. – AndrejaKo Apr 10 '12 at 13:10
@AndrejaKo, even without a scope or a logic analyzer, you study electronics, that is really enthusiastic of you. – abdullah kahraman Apr 10 '12 at 15:04

It seems that I was unaware of the correct procedure to use the hardware $I^2C$ module on this toolchain.

In master mode a value used to determine the the clock of the $I^2C$ bus needs to be stored in the SSPADD register.

The formula used for determining value is:

$$SSPADD=\frac{F_{osc}}{4I^2C_{clock}}-1$$

So a simple example of $I^2C$ write would look like this:

Setup part:

#include <i2c.h>
//and so on for the includes
char result;
TRISCbits.RC4=1; //I2C bits set as inputs
TRISCbits.RC3=1; //I2C bits set as inputs


The code itself would look like this:

OpenI2C (MASTER, SLEW_OFF); //master mode, 100 kHz devices
SSPADD=49;// For 20 MHz, the value is 49, for 40 MHz the value is 99
StartI2C();
while(SSPCON2bits.SEN);//waits for start condition to pass
//seems to work without this too
result=WriteI2C(0x00); //write whatever is needed
// returns zero if ACK is received
if (-2== result)
{
//handle NACK
}
if (-1==result)
{
//handle write collision
}

//write as needed
StopI2C();
while(SSPCON2bits.REN);//waits for stop condition to pass
// not sure if really needed
CloseI2C();//closes the hardware port

-