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.

random failure when read/write EEPROM via I2C

Part Number: MSP430F6721


Tool/software:

Hi,

I have ported code in SLAA208 to work with MSP430F67XX MCU, code attached below. Everything seems to work just fine when tested. However, when I2C EEPROM code is combined into other code, I start to observe random failures.

/******************************************************************************/
/*  Communication with an EEPROM (e.g. 2465) via I2C bus                      */
/*  The I2C module of the MSP430F2274 is used to communicate with the EEPROM. */
/*  The "Byte Write", "Page Write", "Current Address Read", "Random Read",    */
/*  "Sequential Read" and "Acknowledge Polling" commands or the EEPROM are    */
/*  realized.                                                                 */
/*                                                                            */
/*  developed with IAR Embedded Workbench V5.20.1                             */
/*                                                                            */
/*  Texas Instruments                                                         */
/*  William Goh                                                               */
/*  March 2011                                                                */
/*----------------------------------------------------------------------------*/
/*  updates                                                                   */
/*    Jan 2005:                                                               */
/*        - updated initialization sequence                                   */
/*    March 2009:                                                             */
/*        - updated code for 2xx USCI module                                  */
/*        - added Page Write and Sequential Read functions                    */
/*    March 2011:                                                             */
/*        - Fixed Random and Sequential Reads to Restart                      */
/*        - Added Page Write to support greater than 256 bytes                */
/*******************************************************************************
;
; THIS PROGRAM IS PROVIDED "AS IS". TI MAKES NO WARRANTIES OR
; REPRESENTATIONS, EITHER EXPRESS, IMPLIED OR STATUTORY,
; INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
; FOR A PARTICULAR PURPOSE, LACK OF VIRUSES, ACCURACY OR
; COMPLETENESS OF RESPONSES, RESULTS AND LACK OF NEGLIGENCE.
; TI DISCLAIMS ANY WARRANTY OF TITLE, QUIET ENJOYMENT, QUIET
; POSSESSION, AND NON-INFRINGEMENT OF ANY THIRD PARTY
; INTELLECTUAL PROPERTY RIGHTS WITH REGARD TO THE PROGRAM OR
; YOUR USE OF THE PROGRAM.
;
; IN NO EVENT SHALL TI BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
; CONSEQUENTIAL OR INDIRECT DAMAGES, HOWEVER CAUSED, ON ANY
; THEORY OF LIABILITY AND WHETHER OR NOT TI HAS BEEN ADVISED
; OF THE POSSIBILITY OF SUCH DAMAGES, ARISING IN ANY WAY OUT
; OF THIS AGREEMENT, THE PROGRAM, OR YOUR USE OF THE PROGRAM.
; EXCLUDED DAMAGES INCLUDE, BUT ARE NOT LIMITED TO, COST OF
; REMOVAL OR REINSTALLATION, COMPUTER TIME, LABOR COSTS, LOSS
; OF GOODWILL, LOSS OF PROFITS, LOSS OF SAVINGS, OR LOSS OF
; USE OR INTERRUPTION OF BUSINESS. IN NO EVENT WILL TI'S
; AGGREGATE LIABILITY UNDER THIS AGREEMENT OR ARISING OUT OF
; YOUR USE OF THE PROGRAM EXCEED FIVE HUNDRED DOLLARS
; (U.S.$500).
;
; Unless otherwise stated, the Program written and copyrighted
; by Texas Instruments is distributed as "freeware".  You may,
; only under TI's copyright in the Program, use and modify the
; Program without any charge or restriction.  You may
; distribute to third parties, provided that you transfer a
; copy of this license to the third party and the third party
; agrees to these terms by its first use of the Program. You
; must reproduce the copyright notice and any other legend of
; ownership on each copy or partial copy, of the Program.
;
; You acknowledge and agree that the Program contains
; copyrighted material, trade secrets and other TI proprietary
; information and is protected by copyright laws,
; international copyright treaties, and trade secret laws, as
; well as other intellectual property laws.  To protect TI's
; rights in the Program, you agree not to decompile, reverse
; engineer, disassemble or otherwise translate any object code
; versions of the Program to a human-readable form.  You agree
; that in no event will you alter, remove or destroy any
; copyright notice included in the Program.  TI reserves all
; rights not specifically granted under this license. Except
; as specifically provided herein, nothing in this agreement
; shall be construed as conferring by implication, estoppel,
; or otherwise, upon you, any license or other right under any
; TI patents, copyrights or trade secrets.
;
; You may not use the Program in non-TI devices.
;
******************************************************************************/
#include "msp430.h"
#include "i2ceeprom.h"

