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.

MSPM0G3507: How to receive large block of data using SPI in DMA mode?

Part Number: MSPM0G3507

Tool/software:

Hello experts,

I'have to develop a comunication via SPI + DMA from MSPM0G3507 to a FRAM (MB85RS256B). Page 9 of the FRAM's datasheet has the text ..."Risen CS will terminate the WRITE command. However if you continue sending the writing data for 8 bits each before CS rising, it is possible to continue writing with automatic address increment".

I'm able to write and read individual values to/from the FRAM without the DMA.

Well, I started to use the example spi_controller_repeated_fifo_dma_interrupts_LP_MSPM0G3507_nortos_ticlang, the only change I did was: SPI clock, SPI pins, and setting a 3-wire SPI (controlling CS using the GPIO). I keept all the DMA configuration.

Citing Bruce McKenney, I understand that "SPI is intrinsically full-duplex -- for every byte you transmit, you receive one; and, as in this case, if you want to receive a byte you must send one. We refer to "transmit only" and "receive only" transactions, but those only mean that the other end isn't interested in what the data is."

My application will need to write and read a big chunk of data but for now I'm doing tests with 30 samples (baby steps). So, I separete my code in 2 parts:

Part 1) write data to FRAM: I adapted gTxPacket to incorporate the op-codes (WREN and WRITE), start address and the data that needs to be written. Also, gTxPacket is in a one-byte format (uint8_t).
I'm not interested in data coming from the RXFIFO, its trash.
The write part looks okay, I have a timer which counts 1s for the start of transfer. I checked CS, CLK, and PICO on an oscilloscope. CLK is pretty hard to see, CS is lasting ~16us at low and I know that to write 16 bits without DMA takes ~6.6us.

Part 2) read data from FRAM: I adapted gTxDummy to incorporate the op-code (READ), start address and the dummy data that needs to be written in order to generate the clocks. Also, gTxDummy is in a one-byte format (uint8_t).
The read part does not look okay. I'm VERY interested in the data coming from the RXFIFO but when I check my gRxPacket, it has 255 (0xFF) in every position (I cleared it at the begin of the main()). It should have the same content as gTxPacket.

