MSP430 I2C SCL line idling high (when it should be idling low)

439 views Asked by At

I have the MSP430 configured as an i2c master device and it is talking with two slave devices: a battery fuel gauge (LTC2943) and a battery charger (LT8491 evaluation board). I noticed that when talking with the battery charger, a duplicate byte gets sent over the i2c bus (this is bad); let's call it a 'ghost' byte. I only see this ghost/duplicate byte when talking with the battery charger but never with the fuel gauge. Both of those slave devices are on the same bus and I'm performing the same operation on both of them (reading 2-bytes); see code below. After several days of frustration, I noticed a peculiar difference between the two devices…

I noticed that when I talk to the fuel gauge (i2c address 0x64), the i2c clock line (SCL) idles low. But when I talk to the battery charger (i2c address 0x10) the SCL line idles high. Notice the SCL line behavior after the address byte in the provided pictures (links below). Per the MSP430 datasheet, it is my understanding that the SCL line should be idling LOW after the slave the address is pushed on the line (see page 833 of the MSP430 family guide here: www.ti.com/.../slau367p.pdf). There's a comment that says: "Bus stalled (SCL held low) until data available".

A few questions related to this:

  1. Why does the SCL line idle differently on those two devices (see picture links below) even though they are on the same i2c bus and we are performing the same 2-byte read operation and using the same i2c driver code?

  2. The MSP430 family guide (page 833) has that comment that says: "Bus stalled (SCL held low) until data available". Whose responsibility it is to hold the SCL low in this case? Is it the master (MSP430) or the slave device?

  3. In the same page of the family guide (page 833), there are cases where it appears that a certain action needs to be performed DURING the transmission of a data byte, is this a hard requirement? For example, let's consider the case where we want to write one byte to the slave device, then follow it with a repeated start and then read one byte. The timing diagram on page 833 implies that we need to set UCTR=0 and UCTXSTT=1 DURING the transmission of the current data byte. What would happen if we set these registers AFTER the data byte has been transmitted?

  4. The 'ghost' byte that I am seeing when talking with the battery charger is basically the MSP430 pushing the contents of the TXBUF out on the bus again! This occurs even after we have already configured the the MSP430 to be in receive mode. Please notice how/when/where I am toggling P4.6 in code as well as in the logic captures (see pictures). Why does this ghost byte happen?! Why does the MSP430 push it's TXBUF out on the line again?

Zoomed out picture of i2c communication: Zoomed out picture of i2c communication

Zoomed in picture with fuel gauge (good communication): Zoomed in picture with fuel gauge (good communication)

Zoomed in picture with battery charger (bad communication): Zoomed in picture with battery charger (bad communication)

My code:

#include <string.h>
#include "driverlib.h"


#define BATTERY_CHARGER_I2C_ADDR    ( 0x10 )
#define FUEL_GAUGE_I2C_ADDR         ( 0x64 )

#define GENERAL_I2C_TIMEOUT_MS      ( 3    ) //When waiting for an i2c flag/condition, timeout after 3 milliseconds so we don't get stuck waiting forever


//local (static) functions
static void init_hardware( void );
static bool hwm_i2c_master_receive_data( uint8_t slave_addr, uint8_t* read_ptr, uint8_t size_bytes );
static bool hwm_i2c_master_send_data( uint8_t slave_addr, uint8_t* write_ptr, uint8_t size_bytes, bool issue_stop );


void main (void)
{

    uint8_t     write_data;
    uint8_t     read_data[2];

    //Initialize HWM manager
    init_hardware();

    while(1)
    {
        __delay_cycles( 1000000 ); //delay for about 1 second (1 cycle ~ 1us)


        //read 2 bytes from fuel gauge
        write_data = 0x08;//address of voltage register
        hwm_i2c_master_send_data( FUEL_GAUGE_I2C_ADDR, &write_data, 1, false );
        hwm_i2c_master_receive_data(FUEL_GAUGE_I2C_ADDR, read_data, 2);


        //read 2 bytes from battery charger
        write_data = 0x28;//address of Rsense1 configuration register
        hwm_i2c_master_send_data( BATTERY_CHARGER_I2C_ADDR, &write_data, 1, false );
        hwm_i2c_master_receive_data( BATTERY_CHARGER_I2C_ADDR, read_data, 2);
    }
} //main()