volatile int16_t PtrTransmit;
uint8_t I2CBufferArray[32];
volatile uint8_t I2CBuffer;

/*----------------------------------------------------------------------------*/
// Description:
//   Initialization of the I2C Module
/*----------------------------------------------------------------------------*/
void initI2C()
{
  EEPROM_PORT_SEL |= EEPROM_SDA_PIN | EEPROM_SCL_PIN;        // Assign I2C pins to USCI_B0

  // Setup eUSCI_B0
  UCB0CTLW0 |= UCSWRST;                             // Enable SW reset
  UCB0CTLW0 |= UCMST | UCMODE_3 | UCSSEL__SMCLK | UCSYNC;         // I2C Master, use ACCLK

  UCB0BRW = SCL_CLOCK_DIV;                  // fSCL = SMCLK/10 = ~400kHz
  UCB0I2CSA = EEPROM_ADDR;                  // Slave Address
  UCB0CTLW0 &= ~UCSWRST;                    // Clear SW reset, resume operation

  if (UCB0STATW & UCBBUSY)                   // test if bus to be free
  {                                         // otherwise a manual Clock on is generated
    EEPROM_PORT_SEL &= ~EEPROM_SCL_PIN;               // Select Port function for SCL
    EEPROM_PORT_DIR |= EEPROM_SCL_PIN;                // drive SCL low
    EEPROM_PORT_OUT &= ~EEPROM_SCL_PIN;
    EEPROM_PORT_SEL |= EEPROM_SDA_PIN | EEPROM_SCL_PIN;      // select module function for the used I2C pins
  };
}

void resetI2C()
{
	UCB0IE &= ~(UCTXIE0 | UCRXIE0);
  UCB0CTLW0 |= UCSWRST;                      // Reset I2C to stop its operation
  EEPROM_PORT_SEL &= ~(EEPROM_SDA_PIN + EEPROM_SCL_PIN);      // Assign I2C pins to GPIO
	EEPROM_PORT_DIR &= ~(EEPROM_SDA_PIN + EEPROM_SCL_PIN); 		 // Set I2C pins to input
	EEPROM_PORT_REN &= ~(EEPROM_SDA_PIN + EEPROM_SCL_PIN);		   // No pull-up or pulldown resistor
}

/*---------------------------------------------------------------------------*/
// Description:
//   Initialization of the I2C Module for Write operation.
/*---------------------------------------------------------------------------*/
void I2CWriteInit(void)
{
	UCB0CTLW0 |= UCTR;                         // UCTR=1 => Transmit Mode (R/W bit = 0)
	UCB0IFG  &= ~UCTXIFG0;
	UCB0IE &= ~UCRXIE0;                         // disable Receive ready interrupt
	UCB0IE |= UCTXIE0;                          // enable Transmit ready interrupt
}

/*----------------------------------------------------------------------------*/
// Description:
//   Initialization of the I2C Module for Read operation.
/*----------------------------------------------------------------------------*/
void I2CReadInit(void)
{
  UCB0CTLW0 &= ~UCTR;                        // UCTR=0 => Receive Mode (R/W bit = 1)
  UCB0IFG &= ~UCRXIFG0;
  UCB0IE &= ~UCTXIE0;                         // disable Transmit ready interrupt
  UCB0IE |= UCRXIE0;                          // enable Receive ready interrupt
}

