2

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()
the busybee
  • 10,755
  • 3
  • 13
  • 30
  • 2
    Welcome to StackOverflow! Your question is of much better quality than the questions of the average new contributor, thank you very much! You might like to take the [tour] to learn how this site works, however. -- The LTC2943 presumably holds SCL low until it's ready to go on, i2c slaves are allowed to do this. In contrast, the LTC8491 is instantly ready and acknowledges the address, it's the MSP that idles then. -- For the "ghost" byte we need to see your [mre]. Please add it to your question by [edit]ing it. (Unfortunately I have no experience with MSP430, but others have.) – the busybee Jan 03 '22 at 08:04
  • Hello @thebusybee and thanks for your kind words! I am not convinced that the LTC2943 is the one holding the SCL low in this case for a few reasons: 1. The MSP430 datasheet states that the SCL will be held low after the address byte is sent. See page 460 of the family guide: [link] (https://www.ti.com/lit/ug/slau144j/slau144j.pdf?ts=1641149306337&ref_url=https%253A%252F%252Fwww.google.com%252F). 2. I did a quick test where I had no slave devices connected to the MSP430 and I confirmed that the SCL line is held low by the MSP430 after the address byte is sent. – AamerAirlines Jan 03 '22 at 16:34
  • OK, strange. According to [the Wikipedia page](https://en.wikipedia.org/wiki/I%C2%B2C#Clock_stretching_using_SCL) the controller (master) is commonly not allowed to stretch the clock, however, it can. _(Note: I did not find the mentioned limitation in the standard.)_ -- Then there has to be a difference in your code when you address the different targets (slaves). Please show a [mre]. Your code seems to delay the write into the transmission register in the first case, but not in the second case. -- Anyway, no i2c device can actively pull SCL high. – the busybee Jan 04 '22 at 07:17
  • I can provide a minimal reproducible example when I have a moment (it will require some code refactoring to remove irrelevant code); might take me a while to get to this. In any case, I have a feeling that this maybe related to some timing requirement on the MSP430. Please see a slightly different version of this question that I posted here: https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1066367/msp430fr5969-i2c-scl-line-idling-high-when-it-should-be-idling-low – AamerAirlines Jan 04 '22 at 15:58
  • @thebusybee, I just posted the minimal reproducible code, updated the logic capture diagrams (the text inside the pictures is very useful) and added a few questions to the original post. I hope this updated information is good enough to help address my pain with this i2c driver :) – AamerAirlines Jan 04 '22 at 20:51
  • Good luck! As I said, I have no personal experience with this controller, but now there is a lot of useful information for those who have. – the busybee Jan 05 '22 at 08:31
  • Just guessing here: On some microcontrollers where specialized hardware peripherals share the same pins with GPIO, then the hardware peripheral takes precedence as soon as you enable it and GPIO goes dormant. On other microcontrollers, both are present at once and you may or may not be able to route the GPIO away from the pin. I'm not sure if the latter scenario is what applies here, but it could explain bugs like this. – Lundin Jan 05 '22 at 14:52

0 Answers0