static void init_hardware( void )
{
    Timer_A_initContinuousModeParam     timerA_cont_param;

    //Disable internal watchdog timer
    WDTCTL = WDTPW | WDTHOLD;

    //Set VLO to drive ACLK with a divider of 1 (i.e. run ACLK at 10KHz)
    CS_initClockSignal( CS_ACLK, CS_VLOCLK_SELECT, CS_CLOCK_DIVIDER_1 );

    //This block of code basically initializes TimerA1 to run continuously at 1KHz
    memset( &timerA_cont_param, 0, sizeof(timerA_cont_param) );
    timerA_cont_param.clockSource                   = TIMER_A_CLOCKSOURCE_ACLK;         //ACLK to drive TimerA1
    timerA_cont_param.clockSourceDivider            = TIMER_A_CLOCKSOURCE_DIVIDER_10;   //Divide ACLK by 10 in order to get 1KHz
    timerA_cont_param.timerInterruptEnable_TAIE     = TIMER_A_TAIE_INTERRUPT_DISABLE;   //Disable TimerA1 overflow interrupt
    timerA_cont_param.timerClear                    = TIMER_A_DO_CLEAR;                 //Clear/reset TimerA1 counter
    timerA_cont_param.startTimer                    = true;                             //Start TimerA1 counter
    Timer_A_initContinuousMode( TIMER_A1_BASE, &timerA_cont_param );


    //Configure P4.6 as an output pin for debugging (timing purposes)
    GPIO_setAsOutputPin( GPIO_PORT_P4, GPIO_PIN6 );
    GPIO_setOutputLowOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    //This block initializes the i2c peripheral
    //Configure pins P1.6 (SDA) and P1.7 (SCL) for I2C (secondary module functionality)
    GPIO_setAsPeripheralModuleFunctionInputPin( GPIO_PORT_P1, ( GPIO_PIN6 | GPIO_PIN7 ), GPIO_SECONDARY_MODULE_FUNCTION );


    PMM_unlockLPM5(); //Clear the LOCKLPM5 bit so the GPIO and i2c configuration takes effect

    //Configure the I2C bus
    EUSCI_B_I2C_initMasterParam i2c_master_init_param = {0};
    i2c_master_init_param.selectClockSource      = EUSCI_B_I2C_CLOCKSOURCE_SMCLK;       //use SMCLK clock signal
    i2c_master_init_param.i2cClk                 = CS_getSMCLK();                       //Give SMCLK freq in Hz
    i2c_master_init_param.dataRate               = EUSCI_B_I2C_SET_DATA_RATE_100KBPS;   //100KBps datarate
    i2c_master_init_param.byteCounterThreshold   = 0;                                   //Don't care because 'no auto stop'
    i2c_master_init_param.autoSTOPGeneration     = EUSCI_B_I2C_NO_AUTO_STOP;            //We will handle the stop bit manually
    EUSCI_B_I2C_initMaster( EUSCI_B0_BASE, &i2c_master_init_param );

    EUSCI_B_I2C_enable(EUSCI_B0_BASE);  //Enable the I2C bus (i.e. pull it out of reset state)

}