/*----------------------------------------------------------------------------*/
// Description:
//   Byte Write Operation. The communication via the I2C bus with an EEPROM
//   (2465) is realized. A data byte is written into a user defined address.
/*----------------------------------------------------------------------------*/
void EEPROM_ByteWrite(uint16_t Address, uint8_t Data)
{
  uint8_t adr_hi;
  uint8_t adr_lo;

  initI2C();

  adr_hi = Address >> 8;                    // calculate high byte
  adr_lo = Address & 0xFF;                  // and low byte of address

  I2CBufferArray[2] = adr_hi;               // Low byte address.
  I2CBufferArray[1] = adr_lo;               // High byte address.
  I2CBufferArray[0] = Data;
  PtrTransmit = 2;                          // set I2CBufferArray Pointer
  while (UCB0STATW & UCBUSY);                // wait until I2C module has finished all operations.
  EEPROM_AckPolling();
  I2CWriteInit();
  UCB0CTLW0 |= UCTXSTT;                     // start condition generation I2C communication is started

  __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0 w/ interrupts
  UCB0CTLW0 |= UCTXSTP;                      // I2C stop condition
  while(UCB0CTLW0 & UCTXSTP);                // Ensure stop condition got sent

  resetI2C();
}

/*
  Page size of 24XX512 = 128 bytes
  Page write cannot write accross the physical boundary of a page
  One batch of write accross two pages needs to be write separately
 */

void EEPROM_PageWrite(uint16_t StartAddress, uint8_t data[], uint8_t len)
{
  uint8_t adr_hi, adr_lo, secondBatchLen = 0;
  uint16_t i;

  initI2C();

  if(((StartAddress & 0x007F) + len) > 127) {
    secondBatchLen = (StartAddress & 0x007F) + len - 128;
  }

  adr_hi = StartAddress >> 8;                    // calculate high byte
  adr_lo = StartAddress & 0xFF;                  // and low byte of address

  PtrTransmit = len - secondBatchLen + 1 ;                          // set I2CBufferArray Pointer
  I2CBufferArray[len - secondBatchLen + 1] = adr_hi;
  I2CBufferArray[len - secondBatchLen] = adr_lo;
  for(i = 0; i < (len - secondBatchLen); i++) {
    I2CBufferArray[len - secondBatchLen - i - 1] = data[i];
  }
  while (UCB0STATW & UCBUSY);                   // wait until I2C module has finished all operations.
  EEPROM_AckPolling();
  I2CWriteInit();
  UCB0CTLW0 |= UCTXSTT;                         // start condition generation I2C communication is started
  __bis_SR_register(LPM0_bits + GIE);           // Enter LPM0 w/ interrupts
  UCB0CTLW0 |= UCTXSTP;                         // I2C stop condition
  while(UCB0CTLW0 & UCTXSTP);                   // Ensure stop condition got sent
  if(secondBatchLen > 0) {
    StartAddress = StartAddress + len - secondBatchLen;
    adr_hi =  StartAddress >> 8;
    adr_lo =  StartAddress & 0xFF;
    PtrTransmit = secondBatchLen + 1;
    I2CBufferArray[secondBatchLen + 1] = adr_hi;
    I2CBufferArray[secondBatchLen] = adr_lo;
    for(i = 0; i < secondBatchLen; i++) {
      I2CBufferArray[secondBatchLen - i - 1] = data[len - secondBatchLen + i];
    }
    EEPROM_AckPolling();                      // Otherwise the 2nd batch won't be written
    while (UCB0STATW & UCBUSY);
    I2CWriteInit();
    UCB0CTLW0 |= UCTXSTT;
    __bis_SR_register(LPM0_bits + GIE);
    UCB0CTLW0 |= UCTXSTP;
    while(UCB0CTLW0 & UCTXSTP);
  }

  resetI2C();
}

