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.

MSP430 SPI Slave issue

Other Parts Discussed in Thread: MSP430F5329

I'm using the MSP430 as a SPI slave running at 25MHz. The MSP430 communicates with an embedded linux device at 1.5MHz. The communication code works but there is a nasty bug the comes up randomly.

Normally, sometime after the 8th clock cycle coming from the master, the RX interrupt of the slaves fires. The code reads the RX buffer. After a short period, the TX buffer fires and depending what was read on the RX buffer, the code stuffs the TX buffer with the next byte to send.

The thing I can't seem to figure out is, randomly the TX ISR fires before the RX buffer so there is a byte shift in the protocol. Once this happens it doesn't seem to switch back until the msp430 is reset.

I can do many transactions without issue, but then the byte shift happens. I'd appcriate any advice. 

thanks,

  • Yvan,

    Can you provide any SPI code for the MSP430?  That would help more easily figure out where a problem could be.  Also, which MSP430 are you using?  If I'm reading this correctly and you're sending information using the slave MSP430, you may need to wait until the RX interrupt flags are cleared before stuffing the TX buffer.  Also, could you further explain how you think the TX ISR is randomly firing?

    Regards,

    Ryan M. Brown

  • Thanks for the quick reply !

    The mcu is a MSP430F5329

    Here is the SPI code:

    // Init

    /* reset USCI state machine */
    UCA0CTL1 |= UCSWRST;
    UCA0CTL0 |= ( UCMSB + UCSYNC + UCCKPH);

    /* simo input */
    P3SEL |= BIT3;

    /* somi output */
    P3DIR |= BIT4;
    P3SEL |= BIT4;

    /* sclk input */
    P2SEL |= BIT7;

    /* start USCI state machine */
    UCA0CTL1 &= ~UCSWRST;

    // ISR code

    Note: variable 'state' gets reset at the start of a transaction


    ISR(USCI_A0)
    {
    switch(UCA0IV)
    {
    case (USCI_UCRXIFG):

    spi_data = UCA0RXBUF;

    switch(state)
    {
    case(CHECK_SPI_DATA):// start of SPI communication
    if(spi_data < 0x51)
    {
    state = AUTO_INCREMENT_MODE; // valid single byte reg
    reg_address = spi_data;
    }
    else
    {
    reg_address = 0;
    }
    break;

    case(AUTO_INCREMENT_MODE): // Get ready for second byte transfer
    if(reg_address < 0x51)
    {
    reg_address++;
    }
    else
    {
    reg_address = 0x00;
    }
    break;

    default:break;
    }
    break;

    case (USCI_UCTXIFG):
    switch(state)
    {
    case(CHECK_SPI_DATA):
    UCA0TXBUF = SPI_BLANK_REPLY;
    break;

    case(AUTO_INCREMENT_MODE):
    UCA0TXBUF = *data(reg_address);
    break;

    default:
    UCA0TXBUF = SPI_BLANK_REPLY;
    break;
    }

    break;

    default: break;
    }

    }

    // I use a GPIO as a chip-select

    switch(P1IV)
    {
    // TODO: to be moved
    case(P1IV_P1IFG6):
    if(cs == 0)
    {
    cs++;
    uca0_spi_slave_init();
    uca0_spi_slave_enable_int_rx();
    uca0_spi_slave_enable_int_tx();
    cs_pint(); // set up for low-to-high interrupt
    }
    else
    {
    uca0_spi_slave_deinit(); // reset the SPI module
    cs = 0;
    cs_nint(); // set up for high-low interrupt
    }
    break;

    //---------------------------------

    Normally, the receive SPI interrupt (RX) occurs prior to the transmit SPI (TX) interrupt. And my protocol relies on this. At some point (it could be hours of  SPI transactions), the TX interrupt will occur prior to the RX interrupt and stay swapped until it is reset. I don't quite understand what is causing this.

    How do I know the order ISR execution?..I just set/reset a GPIO in the ISR and watch it on the scope/analyzer. Also, once the execution order flips from RX->TX to TX->RX my data gets delayed by 1 cycle.

    Thanks for taking the time to look at this !

  • When you say your data "gets delayed by 1 cycle", do you mean it gets delayed by one cycle of UCxCLK? In other words the data ends up shifted right by one bit when received by the linux device.

    If so, that may be due to a bug I've seen in the USCI's handling of UCCKPH in slave mode. If you can modify the SPI code on the embedded linux device to match, try using UCA0CTL0 = (UCMSB + UCSYNC) instead.

  • Yvan,

    I don't know if this will fix your issue, but it is always good practice to include the line while (!(UCA0IFG&UCTXIFG)); before placing data in the UCA0TXBUF to make sure that the USCI_A0 TX buffer is ready.

    Is there any specific reason for setting UCCKPH?  If it doesn't interfere communication with the master, you may want to try setting UCCKPL instead since this is the more common setting with MSP430 slave operation.  

    Since you expect for the RX interrupt to always occur first you might try setting a global flag inside the RX interrupt that the TX interrupt clears, and if the TX interrupt occurs before the flag is set then you know that the error has occurred.  From there you'll have to figure out how to get your code to reverse the order once again and get back on track.

    Regards,

    Ryan M. Brown

  • What I meant to say is the following:

    The ISR execution sequence after the 8th clock pulse is usually RX  then TX

    Where RX is the interrupt that occurs when the RX buffer is full and

    The TX interrupt occurs when the TX buffer is empty.

    For my protocol, I am relying on the fact that the RX interrupt happens before the TX interrupt. For example,  I can get the requested register address from the RX buffer before filling the TX buffer with the register data.

    For example

    8th clock pulse occurs

    After a short period..

    RX interrupt occurs (get requested register address)

    register_address =  RX_Buffer

    after a short period TX interrupt occurs:

    TX_Buffer = data[register_address]

    So after the next SPI cycle, the master will receive the requested data from the array data[]

    This breaks down if the TX interrupt happens first since I don't correctly fill the TX_buffer with the correct valve. Therefore the correct data[] value will appear on the next cycle. Thus, its not a bit shift but a byte shift due to the TX interrupt occurring before RX. This RX/TX swap can happen at random times and after hundreds of successful SPI transactions. 

    I appreciate any insight you can provide !

    thanks,

  • I don't know if this will fix your issue, but it is always good practice to include the line while (!(UCA0IFG&UCTXIFG));before placing data in the UCA0TXBUF to make sure that the USCI_A0 TX buffer is ready.

    Everything is interrupt driven, so I'm assuming that when the TX interrupt fires then TXBUF is ready to be filled.

    Is there any specific reason for setting UCCKPH?  If it doesn't interfere communication with the master, you may want to try setting UCCKPL instead since this is the more common setting with MSP430 slave operation. 


    I will be trying this next. But should it matter?

    Since you expect for the RX interrupt to always occur first you might try setting a global flag inside the RX interrupt that the TX interrupt clears, and if the TX interrupt occurs before the flag is set then you know that the error has occurred.  From there you'll have to figure out how to get your code to reverse the order once again and get back on track.

    I will also be trying this, but this is a band-aid solution. I need to know what influences when the RX/TX ISRs execute.

  • What would be the recommended setting?

    UCA0CTL0 |= ( UCMSB + UCSYNC +UCCKPL);

    or

    UCA0CTL0 |= ( UCMSB + UCSYNC);

  • Preferably the first, but either should do.  The problem is more centered around UCCKPH.

  • I tried it and seems to work. But I won't call this resolved until at least 1-2 days of successful tests since this bug creeps up randomly.

    Why the first option over the second option ? And why isn't there an errata around using UCCKPH as all modes should work.

    thanks,

  • After a couple hours of running the code the RX/TX interrupts got swapped (TX occurs prior to RX)

    I will try UCA0CTL0 |= ( UCMSB + UCSYNC +UCCKPL);

    ..and see what happens.

  • I think all the modes work fine.

    This is what I discovered. It seems that the RX interrupt fires first followed by TX if the msp is sleeping. If it is not sleeping the order is reversed.

  • In SPI mode, RX and TX interrupts are just 1/2 SPI clock cycle apart. Order depends on phase.

    However, if TX interrupt is first, but MSP is sleeping, it may be (depending in LPM) that waking the PCU and entering the ISR takes longer than this 1/2 SPI clock cycle. In this case, both interrupts are pending, and the IV register reports the one with the higher priority first, which is the RX interrupt.

  • Where is this documented?

    Is it recommended not to use RX and TX interrupts ? Just use the RX interrupt ?

    thanks,

  • Yvan,

    Yvan Pearson1 said:
    Just use the RX interrupt ?


    If you code is running into issues when the TX and RX ISRs are serviced in the wrong order, I would recommend combining them into a single ISR.  I don't think this is formally documented anywhere, but this is what the example code does for a SPI slave device.

    I recommend using just the RXIFG to trigger an interrupt, then in the RX ISR, populate the TXBUF, ensuring the buffer is ready by checking the flag first with the following code (modified as necessary to use the appropriate USCI module):

    while(!(UCA0IFG & UCTXIFG));
    UXA0TXBUF = txData;

    Mike

  • It is documented that if you do stuff the TX buffer before its ready, it will corrupt what gets send. This is prevented only if you use the TX interrupt or if you ensure the TX interrupt flag is high prior to filling the TX buffer.

    while(!(UCA0IFG & UCRXIFG));


    The above code does not see if the TX buffer is ready. Also, using a busy wait loop in an ISR is not appealing. Does TI recommend using a busy wait in an ISR ?

  • Yvan Pearson1 said:
    Where is this documented?

    It is implicitly documented by the timing diagrams and when a bit is sent or sampled. Interrupt priorities are also documented (see the IV register description).
    If TX interrupt comes first, but RX follows shortly, then if you are fast enough, you’ll see TX interrupt reported by the IV register (because RX hasn’t fired yet). If you are slower, both have fired, and you’ll see RX interrupt reported first, as it has the higher priority.

    BTW, on fast SPI clock (> MCLK/10), 1/2 SPI clock cycle is always shorter than the ISR entry latency. So if enabled, you’ll always have both interrupts pending when your ISR code starts and always see RX first.

    A busy wait inside an ISR is neither recommended nor required - there are other ways to do it without waitning inside an ISR, blocking the whole system..
    A premature write to TXBUF does not 'corrupt' the data being sent (in the meaning of corrupted bytes being sent) - it simply overwrites the byte that is already waiting in TXBUF, as if it had never been written there. 
    In theory, tehre is a very, very small chance that you write exactly the moment the byte is moved form TXBUF to the output shift. Depending on the hardware implementation, it might be possible that in this nanosecond, some bits from the old and some from the new byte are copied. It is, however, not very likely that even this chance exists.

    Simply check the IFG flags inside the ISR (through the IV register or by reading the IFG register) to determine which interrupt is waiting, and act accordingly. And use global flags and buffers to signal or store waiting data.
    (e.g. in an echo applicaiton, set a flag if a byte arrived and store it in a buffer variabe. On TX interrupt, check the flag and send the buffered data). However, in a simple UART echo, since RX and TX work with the same baudrate, TXBUF should be empty as soon as a new byte arrived, as it doesn't take longer to send than to receive. :)

  • Yvan Pearson1 said:
    while(!(UCA0IFG & UCRXIFG));

    The above code does not see if the TX buffer is ready. Also, using a busy wait loop in an ISR is not appealing. Does TI recommend using a busy wait in an ISR ?

    You are correct.  I mistyped the line in my previous post.  it should be:

    while(!(UCA0IFG & UCTXIFG));

    I will correct this in my previous post.

    Mike

  • Yvan Pearson1 said:
    It is documented that if you do stuff the TX buffer before its ready, it will corrupt what gets send.

    Maybe a semantics problem:

    When you write to TXBUF, you write to TXBUF. And whatever was in TXBUF before, gets overwritten.
    Now once the USCI is ready with its current byte, I twill read what is at TXBUF in this very moment, and sets TXIFG.
    So writing to TXBUF without waiting for TXIFG won’t corrupt anything. It will simply replace what you wrote there previously, and if it hasn’t been read into the output shift register yet, it will never get sent as if you never wrote it to TXBUF. Like a variable that you change before it has been read by a different function.

    And again, there is no need to do a busy wait for TXIFG. If TXIFG is set, then the ISR is called. And when the ISR got called, because of TXIFG, then you can write the next byte to TXBUF, as its current content has been read. That’s it.
    If you think you need to wait for something in an ISR, then your whole concept is wrong.

    However, since 5x family, RX and TX interrupts share the same interrupt vector, so once the ISR got called, you’ll need to check which IFG flag has called it. However, you should never wait for a flag inside an ISR. Never. ISRs are no parallel execution threads. Unfortunately, some of the people who wrote TI demo codes didn’t obey this basic rule. Perhaps in an attempt to avoid any complexity in the demo code. However, this completely counteracts the purpose of demo code. Demo code shouldn’t be a simple hack but a demonstration of proper implementation. And any needed complexity should be there and properly commented. Otherwise the demo code is no demo code and (best case) only be used as a cheap hardware test function.

  • Thanks for the detailed reply.I'll consider your comment and post back. Yes, I also thought a while() loop in a ISR is something to completely avoid despite the TI demo code that is being circulated as the 'official' way to do things.

  • Well, demo code is not always written by an experienced engineer and coder.
    Also, often it is a rip form existing larger projects and then quickly tailored to just cover the topic.
    If I had to make demo code from my low- and mid-level libraries, it would take quite some time or give a similarly poor result - possibly both.
    However, the more basic a demo is, the more refined it should be and the more detailed the explanation should be about why something is done this or that way

**Attention** This is a public forum