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.

Missing receive interrupt on TMS320F28335 McBSP SPI

Other Parts Discussed in Thread: TMS320F28335, SYSBIOS

With a TMS320F28335 McBSP port configured as a SPI master with transmit and receive interrupts enabled, occasionally a receive interrupt is missing. That is, one fewer receive interrupt is generated than the number of bytes sent. The IFR and PIEIFR registers show that no interrupt is pending, and the McBSP SPCR1 register shows that RRDY is 0. There are no transmit or receive sync errors or receive overflows.

Transmit and receive interrupts are enabled when the system has bytes to send. The transmit ISR disables the transmit interrupt when the required number of bytes have been sent; the receive ISR disables the receive interrupt when the same number of bytes have been received. Sometimes thousands of bytes can be received before missing one, sometimes just a few. The problem seems to be made worse by enabling other interrupts, such as the timer and SCI, and occurs more frequently the faster the clock (greater than 2 MHz).

The McBSP SPI configuration is:

    regs->SPCR2.all = 0x0000;
    regs->SPCR1.all = 0x0000;

    /* Step 3. Program the registers that affect SPI operation. */
    /* Table 6-3 Bit Values Required to Configure the McBSP as an SPI Master */
    regs->SPCR1.bit.DLB    = 0; /* Digital Loopback mode disabled */
    regs->SPCR1.bit.RJUST  = 0; /* Right justify the data and zero fill */
    regs->SPCR1.bit.CLKSTP = 2;
    regs->SPCR1.bit.DXENA  = 0; /* DX delay enabler off */
    regs->SPCR1.bit.RINTM  = 0; /* interrupt when RRDY bit changes from 0 to 1 */

    regs->SPCR2.bit.FREE  = 1; /* Free run bit */
    regs->SPCR2.bit.SOFT  = 0; /* Ignored when FREE=1 */
    regs->SPCR2.bit.FRST  = 0; /* reset frame-synchronization logic */
    regs->SPCR2.bit.GRST  = 0; /* reset sample rate generator */
    regs->SPCR2.bit.XINTM = 0; /* interrupt when XRDY bit changes from 0 to 1 */

    regs->RCR1.bit.RFRLEN1 = 0; /* Receive frame length of 1 word */
    regs->RCR1.bit.RWDLEN1 = 0; /* Receive word length = 8 bits */

    regs->RCR2.bit.RPHASE   = 0; /* Single-phase frame */
    regs->RCR2.bit.RFRLEN2  = 0; /* Ignored when RPHASE=0 */
    regs->RCR2.bit.RWDLEN2  = 0; /* Ignored when RPHASE=0 */
    regs->RCR2.bit.RCOMPAND = 0; /* No companding, any size data, MSB received first */
    regs->RCR2.bit.RFIG     = 0; /* Frame-synchronization detect */
    regs->RCR2.bit.RDATDLY  = 1; /* 1-bit data delay */

    regs->XCR1.bit.XFRLEN1 = 0; /* Transmit frame length of 1 word */
    regs->XCR1.bit.XWDLEN1 = 0; /* Transmit word length = 8 bits */

    regs->XCR2.bit.XPHASE   = 0; /* Single-phase frame */
    regs->XCR2.bit.XFRLEN2  = 0; /* Ignored when XPHASE=0 */
    regs->XCR2.bit.XWDLEN2  = 0; /* Ignored when XPHASE=0 */
    regs->XCR2.bit.XCOMPAND = 0; /* No companding, any size data, MSB received first */
    regs->XCR2.bit.XFIG     = 0; /* Frame-synchronization detect */
    regs->XCR2.bit.XDATDLY  = 1; /* 1-bit data delay */

    regs->SRGR1.bit.FWID   = 0; /* Frame-synchronization pulse width bits for FSG */
    regs->SRGR1.bit.CLKGDV = 0x07;

    regs->SRGR2.bit.GSYNC = 0; /* No clock synchronization */
    regs->SRGR2.bit.CLKSM = 1; /* With SCLKME, use LSPCLK */
    regs->SRGR2.bit.FSGM  = 0; /* Generate transmit frame-synchronization pulse when the content of XDR is copied to XSR */
    regs->SRGR2.bit.FPER  = 0; /* Frame-synchronization period bits */

    regs->PCR.bit.FSXM   = 1; /* Transmit frame synchronization is generated internally by the sample rate generator */
    regs->PCR.bit.FSRM   = 1; /* Receive frame synchronization is generated internally by the sample rate generator */
    regs->PCR.bit.CLKXM  = 1; /* The McBSP is a master in the SPI protocol */
    regs->PCR.bit.CLKRM  = 1; /* Internal MCLKR is driven by the sample rate generator of the McBSP. */
    regs->PCR.bit.SCLKME = 0; /* With CLKSM, use LSPCLK */
    regs->PCR.bit.FSXP   = 1; /* Transmit frame-synchronization pulses are active low */
    regs->PCR.bit.FSRP   = 1; /* Receive frame-synchronization pulses are active low */
    regs->PCR.bit.CLKXP  = 1;
    regs->PCR.bit.CLKRP  = 0;

    regs->MFFINT.bit.RINT = 0; /* Receive interrupt on RRDY is disabled */
    regs->MFFINT.bit.XINT = 0; /* Transmit interrupt on XRDY is disabled */

    /* Step 4. Enable the sample rate generator. */
    regs->SPCR2.bit.GRST = 1;
    wait_two_sample_rate_generator_clock_periods(regs->SRGR1.bit.CLKGDV);

    /* Step 5. Enable the transmitter and receiver. */
	regs->SPCR2.bit.XRST = 1;
	regs->SPCR1.bit.RRST = 1;
    wait_two_sample_rate_generator_clock_periods(regs->SRGR1.bit.CLKGDV);

    /* Step 6. If necessary, enable the frame-synchronization logic of the
     * sample rate generator. */
    regs->SPCR2.bit.FRST = 1;

