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.

I2C Master occasionally NACKing received data bytes

Other Parts Discussed in Thread: TM4C129XNCZAD, TMP102

Hi,

I have a Tiva TM4C129XNCZAD development board. I am attempting to communicate with a Honeywell HumidIcon HIH6030-021 humidity sensor via I2C. Here is the sensor I2C datasheet: http://sensing.honeywell.com/i2c%20comms%20humidicon%20tn_009061-2-en_final_07jun12.pdf

The sensor requires that I send the 7-bit address, a write bit, wait for an ACK, and then create a stop condition. Based on http://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/t/320889.aspx - it seems this is not possible. To circumvent this, I simply send a dummy data byte.

After waiting at least 40 ms (or so), I need to read two bytes of data. The master must ACK the first byte, then NACK the second byte and create a stop condition. However, my current setup results in the master _sometimes_ not ACKing the first received byte. The second byte is then lost. Shown below are my logic analyzer outputs.

The first (zoomed out) shows the write cycle, a short delay, then the read cycle. Then, there is a longer delay between the read cycle and the next write cycle.

The second image below zooms in on a read cycle - this one behaves as required.

The third image below zooms in on a different read cycle - this is where I have problems.

My code is listed below. Any help would be appreciated. Thanks.

#define SENSINGTIME				40	// time needed (ms) for sensor to sense data
#define SENSINGTASKPERIOD			500	// time between measurement requests in ms
#define SENSORADDRESS				0x27	// I2C address of humidity sensor

