4
\$\begingroup\$

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.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Are you waiting on something to happen while(something); style? You may be inadvertently waiting for a low state that never happens. \$\endgroup\$
    – Daniel
    Commented Apr 13, 2016 at 23:38
  • 1
    \$\begingroup\$ Add some debug output or twiddle a GPIO you can see with a scope, so that you can tell what your code is doing - for example, somewhat as Daniel suggested it could be getting stuck in i2c_Wait() until you free it by manually manipulating the bus. Check the datasheet definitions of those status bits you are reading. \$\endgroup\$ Commented Apr 13, 2016 at 23:50
  • \$\begingroup\$ I don't think the code is getting stuck in i2c_wait(). In the infinite loop I toggle an LED on and off. The LED is always toggling whether or not the bus is working. (So before I fix the SDA line the LED flashes the same as after I fix it) \$\endgroup\$
    – Rdd
    Commented Apr 14, 2016 at 2:18
  • 1
    \$\begingroup\$ What do you mean by 'stuck' high? When I2C is idle, SCL and SDA are supposed to be high. Why aren't you checking for ACK/NACK on your address and command byte transmissions? You only appear to be checking for ACK/NACK on your readback which means you assume the rest of the communications are 100% perfect 100% of the time... \$\endgroup\$ Commented Apr 14, 2016 at 21:28
  • \$\begingroup\$ Your SDA line is suspicious. A 10k pullup resistor is normal, a 4.7k pullup is strong, I have never seen a circuit requiring a 1k pullup. This is reinforced because the SCL line should be connected in much the same way and it has nice sharp edges at 4.7k. Something is dragging down SDA, you should find and stop it. \$\endgroup\$
    – lod
    Commented Apr 15, 2016 at 0:50

3 Answers 3

2
\$\begingroup\$

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.

\$\endgroup\$
3
  • \$\begingroup\$ I'll try clearing out some of the i2c_wait and look at your link tomorrow. Thanks. \$\endgroup\$
    – Rdd
    Commented Apr 14, 2016 at 2:22
  • \$\begingroup\$ Update: Even after clearing out most of the I2C_wait(), the same issue keeps happening. Unless the SDA pullup resistor is reinserted, the I2C communication won't start. Not sure what part of the code could be the holdup, since the MSSP module does a lot of the work. \$\endgroup\$
    – Rdd
    Commented Apr 14, 2016 at 19:33
  • \$\begingroup\$ If it isn't an infinite loop in you code, which removing the loop proves and your flashing LED strongly suggests... Then it has to be something about the core I2C module initialisation. I am afraid you have hit the end of my (rather limited) PIC knowledge. \$\endgroup\$
    – lod
    Commented Apr 15, 2016 at 0:47
2
\$\begingroup\$

From the data sheet for your temp sensor:

7.3.2.8 Time-out Function The TMP175 resets the serial interface if either SCL > or SDA is held low for 54 ms (typical) between a START and STOP condition. The TMP175 releases the bus if it is pulled low and waits for a START condition. To avoid activating the time-out function, a communication speed of at least 1 kHz must be maintained for the SCL operating frequency.

Perhaps your sensor is grabbing the bus and not letting go, and your pullup manipulation is sufficient to make it let go.

In any case, it's time to roll up your sleeves and do some serious debugging.

A good way to debug these sorts of things is with a scope with an advanced serial trigger module so you can trigger on a pattern to look for that 54ms delay.

Alternatively, at this point there's little excuse for not knowing where or how your program is hanging. At least toggle a digital bit in main to convince yourself that the while loop isn't running. Once that's done, and you find it isn't, then start turning on bits when you enter a subroutine, and off when you leave it. In short order, you should at least know WHERE you're hanging. After that, turn on a digital bit to serve as a scope trigger for the section of communication you're having trouble with so you can get a very clear picture as to what's going on.

Perhaps even better, use your timers to time-out each communication subroutine, and exit with an error code. Maybe flash the error code out on a DIO.

\$\endgroup\$
5
  • \$\begingroup\$ Thanks for the comment. First, I am using TMP75, and the time out function only applies to the TMP175. Additionally, SCL and SDA are not being held low, once the bus is intialized, both lines go high like they should. Also, the code is NOT hanging. In the while loop in main, RA4 is an output to an LED. Whether or not the bus is working (aka before or after I reinsert SDA pullup), the LED is always toggling. This means the i2c_write command is executing, but it is not showing up on the bus. That is why I am so confused. The code is running fine, but it doesn't work until I reinsert SDA. \$\endgroup\$
    – Rdd
    Commented Apr 15, 2016 at 14:31
  • \$\begingroup\$ After further reading I'll look into the possibility of the TMP75 holding onto the bus, even though I'm fairly sure it isn't. First, that part of the data sheet refers to TMP175, while I have a TMP75. Second, my scope is set to falling edge trigger, and it never detects anything until I reinsert the SDA pullup, which would imply that a start condition is never sent until I reinsert that pullup. Both SDA and SCL remain high until I "fix" it. Also, I am not sure if I have access to a scope with a advanced serial trigger module, I will have to ask the lab technician. \$\endgroup\$
    – Rdd
    Commented Apr 15, 2016 at 14:47
  • \$\begingroup\$ What other peripherals are associated with your I2C pins? Do you need to configure them as Dig inputs before you initialize I2C? There would be NOTHING that should pull your tied-high bus low before I2C starts up. \$\endgroup\$ Commented Apr 15, 2016 at 15:04
  • \$\begingroup\$ There is only the Pic (i2c master) and the TMP75 (i2c slave) on the i2c bus. The only thing I could think of that would pull the i2c lines low before the i2c_init() would be the part where all of the Pic micro pins are configured as outputs first. Once the i2c_init() is called, both i2c lines go high just like they should. \$\endgroup\$
    – Rdd
    Commented Apr 15, 2016 at 18:55
  • \$\begingroup\$ @Rdd try not configuring them as outputs first \$\endgroup\$ Commented Apr 15, 2016 at 23:04
2
\$\begingroup\$

Well this might not be a complete answer, but it does solve the problem. The workaround to this issue was manually toggling the SDA line after I initialized the i2c bus. Here is the code I added after the call to i2c_init().

TRISA2 = 0;
RA2 = 1;
RA2 = 0;
__delay_ms(10);
RA2 = 1;
TRISA2 = 1;

So by manually toggling the SDA line the issue is fixed. I can read and write to the TMP75 temperature sensor just fine now. Thanks for all the help everyone.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.