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