Electronic – I2C communication won’t start unless SDA pullup resistor is reinserted

ci2cmicrocontrollerpullupresistors

I'm working on a project where a PIC12LF1552 microprocessor is using I2C to talk to a TMP75 temperature sensor. Like the title says, a very strange issue keeps happening.

Overview

Every time I power on the micro, the SDA (data line) and SCL (clock line) remain low. After the 18 sec wait period in the code, both lines go high when the I2C_init function starts the I2C bus. At this point I know the micro should be writing to the configuration register in the temperature sensor. The infinite loop flashes an LED every time the micro writes to the temperature sensor.

The problem is that the I2C lines will stay stuck high unless I remove the SDA pullup resistor and then put it back in. Once the SDA pullup is put back in, the scope captures perfectly normal I2C communication, and everything works just fine. (See below)

Scope Screenshot 4.7k pullups

scope shot

Scope Screenshot 1k pullups

scope shot2

Pullup Resistors

I am using 1k pullup resistors now, however in the screenshot above I was using 4.7k, which is too large. With 1k resistors the clock line and data line are much more square and proper looking. With the internal weak pullups on the micro enabled, I2C communication would not work at all.

Additional Info

I have verified that every single bit of the I2C write command that is being sent is correct. I have also used a Raspberry Pi to successfully read and write to the TMP75.

The I2C bus speed set in the micro is 100kHz. The TMP75 can handle up 400kHz, so this is not a problem.

Reinserting the SDA pullup is what allows the normal I2C communication to occur. However, reinserting the SCL pullup while the I2C is "stuck" does not fix the issue.

I have asked multiple professors at my college about this problem, but none of them came up with a solution.

Question

So, my question is: What could possibly be causing the need for the SDA pullup to be reinserted for the I2C to start communicating?

Code for reference

main.c

#include <xc.h>
#include "i2c.h"

//PIC12LF1552 Configuration Bit Settings
// 'C' source line config statements

#define _XTAL_FREQ 16000000
#define I2C_SLAVE 0x48

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = ON         // Flash Program Memory Code Protection (Program memory code protection is enabled)
#pragma config BOREN = OFF       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

// Send a command to I2C chip
void i2c_write_command(unsigned char address, unsigned char command)
{
i2c_Start();                        // Send Start
i2c_Address(I2C_SLAVE, I2C_WRITE);  // Send slave address - write operation
i2c_Write(address);                 // Send register address 
i2c_Write(command);                 // Send register value (command)
i2c_Stop();                         // Send Stop
}

// Read a char from I2C chip
unsigned char i2c_read_command(unsigned char address)
{
unsigned char read_byte;
// Read one byte
i2c_Start();                        // send Start
i2c_Address(I2C_SLAVE, I2C_WRITE);  // Send slave address - write operation
i2c_Write(address);                 // Set register for read
i2c_Restart();                      // Restart
i2c_Address(I2C_SLAVE, I2C_READ);   // Send slave address - read operation  
read_byte = i2c_Read(0);            // Read one byte
                      // If more than one byte to be read, (0) should
                      // be on last byte only
                      // e.g.3 bytes= i2c_Read(1); i2c_Read(1); i2c_Read(0);
i2c_Stop();                         // send Stop
return read_byte;                   // return byte. 
                                    // If reading more than one byte
                                    // store in an array
}

void main(void) {

unsigned char temp1;
int i=0;

OSCCON = 0b01111010;                // set internal osc to 16MHz
TRISA = 0b000000;                   // set ports to outputs
PORTA = 0b000000;
//OPTION_REG = 0b01011000;            //or hex 0x58, enable pull up resistors
//WPUA1 = 1;                          //enable individual pull ups
//WPUA2 = 1;

__delay_ms(9000);
__delay_ms(9000);

i2c_Init();                         // Start I2C as Master 100KHz
i2c_write_command(0x1,0x78);        // Write configuration to register
//temp1 = i2c_read_command(0x0);
//__delay_ms(5000);

while(1)
{
    RA4 = 1;
    __delay_ms(500);
    RA4 = 0;
    __delay_ms(500);
    i2c_write_command(0x01,0x78);
}
return;
}

i2c.h EDIT: UPDATED

#define I2C_WRITE 0
#define I2C_READ 1

// Initialise MSSP port. (12F1822 - other devices may differ)
void i2c_Init(void){

   // Initialise I2C MSSP
   // Master 100KHz
   TRISA1=1;                // set SCL and SDA pins as inputs
   TRISA2=1;

   SSPCON1 = 0b00101000;    // I2C enabled, Master mode
   SSPCON2 = 0x00;      // I2C Master mode, clock = FOSC/(4 * (SSPADD + 1)) 
   SSPADD = 39;         // 100Khz @ 16Mhz Fosc
   SSPSTAT = 0b11000000;    // Slew rate disabled

}

// i2c_Wait - wait for I2C transfer to finish
void i2c_Wait(void)
{
    while ( ( SSP1CON2 & 0x1F ) || ( SSPSTAT & 0x04 ) );
}

// i2c_Start - Start I2C communication
void i2c_Start(void)
{
    //i2c_Wait();
    SEN=1;  //Indicates that start bit has been detected last
    while(SEN);
}

// i2c_Restart - Re-Start I2C communication
void i2c_Restart(void){
    //i2c_Wait();
    RSEN=1; //Enables receiver mode for I2C master mode only 
    while(RSEN);
}

// i2c_Stop - Stop I2C communication
void i2c_Stop(void)
{
    //i2c_Wait();
    PEN=1;  //Initiate stop condition on SDA and SCL pins, auto cleared by hardware
    while(PEN);
}

// i2c_Write - Sends one byte of data
void i2c_Write(unsigned char data)
{
    SSPBUF = data;  //MSSP1 Receive Buffer/Transmit Register
    while(BF);       // wait till complete data is sent from buffer 
    i2c_Wait();
}

// i2c_Address - Sends Slave Address and Read/Write mode
// mode is either I2C_WRITE or I2C_READ
void i2c_Address(unsigned char address, unsigned char mode)
{
    unsigned char l_address;

    l_address=address<<1;
    l_address+=mode;
    SSPBUF = l_address;
    while(BF);       // wait till complete data is sent from buffer
    i2c_Wait();
}

// i2c_Read - Reads a byte from Slave device
unsigned char i2c_Read(unsigned char ack)
{
    // Read data from slave
    // ack should be 1 if there is going to be more data read
    // ack should be 0 if this is the last byte of data read
    unsigned char i2cReadData;

    //i2c_Wait();
    RCEN=1;
    while(!BF);      // wait for buffer full //i2c_Wait();
    i2cReadData = SSPBUF;
    i2c_Wait();
    if ( ack ) ACKDT=0;         // Ack
    else       ACKDT=1;         // NAck
    ACKEN=1;                    // send acknowledge sequence

    return( i2cReadData );
}

Thank you for your time.

EDIT: Updated i2c.h
EDIT2: Uploaded screenshot with 1k pullup resistors.

Best Answer

Your problem is that you have sprinkled i2c_wait() commands through your code.

This is a microcontroller, it is simple, it does exactly what you want. The state doesn't need to be constantly checked. You should only need to call i2c_wait() after interacting with SSPBUF. Have a look at http://www.8051projects.net/wiki/I2C_Implementation_on_PIC

By holding SCL high and toggling SDA (removing and replacing the resistor) you are sending a start bit followed by a stop bit. It isn't clear why this fixes the registers you are checking, as Chris Stratton suggested you could dump out the registers if you really cared.