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.

MSP432P401R: How to transmit faster on SPI?

 

Part Number: MSP432P401R

Hello! I am using MSP432 to communicate with ADS131A04 ADC and I want to use the ADC at 10.24kHz data rate.

The problem is that I cannot read the channel data fast enough before new data is available. At 10.24kHz, I need to read in maximum ~100us. In the read function I wait in a while loop for the next DRDY interrupt.

I have attached the following logic analyzer captures (they can be opened using the free Salae Logic Pro software; channel 4 is DRDY):

1. 1kHz data rate, 1MHz SPI clock - byte time is 8us, time between bytes is ~8us and word time ~314us;

2. 1kHz data rate, 4MHz SPI clock - byte time is 4us, time between bytes is ~10us and word time ~253us;

3. 1kHz data rate, 8MHz SPI clock - byte time is 1us, time between bytes is ~7us and word time ~170us;

4. 10.24kHz data rate, 8MHz SPI clock - byte time is 1us, time between bytes is ~7us and word time ~170us; you can see that DRDY changes before reading all data;

You can find attached an oscilloscope screen capture showing MISO at 1kHz data rate, 24MHz SPI clock. Those lines are the 20 bytes that compose the word and the transmission duration of a word is ~170us.

I need to have a word transmission time smaller than 100us to be able to read the next samples in time. 24MHz is the maximum SPI clock MSP432 can provide and 25MHz is the maximum SPI clock accepted by the ADC. 7us means 336 cycles for MSP432 at 48MHz. Where are these cycles spent?

This is my code:

for (i = 0; i < noSamples; i++)
    {
        while (adcSampleAvailable == false) // Wait for !DRDY
        {
            //MAP_PCM_gotoLPM0InterruptSafe();
            //MAP_Interrupt_enableMaster();
        }
        adcSampleAvailable = false;

        // Read samples
        rxData = ADS131A04_read(20); // STAT_1 x 4 bytes + 4 channels x 4 bytes

        *(readDataCh1 + i) = (*(rxData + 4) << 16) | (*(rxData + 5) << 8) | (*(rxData + 6));
        *(readDataCh2 + i) = (*(rxData + 8) << 16) | (*(rxData + 9) << 8) | (*(rxData + 10));
        *(readDataCh3 + i) = (*(rxData + 12) << 16) | (*(rxData + 13) << 8) | (*(rxData + 14));
        *(readDataCh4 + i) = (*(rxData + 16) << 16) | (*(rxData + 17) << 8) | (*(rxData + 18));
    }

unsigned char* ADS131A04_read(unsigned char bytesNumber)
{
    MAP_GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN4); // !ADC_SPI_CS = 0

    int byte = 0;
    int count = bytesNumber;
    if (bytesNumber > ADS131A04_MAX_RX_BYTES) count = ADS131A04_MAX_RX_BYTES;

    for(byte = 0; byte < count; byte++)
    {
        while (adcTxFlag == false)
        {
            //MAP_PCM_gotoLPM0InterruptSafe();
            //MAP_Interrupt_enableMaster();
        }
        adcTxFlag = false;

        MAP_SPI_transmitData(EUSCI_B0_BASE, 0x00); // Dummy write to clock out data

        while (adcRxFlag == false)
        {
            //MAP_PCM_gotoLPM0InterruptSafe();
            //MAP_Interrupt_enableMaster();
        }
        adcRxFlag = false;

        rxData[byte] = MAP_SPI_receiveData(EUSCI_B0_BASE);
    }

    MAP_GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN4); // !ADC_SPI_CS = 1

    return rxData;
}

void PORT4_IRQHandler(void)
{
    uint32_t status;
    status = MAP_GPIO_getEnabledInterruptStatus(GPIO_PORT_P4);
    MAP_GPIO_clearInterruptFlag(GPIO_PORT_P4, status);
    if(status & GPIO_PIN0) // ADC !DRDY
    {
        if (MAP_GPIO_getInputPinValue(GPIO_PORT_P4, GPIO_PIN0) == 0)
        {
            adcSampleAvailable = true;
            MAP_Interrupt_disableSleepOnIsrExit();
        }
    }
}

