Hello,
We have implemented a polling-based I2C master driver on our project. Under most conditions we get the correct behavior, but the peripheral fails to generate a stop condition after entering an ISR on the last received byte during a read (see capture below). Our function instead blocks waiting for the UCTXSTP flag to clear until we reach our timeout period. If SDA happens to be held low at the end of the final byte, then the peripheral will detect the bus to be in a busy state, causing a permanent lockup on all subsequent transactions. Attaching a debugger in this state confirms that UCTXSTP was set in firmware (see output below).
So far we have discovered two workarounds:
1. After timing out on UCTXSTP, manually toggle the serial lines as GPIO to force a stop condition. This returns the bus to an idle state and prevents a complete lockup.
2. Disable interrupts within the I2C read function. This eliminates the issue completely at the expense of ISR latency.
Our suspicion is we are violating the sequence diagram outlined in the user's guide, but it is not apparent to us. The relevant code from our driver is shown below.
#define MAX_FLAG_WAIT_MS (uint32_t) 100 #define FLAG_WAIT_STEP_US 100 #define FLAG_WAIT_LOOPS (uint32_t)((MAX_FLAG_WAIT_MS*1000) / FLAG_WAIT_STEP_US) static bool _waitForFlag(uint16_t flag) { uint32_t retry_count = 0; while(++retry_count < FLAG_WAIT_LOOPS) { if(GET_INT_FLAGS() & UCNACKIFG) { CLR_INT_FLAGS(UCNACKIFG); // Generate stop condition. HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTP; return false; } else if(GET_INT_FLAGS() & flag) { CLR_INT_FLAGS(flag); return true; } __delay_cycles(DELAY_US_TO_CYCLES(FLAG_WAIT_STEP_US)); } return false; } // Control word wait function // Used for checking completion of start/stop conditions static bool _waitForCwFlag(uint16_t flag) { uint32_t retry_count = 0; while(++retry_count < FLAG_WAIT_LOOPS) { // Wait for flag to clear to indicate operation complete if(!(HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) & flag)) { return true; } __delay_cycles(DELAY_US_TO_CYCLES(FLAG_WAIT_STEP_US)); } return false; } static bool _I2C_MASTER_read(uint8_t i2cAddr, void *pDst, uint16_t nBytes) { ASSERT((pDst != NULL) && (nBytes > 0)); uint8_t *pByte = pDst; // Reinit if peripheral locked in busy state if(EUSCI_B_I2C_isBusBusy(EUSCI_BASE_ADDR)) { _I2C_MASTER_reinit(); } // Clear any previous NACK HWREG16(EUSCI_BASE_ADDR + OFS_UCBxIFG) &= ~UCNACKIFG; // Set I2C address. EUSCI_B_I2C_setSlaveAddress(EUSCI_BASE_ADDR, i2cAddr); // Enable rx mode and generate start condition. HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) &= ~UCTR; HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTT; // Wait for start condition if(!_waitForCwFlag(UCTXSTT)) { return true; } for((void) 0; nBytes > 0; nBytes--) { if(nBytes == 1) { // On last byte, generate stop condition. HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTP; // Wait for stop condition if(!_waitForCwFlag(UCTXSTP)) { return true; } } if(!_waitForFlag(UCRXIFG0)) { // Generate stop condition. HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTP; // Wait for stop condition _waitForCwFlag(UCTXSTP); return true; } // Read a byte. *pByte = HWREG16(EUSCI_BASE_ADDR + OFS_UCBxRXBUF); pByte++; } return false; } I2C_Master_Handler_t *I2C_MASTER_initHandler(I2C_Master_Handler_t *pI2CHdl, uint32_t subsysClk) { ASSERT(pI2CHdl != NULL); EUSCI_B_I2C_initMasterParam i2cParam; // Select I2C peripheral. GPIO_setAsPeripheralModuleFunctionOutputPin(I2C_MASTER_PORT, I2C_MASTER_SDA_PIN, GPIO_PRIMARY_MODULE_FUNCTION); GPIO_setAsPeripheralModuleFunctionOutputPin(I2C_MASTER_PORT, I2C_MASTER_SCL_PIN, GPIO_PRIMARY_MODULE_FUNCTION); // Initialize I2C parameters. i2cParam.selectClockSource = EUSCI_B_I2C_CLOCKSOURCE_SMCLK; i2cParam.i2cClk = subsysClk; I2C_MASTER_subSysClk = subsysClk; i2cParam.dataRate = EUSCI_B_I2C_SET_DATA_RATE_100KBPS; i2cParam.byteCounterThreshold = 0; i2cParam.autoSTOPGeneration = EUSCI_B_I2C_NO_AUTO_STOP; // Configure and enable the I2C bus. EUSCI_B_I2C_initMaster(EUSCI_BASE_ADDR, &i2cParam); EUSCI_B_I2C_setMode(EUSCI_BASE_ADDR, EUSCI_B_I2C_TRANSMIT_MODE); EUSCI_B_I2C_enable(EUSCI_BASE_ADDR); CLR_INT_FLAGS(UCTXIFG0); return pI2CHdl; }