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.

SPI DMA Freezes when using DMA_INT0 with MSP432 Rev. C Silicon

Other Parts Discussed in Thread: MSP432WARE

I am using SPI DMA to read 18 bytes from an external IC. The IC generates a negative edge interrupt that is connected to a port IRQ.

In the PORT IRQ handler, I assert Chip Select ( CS ) low and then  enable the DMA.

In the DMA handler, I de-assert the CS high and then clear flags and copy the received data.

The main symptom is that SPI DMA gets triggered only once successfully... and then when the next port IRQ interrupt arrives, SPI-DMA does not get started: no SPI transactions occur.

I know that the PORT IRQ was executed (because the CS line goes low), however the DMA did not start the SPI transactions.

While debugging, I noticed that the UCTXIFG bit in the UCBxIFG register is 1 when the SPI successfully completes and UCTXIFG is set to 0, when the SPI DMA fails to start.

I looked at UCTXIFG after the execution of the following lines of code in the PORT IRQ handler

MAP_DMA_enableChannel(DMA_CHANNEL_1);

MAP_DMA_enableChannel(DMA_CHANNEL_0);

So it looks like the DMA did write to the eUSCI_B peripheral's TUCxxTXBUF, but for some reason the SPI peripheral did not start the transmission of the 18 bytes.

Also after reading the TRM, I know that the DMA_INT0 is a master interrupt and is different from the DMA_INT1/2/3 interrupts.

The problem is that the DMA_INT1/2/3 are mapped to other channels and I cannot use them, so I am left with DMA_INT0.

I also observed that when the SPI DMA fails and the DMA_INT0_IRQHandler gets called, then the value of INT0_SRCFLG is zeros. This is puzzling: how could the DMA_INT0_IRQHandler  be called and yet have all the bits in the source flag be reset to 0 ? I would think that at least 1 channel caused the interrupt handler to be called.

Another observation regarding DMA_INT0 is that if I do not disable the DMA_INT0 interrupt in the DMA_INT0_IRQHandler then the IRQ gets called again *even if I clear the DMA interrupt flags* . This reminds me of the DMA12 bug in the Rev B. silicon. But I think that was fixed in the production Rev C. silicon.

Also there is no way to assign DMA_INT0 to any specific trigger like a SPI RX or TX channel...

basically it is illegal to do this MAP_DMA_assignInterrupt(DMA_INT0, DMA_CHANNEL_1) because DMA_INT0 maps to all completion sources except those mapped to DMA_INT1/2/3

Ideally I would just want to map a SPI recieve (RX) DMA completion to DMA_INT0, but I guess that is not allowed.

(1) any suggestions to why the SPI DMA freezes

(2) Why am I seeing spurious interrupts on DMA_INT0 where none of the source bits are set?

here is a screen shot showing that the first DMA runs successfully , but the second one fails.

SPI CLK = 6MHz

SMCLK = 12MHz,

Clock is set to DCO = 12MHz

Here are the relevant code pieces :DMA configuration routine and Port and DMA IRQ handlers

const eUSCI_SPI_MasterConfig spiMasterConfig =
{
EUSCI_B_SPI_CLOCKSOURCE_SMCLK, // SMCLK Clock Source
6000000, // SMCLK = DCO/2 = 12MHz/2 = 6MHz
6000000, // SPICLK = 6MHz
EUSCI_B_SPI_MSB_FIRST, // MSB First
EUSCI_B_SPI_PHASE_DATA_CHANGED_ONFIRST_CAPTURED_ON_NEXT, // Phase
EUSCI_B_SPI_CLOCKPOLARITY_INACTIVITY_HIGH, // High polarity
EUSCI_B_SPI_3PIN // 3Wire SPI Mode
};

/* DMA Control Table */
#pragma DATA_ALIGN(1024)
static DMA_ControlTable controlTableInertialSensor[32];

----------------------------------------------------------------------------------------------------------------------------------------------