void EUSCIB0_IRQHandler(void)
{
    unsigned int status;
    status = MAP_SPI_getEnabledInterruptStatus(EUSCI_B0_BASE);
    MAP_SPI_clearInterruptFlag(EUSCI_B0_BASE, status);
    if (status & EUSCI_B_SPI_TRANSMIT_INTERRUPT)
    {
        adcTxFlag = true;
    }
    if (status & EUSCI_B_SPI_RECEIVE_INTERRUPT)
    {
        adcRxFlag = true;
    }
    MAP_Interrupt_disableSleepOnIsrExit();
}

What can I do to read the data faster? Is it a limitation of MSP432?

Captures.zip

  • In increasing order of complexity:
    1) Dispense with the interrupts. At high SPI clock speeds, interrupts only slow you down.
    2) Dispense with the library calls (maybe they're cheap, maybe not) and check the register bits directly.
    3) Use DMA. With 20-byte transactions, the setup overhead amortizes OK. You can probably improve this by pre-setting most of the DMA block fields and doing (minimal) bookkeeping at the right time.
  • Thank you for the advice. I managed to reduce the word duration to 22us @ 24MHz SPI clock (byte time is 0.33us and time between bytes is 0.87us) without using DMA. I removed the while loops for waiting for TX/RX interrupts, disabled these interrupts, and added a 7 cycle delay between writing and reading (with fewer cycles it did not work).

    This is my code in case it would be helpful to anyone:

    unsigned char* ADS131A04_read(unsigned char bytesNumber)
    {
        MAP_GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN4); // !ADC_SPI_CS = 0
    
    	int byte = 0;
    	int count = bytesNumber;
    	if (bytesNumber > ADS131A04_MAX_RX_BYTES) count = ADS131A04_MAX_RX_BYTES;
    
    	for(byte = 0; byte < count; byte++)
    	{
            /*while (adcTxFlag == false)
            {
                //MAP_PCM_gotoLPM0InterruptSafe();
                //MAP_Interrupt_enableMaster();
            }
            adcTxFlag = false;*/
    
            //MAP_SPI_transmitData(EUSCI_B0_BASE, 0x00); // Dummy write to clock out data
            EUSCI_B0->TXBUF = 0x00; // Faster
    
            __delay_cycles(7); // 7 at 24MHz; 40 at 4MHz, 150 at 1MHz
            /*while (adcRxFlag == false) 
            {
                //MAP_PCM_gotoLPM0InterruptSafe(); 
                //MAP_Interrupt_enableMaster();
            }
            adcRxFlag = false;*/
    
            //rxData[byte] = MAP_SPI_receiveData(EUSCI_B0_BASE);
            rxData[byte] = EUSCI_B0->RXBUF; // Faster
    	}
    
    	MAP_GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN4); // !ADC_SPI_CS = 1
    
    	return rxData;
    }

  • Nicely done!

    I'll just toss this in: You should consider replacing the __delay_cycles with a "while (!(EUSCI_B0->IFG & UCRXIFG))/*EMPTY*/;"

    Doing this makes the code correct for any setting of SPI clock and optimization level, at a cost between "0" and "small".

    (Note: Reading RXBUF clears UCRXIFG. Ordinarily you'd want to check TXIFG as well, but (as you've evidently noticed) by keeping the "pipeline" size <=1 byte you've guaranteed that TXIFG is always set when you need it to be.)

    There may (or may not) be a small (< 5 clocks or so) penalty for this: If, depending on the code the compiler generates, that first fetch of IFG is less than 7 clocks later, you'll "miss" the first loop and (always) loop twice. Once, when I really cared about it, I used something like "do {__NOP();} while (!(EUSCI_B0->IFG & UCRXIFG));" with a tuned number of __NOPs in the loop. This made it "hit" the first time at top speed, but it was still correct at any other speed.

    I recommend you do the UCRXIFG check, since it will avoid many future headaches. Whether you do further fiddling depends on whether you're looking for "better" or "good enough".
  • Thank you again! Using the while loop the word duration increases from 22us to ~31us, which is acceptable.
  • That's a bigger jump than I would have expected.

    On the other hand, "good enough" has a lot to be said for it.

**Attention** This is a public forum