This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

MSP432P401R: MSP432P401 I2C read issue

Part Number: MSP432P401R

Hi, 

I'm having some trouble reading data over I2C with the MSP432P401R. I'm working off of the i2c_master_rw_repeated_start_master_code nortos driverlib example project. Here is that code with a few modifications:  

#include <ti/devices/msp432p4xx/driverlib/driverlib.h>

/* Standard Defines */
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* Slave Address for I2C Slave */
#define SLAVE_ADDRESS       0x52
#define NUM_OF_REC_BYTES    2

/* Variables */
const uint8_t TXData[] = {0x32};
static uint8_t RXData[NUM_OF_REC_BYTES];
static volatile uint32_t xferIndex;
static volatile bool stopSent;

/* I2C Master Configuration Parameter */
const eUSCI_I2C_MasterConfig i2cConfig =
{
        EUSCI_B_I2C_CLOCKSOURCE_SMCLK,          // SMCLK Clock Source
        3000000,                                // SMCLK = 3MHz
        EUSCI_B_I2C_SET_DATA_RATE_100KBPS,      // Desired I2C Clock of 100khz
        0,                                      // No byte counter threshold
        EUSCI_B_I2C_NO_AUTO_STOP                // No Autostop
};

int main(void)
{
    /* Disabling the Watchdog  */
    MAP_WDT_A_holdTimer();

    /* Select Port 1 for I2C - Set Pin 6, 7 to input Primary Module Function,
     *   (UCB0SIMO/UCB0SDA, UCB0SOMI/UCB0SCL).
     */
    MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,
            GPIO_PIN6 + GPIO_PIN7, GPIO_PRIMARY_MODULE_FUNCTION);
    stopSent = false;
    memset(RXData, 0xFF, NUM_OF_REC_BYTES);

    /* Initializing I2C Master to SMCLK at 100khz with no autostop */
    MAP_I2C_initMaster(EUSCI_B0_BASE, &i2cConfig);

    /* Specify slave address */
    MAP_I2C_setSlaveAddress(EUSCI_B0_BASE, SLAVE_ADDRESS);

    /* Enable I2C Module to start operations */
    MAP_I2C_enableModule(EUSCI_B0_BASE);
    MAP_Interrupt_enableInterrupt(INT_EUSCIB0);


    /* Making sure the last transaction has been completely sent out */
    while (MAP_I2C_masterIsStopSent(EUSCI_B0_BASE));

    MAP_Interrupt_enableSleepOnIsrExit();

    /* Send start and the first byte of the transmit buffer. */
    MAP_I2C_masterSendMultiByteStart(EUSCI_B0_BASE, TXData[0]);

    /* Sent the first byte, now we need to initiate the read */
    xferIndex = 0;
    MAP_I2C_masterReceiveStart(EUSCI_B0_BASE);
    MAP_I2C_enableInterrupt(EUSCI_B0_BASE, EUSCI_B_I2C_RECEIVE_INTERRUPT0);

//        MAP_PCM_gotoLPM0InterruptSafe();
    while (1);


}

/*******************************************************************************
 * eUSCIB0 ISR. The repeated start and transmit/receive operations happen
 * within this ISR.
 *******************************************************************************/