static bool hwm_i2c_master_receive_data( uint8_t slave_addr, uint8_t* read_ptr, uint8_t size_bytes )
{
    bool proceed;
    proceed = true;

    //Basic sanity checks on address and size
    if( NULL == read_ptr || 0 == size_bytes )
    {
        return false;
    }

    //Set P4.6 high for debugging (see scope captures)
    GPIO_setOutputHighOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    UCB0I2CSA   =  slave_addr;  //Set slave address
    UCB0CTLW0   &= ~UCTR;       //Set I2C bus in receiver (read) mode
    UCB0IFG     &= ~UCNACKIFG;  //Clear NACK interrupt flag (fresh start)
    UCB0CTLW0   |= UCTXSTT;     //Issue START condition

    //Wait for START condition to complete
    TA1R = 0;
    while( proceed && (UCB0CTLW0 & UCTXSTT) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (!(UCB0CTLW0 & UCTXSTT));

    //If size is one byte, request STOP condition now! This is time critical (but not sure why :/)
    if( proceed && (1 == size_bytes) )
    {
        UCB0CTLW0 |= UCTXSTP;
    }

    //Check that we received ACK from slave
    proceed = proceed && (!(UCB0IFG & UCNACKIFG));

    //Loop through and pull the requested number for bytes from the RX buffer
    while( proceed && (size_bytes > 0) )
    {
        //Wait for RX buffer to be ready/full
        TA1R = 0;
        while( (!(UCB0IFG & UCRXIFG0)) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
        proceed = proceed && (UCB0IFG & UCRXIFG0);

        if( proceed )
        {
            *read_ptr = UCB0RXBUF;  //Pull byte out of RX buffer
            read_ptr++;             //Increment pointer
            size_bytes--;           //Decrement number of bytes left to read

            //If there's only one byte left to read, request STOP condition. This is time critical (again, not sure why)
            if( 1 == size_bytes )
            {
                UCB0CTLW0 |= UCTXSTP;
            }
        }
    }

    //Wait for the STOP condition to complete
    TA1R = 0;
    while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (!(UCB0CTLW0 & UCTXSTP));

    if( !proceed )
    {
        //If we got here, it means something went bad (e.g. timed out or slave NACK'ed),
        //let's request STOP to terminate communication
        UCB0CTLW0 |= UCTXSTP;

        //wait for stop to complete
        TA1R = 0;
        while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    }

    //Clear P4.6
    GPIO_setOutputLowOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    return proceed;
} //hwm_i2c_master_receive_data()


static bool hwm_i2c_master_send_data( uint8_t slave_addr, uint8_t* write_ptr, uint8_t size_bytes, bool issue_stop )
{
    bool proceed;
    proceed = true;

    //Basic sanity checks on address and size
    if( NULL == write_ptr || 0 == size_bytes )
    {
        return false;
    }

    UCB0I2CSA   =  slave_addr;  //Set slave address
    UCB0CTLW0   |= UCTR;        //Set I2C bus in transmit mode
    UCB0IFG     &= ~UCNACKIFG;  //Clear NACK interrupt flag (fresh start)
    UCB0CTLW0   |= UCTXSTT;     //Issue START condition

    //Wait for START condition to complete
    TA1R = 0;
    while( proceed && (UCB0CTLW0 & UCTXSTT) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (!(UCB0CTLW0 & UCTXSTT));


    //At this point, we have just pushed the slave address over the i2c line.
    //According to the MSP430 datasheet, the MSP430 would/should hold the
    //SCL line low until we put something in the UCB0TXBUF.
    //In other words, during this delay, the SCL should be held low. This is
    //true when talking with the fuel gauge (LTC2943) but is NOT the case when
    //talking with the battery charger! Why is that?!

    __delay_cycles( 100 ); //delay of ~100us, please notice the SCL line (pictures) during this delay


    //Wait for tx buffer to be ready/empty
    TA1R = 0;
    while( proceed && (!(UCB0IFG & UCTXIFG0)) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    proceed = proceed && (UCB0IFG & UCTXIFG0);

    //Check that we received ACK from slave
    proceed = proceed && (!(UCB0IFG & UCNACKIFG));

    //Loop through and send the data
    while( proceed && (size_bytes > 0) )
    {

        //Set P4.6 high for debugging
        GPIO_setOutputHighOnPin( GPIO_PORT_P4, GPIO_PIN6 );

        //Place byte in tx buffer for transmission
        UCB0TXBUF = *write_ptr;

        //Wait for byte to flush out of tx buffer
        TA1R = 0;
        while( proceed && (!(UCB0IFG & UCTXIFG0)) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
        proceed = proceed && (UCB0IFG & UCTXIFG0);

        //Check the ACK after every byte to make sure it went ok
        proceed = proceed && (!(UCB0IFG & UCNACKIFG));

        //Increment write pointer and decrement remaining size
        write_ptr++;
        size_bytes--;
    }

    //If caller requested a STOP condition to be sent
    if( proceed && issue_stop )
    {
        //Issue STOP condition
        UCB0CTLW0 |= UCTXSTP;

        //Wait for STOP condition to go through
        TA1R = 0;
        while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
        proceed = proceed && (!(UCB0CTLW0 & UCTXSTP));
    }

    if( !proceed )
    {
        //If we got here, it means something went bad (e.g. timed out or slave NACK'ed),
        //let's request STOP to terminate communication
        UCB0CTLW0 |= UCTXSTP;

        //wait for stop to complete
        TA1R = 0;
        while( (UCB0CTLW0 & UCTXSTP) && (TA1R < GENERAL_I2C_TIMEOUT_MS) );
    }

    GPIO_setOutputLowOnPin( GPIO_PORT_P4, GPIO_PIN6 );

    return proceed;
} //hwm_i2c_master_send_data()
0

There are 0 answers