int main(void)
{
    uint32_t ui32Clock;
    ui32Clock =	SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
			SYSCTL_OSC_MAIN |
			SYSCTL_USE_PLL |
			SYSCTL_CFG_VCO_480), 120000000);


    // enable I2C8 peripheral
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C8);

    // using I2C8 on pins D2 and D3
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);

    // configure PD2 and PD3 as I2C clock and data lines
    GPIOPinConfigure(GPIO_PD2_I2C8SCL);
    GPIOPinConfigure(GPIO_PD3_I2C8SDA);
    GPIOPinTypeI2CSCL(GPIO_PORTD_BASE, GPIO_PIN_2);
    GPIOPinTypeI2C(GPIO_PORTD_BASE, GPIO_PIN_3);

    // set I2C8 as master; use 400 kbps data rate (last param false for 100 kbps)
    I2CMasterInitExpClk(I2C8_BASE, ui32Clock, true);

    uint32_t ui32SensingTime = SENSINGTIME;
    uint32_t ui32SensingPeriod = SENSINGTASKPERIOD;
    uint8_t ui8SensorAddr = SENSORADDRESS;
    uint8_t ui8DummyData = '0';

    uint32_t ui32ErrStat;
    uint32_t ui32DataOne;
    uint32_t ui32DataTwo;

    while(1)
    {
    	/* Send measurement request */

    	// set master to send to humidity sensor
    	// this makes up the "measurement request" in the sensor datasheet terminology
        I2CMasterSlaveAddrSet(I2C8_BASE, ui8SensorAddr, false);

        // place dummy data byte to send to sensor
        I2CMasterDataPut(I2C8_BASE, ui8DummyData);

        // actually send data
        I2CMasterControl(I2C8_BASE, I2C_MASTER_CMD_SINGLE_SEND);

        // wait until Tx done, then wait for measurement to be done
        while (I2CMasterBusy(I2C8_BASE)) {}

        ui32ErrStat = I2CMasterErr(I2C8_BASE);
        if (I2C_MASTER_ERR_NONE == ui32ErrStat)
        	ui32ErrStat = 0;
        else if (I2C_MASTER_ERR_ADDR_ACK == ui32ErrStat)
        	ui32ErrStat = 1;
        else if (I2C_MASTER_ERR_DATA_ACK == ui32ErrStat)
        	ui32ErrStat = 2;
        else if (I2C_MASTER_ERR_ARB_LOST == ui32ErrStat)
        	ui32ErrStat = 3;

        int i = 0;
        for (i = 0; i <= 1000000; i++) {}

        // get two bytes from sensor
        I2CMasterSlaveAddrSet(I2C8_BASE, ui8SensorAddr, true);

        I2CMasterControl(I2C8_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START);
        while (I2CMasterBusy(I2C8_BASE));
        ui32DataOne = I2CMasterDataGet(I2C8_BASE);

        I2CMasterControl(I2C8_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH);
        while (I2CMasterBusy(I2C8_BASE));
        ui32DataTwo = I2CMasterDataGet(I2C8_BASE);

        // delay
        int j = 0;
        for (j = 0; j <= 2500000; j++) {}
    }
}

  • Hello Badvani,

    When polling for the I2CMasterBusy can you do the following. Basically wait for the Master to get busy and then wait for it to not be busy before doing the next set of transfer.

    while(!I2CMasterBusy(I2C8_BASE));

    while(I2CMasterBusy(I2C8_BASE));

    Regards

    Amit

  • @ Amit,

    Such an unusual construct - in years of I2C work (long prior to even LMI) - we've never seen such.  How does the MCU's I2C Master "become busy" just after it "releases" from its past, "busy" - minus any further I2C Master code execution?

    Very clever if this achieves poster's goal - but the supporting logic is scant (far beyond this reporter)...  (unless the Slave's I2C activity "self-starts" post the Master's release from busy - and that this Slave I2C bus activity causes the Master to once again, "go busy...")  Again - point of interest is the "unusual" (hopefully) clever/proper - back to back calls - to first "Not Busy" followed upon by the immediate call to, "Busy."  (w/no Master MCU I2C code execuiton in between!)  (perhaps this calls for some, slight explanation - we've never seen incidence of such back to back, "flipped busy calls" in years of I2C code review here/elsewhere...)

  • Hello cb1_mobile,

    The Master State Machine takes some clocks to start when the command is given. During this time the Master is not busy. Hence the original while loop will be false and the code execution will skip to the next command causing such a behavior. By putting the two while loop's we make sure that the Master State Machine does not get the second command while the first command has not completed. This is a side-effect of running the core at 120MHz and I2C at a lower frequency.

    Regards

    Amit

  • Once more - a well thought/presented/rapid response.  Thank you, Amit.

    Again - I'm (bit) active here, LMI prior, multiple other ARM tech assemblies - and have never noted such, "back to back, busy "flipped"" constructs. 

    Is this then a requirement unique to your (perhaps others) higher speed (120MHz - your case) core - and that is why such "back to back" is now required?

    Apparently - should this "back to back/flipped busy" be now a standard, I2C usage requirement - such documentation has escaped poster - and this reporter...  Is this documented, anywhere?

    Thanks again.

  • Hello cb1_mobile

    Yes, this requirement has come because of the higher CPU speed and with a larger prefetch buffer, the ability of the CPU to execute linear code as if there is no delay.

    Right now there is no document to say so, and we need to see how if we do this in a more readable format rather than embedding it in a massive data sheet.

    Regards

    Amit

  • @ Amit,

    Again thanks - you really are evolving into forum "treasure!"

    Clearly you are not the "bad guy" here.  (and - just maybe - I escape that label, too.)

    May I ask if this new (back to back, busy) construct is required in, "general, I2C usage" or just in answer to this user's specific (bit out of the ordinary) I2C application?

    Appears that there is significant "insider tech knowledge bank" which sits/percolates @ HQ - and is distributed upon unique/specific demand - only after client user has stumbled or become becalmed.  Is this best? 

    Clearly not for you (nor humble outsider) to decide - but at least the issue has been now "flagged" - and potentially procedures will be added which bring about a faster, perhaps more efficient flow of "needed tech data" to your (ever-faithful) user community. 

  • bump...  "is this new (back to back, flipped busy call) now required in "general I2C usage" w/this particular MCU?  (i.e. beyond poster's very specific application)

    Thank you.

  • Hello cb1_mobile,

    Yes. Especially when there is a lot many changing MCS operations being done back to back, with low I2C Baud and Higher System Clocks.

    Regards

    Amit

  • Hi Amit,

    Thank you. I added the not busy check before receiving data bytes, and that seems to have done the job. Is it also necessary to use the same checks (check not busy, then busy) while the master is transmitting data?

    With that said, I am also worried about a potential situation here. What happens if the master completes transmission/reception before the line

    while(!I2CMasterBusy(I2C8_BASE)); ?

    Would that ever happen? If it does, would the code not be stuck in an infinite loop?

    Thanks.

    Edit: font colours

  • Hello badvani

    The second case will not happen unless there is an interrupt which takes so long for processing that the entire I2C transmission is over.

    Regards

    Amit

  • Hello Amit,

    I am using the TM4C1294. I use the I2C to interface to a temperature sensor in a multi-threaded program (TI-RTOS).

    At first I found that occasionally the I2C read failed because of the problem described above. The read of the first byte failed because the while(I2C_Busy()) was checked before the I2C state machine actually started transmitting.

    So I added the while(!I2C_Busy()) as suggested and it worked much better. But I still wasn't convinced, so I set up a test that reads the Temperature every 5 seconds and left my program running overnight. After ~3 hrs temperature read task was blocked on the new while(!I2C_Busy()) call. It looks like this time the I2C did the complete transmission before my task ever saw it go busy??

    I've attached the code used to read the temperature, but it is part of a much bigger project with several tasks and interrupts occurring. The Temperature task is a lower priority than two of the other tasks.

    Temperature_I2C.c
    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    ////////////////////////////////////////////////////////////////////////
    // Temperature_I2C.h - Temperature Module
    //
    // Temperature_I2C encapsulates the logic to initialize and drive the I2C8
    // bus in order to read the Digital Microtrak 3.1 board temperature from a
    // TMP102 chip.
    //
    //
    ////////////////////////////////////////////////////////////////////////
    #include <stdint.h>
    #include <stdbool.h>
    //-------------------
    // MWARE Header files
    //-------------------
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "inc/hw_sysctl.h"
    #include "inc/hw_i2c.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/i2c.h"
    #include "driverlib/gpio.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/rom_map.h"
    //-------------------------
    // Application Header Files
    //-------------------------
    #include "Temperature_I2C.h"
    #include "Common.h"
    #include "Parameters.h"
    //*****************************************************************************
    // Set the address for slave module. This is a 7-bit address sent in the
    // following format:
    // [A6:A5:A4:A3:A2:A1:A0:RS]
    //
    // A zero in the "RS" position of the first byte means that the master
    // transmits (sends) data to the selected slave, and a one in this position
    // means that the master receives data from the slave.
    //
    // The slave address for the TMP102 is 1001000
    //*****************************************************************************
    #define TEMPERATURE_I2C_BASE I2C8_BASE
    #define TEMPERATURE_SLAVE_ADDRESS 0x48 // 0100 1000
    void Temperature_I2C_Initialize(void)
    {
    // The I2C0 peripheral must be enabled before use.
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C8);
    // Enable and initialize the I2C8 master module. Use the system clock for
    // the module. The last parameter sets the I2C data transfer rate.
    // If false the data rate is set to 100kbps and if true the data rate will
    // be set to 400kbps. We will use a data rate of 400kbps.
    MAP_I2CMasterInitExpClk(TEMPERATURE_I2C_BASE, PRM_GetSystemClockFrequency(), true);
    }
    //*************************************************************************************
    // float32 Temperature_I2C_Read(void)
    //
    // This function uses the I2C to read the current temperature in the register.
    // on a TMP102.
    //
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    .

  • Hello Issac,

    The second use of I2CMasterControl to finish the transaction also needs to have the same set of loop.

    Regards
    Amit
  • The second set also waits on I2CMasterBusBusy() which became busy during the first set, and is still busy now.
    My program is hanging on the first set, how can changing the second set help this problem?
  • Hello Issac

    Bus Busy would mean that someone Master is using the bus. Since it is this master using the bus, it will remain true till you complete the transaction. So instead of using the MasterBusBusy, the check of the MasterBusy is required.

    Regards
    Amit
  • Hello Amit,

    I still don't see any problem with my code. After the second transmission, the transaction is complete, so there is no problem with checking Bus Busy.

    This works 99.9% of the time. But after 1000s of reads it blocks on the

    //Delay until transmission starts
    while( !MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){}

    in the first transmission. How is this possible? And how does this problem relate to the solutions you are suggesting?

    It seems to me that sometimes the bus becomes busy and then becomes not busy before this line of code is even executed.
  • Hello Issac,

    Do you have multiple masters in your system?

    What I want you to change is the second line of the code

    while( MAP_I2CMasterBusBusy(TEMPERATURE_I2C_BASE) || MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){};

    changed to

    //Delay until transmission starts
    while( !MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){}

    //Delay until transmission completes
    while( MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){}

    And then see if the issue occurs.
    Regards
    Amit
  • My processor is the only master in the system. In fact it is a dedicated bus between the uC and the TMP102. Furthermore, the temperature is only read from one thread in the program.

    I will make the change you suggest and see what happens. In the mean time, can you explain why this change might help? Why would changing code that occurs after the lock-up location have any effect? I make sure both the bus and the master are not busy before I start the first half of the transaction, after all.
  • I made the change and now the I2C Read locks up much faster. This time it happened to lock on the
    while( !MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){}
    that you had me add in the second half of the transmission
  • Hello Issac,

    Can you please attach the final code? Also what is the system frequency and the I2C Baud Rate?

    Regards
    Amit
  • Sure. I attached the code. I followed the flow chart (Figure 18-11) in the datasheet.

    5584.Temperature_I2C.c
    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    ////////////////////////////////////////////////////////////////////////
    // Temperature_I2C.h - Temperature Module
    //
    // Temperature_I2C encapsulates the logic to initialize and drive the I2C8
    // bus in order to read the Digital Microtrak 3.1 board temperature from a
    // TMP102 chip.
    //
    //
    ////////////////////////////////////////////////////////////////////////
    #include <stdint.h>
    #include <stdbool.h>
    //-------------------
    // MWARE Header files
    //-------------------
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "inc/hw_sysctl.h"
    #include "inc/hw_i2c.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/i2c.h"
    #include "driverlib/gpio.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/rom_map.h"
    //-------------------------
    // Application Header Files
    //-------------------------
    #include "Temperature_I2C.h"
    #include "Common.h"
    #include "Parameters.h"
    //*****************************************************************************
    // Set the address for slave module. This is a 7-bit address sent in the
    // following format:
    // [A6:A5:A4:A3:A2:A1:A0:RS]
    //
    // A zero in the "RS" position of the first byte means that the master
    // transmits (sends) data to the selected slave, and a one in this position
    // means that the master receives data from the slave.
    //
    // The slave address for the TMP102 is 1001000
    //*****************************************************************************
    #define TEMPERATURE_I2C_BASE I2C8_BASE
    #define TEMPERATURE_SLAVE_ADDRESS 0x48 // 0100 1000
    void Temperature_I2C_Initialize(void)
    {
    // The I2C0 peripheral must be enabled before use.
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C8);
    // Enable and initialize the I2C8 master module. Use the system clock for
    // the module. The last parameter sets the I2C data transfer rate.
    // If false the data rate is set to 100kbps and if true the data rate will
    // be set to 400kbps. We will use a data rate of 400kbps.
    MAP_I2CMasterInitExpClk(TEMPERATURE_I2C_BASE, PRM_GetSystemClockFrequency(), true);
    }
    //*************************************************************************************
    // float32 Temperature_I2C_Read(void)
    //
    // This function uses the I2C to read the current temperature in the register.
    // on a TMP102.
    //
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    My system frequency is 120 Mhz. The I2c baud rate is 400kbps.

  • Hello Issac,

    What is the function doing? On TM4C129 there is no function to get the System Clock Frequency but the variable returned by SysCtlClockFreqSet.

    PRM_GetSystemClockFrequency

    Regards
    Amit
  • I store the return value from SysCtlClockFreqSet in a parameter bank (parameters are protected by a semaphore).

    //**************************************************************************************************
    Code snippet from setup at beginning of main():

    //---------------------------------------------------------------------
    // Setup System Clock and PLL
    //
    // Run from PLL at 120 MHz using external 25Mhz Crystal
    //
    // Die revision 1: ROM version of SysCtlClockFreqSet does not work.
    //---------------------------------------------------------------------
    uint32_t SysClockFreq = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
    SYSCTL_OSC_MAIN | SYSCTL_USE_PLL |
    SYSCTL_CFG_VCO_480), 120000000);

    PRM_SetSystemClockFrequency(SysClockFreq);

    //**************************************************************************************************
    //**************************************************************************************************
    code snippet from PRM module:

    uint32 PRM_GetSystemClockFrequency()
    //=====================================================================
    // PRM_GetSystemClockFrequency - Return system clock frequency in hz
    //
    // INPUTS: NONE
    //
    // OUTPUTS: NONE
    //
    // RETURNS: freq
    //=====================================================================
    {
    uint32 value;

    PRM_GetReadAccess();
    {
    value = sSysClockFreq;
    }
    PRM_ReleaseReadAccess();

    return value;
    }
  • Hello Issac,

    Thanks for the info. Are interrupts enabled in your system: The reason why I ask that the use of the complementary while loop has this hazard that before the while loop is executed if an interrupt gets called, then then I2C Master gets busy and the code gets stuck in the first loop.

    Since I do not have the exact same setup, there are two changes I can suggest

    1. Change of the I2C Data Acquistion using Interrupt rather than polling mechanism, where the CPU does a Control Word write and waits for an interrupt to set a flag, before it can read the data of the previous transfer put the next control word for the final transfer.
    2. Use of SysCtlDelay loop to replace the !MAP_I2CMasterBusy so that the code can wait for some time before the Master Busy is set.

    Regards
    Amit
  • Amit, thank you for the suggestions, I will give one of them a try.
    Interrupts are enabled in the system, and in fact a number of them occur regularly.

    If i go the delay route, do you have any suggestions on how long the delay should be?

    Regards,
    Isaac
  • Hello Issac,

    I will suggest a delay loop of 100. However to get the most minimal value accurately, disable all interrupts and set a GPIO high after sending the control word, making it low when the BUSY bit gets set. That will be the maximum delay, so that an interrupt if occurring will only allow it go past the Delay loop and still be correct for the BUSY bit being set,

    Regards
    Amit