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.

AM6442: AM6442 (R5F + FreeRTOS): SPI RX peripheral interrupt reliability issues under light system load

Part Number: AM6442
Other Parts Discussed in Thread: AM263P4

Hi all,
I’m looking for advice on how to make SPI peripheral reception more reliable on AM6442 when running on Cortex-R5F (core 0) with FreeRTOS.

Platforms affected

  • AM6442, firmware running on R5FSS0 Core 0

  • Similar behavior previously observed on AM263P4, but the issue was significantly harder to trigger there

Problem description

SPI reception works correctly when the system is mostly idle. However, once the system experiences even light additional load, SPI reception becomes unreliable and frequently fails with FIFO errors (RX overrun / TX underrun – I need to double-check which exact one is dominant).

By “load” I don’t mean heavy CPU usage — often it is enough to:

  • run a few additional FreeRTOS tasks (even if total non-idle time is low),

  • enable debug logging (CCS IDE print / logging),

  • or generally increase task scheduling activity.

On AM6442, the issue can appear even at ~5% non-idle time.
On AM263P4, the same firmware required noticeably higher activity before showing similar symptoms.

SPI configuration / traffic characteristics

  • SPI clock: 17 MHz

  • Transfer period: every 100 ms (≈10 FPS)

  • Payload size: fixed 2012 bytes

  • Interface: 3-wire SPI (no dedicated CS pin)

Reception is interrupt-driven. I would like to keep it that way (i.e. no polling / foreground blocking).

Observed behavior

  • SPI data is known to be present on the bus.

  • Under load, the SPI RX interrupt appears to be serviced too late or not serviced at all, resulting in FIFO errors.

  • Once this happens, communication breaks down.

What I have tried (without success)

1) Increasing interrupt priority

  • Tried multiple SPI interrupt priority levels.

  • Ensured the priority still allows FreeRTOS FromISR API usage.

  • Did not improve reliability.

2) FreeRTOS interrupt priority masking

  • Rebuilt FreeRTOS with priority masking enabled using:
    EN_MAX_SYSCALL_INTR_PRI_CRIT_SECTION

  • Tested different interrupt priority configurations.

  • Did not observe any improvement.

3) FIQ Interrupts

  • Made the SPI peripheral interrupt a FIQ interrupt instead of IRQ.

  • SPI reception is totally broken, with this interrupt. It appears that the driver doesn't receive any data at all.

What I’m asking

I’d appreciate guidance on how to make SPI peripheral reception robust and reliable on AM6442 R5F, specifically:

  1. Is this a known limitation or common issue on AM64x R5F when using SPI RX interrupts under FreeRTOS?

  2. Are there recommended SPI FIFO thresholds, interrupt trigger levels, or driver settings that improve tolerance to interrupt latency?

  3. Is DMA strongly recommended for SPI RX on this platform for reliable operation, even at moderate data rates like this?

  4. Are CCS debug prints / logging known to introduce enough latency or critical sections to break time-sensitive SPI RX?

  5. Are there R5F-specific or VIM-specific interrupt configuration pitfalls that could explain why the SPI ISR is not serviced in time?

My goal is to achieve near-100% reliable SPI reception without occupying the foreground (interrupt-driven or DMA-based solutions are fine).

If needed, I can provide:

  • SPI driver configuration details (FIFO, thresholds, driver API),

  • interrupt numbers and priorities,

  • whether this uses TI MCU+ SDK SPI drivers or a custom implementation.

