Electronic – PIC18, I2C Master Receive, Cant Read from SSPBUF to a Variable

i2cmicrocontroller

I am using a Master PIC18F4620, Slave DS1307.
All I am doing is setting the seconds to (0) at the beginning of the program, then I read it.
I can't seem to read correctly, the value that is being returned from the SSPBUF register is the Slave+Writebit Address.

Everything is perfect on HW & Simulation, how is so?
I used an I2C Debugger on Proteus and it is all working fine, I am indeed writing and reading CORRECT values, I then checked using my PicKit3 Debugger HW, and I found out something very interesting.
The SSPBUF DOES transmit and receive, therefore its value changes, but the assignment statement from the SSPBUF to a variable FAILS.

#include "18F4620_Config.h"
#include "I2C.h"
#include "Generic_LCD.h"
#include "stdio.h"

#define _XTAL_FREQ 16e6
#define _RTC_WRITE 0xD0
#define _RTC_READ 0xD1 
#define SEC_ADDRESS   0x00 // Address to access Ds1307 SEC register
#define CONTROL 0x07       // Address to access Ds1307 CONTROL register

unsigned char seconds;
char myString[6] = {0};

void main(void)
{
    LCD_Init();
    LCD_Clear();
    LCD_Print("Read:");

    I2C_Master_Init();
    
    DS1307_Set_Seconds(35);
    while (1)
    {

        seconds = DS1307_Read_Seconds();

        LCD_Goto(6, 1);
        sprintf(myString,"%x ",seconds);
        LCD_Print(myString);

        __delay_ms(1000);
    }
}

As for the two used functions.

void DS1307_Set_Seconds(unsigned char secs)
{
    if (secs <= 60)
    {
        unsigned char higher = (secs / 10) << 4;
        unsigned char lower = (secs % 10);
        unsigned data = higher | lower;
        I2C_Start();
        I2C_Write(_RTC_WRITE);
        I2C_Write(0x00);
        I2C_Write(data);
        I2C_Stop();
    }
}

unsigned char DS1307_Read_Seconds(void)
{
    unsigned char sec;
    I2C_Start();
    I2C_Write(_RTC_WRITE);
    I2C_Write(SEC_ADDRESS);
    I2C_Restart();
    I2C_Write(_RTC_READ);
    sec = I2C_Read_Byte();
    I2C_NACK();
    I2C_Stop();
    return (sec);
}

The only thing that is being printed right now is (D1) on the LCD.
I noticed another strange behavior, if instead of assigning SSPBUF value to a variable, I pass the SSPBUF as a parameter to the sprintf function, IT WORKS!.
Like this

while (1)
        {
    
            seconds = DS1307_Read_Seconds();
    
            LCD_Goto(6, 1);
            sprintf(myString,"%x ",SSPBUF);
            LCD_Print(myString);

            __delay_ms(1000);
        }

Edit 1: Solution
As mentioned in the comments, the solution is to read from the SSPBUF AFTER the I2C Communication has stopped entirely.

Therefore these are the code changes

void I2C_Read_Byte(void)
{
    //---[ Receive & Return A Byte ]---
    RCEN = 1; // Enable & Start Reception
    while (!SSPIF); // Wait Until Completion
    SSPIF = 0; // Clear The Interrupt Flag Bit
}

unsigned char DS1307_Read_Seconds(void)
{
    I2C_Start();
    I2C_Write(_RTC_WRITE);
    I2C_Write(SEC_ADDRESS);
    I2C_Restart();
    I2C_Write(_RTC_READ);
    I2C_Read_Byte();
    I2C_NACK();
    I2C_Stop();
    return (SSPBUF);
}

while (1)
    {

        seconds = DS1307_Read_Seconds();

        LCD_Goto(6, 1);
        sprintf(myString, "%x ", seconds);
        LCD_Print(myString);
        
        __delay_ms(1000);
    }

Best Answer

I can't be sure, but I suspect the PIC is latching the address into SSPBUF, and once you read that it will then put the data there. So if you read it a second time, you'll get the data. Note that in your second example, you call DS1307_Read_Seconds(), which tries to read it, and then you read SSPBUF which would be a second read from that register.