void Sensor::ConfigureDMA(void)
{
/* Configuring DMA module */
MAP_DMA_enableModule();
MAP_DMA_setControlBase(controlTableInertialSensor);

/* Assign DMA channel 0 to EUSCI_B0_TX0, channel 1 to EUSCI_B0_RX0 */
MAP_DMA_assignChannel(DMA_CH0_EUSCIB0TX0);
MAP_DMA_assignChannel(DMA_CH1_EUSCIB0RX0);

/* Setup the TX transfer characteristics & buffers */
MAP_DMA_setChannelControl(DMA_CH0_EUSCIB0TX0 | UDMA_PRI_SELECT,
UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_1);

MAP_DMA_setChannelTransfer(DMA_CH0_EUSCIB0TX0 | UDMA_PRI_SELECT, UDMA_MODE_BASIC,
mTXData, (void *) MAP_SPI_getTransmitBufferAddressForDMA(EUSCI_B0_BASE),
INERTIAL_NUM_BYTES_TO_READ_PER_SAMPLE);

/* Setup the RX transfer characteristics & buffers */
MAP_DMA_setChannelControl(DMA_CH1_EUSCIB0RX0 | UDMA_PRI_SELECT,
UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_1);

MAP_DMA_setChannelTransfer(DMA_CH1_EUSCIB0RX0 | UDMA_PRI_SELECT, UDMA_MODE_BASIC,
(void *) MAP_SPI_getReceiveBufferAddressForDMA(EUSCI_B0_BASE), mRXData,
INERTIAL_NUM_BYTES_TO_READ_PER_SAMPLE);

// Enable DMA interrupt and clear any previously set completion flags

MAP_DMA_enableInterrupt(INT_DMA_INT0);
MAP_Interrupt_enableInterrupt(INT_DMA_INT0);
MAP_DMA_clearInterruptFlag(DMA_CH0_EUSCIB0TX0);
MAP_DMA_clearInterruptFlag(DMA_CH1_EUSCIB0RX0);
}

----------------------------------------------------------------------------------------------------------------------------------------------

void InertialSensor::PORT2_IRQHandler(void)
{
/*
* disable the interrupt on the I2C DRDY port
* DMA operation on SPI and I2C should be atomic.
* Disable the other interrupts so that the current operation
* completes atomically before starting the other peripheral
*/
MAP_Interrupt_disableInterrupt(INT_PORT3);

uint32_t status;
status = MAP_GPIO_getEnabledInterruptStatus(GPIO_PORT_P2);
MAP_GPIO_clearInterruptFlag(GPIO_PORT_P2, 0xFF);

/*
* LSM6DS3 is signaling data is ready.
* Trigger the SPI read operation using DMA
*/
// reset the DMA transfer complete flag
mDMAXferComplete = false;

/* first assert the Chip Select (CS) to signal the start of the SPI transmission */
MAP_GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN4);

// Enabling DMA interrupts
MAP_DMA_enableInterrupt(INT_DMA_INT0);
MAP_Interrupt_enableInterrupt(INT_DMA_INT0);

//enable DMA RX and TX channel

MAP_DMA_enableChannel(DMA_CHANNEL_1);
MAP_DMA_enableChannel(DMA_CHANNEL_0);

/*
* at this point DMA will take over and complete reception of rest of the data from the
* inertial sensor
*/
}

----------------------------------------------------------------------------------------------------------------------------------------------

void InertialSensor::DMA_INT0_IRQHandler(void)
{

uint32_t int0_srcflg_dma = DMA_Channel->INT0_SRCFLG;

if ( !(DMA_Channel->INT0_SRCFLG & (DMA_INT0_SRCFLG_CH0 | DMA_INT0_SRCFLG_CH1)) )
{
return;
}

// clear all the interrupt flag bits in the "DMA_INT0_SRCFLG" register
// that correspond to the DMA channels 0 and 1
MAP_DMA_clearInterruptFlag(DMA_CH0_EUSCIB0TX0);
MAP_DMA_clearInterruptFlag(DMA_CH1_EUSCIB0RX0);

// Disable the DMA interrupts
MAP_Interrupt_disableInterrupt(INT_DMA_INT0);
MAP_DMA_disableInterrupt(INT_DMA_INT0);

}

----------------------------------------------------------------------------------------------------------------------------------------------

Details of tool chain:

MSPWARE is version 3.50.0.2

Driverlib is version 3_21_00_05

Code Composer Studio  Version: 6.1.3.00033 

