MSP430FR5994: MSP430FR5994 I2C EEPROM

Part Number: MSP430FR5994
Other Parts Discussed in Thread: ENERGYTRACE

Tool/software:

Dear all,

I came across the following situation when using I2Croutines using EEPROM memory.

I2C configuration on the MSP430FR5994

////////////////////////////////////////////////////////////////////////////////
//         Definição da Porta P5 como Serial I2C das Memórias EEPROMS         //
////////////////////////////////////////////////////////////////////////////////
   P5OUT = 0;                                                                   // Reseta Todos os Pinos da Porta P5
   P5DIR = 0xFF;                                                                // Define Todos os Pinos da Porta P5 como Saídas
   P5OUT &=~ 0xFF;                                                              // Inicializa Todos os Pinos da Porta P5 com Nível Lógico "0" Baixo
   P5SEL0 |=  (SDA | SCL);     // P5.0 e P5.1 options select P5SEL0 = 0         // Seleciona o Pino da Porta P5.0 UCB1SDA e P5.1 UCB1SCL como Barramento I2C                                                   
   P5SEL1 &=~ (SDA | SCL);     // P5.0 e P5.1 options select P5SEL1 = 1         // Seleciona Módulo Secundário Conforme Tabela I/O Function Selection = 1 | 0
   // Configuração do Barramento I2C das Memórias EEPROMs
   UCB1CTLW0 |= UCSWRST + UCMST + UCMODE_3 + UCSYNC + UCSSEL__SMCLK;            // Enable SW reset, I2C Master, Modo i2c, synchronous mode
   UCB1BRW = 40;                                                                // fSCL = SMCLK = 16.000.000/40 = 400 kHz
   UCB1CTLW0 &=~ UCSWRST;                                                       // Clear SW reset, resume operation
   UCB1IE |= UCNACKIE + UCTXIE0 + UCRXIE0;                                      // Interrupts Enable

When using EEPROM_AckPolling(); as follows:

/*----------------------------------------------------------------------------*/
// 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)
{                                                                               // finished all operations
   do
     {
        UCB1STATW = 0x00;                                                       // clear I2C interrupt flags
        UCB1CTLW0 |= UCTR;                                                      // I2CTRX=1 => Transmit Mode (R/W bit = 0)
        UCB1CTLW0 &= ~UCTXSTT;
        UCB1CTLW0 |= UCTXSTT;                                                   // start condition is generated
        while (UCB1CTLW0 & UCTXSTT)                                             // wait till I2CSTT bit was cleared
          {
             if (!(UCNACKIFG & UCB1STATW))                                      // Break out if ACK received
             break;
          }
        UCB1CTLW0 |= UCTXSTP;                                                   // stop condition is generated after
                                                                                // slave address was sent => I2C communication is started
        while (UCB1CTLW0 & UCTXSTP);                                            // wait till stop bit is reset
        Delay_TA3_ms(6);                                                        // Aguarda 6 milisegundo
     }
   while (UCNACKIFG & UCB1STATW);
}

I notice that the current consumption in LPM3 is almost 10 times higher, around 640 uA, than if I simply use it this way, which is around 67 uA:

/*----------------------------------------------------------------------------*/
// 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)
{
  Delay_TA3_ms(6);                                                             // Aguarda 6 milisegundo
}


Has anyone here on the forum come across this difference in current consumption after writing to EEPROM memory?

Best regards,