/*----------------------------------------------------------------------------*/
// Description:
//   Random Read Operation. Data is read from the EEPROM. The EEPROM
//   address is defined with the parameter Address.
/*----------------------------------------------------------------------------*/
uint8_t EEPROM_RandomRead(uint16_t Address)
{
  uint8_t adr_hi;
  uint8_t adr_lo;

  initI2C();

  adr_hi = Address >> 8;                    // calculate high byte
  adr_lo = Address & 0xFF;                  // and low byte of address

  I2CBufferArray[1] = adr_hi;               // store single bytes that have to
  I2CBufferArray[0] = adr_lo;               // be sent in the I2CBuffer.
  PtrTransmit = 1;

  while (UCB0STATW & UCBUSY);                // wait until I2C module has finished all operations
  // Write Address first
  I2CWriteInit();
  UCB0CTLW0 |= UCTXSTT;                      // start condition generation => I2C communication is started
  __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0 w/ interrupts
  // Read Data byte
  I2CReadInit();
  UCB0CTLW0 |= UCTXSTT;                      // I2C start condition
  while(UCB0CTLW0 & UCTXSTT);                // Start condition sent?
  UCB0CTLW0 |= UCTXSTP;                      // I2C stop condition
  __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0 w/ interrupts
  while(UCB0CTLW0 & UCTXSTP);                // Ensure stop condition got sent

  resetI2C();

  return I2CBuffer;
}
/*----------------------------------------------------------------------------*/
// Description:
//   Sequential Read Operation. Data is read from the EEPROM in a sequential
//   form from the parameter address as a starting point. Specify the size to
//   be read and populate to a Data buffer.
// TODO: bug
// probably due to some timing issue(s),
// immediately calling SequentialRead() right after a previous call
// data read back will be all 0xFF(s)
/*----------------------------------------------------------------------------*/
void EEPROM_SequentialRead(uint16_t Address , uint8_t * Data , uint8_t Size)
{
  uint8_t adr_hi;
  uint8_t adr_lo;
  uint16_t counterSize;

  initI2C();

  adr_hi = Address >> 8;                    // calculate high byte
  adr_lo = Address & 0xFF;                  // and low byte of address

  I2CBufferArray[1] = adr_hi;               // store single bytes that have to
  I2CBufferArray[0] = adr_lo;               // be sent in the I2CBuffer.
  PtrTransmit = 1;                          // set I2CBufferArray Pointer

  while (UCB0STATW & UCBUSY);                // wait until I2C module has finished all operations
  // Write Address first
  I2CWriteInit();
  UCB0CTLW0 |= UCTXSTT;                      // start condition generation
                                            // => I2C communication is started
  __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0 w/ interrupts

  // Read Data byte
  I2CReadInit();

  UCB0CTLW0 |= UCTXSTT;                      // I2C start condition
  while(UCB0CTLW0 & UCTXSTT);                // Start condition sent?

  for(counterSize = 0 ; counterSize < Size ; counterSize++)
  {
    __bis_SR_register(LPM0_bits + GIE);     // Enter LPM0 w/ interrupts
    Data[counterSize] = I2CBuffer;
  }
  UCB0CTLW0 |= UCTXSTP;                      // I2C stop condition
  __bis_SR_register(LPM0_bits + GIE);       // Enter LPM0 w/ interrupts
  while(UCB0CTLW0 & UCTXSTP);                // Ensure stop condition got sent

  resetI2C();
}

/*----------------------------------------------------------------------------*/
// Description:
//   Acknowledge Polling. The EEPROM will not acknowledge if a write cycle is
//   in progress. It can be used to determine when a write cycle is completed.
/*----------------------------------------------------------------------------*/
void EEPROM_AckPolling(void)
{
  while (UCB0STATW & UCBUSY);                // wait until I2C module has
                                            // finished all operations
  do
  {
    UCB0IFG = 0x0000;                        // clear I2C interrupt flags
    UCB0CTLW0 |= UCTR;                       // I2CTRX=1 => Transmit Mode (R/W bit = 0)
    UCB0CTLW0 &= ~UCTXSTT;
    UCB0CTLW0 |= UCTXSTT;                    // start condition is generated
    while(UCB0CTLW0 & UCTXSTT)               // wait till I2CSTT bit was cleared
    {
      if(!(UCNACKIFG & UCB0IFG))           // Break out if ACK received
        break;
    }
    UCB0CTLW0 |= UCTXSTP;                    // stop condition is generated after
                                             // slave address was sent => I2C communication is started
    while (UCB0CTLW0 & UCTXSTP);             // wait till stop bit is reset
    __delay_cycles(500);                     // Software delay
  }while(UCNACKIFG & UCB0IFG);
}