Thanks in advance for any insights or best-practice recommendations.

  • Hello,

    Reception is interrupt-driven. I would like to keep it that way (i.e. no polling / foreground blocking).

    So the configuration is in Interrupt mode and callback mode. Correct my understanding.

    SPI driver configuration details (FIFO, thresholds, driver API),

    Please share with me the MCSPI configuration, you can simply share the example.syscfg file and I can retrieve the configuration of SPI.

    whether this uses TI MCU+ SDK SPI drivers or a custom implementation.

    Please share this as well.

    Regards,

    Vaibhav

  • MCSPI syscfg:

    The IRQ handler:

    #define INTC_BASE_ADDR 0x2FFF0000u
    
    #define RADAR_SPI_INTR_NUM 204
    
    const MCSPILLD_Handle radarSpiMcspiHandle = &gMcspiObject[RADAR_SLAVE_SPI];
    const uint32_t radarSpiMcspiIntrNum = RADAR_SPI_INTR_NUM;
    const uint32_t radarSpiMcspiVimStsAddr = INTC_BASE_ADDR + (0x404U + (((RADAR_SPI_INTR_NUM) >> 5) & 0xFU) * 0x20U);
    const uint32_t radarSpiMcspiVimClrMask = 0x1U << ((RADAR_SPI_INTR_NUM) &0x1FU);
    
    
    __attribute__((__section__(".text.hwi"), noinline, naked, target("arm"), aligned(4))) void spi_hal_radar_isr(void)
    {
    ISR_CALL_LEVEL_NONFLOAT_REENTRANT(MCSPI_lld_peripheralIsr,
        radarSpiMcspiHandle,
        radarSpiMcspiIntrNum,
        radarSpiMcspiVimStsAddr,
        radarSpiMcspiVimClrMask,
        intcBaseAddr);
    }
    
    


  • Hello Kacper,

    Looks like for the 3 pin mode, only RX ONLY mode has been tested for. 

    Please refer the test folder in the github repository, this will help you understand already existing and tested combinations of Peripheral mode with other combinations like number of wires(3/4), modes and so on.

    Here is the link to it: https://github.com/TexasInstruments/mcupsdk-core/blob/next/test/drivers/mcspi/mcspi_controller_peripheral/test_mcspi_peripheral.c

    Let me knwo if you find something useful, else we can do a deep dive into the drivers to improve the same.

    Regards,

    Vaibhav

  • Hi,

    Following up on my previous thread about SPI RX reliability issues, I've made several
    changes based on suggestions:

    Changes Made:

    1. Switched to DMA mode as an experiment to see if it's more reliable than interrupt mode
    2. Changed to RX-only mode as you suggested - I see no difference in operation between
    RX-only and TX+RX modes
    3. Started with TI's mcspi_loopback_dma_lld FreeRTOS example as a reference to compare
    with my implementation

    Initial Testing:

    The TI example worked perfectly - but it only performs <10 transfers then exits. I
    modified it to run infinitely (continuous SPI RX transfers in an endless loop) to match
    my actual use case.

    Problem Description:

    After the modification, the system now hangs in the ISR polling loop at
    mcspi_dma_udma.c:787:

    while ((effByteCnt != peerData) && (elapsedTicks < transaction->timeout))
    {
        retVal += Udma_getPeerData(rxChHandle, &peerData);
        elapsedTicks = hMcspiInit->clockP_get() - startTicks;
    }

    Specific Symptoms:

    - effByteCnt = 2012 (the correct transfer size)
    - peerData = 2316 (consistently +304 bytes more than expected)
    - The condition effByteCnt != peerData is always true, causing infinite polling
    - The timeout never triggers because the FreeRTOS tick interrupt is starved while
    spinning in this ISR
    - Sometimes the system gets unstuck and continues for a while, but then hangs again in
    the same loop
    - The hang typically occurs after 10-20 successful transfers

    What I've tried:

    I attempted to use a FreeRTOS build where the tick interrupt has the highest priority
    (hoping the ISR would timeout properly), but the tick interrupt is still starved - the
    polling loop prevents it from firing.

    Questions:

    1. Why does peerData consistently show +304 bytes? Is this a known overhead in the PEER4
    register on AM64x? The value is constant across all failing transfers.
    2. How can I fix this? Since I'm on AM6442 (not AM65x), the isCqRingMem option to bypass
    this polling loop isn't available. The polling loop in ISR context appears unavoidable,
    but it assumes peerData will match effByteCnt.
    3. Is the +304 byte offset expected behavior? Should the comparison account for this
    offset, or is this indicating a configuration issue?

    Any guidance would be greatly appreciated!

    Hardware: AM6442 SK-EVM
    SDK: MCU+ SDK 11.00.00.15
    Mode: SPI peripheral (slave), RX-only, DMA with UDMA
    Transfer size: 2012 bytes

    The code that I'm running:

    #include <kernel/dpl/DebugP.h>
    #include <kernel/dpl/HwiP.h>
    #include <kernel/nortos/dpl/r5/HwiP_armv7r_vim.h>
    
    #include "osal/eventgroup.h"
    
    #include "ti_board_open_close.h"
    #include "ti_drivers_config.h"
    #include "ti_drivers_open_close.h"
    
    /* MCSPI Channel Macro */
    #define APP_MCSPI_MSGSIZE            (2012U)
    #define APP_MCSPI_TRANSFER_LOOPCOUNT (10U)
    
    /* Event bits for SPI transfer synchronization */
    #define SPI_EVENT_TRANSFER_DONE  0x01U
    #define SPI_EVENT_TRANSFER_ERROR 0x02U
    
    static Osal_Eventgroup_t gSpiEvents;
    
    uint8_t gMcspiTxBuffer[APP_MCSPI_TRANSFER_LOOPCOUNT][APP_MCSPI_MSGSIZE]
        __attribute__((aligned(CacheP_CACHELINE_ALIGNMENT)));
    uint8_t gMcspiRxBuffer[APP_MCSPI_TRANSFER_LOOPCOUNT][APP_MCSPI_MSGSIZE]
        __attribute__((aligned(CacheP_CACHELINE_ALIGNMENT)));
    
    void* mcspi_dma_lld_main(void* args)
    {
        int32_t status;
        uint32_t count;
        uint32_t timeout = MCSPI_WAIT_FOREVER;
        MCSPI_ExtendedParams extendedParams;
        uint32_t frameCount = 0;
    
        DebugP_log("[MCSPI] Example started ...\r\n");
    
        /* Initialize event group for transfer synchronization */
        Eventgroup_Result evt_result = Eventgroup_init(&gSpiEvents);
        if (EVENTGROUP_OK != evt_result || gSpiEvents.eventgroupHandle == 0)
        {
            DebugP_log("ERROR: Failed to create event group\r\n");
            return NULL;
        }
    
        /* populate extended parameters */
        extendedParams.channel = gRadarSlaveSpiChCfg[0].chNum;
        extendedParams.csDisable = TRUE;
        extendedParams.dataSize = 8;
        count = APP_MCSPI_MSGSIZE / (extendedParams.dataSize / 8);
    
        /* Run infinite loop with circular buffer */
        while (1)
        {
            uint32_t bufIndex = frameCount % APP_MCSPI_TRANSFER_LOOPCOUNT;
    
            /* Clear event bits before starting transfer */
            Eventgroup_clearBits(&gSpiEvents, SPI_EVENT_TRANSFER_DONE | SPI_EVENT_TRANSFER_ERROR);
    
            /* Writeback buffer */
            CacheP_wb(gMcspiTxBuffer[bufIndex], sizeof(gMcspiTxBuffer[0]), CacheP_TYPE_ALLD);
            CacheP_wb(gMcspiRxBuffer[bufIndex], sizeof(gMcspiRxBuffer[0]), CacheP_TYPE_ALLD);
    
            status = MCSPI_lld_readWriteDma(gMcspiHandle[RADAR_SLAVE_SPI],
                                            gMcspiTxBuffer[bufIndex],
                                            gMcspiRxBuffer[bufIndex],
                                            count,
                                            timeout,
                                            &extendedParams);
    
            if (status != MCSPI_STATUS_SUCCESS)
            {
                DebugP_log("ERROR: Transfer initiation failed with status %d\r\n", status);
                continue;
            }
    
            /* Wait for transfer completion with timeout */
            uint32_t event = 0;
            bool const doNotClearBitsOnExit = false;
            bool const doWaitForAnyBit = false;
            static const uint32_t timeout_ms = 1000; /* 1 second timeout */
    
            evt_result = Eventgroup_waitForBits(&gSpiEvents,
                                                SPI_EVENT_TRANSFER_DONE | SPI_EVENT_TRANSFER_ERROR,
                                                doNotClearBitsOnExit,
                                                doWaitForAnyBit,
                                                timeout_ms,
                                                &event);
    
            /* Invalidate cache after DMA completes */
            CacheP_inv(gMcspiTxBuffer[bufIndex], sizeof(gMcspiTxBuffer[0]), CacheP_TYPE_ALLD);
            CacheP_inv(gMcspiRxBuffer[bufIndex], sizeof(gMcspiRxBuffer[0]), CacheP_TYPE_ALLD);
    
            if (evt_result != EVENTGROUP_OK)
            {
                DebugP_log("ERROR: Transfer timeout\r\n");
                continue;
            }
    
            if (event & SPI_EVENT_TRANSFER_ERROR)
            {
                DebugP_log("ERROR: Transfer error detected\r\n");
                continue;
            }
    
            /* Increment frame count and log on buffer wrap-around */
            frameCount++;
            if ((frameCount % 10) == 0)
            {
                DebugP_log("%u\r\n", frameCount);
            }
        }
    
        return NULL;
    }
    
    void radarSpiTransferCallback(void* args, uint32_t transferStatus)
    {
        if (transferStatus == MCSPI_TRANSFER_COMPLETED)
        {
            Eventgroup_setBitsFromISR(&gSpiEvents, SPI_EVENT_TRANSFER_DONE);
        }
        else
        {
            Eventgroup_setBitsFromISR(&gSpiEvents, SPI_EVENT_TRANSFER_ERROR);
        }
    }
    
    void radarSpiErrorCallback(void* args)
    {
        Eventgroup_setBitsFromISR(&gSpiEvents, SPI_EVENT_TRANSFER_ERROR);
    }
    
    

  • Hello,

    Just a quick heads up, if you want all the transfers to happen under just 1 Chip Select and you do not want the chip select to deassert between multiple MCSPI transfers, then you can set:

     extendedParams.csDisable = TRUE;

    to

     extendedParams.csDisable = FALSE;

    I would also recommend you to test the same example which you have built, but by using the latest SDK 11.2, which has release the mid of this month, December.

    It has lots of fixes, from FIFO issues to timeout implementations.

    Here is the link to the latest SDK: www.ti.com/.../11.02.00.24

    Regards,

    Vaibhav

  • Hello Vaibhav,

    Yes, I tied both with `csDisable = TRUE` and `csDisable = FALSE`, but it doesn't make a difference.

    Also with the newest SDK, the issue is still the same.

    Could you help me dive a little bit deeper into this topic? I really need this issue to be resolved.

    Kind regards,

    Kacper

  • Hi Kacper,

    The TI example worked perfectly - but it only performs <10 transfers then exits. I
    modified it to run infinitely (continuous SPI RX transfers in an endless loop) to match
    my actual use case.

    Upon transferring 2012 bytes less than 10 number of times, it worked correctly, but when running in an endless loop, it gets stuck at the following line: 

    https://github.com/TexasInstruments/mcupsdk-core/blob/next/source/drivers/mcspi/v0/lld/dma/udma/mcspi_dma_udma.c#L787

    Please correct my understanding.

    Could you also tell me if this behaviour is seen consistently at the nth transfer, or it is seen randomly at any transfer?

    Looking forward to your response.

    Meanwhile, I will try incorporating the same example and receive some bytes in a similar fashion.

    Regards,

    Vaibhav