void EUSCIB0_IRQHandler(void)
{
    uint_fast16_t status;

    status = MAP_I2C_getEnabledInterruptStatus(EUSCI_B0_BASE);
    MAP_I2C_clearInterruptFlag(EUSCI_B0_BASE, status);

    /* Receives bytes into the receive buffer. If we have received all bytes,
     * send a STOP condition */
    if (status & EUSCI_B_I2C_RECEIVE_INTERRUPT0)
    {
        if (xferIndex == NUM_OF_REC_BYTES - 2)
        {
            MAP_I2C_disableInterrupt(EUSCI_B0_BASE,
                    EUSCI_B_I2C_RECEIVE_INTERRUPT0);
            MAP_I2C_enableInterrupt(EUSCI_B0_BASE, EUSCI_B_I2C_STOP_INTERRUPT);

            /*
             * Switch order so that stop is being set during reception of last
             * byte read byte so that next byte can be read.
             */
            MAP_I2C_masterReceiveMultiByteStop(EUSCI_B0_BASE);
            RXData[xferIndex++] = MAP_I2C_masterReceiveMultiByteNext(
                    EUSCI_B0_BASE);
        } else
        {
            RXData[xferIndex++] = MAP_I2C_masterReceiveMultiByteNext(
            EUSCI_B0_BASE);
        }
    }
    else if (status & EUSCI_B_I2C_STOP_INTERRUPT)
    {
//        MAP_Interrupt_disableSleepOnIsrExit();
        MAP_I2C_disableInterrupt(EUSCI_B0_BASE,
                                 EUSCI_B_I2C_STOP_INTERRUPT);
    }
}

I'm a little confused as to how the code in the interrupt handler works. Specifically this section:

if (status & EUSCI_B_I2C_RECEIVE_INTERRUPT0)
    {
        if (xferIndex == NUM_OF_REC_BYTES - 2)
        {
            MAP_I2C_disableInterrupt(EUSCI_B0_BASE,
                    EUSCI_B_I2C_RECEIVE_INTERRUPT0);
            MAP_I2C_enableInterrupt(EUSCI_B0_BASE, EUSCI_B_I2C_STOP_INTERRUPT);

            /*
             * Switch order so that stop is being set during reception of last
             * byte read byte so that next byte can be read.
             */
            MAP_I2C_masterReceiveMultiByteStop(EUSCI_B0_BASE);
            RXData[xferIndex++] = MAP_I2C_masterReceiveMultiByteNext(
                    EUSCI_B0_BASE);
        } else
        {
            RXData[xferIndex++] = MAP_I2C_masterReceiveMultiByteNext(
            EUSCI_B0_BASE);
        }
    }

The most confusing part to me is why the stop function is called before the receive next byte function. With this code I get the following activity on my I2C bus: 

Which would be good, except I don't understand why the last byte is NAK. Additionally, with this code, the second index of my rxArray is not updated with the received value. 

So my attempt to fix this was by editing the interrupt handler :

if (status & EUSCI_B_I2C_RECEIVE_INTERRUPT0)
    {
        RXData[xferIndex++] = MAP_I2C_masterReceiveMultiByteNext(EUSCI_B0_BASE);
        if (xferIndex == NUM_OF_REC_BYTES )
        {
            MAP_I2C_masterReceiveMultiByteStop(EUSCI_B0_BASE);
            MAP_I2C_disableInterrupt(EUSCI_B0_BASE, EUSCI_B_I2C_RECEIVE_INTERRUPT0);
            MAP_I2C_enableInterrupt(EUSCI_B0_BASE, EUSCI_B_I2C_STOP_INTERRUPT);
    }
}

This works more like what I would expect, except this reads an additional byte which is also NAK. 

So clearly, I'm misunderstanding something about how I2C works on the MSP432. I would appreciate it very much if someone could help me figure out what that is.

Thanks,

