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.

MSP430F2274 Interrupt corruption

Other Parts Discussed in Thread: MSP430F2274, MSP430F5529

Hi,

I hope someone can help. I have been working with the MSP430F2274 and 38-pin dev board. I am trying to program the mcu to work with a sensor through i2c. I have had success using the example code which is posted below. My problem just started today. The sensor and mcu were communicating just fine through i2c and I did something that caused a permanent problem and the interrupts no longer seem to be working. I have created new projects and copied the original example files into the project and still the same error. Re-installed ccs. Changed out chips on the dev. board and no success.

Stepping through the code:

Within the while loop the debugger hangs after the __bis_SR_register(CPUOFF + GIE);, and sits at the following instruction. I assume the error has something to do with the low power mode. If I remove the CPUOFF in the above function, I can step through the code but still no interrupt occurs.

I believe that my problem originated from me changing the interrupt function from void to USHORT and compiling and debugging. Which I now know is a no no. Is there a global file that needs to be deleted to restore defaults?

Thank you,

The code below is identical to the example code for the i2c for the 2274.

#include "msp430x22x4.h"

unsigned int RxByteCtr;
unsigned int RxWord;

void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
  P1DIR |= 0x01;                            // P1.0 output
  P3SEL |= 0x06;                            // Assign I2C pins to USCI_B0
  UCB0CTL1 |= UCSWRST;                      // Enable SW reset
  UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;     // I2C Master, synchronous mode
  UCB0CTL1 = UCSSEL_2 + UCSWRST;            // Use SMCLK, keep SW reset
  UCB0BR0 = 12;                             // fSCL = SMCLK/12 = ~100kHz
  UCB0BR1 = 0;
  UCB0I2CSA = 0x40;                         // Set slave address
  UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
  IE2 |= UCB0RXIE;                          // Enable RX interrupt
  TACTL = TASSEL_2 + MC_2;                  // SMCLK, contmode

  while (1)
  {
    RxByteCtr = 2;                          // Load RX byte counter
    UCB0CTL1 |= UCTXSTT;                    // I2C start condition
    __bis_SR_register(CPUOFF + GIE);        // Enter LPM0, enable interrupts
                                            // Remain in LPM0 until all data
                                            // is RX'd

    __disable_interrupt();
    TACCTL0 |= CCIE;                        // TACCR0 interrupt enabled
    __bis_SR_register(CPUOFF + GIE);        // Enter LPM0, enable interrupts
                                            // Remain in LPM0 until TACCR0
                                            // interrupt occurs
    TACCTL0 &= ~CCIE;                       // TACCR0 interrupt disabled
  }
}

#pragma vector = TIMERA0_VECTOR
__interrupt void TA0_ISR(void)
{
  __bic_SR_register_on_exit(CPUOFF);        // Exit LPM0
}