Anderson Portela

  • Are you (also) measuring the consumption of the EEPROM device itself? I think EnergyTrace measures everything going through the 3V3 power bus on the Launchpad.

    Since the code appears to wait for the I2C to idle, the EEPROM internal programming cycle would mostly happen during your delay function. The 24LC1025 (e.g.) data sheet [MCHP Doc 21941E] in Table 1-1 mentions that it can draw 5mA (Max) while programming; I suppose this is a peak rather than a constant, and I've never measured the profile.

  • If the I2C port is left in a state where either SDA or SCL is pulled low, you might see about that much current. Depending on the pullup resistors used.

  • Figure 7.1 for ACK polling in the 24LC1025 data sheet shows a different sequence than is used here.

    This loop bugs me:

            while (UCB1CTLW0 & UCTXSTT)                                             // wait till I2CSTT bit was cleared
              {
                 if (!(UCNACKIFG & UCB1STATW))                                      // Break out if ACK received
                 break;
              }

    There is a disconnect between the flow chart and the text on the timing of when STT gets cleared. The text (32.2.5.2.1) , in bold, says: "The UCTXSTT flag is cleared as soon as the complete address is sent." It also says that a NACK will clear STT and STP. The flow chart implies that STT is cleared after receiving an ACK or NACK. It can't be both and the first would be a problem.

    In any case, checking for a NACK before STT is cleared is pointless.

  • Hello,

    Now I've come across the following situation:

    I use Timers TA3 and TA4, both CCR0, to generate various delays (10 ms to 60 ms maximum) instead of using __delay_cycles. The timers work well and accurately.

    ////////////////////////////////////////////////////////////////////////////////
    //Rotina para Gerar Atrasos Durante Leituras dos Sensores de Pressões e Baterias//
    ////////////////////////////////////////////////////////////////////////////////
    void Delay_TA3_ms(volatile unsigned int timer)
    {
       TA3CCR0 = ((65535 * timer) / 2000);                                          // Periodo de Tempo da Leitura dos Sensores de Pressões e Baterias em Milisgundos
       TA3CCTL0 |= CCIE;                                                            // Habilita a Interrupção do TIMER3_A0
       TA3CTL = TASSEL_1 + MC_1 + ID_0 + TACLR;                                     // Configura o TIMER3_A0 (ACLK, UP Mode, Divide / 1, Limpa Timer)
       __bis_SR_register(LPM3_bits | GIE);                                          // Entra em LPM3 (Modo de Baixo Consumo)
    }
    ////////////////////////////////////////////////////////////////////////////////
    //   Rotina para Gerar Atrasos Durante Acionamentos das Válvulas Solenóides   //
    ////////////////////////////////////////////////////////////////////////////////
    void Delay_TA4_ms(volatile unsigned int timer)
    {
       TA4CCR0 = ((65535 * timer) / 2000);                                          // Periodo de Tempo da Acionamento das Válvulas Solenóides ON to OFF em Milisegundos
       TA4CCTL0 |= CCIE;                                                            // Habilita a Interrupção do TIMER4_A0
       TA4CTL = TASSEL_1 + MC_1 + ID_0 + TACLR;                                     // Configura o TIMER4_A0 (ACLK, UP Mode, Divide / 1, Limpa Timer)
       __bis_SR_register(LPM3_bits | GIE);                                          // Entra em LPM3 (Modo de Baixo Consumo)
    }
    
    ...
    
    // Timer3_A0 interrupt service routine
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=TIMER3_A0_VECTOR
    __interrupt void TIMER3_A0_ISR(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(TIMER3_A0_VECTOR))) TIMER3_A0_ISR (void)
    #else
    #error Compiler not supported!
    #endif
    {
       TA3CTL &=~ MC_3;                                                             // Interrompe a Contagem do TIMER3_A0
       TA3CCR0 = 0;                                                                 // Limpa o Contador TA3CCR0 do IMER3_A0
       TA3CCTL0 &=~ CCIE;                                                           // Desabilita a Interrupção do TIMER3_A0
       TA3CCTL0 &=~ CCIFG;                                                          // Limpa Flag de Interrupção do TIMER3_A0 
       TA3CTL = 0;                                                                  // Desabilita o TIMER3_A0
       __bic_SR_register_on_exit(LPM3_bits);                                        // Sai do Modo de Baixo Consumo
    }
    
    // Timer4_A0 interrupt service routine
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=TIMER4_A0_VECTOR
    __interrupt void TIMER4_A0_ISR(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(TIMER4_A0_VECTOR))) TIMER4_A0_ISR (void)
    #else
    #error Compiler not supported!
    #endif
    {
       TA4CTL &=~ MC_3;                                                             // Interrompe a Contagem do TIMER4_A0
       TA4CCR0 = 0;                                                                 // Limpa o Contador TA4CCR0 do IMER4_A0
       TA4CCTL0 &=~ CCIE;                                                           // Desabilita a Interrupção do TIMER4_A0
       TA4CCTL0 &=~ CCIFG;                                                          // Limpa Flag de Interrupção do TIMER4_A0
       TA4CTL = 0;                                                                  // Desabilita o TIMER4_A0
       __bic_SR_register_on_exit(LPM3_bits);                                        // Sai do Modo de Baixo Consumo
    }

    The problem is that when using the timers, the ISR interrupt of the I2C EEPROM memory after an LPM3 has not generated the interrupt, getting stuck in __bis_SR_register(LPM3_bits | GIE); of the EEPROM_ByteWrite routine. Maybe because the ISR of the timers comes to the front and ends up canceling the ISR of the I2C EEPROM.

    /*----------------------------------------------------------------------------*/
    // 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(unsigned int Address, char Data)
    {
       char adr_hi;
       char adr_lo;
       
       while (UCB1STAT & UCBUSY);                                                   // Aguarda até que as Operações I2C tenham terminado
       
       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
    
       I2CWriteInit();
       UCB1CTLW0 |= UCTXSTT;                                                        // start condition generation
                                                                                    // => I2C communication is started    
       😔__bis_SR_register(LPM3_bits | GIE);                                          // Enter LPM3 w/ interrupts
          
       UCB1CTLW0 |= UCTXSTP;                                                        // I2C stop condition
       while (UCB1CTLW0 & UCTXSTP);                                                 // Ensure stop condition got sent
    }
    
    ...
    
    /*----------------------------------------------------------------------------*/
    // 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 (UCB1STAT & UCBUSY);                                                   // Aguarda até que as Operações I2C tenham terminado
       
       Delay_TA4_ms(6);                                                             // Aguarda 6 milisegundo
    }
    
    /*----------------------------------------------------------------------------*/
    //  Interrupt Service Routines                                               
    //     Note that the Compiler version is checked in the following code and   
    //     depending of the Compiler Version the correct Interrupt Service       
    //     Routine definition is used.                                           
    /*----------------------------------------------------------------------------*/
    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector = EUSCI_B1_VECTOR
    __interrupt void USCI_B1_ISR(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(EUSCI_B1_VECTOR))) USCI_B1_ISR (void)
    #else
    #error Compiler not supported!
    #endif
    {
       switch(__even_in_range(UCB1IV, USCI_I2C_UCBIT9IFG))
         {
            case USCI_NONE:          break;                                         // Vector 0: No interrupts
            case USCI_I2C_UCALIFG:   break;                                         // Vector 2: ALIFG
            case USCI_I2C_UCNACKIFG: break;                                         // Vector 4: NACKIFG
            case USCI_I2C_UCSTTIFG:  break;                                         // Vector 6: STTIFG
            case USCI_I2C_UCSTPIFG:  break;                                         // Vector 8: STPIFG
            case USCI_I2C_UCRXIFG3:  break;                                         // Vector 10: RXIFG3
            case USCI_I2C_UCTXIFG3:  break;                                         // Vector 12: TXIFG3
            case USCI_I2C_UCRXIFG2:  break;                                         // Vector 14: RXIFG2
            case USCI_I2C_UCTXIFG2:  break;                                         // Vector 16: TXIFG2
            case USCI_I2C_UCRXIFG1:  break;                                         // Vector 18: RXIFG1
            case USCI_I2C_UCTXIFG1:  break;                                         // Vector 20: TXIFG1
            case USCI_I2C_UCRXIFG0:                                                 // Vector 22: RXIFG0
                    I2CBuffer = UCB1RXBUF;                                          // store received data in buffer
                    __bic_SR_register_on_exit(LPM3_bits);                           // Exit LPM3
            break;
            case USCI_I2C_UCTXIFG0:  	                                        // Vector 24: TXIFG0
                    UCB1TXBUF = I2CBufferArray[PtrTransmit];                        // Load TX buffer
                    PtrTransmit--;                                                  // Decrement TX byte counter
                    if(PtrTransmit < 0)
                     {
                        while (!(UCB1IFG & UCTXIFG0));                              // USCI_B1 TX buffer ready?
                        UCB1IE &= ~UCTXIE0;                                         // disable interrupts.
                        UCB1IFG &= ~UCTXIFG0;                                       // Clear USCI_B1 TX int flag
                        __bic_SR_register_on_exit(LPM3_bits);                       // Exit LPM3
                     }
            break;
            case USCI_I2C_UCBCNTIFG: break;                                         // Vector 26: BCNTIFG
            case USCI_I2C_UCCLTOIFG: break;                                         // Vector 28: clock low timeout
            case USCI_I2C_UCBIT9IFG: break;                                         // Vector 30: 9th bit
            default: break;
         }
    }

    Any suggestions on how to generate the necessary delays with the timers together with I2C EEPROM and that do not generate problems with the ISR of interrupts?

  • The LPM/Wakeup model is ingenious, but very simple.

    In particular, wakeups are anonymous; if there are multiple wakeup sources, it's the LPM code's responsibility to make sure it got the correct one. In general, this is a system-level thing, which suggests avoiding use of LPM in a library function. (Keep in mind that SLAA208 was intended as Example code.)

    One possible method is a completion criterion e.g. a timer millisecond counter with "while (!complete) {LPM;}". The I2C driver would need to do something similar.

    Another artifact I'll mention is that wakeups are asynchronous: a wakeup which is issued while main() is active is lost, leading to race conditions. In general this requires checking for completion (rigorously: with GIE=0) before LPM.

    For simpler designs, a "backstop" such as a millisecond-tick wakeup (as seen in some RTOSes) will keep things going in a pinch.

  • Any suggestions on how to generate the necessary delays with the timers

    Don't use the timers that way.

    With multiple ISRs that can causes an exit from a low power mode, you have no idea if it was the timer that did it. The code just assumes that it did. Plus I am not seeing any advantage to entering LPM3 while waiting on the timer.

    But then I hate things like:  "while (UCB1STAT & UCBUSY);" All of these wait loops assume that the I2C transaction will proceed as expected. If it doesn't, the code gets stuck in one of these loops. Acceptable, perhaps, on the bench and connected to a debugger.

    I prefer including a timeout check.

  • Hello,

    I also discovered that the I2C pull-up resistors are 10 k. For a frequency of 400 kHz, the correct value is 2 k. Already identified and corrected!

    Dear David Schultz,

    Don't use the timers that way.

    Any different way to use the timers as a delay? Can you help me with your application idea? An example so I can understand better?

    Plus I am not seeing any advantage to entering LPM3 while waiting on the timer.

    I have to enter LPM3 to generate a delay and wait a while after exiting LPM3 to close, for example, the solenoid that is pulsed. Example:

    if (Low_Control == 1) // If Low Pressure Control?
    {
    PORTA_P8OUT |= G2012_S32;
    Delay_TA4_ms(10);
    PORTA_P8OUT &=~ G2012_S32;
    }
    else
    {
    if (High_Control == 1) // If High Pressure Control?
    {
    PORTA_P8OUT |= G2012_S31;
    Delay_TA4_ms(10);
    PORTA_P8OUT &=~ G2012_S31;
    }
    }

    Best regards,

  • I have to enter LPM3 to generate a delay

    No you don't. Since you are effectively just waiting for the timer to set its interrupt flag, and doing nothing while waiting, it is simpler to just wait on the interrupt flag. Being careful to not enable that interrupt of course.

    But still a complicated way of avoiding __delay_cycles().

**Attention** This is a public forum