void SPI_send(void)
{
    // Part 1: writing data to FRAM

    /*
     * Configure DMA source, destination and size to transfer data from
     * gTxPacket to TXDATA. The DMA transfer will start when TX interrupt is
     * set, which it should already be set (which indicates that the SPI is
     * ready to transfer) so the DMA will begin this transfer when the channel
     * is enabled.
     */
    DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &gTxPacket[0]);
    DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&SPI_0_INST->TXDATA));
    DL_DMA_setTransferSize(
        DMA, DMA_CH0_CHAN_ID, sizeof(gTxPacket) / sizeof(gTxPacket[0]));

    /*
     * Configure DMA source, destination and size from RXDATA to gRxPacket.
     * The DMA transfer will start when the RX interrupt is set, which happens
     * when the device receives data.
     */
    DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t)(&SPI_0_INST->RXDATA));
    DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) &gRxPacket[0]);
    DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, ARRAY_SIZE);

    DL_GPIO_clearPins(GPIO_PORT, GPIO_CS_SPI_PIN);

    /*
     * The SPI TX interrupt is already set, indicating the SPI is ready to
     * transmit data, so enabling the DMA will start the transfer
     */
    DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);
    DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);  

    /*
     * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
     * from gTxPacket to the SPI TXFIFO, and the DMA interrupt is triggered
     */
    while (false == isDMATXDataTransferred) {
        __WFE();
    }

    /*
     * Wait until the SPI has transmitted all the data and the TXFIFO
     * is empty
     */
    while (false == isSPIDataTransmitted) {
        __WFE();
    }

    /*
     * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
     * from SPI TXFIFO to gRxPacket, and the DMA interrupt is triggered
     */
    while (false == isDMARXDataTransferred) {
        __WFE();
    }

    /*
     * Optional SW breakpoint to check results.
     * If this example is used with the
     * spi_peripheral_repeated_fifo_dma_interrupts example,
     * the expected data that will be received in gRxPacket is
     * {'x', 0x2, 0x3, 0x4}, where 'x' starts at 0x0 and
     * should increment every time the Peripheral example
     * sends a new data packet.
     */
    //        __BKPT(0);

    isSPIDataTransmitted   = false;
    isDMATXDataTransferred = false;
    isDMARXDataTransferred = false;

    DL_GPIO_setPins(GPIO_PORT, GPIO_CS_SPI_PIN);

    // Part 2: reading data from FRAM

    // Sugestion from Chris https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/1439766/mspm0g3507-cant-get-spi-to-automatically-clock-in-receive-data-in-dma-mode
    
    /*
     * Configure DMA source, destination and size to transfer data from
     * tx_pbuf to TXDATA. The DMA transfer will start when TX interrupt is
     * set, which it should already be set (which indicates that the SPI is
     * ready to transfer) so the DMA will begin this transfer when the channel
     * is enabled.
     */
    DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &gTxDummy[0]);
    DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&SPI_0_INST->TXDATA));
        DL_DMA_setTransferSize(
        DMA, DMA_CH0_CHAN_ID, sizeof(gTxDummy) / sizeof(gTxDummy[0]));

    NVIC_EnableIRQ(SPI_0_INST_INT_IRQN);

    // enable chip select
    DL_GPIO_setPins(GPIO_PORT, GPIO_CS_SPI_PIN);
   
    /*
     * The SPI TX interrupt is already set, indicating the SPI is ready to
     * transmit data, so enabling the DMA will start the transfer
     */
    DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);

    /*
     * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
     * from tx_pbuf to the SPI TXFIFO, and the DMA interrupt is triggered
     */
    while (false == isDMATXDataTransferred) {
        __WFE();
    }

    /*
     * Wait until the SPI has transmitted all the data and the TXFIFO
     * is empty
     */
    while (false == isSPIDataTransmitted) {
        __WFE();
    }

    // dummy read clears spi data waiting interrupt caused by spi tx
    DL_SPI_receiveData8(SPI_0_INST);
   
    /*
     * Configure DMA source, destination and size from RXDATA to rx_pbuf.
     * The DMA transfer will start when the RX interrupt is set, which happens
     * when the device receives data.
     */
    DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t)(&SPI_0_INST->RXDATA));
    DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) &gRxPacket[0]);
    DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, ARRAY_SIZE);
    DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);

    /*
     * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
     * from SPI TXFIFO to rx_pbuf, and the DMA interrupt is triggered
     */
    /*while ((false == gDMARXDataTransferred) && (ARRAY_SIZE > 0)) {
        // generates sclks until ARRAY_SIZE requested number of bytes have been read
        DL_SPI_transmitData8(SPI_0_INST, 0x00);
    }*/
}

void SPI_0_INST_IRQHandler(void)
{
    switch (DL_SPI_getPendingInterrupt(SPI_0_INST)) {
        case DL_SPI_IIDX_DMA_DONE_TX:
            /* DMA is done transferring data from gTxPacket to TXFIFO */
            isDMATXDataTransferred = true;
            break;
        case DL_SPI_IIDX_TX_EMPTY:
            /* SPI is done transmitting data and TXFIFO is empty */
            isSPIDataTransmitted = true;
            break;
        case DL_SPI_IIDX_DMA_DONE_RX:
            /* DMA is done transferring data from RXFIFO to gRxPacket*/
            isDMARXDataTransferred = true;
        default:
            break;
    }
}

I looked into other post here (e2e.ti.com/.../mspm0g3507-cant-get-spi-to-automatically-clock-in-receive-data-in-dma-mode). I implemented it, but the solution Chris kindly suggested is not working either.

I also checked:

e2e.ti.com/.../mspm0g3507-dma-enabled-spi-communication-with-ads8684a

e2e.ti.com/.../mspm0g3507-spi-dma-receive-abnormal

e2e.ti.com/.../ads1258-ep-using-dma-with-ads1258