//------------------------------------------------------------------------------
// The USCIAB0_ISR is structured such that it can be used to transmit any
// number of bytes by pre-loading TXByteCtr with the byte count.
//------------------------------------------------------------------------------
// USCI_B0 interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = USCI_B0_VECTOR
__interrupt void USCI_B0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCI_B0_ISR (void)
#else
#error Compiler not supported!
#endif
{
    switch (__even_in_range(UCB0IV, 30))
    {
        case USCI_NONE: break;                        	// No interrupts
        case USCI_I2C_UCALIFG: break;                 	// ALIFG
        case USCI_I2C_UCNACKIFG: break;               	// NACKIFG
        case USCI_I2C_UCSTTIFG: break;                	// STTIFG
        case USCI_I2C_UCSTPIFG: break;                	// STPIFG
        case USCI_I2C_UCRXIFG3: break;                	// RXIFG3
        case USCI_I2C_UCTXIFG3: break;                	// TXIFG3
        case USCI_I2C_UCRXIFG2: break;                	// RXIFG2
        case USCI_I2C_UCTXIFG2: break;                	// TXIFG2
        case USCI_I2C_UCRXIFG1: break;                	// RXIFG1
        case USCI_I2C_UCTXIFG1: break;                	// TXIFG1
        case USCI_I2C_UCRXIFG0: 						            // RXIFG0
            I2CBuffer = UCB0RXBUF;                  	  // store received data in buffer
            __bic_SR_register_on_exit(LPM0_bits);   	  // Exit LPM0
        	break;
        case USCI_I2C_UCTXIFG0:                       	// TXIFG0
            UCB0TXBUF = I2CBufferArray[PtrTransmit];	  // Load TX buffer
            PtrTransmit--;                          	  // Decrement TX byte counter
            if(PtrTransmit < 0)
            {
              while(!(UCB0IFG & UCTXIFG0));
              UCB0IE &= ~UCTXIE0;                     	// disable interrupts.
              UCB0IFG &= ~UCTXIFG0;                   	// Clear USCI_B0 TX int16_tflag
              __bic_SR_register_on_exit(LPM0_bits); 	  // Exit LPM0
            }
            break;
        case USCI_I2C_UCBCNTIFG: break;               	// CNTIFG
        case USCI_I2C_UCCLTOIFG: break;               	// LTOIFG
        case USCI_I2C_UCBIT9IFG: break;               	// BIT9IFG
        default: break;
    }
}

In one case, system will wake up from LPM3 from RTC interrupt every second, do a bunch of routine including reading/writing several UART port, etc. then read 16 bytes from EEPROM, about half (every other) of those reading will contain gibberish, usually a few 0xFF plus frame shifting. The most common failed read is listed below:

Expected: 016834EEC107009101007D
Actual:     6834EEC107009101007DFF

If I place an arbitrary delay of 5 or 10ms before calling the function to read EEPROM, such problem is largely prevented, with no failure detected after several thousand reads.

	// Due to timing or some unknown issue related to I2C EEPROM
	// A delay longer than 1ms is needed
	// Otherwise about half of cache messages contain errors
	// When read from EEPROM and trying to decide if they need to be resent
	// Refer to known-issues
	delay_ms(5);
	EEPROM_SequentialRead(readAddr, dataEntry, CACHE_SHORT_MESSAGE_ACTUAL_SIZE);

In a post from 10 years ago, it was mentioned that USCI_A and USCI_B sharing interrupt flag can cause problems. This doesn't seem apply for MSP430F67XX MCU. The other possible issue I can think of is that to reduce power consumption, I have modified the code from SLAA208 to close the I2C port when read/write is done and re-initialize the port before each read/write. I suppose SMCLK will need time to stabilize. But I2C is synchronized between master and slave, I don't think this will cause problems.

What am I missing here? Please advise.

Thanks,

ZL

  • 1) If you're using the AckPolling function, I suggest you replace it with a simple delay for 5ms to let the EEPROM do the memory write. (It's usually about 5ms, but check the EEPROM data sheet; it's probably named something like Twr). A number of people here have had trouble with that function, and to me (personally) the code doesn't look right (maybe it's an eUSCI thing).

    2) The SLAA208 code uses LPM/Wakeup for the I2C (except for the AckPolling), and it assumes that the Wakeup indicates that the transaction is complete. If you introduce other LPM/Wakeup sources into the program, it may intercept those and trip over itself.

  • Hi Bruce

    Thanks for your reply. You are right that the use of LPM/Wakeup can lead to unreliable read/write when other sources of Wakeup exists in the program. Once I rewrote the code to use poll instead of LPM/Wakeup, I haven't observed any gibberish after a few thousand read operations.

    I am not quite sure about the AckPolling code. On a high level it seems to match what is described in SLAA208 documentation. It doesn't seem to cause problem once LPM/Wakeup is removed. So I will leave it as it is for now.

    Best,

    ZL

**Attention** This is a public forum