Transmit ISR:

static __interrupt void halo_spi_transmit_isr_mcbsp_a(void)
{
    halo_spi_t* spi = &halo_spis[halo_spi_mcbsp_a];
    uint16_t word_out;

    assert(spi->mcbsp_regs->MFFINT.bit.XINT);
    assert(spi->buf.tx_begin != spi->buf.end);
    assert(spi->mcbsp_regs->SPCR2.bit.XRDY);

    word_out = *spi->buf.tx_begin;
    spi->buf.tx_begin++;

    spi->mcbsp_regs->DXR1.all = word_out;

    /* If the transmit buffer is empty, then disable the transmit interrupts and
     * rely solely on receive interrupts to complete the asynchronous
     * transaction. */
    if (spi->buf.tx_begin == spi->buf.end)
    {
        spi->mcbsp_regs->MFFINT.bit.XINT = 0;
    }

    PieCtrlRegs.PIEACK.all = M_INT6;
}

Receive ISR:

static __interrupt void halo_spi_receive_isr_mcbsp_a(void)
{
    halo_spi_t* spi = &halo_spis[halo_spi_mcbsp_a];
    const uint16_t input_mask = (1 << halo_spi_character_size) - 1;

    assert(spi->mcbsp_regs->MFFINT.bit.RINT);
    assert(spi->mcbsp_regs->SPCR1.bit.RRDY);
    assert(spi->buf.rx_begin != spi->buf.tx_begin);

    /* TODO: Handle RFULL and RSYNCERR separately */
    assert(!spi->mcbsp_regs->SPCR1.bit.RFULL);
    assert(!spi->mcbsp_regs->SPCR1.bit.RSYNCERR);

    const uint16_t byte_in = spi->mcbsp_regs->DRR1.all & input_mask;

    /* The receive pointer is always less than the transmit pointer
     * (transmits always occur first) */
    assert(spi->buf.rx_begin < spi->buf.tx_begin);

    /* The receive pointer can never reach the end of the buffer except
     * through reading from the receive register. */
    assert(spi->buf.rx_begin < spi->buf.end);

    /* Only write into the buffer if write permissions are enabled, either
     * by async_read or async_transfer. Otherwise, just increment
     * the receive pointer. The receive pointer is the basis for determining
     * when the transfer is complete, so it must be updated and tracked even
     * if it is not used for writing into the buffer. */
    *spi->buf.rx_begin++ = byte_in;

    /* The transfer is complete when there are no more pending bytes in the
     * receive buffer. */
    if (spi->buf.rx_begin == spi->buf.end)
    {
        /* Disable interrupts for the SPI receive. There are no more characters
         * in the input buffer or the tx FIFO. One of the async operations will
         * re-enable the interrupts. */
        spi->mcbsp_regs->MFFINT.bit.RINT = 0;

io_service_post(&spi->buf.context); /* Add a node to a ready queue */ } PieCtrlRegs.PIEACK.all = M_INT6; }

  • Hello Colin,

    If you disable all other interrupts, do you still lose data? Can you confirm on an oscilloscope if you are transmitting all of your data? Can you enable internal loopback and see if the issue still persists?

    What happens when your asserts fail? It may be possible that you get the interrupt and then somehow the other code in the ISR causes the byte to be ignored. A way to test this might be to just add a simple TX and RX counter in each ISR. When you appear to lose data, you can check to see if the ISR fired.

    I hope this gives you somewhere to start.

    Regards,
    Mark
  • Mark,

    Thank you for your reply. At this point, I'm looking for any other ideas that I haven't tried. If I disable all other interrupts, I do not appear to lose data. I have confirmed on an oscilloscope that data is clocked out and the problem is still the same even with internal loopback.

    Also, running out of flash memory or out of RAM does not make a difference.

    None of the asserts fail - I put them all in there to check my assumptions about the state of the various registers and none of them trigger. The interrupt flags are on when they are supposed to be, the XRDY and RRDY flags are correct, and none of the error conditions (RFULL or RSYNCERR) are indicated. I simplified the transmit and receive ISRs a bit when I included them here - I do keep metrics on all of the interrupts (rx and tx interrupt counts, bytes received and transmitted) and can confirm that one fewer receive interrupts is generated than transmit interrupts. So the metrics end up looking like:

    • tx_bytes: 360
    • rx_bytes: 359
    • tx_interrupts: 360
    • rx_interrupts: 359

    If it were just a problem with interrupts, then I would expect IFR to not indicate that an interrupt was pending, but that RRDY would still be set to indicate that I hadn't yet pulled the most received data out of DDR1. But neither IFR nor PIEIFR indicate that a receive interrupt is pending, and RRDY is cleared so there is no data to get from DDR1.

    Even when it looks like everything is working fine, I can cause the problem to occur by using the debugger. Based on the pipeline for transmit bytes (XDR1 -> XSR1 -> MDX1) and receive bytes (RSR1 -> RBR1 -> DDR1), I get two transmit interrupts before I get the first receive interrupt. The first second transmit interrupt occurs when the first byte is being clocked out, and the first receive interrupt occurs around the time the second byte is being clocked out.

    If I suspend the execution while the receive interrupts are two behind the transmit interrupts, I miss one receive interrupt after resuming execution. If the receive interrupts are only one behind, or I have received the same number as I have transmitted, I can resume execution without a problem. If it only happened during debugging, I wouldn't care (so much), but I appear to run into the same problem during normal operation.

  • Colin,

    If you do not have the debugger connected, you still receive a missed interrupt? Can you set McBSP to Soft Stop mode? Set SPCR2.FREE =0, and SPCR2.SOFT = 1. This might help out the Emulation suspend case. it will allow the McBSP to complete the transmission before halting operation.

    Could you artificially slow down the transfer? Try first by increasing the time between each transmitted word, then alternately add time in between each byte transmit? 

    Is there any other code running on the device? Are you running TIRTOS/SYSBIOS/DSPBIOS?

    This behavior is quite odd. I am going to try and run the code locally to see if I can replicate it. How many words are typically sent each time the buffer is enabled?

    -Mark

  • Mark,

    If the debugger is connected, the problem still occurs. It also still occurs in Soft Stop mode - pausing the execution while the receive is two bytes behind the transmit still causes a missed receive interrupt.

    Putting a 1 ms delay between transmit blocks does not change the behavior. The test that I am running now sends three bytes at a time, using the transmit interrupts to pull each byte out of a buffer. The buffer is re-sent by the main loop when a flag is set after the final byte is received. When the third byte of the trio isn't received, the main loop just spins waiting for an event that won't ever come.

    However, if I put a 2 usec delay within the transmit ISR, everything appears to behave as expected. A 1 usec delay does not change the behavior, but a 2 usec delay does. Does this point to anything useful? (Although I was still able to trip it up by suspending execution at just the right time, even with the 2 usec delay.)

    There is no other code running; just the McBSP, Timer 0, and SCI interrupts. (No TIRTOS, SYSBIOS, or DSPBIOS).

    -Colin.
  • Colin,

    How about when the debugger is NOT connected? When you remove Timer 0 and SCI interrupts, the problem goes away? You would think that if you were starving the RX interrupt with the SCI or Timer ISR, you would be overwriting the DRR, and would get an overwrite flag.

    Do you consistently lose the Third byte of the burst or does it vary?

    I will not be able to test locally today. I will try to respond with some local testing by the end of the week. I will see if anyone here might have ideas as well.

    Thanks,
    Mark
  • Mark,

    When the debugger is not connected, adding that small delay seems to get past the problem. And if the SPI is the only thing that's running, it also never fails.

    I haven't tested which byte was lost; I have always assumed it was the last byte, but I will confirm.

    By the way, all of this development has been done on the TMS320F28335 Experimenter Kit. Is there any chance that this is caused by a faulty board? I have another one on order just in case.