Compiler Version is V15.12.3 LTS

----------------------------------------------------------------------------------------------------------------------------------------------

  • I will try and go through this to give some more specific recommendations, but the DMA interrupt will be called twice, once when the last TX byte is loaded and then again once the last byte is received. There is a 'double-buffer' into the TX which means that the TXIFG is set when the byte is removed from the buffer into the shift register and not when it is transmitted. Maybe this is not applicable, but at first glance I think you may be disabling the interrupt before the receive DMA is finished.

    Thank you for providing the code. I will see if I can recreate and get back to you on Monday.

    Chris
  • Hi Chris, Thank you for your reply and help.

    I tried a few things based on your reply.

    I too was thinking that the second time the DMA IRQ handler executed was probably because of the DMA RX completing...However, when I read the DMA_INT0_SRCFLG register just after entering the DMA_INT0_IRQHandler (the 2nd time), I noticed all bits were zero. Perhaps this happened because I had already cleared the DMA RX interrupt flag in the DMA_INT0_IRQHandler so when the handler ran because of the TX DMA completion, my code cleared both the TX and RX interrupt flags.

    Regarding your comment about disabling the interrupt before the receive DMA is finished: this seems unlikely because when I read the DMA_INT0_SRCFLG  register right after the DMA_INT0_IRQHandler was entered, I read a value of 0x3 : which means that both the DMA RX and TX channels are signaling that they are done (am I correct in this assumption?)

    So now to my 2 modifications based on your suggestions. (see code below) 

    First, I added a while loop in the Port IRQ to wait for the TXIFG flag. I saw this line of code in a previous post in the forums, but I think it slightly defeats the purpose of DMA : I do not think one should have to poll the eUSCI to check if the TXIFG bit is ready, shouldn't the DMA just handle that on its own: I should just enable the DMA channels and exit the ISR and be able to enter a LPM instead of polling the eUSCI to check if the TX Buffer was ready.

    In any case I added the while loop, just in case I was not waiting long enough for the eUSCI module to be ready.

    Secondly, I modified the DMA_INT0_IRQHandler code to just clear the individual TX , RX DMA interrupt flags and also disable the channels based on which DMA channel (RX , TX) caused the DMA_INT0_IRQHandler to execute.

    The problem still exists and the symptom is exactly the same as I had shown in my previous post.

    Since you are going to re-create the test on your setup I am also adding some additional defines to help you. I am also including the code I used to setup the SPI peripheral 

    Again , thanks a lot for your help.

    -----------------------------------------------------------------------------------------

    // number of bytes in the register file
    #define INERTIAL_REG_FILE_SIZE_BYTES (46U)

    #define INERTIAL_NUM_BYTES_TO_READ_PER_SAMPLE (18U)

    /*
    * The receive buffer will store the reply from the sensor IC
    * 1 additional byte is required to store the reply on the MISO
    * line for the address byte transmitted on the MOSI line.
    * This additional byte stored at mRXData[0] is discarded
    */
    #define INERTIAL_NUM_BYTES_RX_BUFFER (INERTIAL_NUM_BYTES_TO_READ_PER_SAMPLE + 1U)

    // define and clear receive and transmit buffers
    uint8_t InertialSensor::mRXData[INERTIAL_NUM_BYTES_RX_BUFFER] = {0x00};
    uint8_t InertialSensor::mTXData[INERTIAL_REG_FILE_SIZE_BYTES] = {0x00};

    -----------------------------------------------------------------------------------------------------------------------------

    void InertialSensor::PORT2_IRQHandler(void)
    {

    /*
    * disable the interrupt on the I2C DRDY port
    * DMA operation on SPI and I2C should be atomic.
    * Disable the other interrupts so that the current operation
    * completes atomically before starting the other peripheral
    */
    MAP_Interrupt_disableInterrupt(INT_PORT3);

    uint32_t status;
    status = MAP_GPIO_getEnabledInterruptStatus(GPIO_PORT_P2);
    MAP_GPIO_clearInterruptFlag(GPIO_PORT_P2, 0xFF);

    /*
    * LSM6DS3 is signaling data is ready.
    * Trigger the SPI read operation using DMA
    */
    // reset the DMA transfer complete flag
    mDMAXferComplete = false;

    /* first assert the Chip Select (CS) to signal the start of the SPI transmission */
    MAP_GPIO_setOutputLowOnPin(GPIO_PORT_P1, GPIO_PIN4);

    // Enabling DMA interrupts
    MAP_DMA_enableInterrupt(INT_DMA_INT0);
    MAP_Interrupt_enableInterrupt(INT_DMA_INT0);

    MAP_DMA_enableChannel(DMA_CHANNEL_1);

    MAP_DMA_enableChannel(DMA_CHANNEL_0);

    /* Polling to see if the TX buffer is ready */
    while (!(MAP_SPI_getInterruptStatus(EUSCI_B0_BASE,EUSCI_B_SPI_TRANSMIT_INTERRUPT)));

    /

    * at this point DMA will take over and complete reception of rest of the data from the

    * inertial sensor
    */

    }

    ---------------------------------------------------------------------------------------------------------------------------------

    void InertialSensor::DMA_INT0_IRQHandler(void)
    {

    uint32_t int0_srcflg_dma = DMA_Channel->INT0_SRCFLG;

    if (int0_srcflg_dma & DMA_INT0_SRCFLG_CH0) // Transmit DMA completed
    {
    MAP_DMA_clearInterruptFlag(DMA_CH0_EUSCIB0TX0); // clear the TX DMA interrupt flag
    MAP_DMA_disableChannel(DMA_CHANNEL_0); // disable the TX channel
    }
    else if (int0_srcflg_dma & DMA_INT0_SRCFLG_CH1) // Recieve DMA completed
    {
    MAP_DMA_clearInterruptFlag(DMA_CH1_EUSCIB0RX0); // clear the RX DMA interrupt flag
    MAP_DMA_disableChannel(DMA_CHANNEL_1); // disable the RX channel
    }
    else
    {
    __nop();
    return;
    }

    //deassert the chip select line to indicate end of SPI transmission

    MAP_GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN4);

    // set the DMA transfer complete flag
    mDMAXferComplete = true;

    }

    -----------------------------------------------------------------------------

    void InertialSensor::ConfigureSPI()
    {

    /* Configure P1.4 is CS */

    MAP_GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN4);
    MAP_GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN4);

    /* Selecting P1.5 (SCLK) P1.6 (MOSI) in SPI mode */
    MAP_GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1,
    GPIO_PIN5 | GPIO_PIN6 , GPIO_PRIMARY_MODULE_FUNCTION);

    /* Selecting P1.7 (MISO) in SPI mode */
    MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,
    GPIO_PIN7, GPIO_PRIMARY_MODULE_FUNCTION);

    /* Configuring SPI in 3wire master mode */
    MAP_SPI_initMaster(EUSCI_B0_BASE, &spiMasterConfig);

    /* Enable SPI module */
    MAP_SPI_enableModule(EUSCI_B0_BASE);

    /*
    * if the mode for SPI is set to read samples :
    * DMA is used and therefore SPI TX and RX interrupts are not used so disable them
    */
    MAP_SPI_disableInterrupt(EUSCI_B0_BASE, EUSCI_B_SPI_RECEIVE_INTERRUPT);
    MAP_SPI_disableInterrupt(EUSCI_B0_BASE, EUSCI_B_SPI_TRANSMIT_INTERRUPT);
    MAP_Interrupt_disableInterrupt(INT_EUSCIB0);

    }

  • dma_spi_mstr_read.c
    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    /*
    * -------------------------------------------
    * MSP432 DriverLib - v3_21_00_05
    * -------------------------------------------
    *
    * --COPYRIGHT--,BSD,BSD
    * Copyright (c) 2016, Texas Instruments Incorporated
    * All rights reserved.
    *
    * Redistribution and use in source and binary forms, with or without
    * modification, are permitted provided that the following conditions
    * are met:
    *
    * * Redistributions of source code must retain the above copyright
    * notice, this list of conditions and the following disclaimer.
    *
    * * Redistributions in binary form must reproduce the above copyright
    * notice, this list of conditions and the following disclaimer in the
    * documentation and/or other materials provided with the distribution.
    *
    * * Neither the name of Texas Instruments Incorporated nor the names of
    * its contributors may be used to endorse or promote products derived
    * from this software without specific prior written permission.
    *
    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
    * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
    * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
    * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
    * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    * --/COPYRIGHT--*/
    /*******************************************************************************
    * MSP432 DMA - eUSCI SPI Transfer Using DMA
    *
    * Description: In this code example, the MSP432 's DMA controller is used in
    * conjunction with an SPI loopback configuration to demonstrate how to use
    * hardware triggered DMA transfers. Four DMA transfers are setup using four
    * separate DMA channels. For each SPI instance, a DMA channel is setup to
    * receive and transfer from the SIMO and SOMI ports respectively. After an
    * arbitrary string is sent through SPI via the loopback configuration, an
    * ISR counter is incremented and triggers a NOP condition in the main loop
    * where the user can set a breakpoint to examine memory contents.
    *
    *
    * MSP432P401
    * ---------------------------
    * /|\| |
    * | | |
    * --|RST P1.5 (CLK) |---------
    * | P1.7 (SIMO) |----------
    * | P1.6 (SOMI) |--------
    * | |
    * | |
    *
    * Author: Timothy Logan
    ******************************************************************************/
    /* DriverLib Includes */
    #include "driverlib.h"
    /* Standard Includes */
    #include <stdint.h>
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Thank you for providing the additional code.  I failed to notice that the the DMA is not 'reinitialized' in the DMA isr.  Specifically the DMA decrements the size and also changes the control congifuration upon completion from basic mode to stop.  I have gone ahead and attached an example.  Regrettfully, I have not been able (due to time) to confirm the recieve data array is being filled properly.  Please let me know if you have any issues or questions.

    Chris

  • Thank you Chris !
    I can verify that your solution worked. Also, I would suggest that your modification be made a part of the example code for the SPI loopback code in the TI driverlib, so that others may benefit from it.
    Again, thanks for your help and time.
  • I agree with EE_2001 that some things should be added to the examples. For example I always thought i only need to call DMA_setChannelTransfer to initiate a transfer, but it seems I always need to call DMA_setChannelControl too before I can transfer the same things again. You could add a loop where you repeat the transfer in the examples. Also is there anyway to get the source code for MSPWare because it would help to know how the functions work and what registers they set.
  • Thank you. I will make a request to add the examples. The source code for MSPWare is available. In this instance you are looking for dma.c/.h and this is found in the C:\ti\tirex-content\msp\msp432ware__3.50.00.02\driverlib\driverlib\MSP432P4xx when driverLib is downloaded. I would highly recommend looking at the configuration register described in the TRM (www.ti.com/.../slau356d.pdf. The APIs that need to be called again to 'reset' the configuration resister do contain overhead. All that is needed is to reset the counter and the cycle_ctrl.

    I have proposed the following in other examples:

    if (MAP_DMA_getChannelAttribute(7) & UDMA_ATTR_ALTSELECT)
    {
    // MAP_DMA_setChannelControl(UDMA_PRI_SELECT | DMA_CH7_ADC14,
    // UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);
    // MAP_DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_CH7_ADC14,
    // UDMA_MODE_PINGPONG, (void*) &ADC14->MEM[0],
    // resultsBufferPrimary, ARRAY_LENGTH);
    MSP_EXP432P401RLP_DMAControlTable[7].control =
    (MSP_EXP432P401RLP_DMAControlTable[7].control & 0xff000000 ) |
    (((ARRAY_LENGTH)-1)<<4) | 0x03;
    }
    else
    {
    // MAP_DMA_setChannelControl(UDMA_ALT_SELECT | DMA_CH7_ADC14,
    // UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);
    // MAP_DMA_setChannelTransfer(UDMA_ALT_SELECT | DMA_CH7_ADC14,
    // UDMA_MODE_PINGPONG, (void*) &ADC14->MEM[0],
    // resultsBufferAlternate, ARRAY_LENGTH);
    MSP_EXP432P401RLP_DMAControlTable[15].control =
    (MSP_EXP432P401RLP_DMAControlTable[15].control & 0xff000000 ) |
    (((ARRAY_LENGTH)-1)<<4) | 0x03;
    }

    Please let me know if you have additional questions.
    Chris

**Attention** This is a public forum