Please, could someone give me any advice or suggestion to fix my reading part of the code?
Thanks,
Andrea

  • The WREN needs to be sent in a separate transaction (framed by /CS). From your description it sounds like you're sending it all in one transaction.

  • Hey Bruce, 

    Well, I separated the WREN and still not working using Chris suggestion (from his post).

    Than, I started to do some tests which I'm repeating the same code I used to write to read (except I'm using the gTxDummy).

    I'm attaching one figure, I "think" I can see the writing part of the process but not the reading.

    I'm attaching my code here, it would be great if you could run and have your impressions about it. You can keep the syscfg file from the example, you only have to add a timer (with interruption every 1 second).

    #include "FRAM_SPI.h"
    #include "ti/driverlib/dl_adc12.h"
    #include "ti/driverlib/dl_comp.h"
    #include "ti/driverlib/dl_gpio.h"
    #include "ti/driverlib/dl_timera.h"
    #include "ti/driverlib/dl_timerg.h"
    //#include "ti/driverlib/dl_uart_main.h"
    #include "ti_msp_dl_config.h"
    #include <stdio.h>
    #include <string.h>
    
    #define ARRAY_SIZE 30
    
    void cleargRxPacket();
    void creategTxPacket();
    void creategTxDummy();
    void FRAM_WREN();
    void SPI_send();
    
    uint8_t gTxPacket[ARRAY_SIZE];
    uint8_t gRxPacket[ARRAY_SIZE];
    uint8_t gTxDummy[ARRAY_SIZE];
    uint8_t trash = 0;
    
    volatile bool isSPIDataTransmitted, isDMATXDataTransferred,
        isDMARXDataTransferred, isTransmitReady;
    
    int main(void)
    {
    
        isSPIDataTransmitted   = false;
        isDMATXDataTransferred = false;
        isDMARXDataTransferred = false;
        isTransmitReady = false;
    
        SYSCFG_DL_init();
    
        cleargRxPacket();
        creategTxPacket();
        creategTxDummy();
    
        DL_GPIO_clearPins(GPIO_PORT, GPIO_LED_1_PIN | GPIO_EN_MUX_PIN | GPIO_SEL_MUX_PIN); // necessary to access the FRAM
    
        DL_GPIO_setPins(GPIO_PORT, GPIO_CS_SPI_PIN);
        
        NVIC_EnableIRQ(SPI_TIMER_INST_INT_IRQN);
        NVIC_EnableIRQ(SPI_0_INST_INT_IRQN);
    
        while(1)
        { 
                      
            if(isTransmitReady){
                isTransmitReady = false;
                SPI_send();
            } else {
                __WFI();
            }
    
        }
    
    }
    
    // clear gRxPacket
    void cleargRxPacket(void) {
      for (uint16_t i = 0; i < ARRAY_SIZE; i++) {
        gRxPacket[i] = 0;
      }
    }
    
    // create gTxPacket
    void creategTxPacket(void) {
    
        gTxPacket[0] = 0x02; // op-code write
        gTxPacket[1] = 0x00; // LSB startAddress
        gTxPacket[2] = 0x00; // MSB startAddress
    
        uint16_t offset = 3;
        for (uint16_t i = offset; i < ARRAY_SIZE/2 ; i ++) {
    
            uint16_t aux = i;
            gTxPacket[2 * i - offset] = ((aux - offset) >> 8) & 0xFF; // MSB
            gTxPacket[2 * i - offset + 1] = (aux - offset) & 0xFF; // LSB
        }
    }
    
    // create gTxDummy
    void creategTxDummy(void) {
      for (uint16_t i = 3; i < ARRAY_SIZE; i++) {
    
        gTxDummy[0] = 0x03; // op-code read
        gTxDummy[1] = 0x00; // LSB startAddress
        gTxDummy[2] = 0x00; // MSB startAddress
        gTxDummy[i] = 0xFF; // dummy
      }
    }
    
    void FRAM_WREN(void)
    {
        uint8_t notUsed = 0;
    	DL_GPIO_clearPins(GPIO_PORT, GPIO_CS_SPI_PIN);
        DL_SPI_transmitDataBlocking8(SPI_0_INST, 0x06);
        while (DL_SPI_isBusy(SPI_0_INST));
        notUsed = DL_SPI_receiveDataBlocking8(SPI_0_INST);
    	DL_GPIO_setPins(GPIO_PORT, GPIO_CS_SPI_PIN);
    }
    
    void SPI_send(void)
    {
        // Part 1: writing data to FRAM
    
        FRAM_WREN(); // Send WREN first
    
        /*
         * Configure DMA source, destination and size to transfer data from
         * gTxPacket to TXDATA. The DMA transfer will start when TX interrupt is
         * set, which it should already be set (which indicates that the SPI is
         * ready to transfer) so the DMA will begin this transfer when the channel
         * is enabled.
         */
        DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &gTxPacket[0]);
        DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&SPI_0_INST->TXDATA));
        DL_DMA_setTransferSize(
            DMA, DMA_CH0_CHAN_ID, sizeof(gTxPacket) / sizeof(gTxPacket[0]));
    
        /*
         * Configure DMA source, destination and size from RXDATA to gRxPacket.
         * The DMA transfer will start when the RX interrupt is set, which happens
         * when the device receives data.
         */
        DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t)(&SPI_0_INST->RXDATA));
        DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) &gRxPacket[0]);
        DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, ARRAY_SIZE);
    
        DL_GPIO_clearPins(GPIO_PORT, GPIO_CS_SPI_PIN);
    
        /*
         * The SPI TX interrupt is already set, indicating the SPI is ready to
         * transmit data, so enabling the DMA will start the transfer
         */
        DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);
        DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);  
    
        /*
         * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
         * from gTxPacket to the SPI TXFIFO, and the DMA interrupt is triggered
         */
        while (false == isDMATXDataTransferred) {
            __WFE();
        }
    
        /*
         * Wait until the SPI has transmitted all the data and the TXFIFO
         * is empty
         */
        while (false == isSPIDataTransmitted) {
            __WFE();
        }
    
        /*
         * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
         * from SPI TXFIFO to gRxPacket, and the DMA interrupt is triggered
         */
        while (false == isDMARXDataTransferred) {
            __WFE();
        }
    
        isSPIDataTransmitted   = false;
        isDMATXDataTransferred = false;
        isDMARXDataTransferred = false;
    
        DL_GPIO_setPins(GPIO_PORT, GPIO_CS_SPI_PIN);
    
        // Part 2: reading data from FRAM
       
        /*
         * Configure DMA source, destination and size to transfer data from
         * gTxDummy to TXDATA. The DMA transfer will start when TX interrupt is
         * set, which it should already be set (which indicates that the SPI is
         * ready to transfer) so the DMA will begin this transfer when the channel
         * is enabled.
         */
        DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &gTxDummy[0]);
        DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&SPI_0_INST->TXDATA));
        DL_DMA_setTransferSize(
            DMA, DMA_CH0_CHAN_ID, sizeof(gTxDummy) / sizeof(gTxDummy[0]));
    
        /*
         * Configure DMA source, destination and size from RXDATA to gRxPacket.
         * The DMA transfer will start when the RX interrupt is set, which happens
         * when the device receives data.
         */
        DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t)(&SPI_0_INST->RXDATA));
        DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) &gRxPacket[0]);
        DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, ARRAY_SIZE);
    
        DL_GPIO_clearPins(GPIO_PORT, GPIO_CS_SPI_PIN);
    
        /*
         * The SPI TX interrupt is already set, indicating the SPI is ready to
         * transmit data, so enabling the DMA will start the transfer
         */
        DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);
        DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);  
    
        /*
         * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
         * from gTxPacket to the SPI TXFIFO, and the DMA interrupt is triggered
         */
        while (false == isDMATXDataTransferred) {
            __WFE();
        }
    
        /*
         * Wait until the SPI has transmitted all the data and the TXFIFO
         * is empty
         */
        while (false == isSPIDataTransmitted) {
            __WFE();
        }
    
        /*
         * Wait in SLEEP mode until SPI_PACKET_SIZE bytes have been transferred
         * from SPI TXFIFO to gRxPacket, and the DMA interrupt is triggered
         */
        while (false == isDMARXDataTransferred) {
            __WFE();
        }
    
        DL_GPIO_setPins(GPIO_PORT, GPIO_CS_SPI_PIN);
    }
    
    void SPI_0_INST_IRQHandler(void)
    {
        switch (DL_SPI_getPendingInterrupt(SPI_0_INST)) {
            case DL_SPI_IIDX_DMA_DONE_TX:
                /* DMA is done transferring data from gTxPacket to TXFIFO */
                isDMATXDataTransferred = true;
                break;
            case DL_SPI_IIDX_TX_EMPTY:
                /* SPI is done transmitting data and TXFIFO is empty */
                isSPIDataTransmitted = true;
                break;
            case DL_SPI_IIDX_DMA_DONE_RX:
                /* DMA is done transferring data from RXFIFO to gRxPacket*/
                isDMARXDataTransferred = true;
            default:
                break;
        }
    }
    
    void SPI_TIMER_INST_IRQHandler(void)
    {
        switch (DL_TimerG_getPendingInterrupt(SPI_TIMER_INST)) {
            case DL_TIMERG_INTERRUPT_ZERO_EVENT:
                isTransmitReady = true;
                DL_TimerG_stopCounter(SPI_TIMER_INST); // not sure it is necessary
                DL_TimerG_startCounter(SPI_TIMER_INST); // start counter 
                //printf("isTransmitReady! \n"); 
                printf("gTxPacket[24] = %d \n", gTxPacket[24]);
                printf("gRxPacket[24] = %d \n", gRxPacket[24]);
                break;
            default:
                break;
        }
    }

    I know, you don't have a FRAM but any thoughts will help!

    Thanks,

    Andrea

  • Have you also tried writing to the FRAM as shown in your code, then reading each byte back one by one, to see if sending all the bytes as a big block with DMA worked, before attempting to read as one big block as well?

    Might be worthwhile to check. I see that you've tried individual reads and writes, since the block read and block write dont work together, try one at a time to isolate the issue.

  • Hi,

    I'm doing more tests and I found some interesting things using breakpoints.

    First time I run SPI_send(), write and read is working fine (below figure), my gRxPacket is the same as my gTxPacket (line 191).

    Second time I run SPI_send() I'm storing "trash" in my gRxPacket (line 131). I know, it is stupid. I create a gRxTrash to receive these data. It is fixed now.

    Most intriguing is that after the first run it does not work anymore. I don't see the CS, CLK, and POCI for the reading part. The oscilloscope data looks like the figure I sent before.

    Please, could someone give some help to try to fix why it only works once?

    Thanks again,

    Andrea

  • Are you using a commercial breakout board? I'm particularly interested in how /WP and /HOLD are connected.

    On the Adafruit board, they're pulled up, but we had someone here a few weeks ago with a breakout where those were floating.

  • It looks like you're not clearing the completion booleans (notably isDMARXDataTransferred) after the read transaction. That would mean that on the second call to SPI_send you're raising /CS too early which truncates the write but the DMA/SPI keeps running. 

    I didn't find a statement, but based on the DMA's similarity to that in the MSP430, I expect the DMA control registers are effectively read-only while the DMA is busy, so the read transaction never happens.

    The fix (as you guessed) is to set those variables back to false after the read transaction.

  • I had been constructing a small library for SPI EEPROMs based on TI's App Note SLAA208, so I thought it might be timely to polish and publish it.

    It hasn't been heavily exercised, but it has been run on an MB85RS1 as well as an M95P32. Maybe there's something here you can use:

    https://github.com/brucemckenney/spi-eeprom-mspm0

  • Hey Bruce,

    Thanks man, you nailed it. Well, I didn't guess and you were right, I was forgetting to set the variables back to false.

    Just answering your previous question, we have our own board and the WP and HOLD have pull-up resistors to 3V3.

    Thanks a lot!

    Andrea

  • Thanks Bruce for sharing,
    Looks cool, I'll look into it and see how much I can use it. Looks like high level coding, I'll give it a try ;)
    Thanks again,
    Andrea