// The USCIAB0TX_ISR is structured such that it can be used to receive any
// 2+ number of bytes by pre-loading RxByteCtr with the byte count.
#pragma vector = USCIAB0TX_VECTOR
__interrupt void USCIAB0TX_ISR(void)
{
  RxByteCtr--;                              // Decrement RX byte counter

  if (RxByteCtr)
  {
    RxWord = (unsigned int)UCB0RXBUF << 8;  // Get received byte
    if (RxByteCtr == 1)                     // Only one byte left?
      UCB0CTL1 |= UCTXSTP;                  // Generate I2C stop condition
  }
  else
  {
    RxWord |= UCB0RXBUF;                    // Get final received byte,
                                            // Combine MSB and LSB
    __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
  }
}

  • Hi Cody,

     

    Please do the following changes in your code and try to test it. It will solve your interrupt problems.

    #include "msp430x22x4.h"

    unsigned int RxByteCtr;
    unsigned int RxWord;

    void main(void)
    {
      WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
      P1DIR |= 0x01;                            // P1.0 output
      P3SEL |= 0x06;                            // Assign I2C pins to USCI_B0
      UCB0CTL1 |= UCSWRST;                      // Enable SW reset
      UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;     // I2C Master, synchronous mode
      UCB0CTL1 = UCSSEL_2 + UCSWRST;            // Use SMCLK, keep SW reset
      UCB0BR0 = 12;                             // fSCL = SMCLK/12 = ~100kHz
      UCB0BR1 = 0;
      UCB0I2CSA = 0x40;                         // Set slave address
      UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
      IE2 |= UCB0RXIE;                          // Enable RX interrupt
      TACTL = TASSEL_2 + MC_2;                  // SMCLK, contmode

     TACCTL0 |= CCIE;                        // TACCR0 interrupt enabled

     __bis_SR_register(CPUOFF + GIE);        // Enter LPM0, enable interrupts

      while (1)
      {
        RxByteCtr = 2;                          // Load RX byte counter
        UCB0CTL1 |= UCTXSTT;                    // I2C start condition
       
                                                // Remain in LPM0 until all data
                                                // is RX'd

      }
    }

    #pragma vector = TIMERA0_VECTOR
    __interrupt void TA0_ISR(void)
    {
      __bic_SR_register_on_exit(CPUOFF);        // Exit LPM0
    }

    // The USCIAB0TX_ISR is structured such that it can be used to receive any
    // 2+ number of bytes by pre-loading RxByteCtr with the byte count.
    #pragma vector = USCIAB0TX_VECTOR
    __interrupt void USCIAB0TX_ISR(void)
    {
      RxByteCtr--;                              // Decrement RX byte counter

      if (RxByteCtr)
      {
        RxWord = (unsigned int)UCB0RXBUF << 8;  // Get received byte
        if (RxByteCtr == 1)                     // Only one byte left?
          UCB0CTL1 |= UCTXSTP;                  // Generate I2C stop condition
      }
      else
      {
        RxWord |= UCB0RXBUF;                    // Get final received byte,
                                                // Combine MSB and LSB
        __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
      }
    }

  • cody lohse said:
    I believe that my problem originated from me changing the interrupt function from void to USHORT and compiling and debugging.

    It won't destry the chip doing so, but it will definitely destroy the currently running program.
    An interrupt function MUST leave everything as it was before the ISR was called. 'everything' means processor status, stack and all other registers. It is called by hardware (and therefore cannot take any parameters) and returns into the void (and therefore has no return value). Having a return value means that this value would be returned through the registers (usually R15). And since the main loop has not explicitely called the ISR, it would be really scared to death if the R15 register suddenly changes its value.

    Anyway, you shouldn't switch GIE off immediately after entering LPM. Why? Well, when LPM is actually entered, the next instruction (GIE off) is already fetched from flash (pipelining and instruciton interlieve). So if an interrupt comes and th eMCLK is enabled again (this is what LPM does: just stopping the clock), this GIE off is executed first, then the ISR is executed. But on exit of the ISR, LPM is entered again and this time with GIE off. No more interrupts.
    You should place a __no_operation() behind any instruction entering LPM, and you should not set a breakpoint onto this instruction too, including single stepping (same reason - the breakpoint would be triggered before the ISR is called, since the instruction is fetched before the LPM is active)

    If you don't see an interrupt from I2C, this may have some other reasons too. Most common is the missing of a pullup resistor on the SDA and SCL lines. THe I2C hardware only pulls these lines low, but never pulls them high. it also waits until the lines are high before it starts sending. Without pullup resistors (the internal ones are disabled in I2C mode - for a reason). the I2C bus is stalled and no interrupt will occur ever.

     

  • Jens-Michael Gross said:

    Anyway, you shouldn't switch GIE off immediately after entering LPM. Why? Well, when LPM is actually entered, the next instruction (GIE off) is already fetched from flash (pipelining and instruciton interlieve). So if an interrupt comes and th eMCLK is enabled again (this is what LPM does: just stopping the clock), the GIE off is executed first [emphasis added], then the ISR is executed. But on exit of the ISR, LPM is entered again and this time with GIE off. No more interrupts.

    JMG - Fetching the DINT instruction is expected in this case, but executing it?  Are you sure?  I ran a couple of quick tests.  My dev kit (MSP430F5529) does not execute the instruction as you describe.  Interrupts keep coming, ISR keeps executing, and the DINT instruction is never executed.  In other words, it doesn't seem to have the flaw you described!  Is the behavior you describe (executing the instruction after the LPM instruction) an erratum for some processor or is it standard behavior for MSP430?  CPU vs CPUX?  (Feel free to start another thread if needed.)   Thanks.

    In any case, putting a NOP after an LPM instruction is useful if you plan on running your code in a debugger for the reasons you described.

    Jeff

  • Jeff Tenney said:
    JMG - Fetching the DINT instruction is expected in this case, but executing it?  Are you sure?

    Unless there has been a patch in the microcode that handles writes to the status register differently to writes to any other register, this should be the normal behaviour.
    Entering LPM is not a processor microcode function. It is a 'side-effect' that the bit in the status register is routed to external hardware that stops MCLK. So the moment this is done and MCLK is stopped, the next instruction is already fetched. Then MCLK stops. If an interrupt happens now, the already fetched instruction must be executed, as it is already in the execution queue. Then the interrupt is executed.
    Upon return from the interrupt, the next instruction in main isn't executed because before the return address is fetched from stack, the old status register is fetched and thsi stops MCLK again. So the next interrupt will complete the return address fetch and immediately enter the ISR cycle again.

    It's possible that there is a microcode patch that handles status register writes differently - or the compiler intrinsic generates an implicit NOP instruction. There have been some microcode patches for writes to the PC (there they are definitely necessary to prevent wrong execution)
    I neither use IAR/CCS nor do I use the debugger. Actually I never used the LPM features since none of my devices run on battery power. But from observations as well as problems reported here in the forum (and my understanding of RISC processor architecture in general and the MSP in detail, as far as available and documented) this is the expected behaviour.

    Anyway, there are several entries in the errata lists for side-effects after instructions setting/clearing interrupts flags or entering LPM. Some of them are obvious if you analyze the inner workings of the processor core (such as that there cannot be 1-cycle instructions, as you cannot fetch the instruction, do the math and write the results in the same cycle - these 1-cycle instructions are only possible by doing the math/write operation parallel with the next instructions fetch)

    Can you check your test program that keeps running in interrupt on assembly level? Is there a hidden NOP? After all, it is a compiler intrinsic and the compiler can generate what it wants from the C source.

    What I know from own experience is that after clearing GIE, the very next instruction can be executed and then still interrupted by an ISR when the interrupt appears before GIE is actually cleared buft after fetching of the next isntruction has begun. It is a very small time window, but I was able to reproduce/provoke this on the older MSP1611. It's especially nasty if you're working with the hardware multiplier and the ISR messes it up.

  • As you know, interrupt processing is done with microcode, and it is simple enough for the microcode to purge the fetched instruction, which is the "correct" behavior.  After all, the address of the fetched instruction will be placed on the stack, and we can't have that instruction being execute twice (once before the ISR and once after).  Fetching it twice is just fine though.

    Yes, I double checked the disassembly in the debugger - there are no instructions between the BIS.W #xx, SR and the DINT (which can also be disassembled as BIC.W CG, SR).  I also tested with another instruction (not writing to the SR) after the LPM instruction, and it also was not executed.  I ran this other test just in case the return from interrupt somehow masked the possible execution of the DINT instruction just prior to the ISR.

    Also it seems to me that your own experience (the one you mentioned with the '1611) is slightly different.  The difference is LPM.  Entering LPM actually saves you from executing that next instruction, by stopping MCLK as you mentioned.  Then specialized microcode handles interrupt processing, further ensuring that we don't execute the next (already fetched) instruction.  However, if I had reproduced the problem you mentioned where an ISR begins execution after the first instruction inside the critical section, I would have cried foul with TI.  I think they have always said you need a NOP after DINT only for debugging.

    Anyway, point is, if any MSP430 out there executes the instruction after the LPM instruction as part of its interrupt processing from LPM, it really is just a bug.

    Jeff

     

  • Jeff Tenney said:
    it is simple enough for the microcode to purge the fetched instruction, which is the "correct" behavior. 

    I agree, partly. But it is more difficult than it seems. See the CPU27 erratum for the 54xx, which seems to be the direct result of the attempt to include this in microcode. Same for CPU24 and CPU18. i also remember an erratum where an interrupt at the wrong tiem (a very small window of few ns) can crash the CPU. I cannot locate it right now as I don't know on which processor and whether it was in the CPUx list or in a different section. There are way too many MSPs out there :)
    The problem is that in normal operation, it is the recommended behaviour to let the currently fetched and scheduled instruction execute before entering the ISR.
    In normal operation, an ISR is expected to occur randomly and outside the program flow. And the CPU shall run as fast as possible, not always wasting time expecting a rare interupt.
    Also, normally it wouldn't hurt if the instruction right after clearing GIE is interrupted by an ISR or not. The most prominent case where it DOES hurt is the introduction of the hardware multiplier, where its usage inside an ISR will corrupt its usage in the main thread.
    The LPM thing, however, just freezes the MSP and its microcode. If an IRQ happens, it is as if this IRQ just happened during normal program execution at this very point.
    You can simulate LPM0 if you run MCLK from an external digital clock source gate by an R/S flipflop. Reset (stop) is done by a port pin (simulates the CPUOFF bit) and set is done by an external source. If this external soure also triggers an IRQ (e.g. Port1 or capture), neither microcode nor program execution will notice a difference, except for the lack of this 6µs wakeup time which won't occur, if it happens at all if MCLK isn't clocked by DCO). So shall the microcode always check whether you're writing to R2 and setting the GIE or CPUOFF bit just in case? Putting a NOP behind this kind of instructions (which are outside the scope of the C language anyway) won't hurt. And it circumvents many CPU bugs.

    Jeff Tenney said:
    I think they have always said you need a NOP after DINT only for debugging.

    Unfortunately perople reading this advice are placing their breakpoints on this NOP rather than on the instruction behind. This is because there is no explanation WHY this should be done. Only THAT it is recommended. I doubt that many people programming the MSP are even know about this prefetch and instruction interleave thing. (which is also the reason why any instruction which writes to a memory location is soo slow).
    Also, I never used a debugger, so if I read this advice with the addition 'only for debugging' I had surely ignored it as being nort relevant for me. At least when I started with MSP´, I must have read it without the 'for debugging' addition. And I followed it and had problems once when I didn't.

    Jeff Tenney said:
    Anyway, point is, if any MSP430 out there executes the instruction after the LPM instruction as part of its interrupt processing from LPM, it really is just a bug.

    I disagree. Whether it's a bug or a feature depends on what is the intended/expected behaviour. And people often expect things without any reason to do so.
    It's rather a lack of information/documentation.
    Did you ever work with the sparc processors? The first (more or less) true RISC processors? There it was documented that the instruction after any instruction that alters the PC will be executed in any case. It was called the 'delay slot'. If you made a function call, teh isntruction behind the call instruction was always executed after the call. Same for any conditional or unconditional jump.
    It was documented and everyone was informed and wrote the programs according to it, normally placing a NOP behind any of these instructions.
    On the MSP, this kind of internal operation is hidden from the user and therefore all possible pitfalls need to be dealt with in microcode or listed as erratum.
    But whether this behaviour is a bug or the fact that the reasons for it are not documented is the bug depends on the point of view. After all, the MSP is not a consumer article, and there are engineers supposed to work with it, rather than kids.

  • Jens-Michael Gross said:

    i also remember an erratum where an interrupt at the wrong tiem (a very small window of few ns) can crash the CPU.

    Yes, the synchronization bug between the flash controller and LPM entry.  SYS8 on 54xx.  That one made me change code in fielded units!

    Jens-Michael Gross said:

    Also, normally it wouldn't hurt if the instruction right after clearing GIE is interrupted by an ISR or not. The most prominent case where it DOES hurt is the introduction of the hardware multiplier, where its usage inside an ISR will corrupt its usage in the main thread.

     

    Also this:

          PUSH.W   SR

    A     DINT                        ; begin critical section

    B     TST.B    &SEMAPHORE

    C     JNE      D

          MOV.B    #MY_ID, &SEMAPHORE ; lock semaphore

    D     POP.W    SR                 ; end critical section

    ISR executions are expected between A and B, but not between B and C.  It would be devastating in most critical-section code.

    Jens-Michael Gross said:

    Did you ever work with the sparc processors?

    No.  I cut my teeth on the MIPS architecture.  But I do have Patterson's book Computer Organization & Design from school.  He helped Sun do SPARC.  That's actually where I learned about how exception processing should purge instructions not yet executed.

    Sure is fun to think about how these machines might work inside.  The errata give only the faintest of notions how TI might have implemented things.

    Jeff

**Attention** This is a public forum