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.
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.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. // //************************************************************************************* float32 Temperature_I2C_Read(void) { uint32_t low_byte; uint32_t high_byte; float32 tempFloat; sint16 tempCnts; //Make sure bus is free while( MAP_I2CMasterBusBusy( TEMPERATURE_I2C_BASE) || MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} //Setup to do a read to chip MAP_I2CMasterSlaveAddrSet(TEMPERATURE_I2C_BASE, TEMPERATURE_SLAVE_ADDRESS, true); // Initiate Read: MAP_I2CMasterControl(TEMPERATURE_I2C_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START); //Delay until transmission starts while( !MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} //Delay until transmission completes while( MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} //Check to see if the device responded: if( MAP_I2CMasterErr(TEMPERATURE_I2C_BASE) != I2C_MASTER_ERR_NONE ) { return ERROR_MEASUREMENT; } //Retrieve the data byte high_byte = MAP_I2CMasterDataGet(TEMPERATURE_I2C_BASE); // Initiate Read: MAP_I2CMasterControl(TEMPERATURE_I2C_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH); //Delay until transmission completes while( MAP_I2CMasterBusBusy(TEMPERATURE_I2C_BASE) || MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){}; //Check to see if the device responded: if( MAP_I2CMasterErr(TEMPERATURE_I2C_BASE) != I2C_MASTER_ERR_NONE ) { return ERROR_MEASUREMENT; } //Retrieve the data byte low_byte = MAP_I2CMasterDataGet(TEMPERATURE_I2C_BASE); //Each read should only return 8 bits low_byte &= 0x000000FF; high_byte &= 0x000000FF; tempCnts = (sint16)( (high_byte << 8) | low_byte ); tempCnts = tempCnts >> 4; //Should sign extend tempFloat = (float32)tempCnts * 0.0625; //Compute temperature return tempFloat; }
Sure. I attached the code. I followed the flow chart (Figure 18-11) in the datasheet.
//////////////////////////////////////////////////////////////////////// // 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. // //************************************************************************************* float32 Temperature_I2C_Read(void) { uint32_t low_byte; uint32_t high_byte; float32 tempFloat; sint16 tempCnts; //Setup to do a read to chip MAP_I2CMasterSlaveAddrSet(TEMPERATURE_I2C_BASE, TEMPERATURE_SLAVE_ADDRESS, true); //Make sure bus is free while( MAP_I2CMasterBusBusy(TEMPERATURE_I2C_BASE) || MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} // Initiate Read: MAP_I2CMasterControl(TEMPERATURE_I2C_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START); //Delay until transmission starts while( !MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} //Delay until transmission completes while( MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} //Check to see if the device responded: if( MAP_I2CMasterErr(TEMPERATURE_I2C_BASE) != I2C_MASTER_ERR_NONE ) { return ERROR_MEASUREMENT; } //Retrieve the data byte high_byte = MAP_I2CMasterDataGet(TEMPERATURE_I2C_BASE); // Initiate Read: MAP_I2CMasterControl(TEMPERATURE_I2C_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH); //Delay until transmission starts while( !MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} //Delay until transmission completes while( MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){} //Delay until transmission completes //while( MAP_I2CMasterBusBusy(TEMPERATURE_I2C_BASE) || MAP_I2CMasterBusy(TEMPERATURE_I2C_BASE) ){}; //Check to see if the device responded: if( MAP_I2CMasterErr(TEMPERATURE_I2C_BASE) != I2C_MASTER_ERR_NONE ) { return ERROR_MEASUREMENT; } //Retrieve the data byte low_byte = MAP_I2CMasterDataGet(TEMPERATURE_I2C_BASE); //Each read should only return 8 bits low_byte &= 0x000000FF; high_byte &= 0x000000FF; tempCnts = (sint16)( (high_byte << 8) | low_byte ); tempCnts = tempCnts >> 4; //Should sign extend tempFloat = (float32)tempCnts * 0.0625; //Compute temperature return tempFloat; }
My system frequency is 120 Mhz. The I2c baud rate is 400kbps.