Adam

  • I think I have some answers (but not all):

    1) NAK-ing the final received byte before the Stop is fairly standard procedure, though many/most Slave devices don't care too much. Requesting the Stop causes the next (completed) receive byte to be NAK-ed. [Ref TRM (SLAU356H) Fig 26-13 (top right)]

    2) Master Receive mode provides no flow control, so by the time you get an RXIFG the next receive byte is already in progress. The program requests the Stop after the next-to-final byte is complete, and (we hope) before the final byte is complete, so the I2C unit doesn't start on the (final+1)th byte, which could disturb the Slave.

    That said, I'm not quite sure how the final byte is supposed to get into your program, since (as you point out) it disables the RX interrupt on the next-to-final byte. 

    One possibility is that it uses the final RXIFG that is presented with the STOPIFG. That is, when the Stop is complete, the ISR is entered with both RXIFG and STOPIFG set, and the final byte is received then. (This sounds to me a bit like "works by accident".)

    The proper fix is probably a separate case in the RXIFG0 block for "else if (xferIndex == NUM_OF_REC_BYTES - 1)" which disables the RXIFG0, but I don't have the materials to try it. Alternatively you might do a ReceiveMultiByteNext in the STOPIFG block.

    Personally, I use the auto-stop (UCASTP) mechanism for Master Receiver mode whenever I can, just to avoid all these races.

  • Thanks for the reply! That helped clear some things up. 

    So is the conclusion that the driverlib examples have a bug that doesn't allow the last byte to be written to the rxArray? I added a workaround that writes to the last byte of the rxArray in the stop event in the interrupt handler. This seems to work.

    However, I still have an issue with the NAK being sent on the last byte. I think the slave I'm using requires an ACK on the last byte, without it, it goes into a bad state because I can't write to or read from it without a reset following the initial read. (When an ack is present it works) So is there anyway to send an ack on the last byte?

    Edit: the NAK is not the issue. After some debugging I found if I made the NUM_REC_BYTES = 3, it would read 3 bytes once, then from then on read 2 bytes... it's supposed to be reading the same thing over and over, is it not? 


    This is the code I'm working with now.. almost exactly the driverlib example

    #include <ti/devices/msp432p4xx/driverlib/driverlib.h>
    
    /* Standard Defines */
    #include <stdint.h>
    #include <stdbool.h>
    #include <string.h>
    
    /* Slave Address for I2C Slave */
    #define SLAVE_ADDRESS       0x52
    #define NUM_OF_REC_BYTES    3
    
    
    int cnt = 0;
    /* Variables */
    const uint8_t TXData[] = {0x32};
    static uint8_t RXData[NUM_OF_REC_BYTES];
    static volatile uint32_t xferIndex;
    static volatile bool stopSent;
    
    /* I2C Master Configuration Parameter */
    const eUSCI_I2C_MasterConfig i2cConfig =
    {
            EUSCI_B_I2C_CLOCKSOURCE_SMCLK,          // SMCLK Clock Source
            3000000,                                // SMCLK = 3MHz
            EUSCI_B_I2C_SET_DATA_RATE_100KBPS,      // Desired I2C Clock of 100khz
            0,                                      // No byte counter threshold
            EUSCI_B_I2C_NO_AUTO_STOP                // No Autostop
    };
    
    int main(void)
    {
        /* Disabling the Watchdog  */
        MAP_WDT_A_holdTimer();
    
        /* Select Port 1 for I2C - Set Pin 6, 7 to input Primary Module Function,
         *   (UCB0SIMO/UCB0SDA, UCB0SOMI/UCB0SCL).
         */
        MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,
                GPIO_PIN6 + GPIO_PIN7, GPIO_PRIMARY_MODULE_FUNCTION);
        stopSent = false;
        memset(RXData, 0xfe, NUM_OF_REC_BYTES);
    
        /* Initializing I2C Master to SMCLK at 100khz with no autostop */
        MAP_I2C_initMaster(EUSCI_B0_BASE, &i2cConfig);
    
        /* Specify slave address */
        MAP_I2C_setSlaveAddress(EUSCI_B0_BASE, SLAVE_ADDRESS);
    
        /* Enable I2C Module to start operations */
        MAP_I2C_enableModule(EUSCI_B0_BASE);
        MAP_Interrupt_enableInterrupt(INT_EUSCIB0);
    
        while (1)
        {
            /* Making sure the last transaction has been completely sent out */
            while (MAP_I2C_masterIsStopSent(EUSCI_B0_BASE));
    
            MAP_Interrupt_enableSleepOnIsrExit();
    
            /* Send start and the first byte of the transmit buffer. */
            MAP_I2C_masterSendMultiByteStart(EUSCI_B0_BASE, TXData[0]);
    
            /* Sent the first byte, now we need to initiate the read */
            xferIndex = 0;
            MAP_I2C_masterReceiveStart(EUSCI_B0_BASE);
            MAP_I2C_enableInterrupt(EUSCI_B0_BASE, EUSCI_B_I2C_RECEIVE_INTERRUPT0);
    
            MAP_PCM_gotoLPM0InterruptSafe();
    
        }
    }
    
    /*******************************************************************************
     * eUSCIB0 ISR. The repeated start and transmit/receive operations happen
     * within this ISR.
     *******************************************************************************/
    void EUSCIB0_IRQHandler(void)
    {
        uint_fast16_t status;
    
        status = MAP_I2C_getEnabledInterruptStatus(EUSCI_B0_BASE);
        MAP_I2C_clearInterruptFlag(EUSCI_B0_BASE, status);
    
        /* Receives bytes into the receive buffer. If we have received all bytes,
         * send a STOP condition */
        if (status & EUSCI_B_I2C_RECEIVE_INTERRUPT0)
        {
            cnt++;
            if (xferIndex == NUM_OF_REC_BYTES - 2)
            {
                MAP_I2C_disableInterrupt(EUSCI_B0_BASE,
                        EUSCI_B_I2C_RECEIVE_INTERRUPT0);
                MAP_I2C_enableInterrupt(EUSCI_B0_BASE, EUSCI_B_I2C_STOP_INTERRUPT);
    
                /*
                 * Switch order so that stop is being set during reception of last
                 * byte read byte so that next byte can be read.
                 */
                MAP_I2C_masterReceiveMultiByteStop(EUSCI_B0_BASE);
                RXData[xferIndex++] = MAP_I2C_masterReceiveMultiByteNext(
                        EUSCI_B0_BASE);
            } else
            {
                RXData[xferIndex++] = MAP_I2C_masterReceiveMultiByteNext(
                EUSCI_B0_BASE);
            }
        }
        else if (status & EUSCI_B_I2C_STOP_INTERRUPT)
        {
            MAP_Interrupt_disableSleepOnIsrExit();
            MAP_I2C_disableInterrupt(EUSCI_B0_BASE,
                                     EUSCI_B_I2C_STOP_INTERRUPT);
        }
    }

  • Hm. I haven't seen a Slave that required an ACK on the last byte (I've seen requires-NAK and don't-care), but there's enough (ahem) "variety" in I2C devices that I suppose that's possible. The more common problem is reading one-too-many and throwing off the Slave's internal register pointer. I don't' know how to make it not do the NACK. [Ref TRM Sec 26.3.4.2.2, paragraph 5]

    I'm also slightly disturbed that TRM Fig 26-13 (top right) doesn't explicitly say that RXIFG is presented on that final (NACK-ed) byte. The driverlib code (I'm looking at I2C_masterReceiveMultiByteFinish) does seem to expect it, though. .

    I don't know what to tell you about the Example code. It's always possible that it "worked by accident" in the lab due to the particulars of the Slave used (I think that's usually another MSP432 using a different Example). I don't see it clearing RECEIVE_INTERRUPT0 before enabling it; it's possible that some stale status from the previous round is throwing the count off.

    Are you using the debugger? On most of these devices the I2C clock continues to run while you're at a breakpoint, which can lead to some illusions.

    [Edit: Fixed wording slightly.]

  • Hi Adam,

    could you please give us an update from your side? Is there still something we can do for you on this?

    Best regards

    Peter

  • I was using the debugger, but I made sure to not have any breakpoints in any part of the code that could mess it up. 

    Eventually, I just added the last received value to the rx array once the stop condition was sent. 

    Thanks for your help

  • I've resolved the issue by adding a the last value to the rx array in the stop interrupt handler. 

    But I'd still be interested to know why the last byte wasn't being added to the rx array in the driverlib example code using a different slave device. 

    Here is the code I used that seems to work well (ignore the instance, it's just a struct holding the i2c instance and transfer data) 

    uint_fast16_t status;
    status = MAP_I2C_getEnabledInterruptStatus(instance->baseAddr);
    MAP_I2C_clearInterruptFlag(instance->baseAddr, status);
    
    uint16_t   length = instance->transferData->length;
    uint16_t * xferIndex = &(instance->xferIndex);
    uint8_t  * rxArray = instance->transferData->rxBuf;
    
    /* If transmit interrupt 0 received, send another start to initiate receive */
    if ( status & EUSCI_B_I2C_TRANSMIT_INTERRUPT0 )
    {
      MAP_I2C_disableInterrupt(instance->baseAddr, EUSCI_B_I2C_TRANSMIT_INTERRUPT0);
      MAP_I2C_enableInterrupt(instance->baseAddr, EUSCI_B_I2C_RECEIVE_INTERRUPT0);
      MAP_I2C_masterReceiveStart(instance->baseAddr);
    }
    
    /* Receives bytes into the receive buffer. If we have received all bytes,
     * send a STOP condition */
    if ( status & EUSCI_B_I2C_RECEIVE_INTERRUPT0 )
    {
    
      if ( (*xferIndex) ==  length - 2 )
      {
        MAP_I2C_disableInterrupt(instance->baseAddr, EUSCI_B_I2C_RECEIVE_INTERRUPT0);
        MAP_I2C_enableInterrupt(instance->baseAddr, EUSCI_B_I2C_STOP_INTERRUPT);
    
        /*
         * Switch order so that stop is being set during reception of last
         * byte read byte so that next byte can be read.
         */
        MAP_I2C_masterReceiveMultiByteStop(instance->baseAddr);
        rxArray[(*xferIndex)++] = MAP_I2C_masterReceiveMultiByteNext(instance->baseAddr);
      }
      else
      {
        rxArray[(*xferIndex)++] = MAP_I2C_masterReceiveMultiByteNext(instance->baseAddr);
      }
    
    }
    else if ( status & EUSCI_B_I2C_STOP_INTERRUPT )
    {
      rxArray[(*xferIndex)++] = MAP_I2C_masterReceiveMultiByteNext(instance->baseAddr);
      MAP_I2C_disableInterrupt(instance->baseAddr, EUSCI_B_I2C_STOP_INTERRUPT);
    
      call_instance_xfer_done_cb(instance, FIO_ERROR_NONE);
    }
    

  • Hi Adam,

    many thanks for the update. I am glad to here you have been able to get it functional.

    As far as I understood, the communication is related to a non-MSP432 slave, correct? Did you have a chance to test it with 2 MSP432s? The reason why I am asking, if I would like to dig into it deeper, it would be necessary to reproduce the problem. This might be possible with two MSP432s, one as master and the other one as slave. Or is the slave device by any chance a TI device? In this case it might be also possible for me to reproduce the problem. Please let me know. Many thanks in advance.

    Best regards

    Peter

  • I don't have two MSP432s but I was using the TI DRV10987 chip.

  • Hi Adam,

    many thanks for the information. I've checked on the options to look into reproducing, what you've experienced. Doing so I came across quite few things, which might be of some help for as well.

    1. There is an evaluation module for the DRV10987, which you're probably familiar with, or even using on your side.

    2. There is a reference design, which can be used to drive various DRV parts from TI, the which you can find on the home page DRV10987. 

    3. The interesting part of the  is, that it's using an MSP430G2553 for the I2C communication with the DRV devices. Now of course the MSP430 is not the MSP432, but the USCI interface/module of the MSP430G2553 is very similar to the eUSCI of the MSP432P401. The design files of this design contain also the source files for the MSP430G2553, including all the required I2C functions to drive all the addressed DRV devices, including DRV10987. www.ti.com/.../export-control

    The code is no based on the DriverLib, but is in C. I think it would be worth looking into the code, and transfer it to MSP430P401, as it is certainly with less overhead than the DriverLib. Also it might give you a better idea on the concept, how to drive the I2C communication with the DRV parts.

    Best regards

    Peter

    Peter

**Attention** This is a public forum