Dear experts,
Hope you have a wonderful time with your family during the Thanksgiving. Recently I am helping a TM4C customer to build a DMA demo code with TM4C129X since they have more strict timing requirement on a mature project. The target is to add DMA operation to SSI0 transmit/receive. I try to build DMA enabled SSI0 operation code, but found that the code will stuck at peripheral DMA interrupt handler and will not run normally. The reference code I use is udmademo in DK-TM4C129X and the board I use is DK-TM4C129X. Could you please help to take a look at what I am missing? Is that because of CPU and DMA competition for the bus? Since I did not add CPU sleep in the code. BTW, from the step by step debug, I found the MCU is always executing SSI interrupt handler, but I try to clear the flag bit, not works. I attached the code I am using, in this code, I change the peripheral from SSI to UART, since I think the logic will be similar, but I got same failure.
Eric
-//***************************************************************************** // // spi_master.c - Example demonstrating how to configure SSI0 in SPI master // mode. // // Copyright (c) 2010-2020 Texas Instruments Incorporated. All rights reserved. // Software License Agreement // // 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. // // This is part of revision 2.2.0.295 of the Tiva Firmware Development Package. // //***************************************************************************** #include <stdbool.h> #include <stdint.h> #include "inc/hw_memmap.h" #include "driverlib/gpio.h" #include "driverlib/pin_map.h" #include "driverlib/ssi.h" #include "driverlib/sysctl.h" #include "driverlib/udma.h" #include "driverlib/uart.h" #include "inc/hw_ssi.h" #include "driverlib/interrupt.h" #include "inc/hw_ints.h" #include "inc/hw_types.h" #include "inc/hw_udma.h" #include "inc/hw_uart.h" //***************************************************************************** // //! \addtogroup ssi_examples_list //! <h1>SPI Master (spi_master)</h1> //! //! This example shows how to configure the SSI0 as SPI Master. The code will //! send three characters on the master Tx then polls the receive FIFO until //! 3 characters are received on the master Rx. //! //! This example uses the following peripherals and I/O signals. You must //! review these and change as needed for your own board: //! - SSI0 peripheral //! - GPIO Port A peripheral (for SSI0 pins) //! - SSI0Clk - PA2 //! - SSI0Fss - PA3 //! - SSI0Rx (TM4C123x) / SSI0XDAT0 (TM4C129x) - PA4 //! - SSI0Tx (TM4C123x) / SSI0XDAT1 (TM4C129x) - PA5 //! //! The following UART signals are configured only for displaying console //! messages for this example. These are not required for operation of SSI0. //! - UART0 peripheral //! - GPIO Port A peripheral (for UART0 pins) //! - UART0RX - PA0 //! - UART0TX - PA1 //! //! This example uses the following interrupt handlers. To use this example //! in your own application you must add these interrupt handlers to your //! vector table. //! - None. // //***************************************************************************** //***************************************************************************** // // Number of bytes to send and receive. // //***************************************************************************** #define NUM_SSI_DATA 3 #define SSI_TX 0x00000800 #define SSI_RX 0x00000400 uint32_t EEG_dataready = 0; uint32_t g_ui32uDMAErrCount = 0; uint8_t EEG_TXBuffer[132]; uint8_t EEG_RXBufferA[132]; uint8_t EEG_RXBufferB[132]; #pragma DATA_ALIGN(ui8ControlTable, 1024) uint8_t ui8ControlTable[1024]; //***************************************************************************** // // The interrupt handler for SSI0. This interrupt will occur when a DMA // transfer is complete using the SSI0 uDMA channel. It will also be // triggered if the peripheral signals an error. // //***************************************************************************** void SSI0IntHandler(void) { uint32_t ui32Status; // // // Read the interrupt status of the SSI0. // ui32Status = HWREG(SSI0_BASE + SSI_O_MIS); HWREG(SSI0_BASE + SSI_O_ICR) = ui32Status; // // Clear any pending status, even though there should be none since no UART // interrupts were enabled. If UART error interrupts were enabled, then // those interrupts could occur here and should be handled. Since uDMA is // used for both the RX and TX, then neither of those interrupts should be // enabled. // //SSIIntClear(SSI0_BASE, ui32Status); ui32Status = HWREG(UDMA_CHIS); HWREG(UDMA_CHIS) = ui32Status; // // If the SSI0 DMA RX channel is disabled, that means the RX DMA transfer // is done. // //if(!uDMAChannelIsEnabled(UDMA_CHANNEL_SSI0RX)) // // Notify main that EEG data is ready. // if(!uDMAChannelIsEnabled(UDMA_CHANNEL_SSI0TX)) { // EEG_dataready = 1; } } void uDMAErrorHandler(void) { uint32_t ui32Status; // // Check for uDMA error bit // ui32Status = uDMAErrorStatusGet(); // // If there is a uDMA error, then clear the error and increment // the error counter. // if(ui32Status) { uDMAErrorStatusClear(); g_ui32uDMAErrCount++; } } //***************************************************************************** // // The interrupt handler for UART0. This interrupt will occur when a DMA // transfer is complete using the UART0 uDMA channel. It will also be // triggered if the peripheral signals an error. This interrupt handler will // switch between receive ping-pong buffers A and B. It will also restart a TX // uDMA transfer if the prior transfer is complete. This will keep the UART // running continuously (looping TX data back to RX). // //***************************************************************************** void UART0IntHandler(void) { uint32_t ui32Status; uint32_t ui32Mode; // // Read the interrupt status of the UART. // ui32Status = UARTIntStatus(UART0_BASE, 1); // // Clear any pending status, even though there should be none since no UART // interrupts were enabled. If UART error interrupts were enabled, then // those interrupts could occur here and should be handled. Since uDMA is // used for both the RX and TX, then neither of those interrupts should be // enabled. // UARTIntClear(UART0_BASE, ui32Status); // // Check the DMA control table to see if the ping-pong "A" transfer is // complete. The "A" transfer uses receive buffer "A", and the primary // control structure. // ui32Mode = uDMAChannelModeGet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT); // // If the primary control structure indicates stop, that means the "A" // receive buffer is done. The uDMA controller should still be receiving // data into the "B" buffer. // if(ui32Mode == UDMA_MODE_STOP) { // // Set up the next transfer for the "A" buffer, using the primary // control structure. When the ongoing receive into the "B" buffer is // done, the uDMA controller will switch back to this one. This // example re-uses buffer A, but a more sophisticated application could // use a rotating set of buffers to increase the amount of time that // the main thread has to process the data in the buffer before it is // reused. // uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG, (void *)(UART0_BASE + UART_O_DR), EEG_RXBufferA, sizeof(EEG_RXBufferA)); } // // Check the DMA control table to see if the ping-pong "B" transfer is // complete. The "B" transfer uses receive buffer "B", and the alternate // control structure. // ui32Mode = uDMAChannelModeGet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT); // // If the alternate control structure indicates stop, that means the "B" // receive buffer is done. The uDMA controller should still be receiving // data into the "A" buffer. // if(ui32Mode == UDMA_MODE_STOP) { // // Set up the next transfer for the "B" buffer, using the alternate // control structure. When the ongoing receive into the "A" buffer is // done, the uDMA controller will switch back to this one. This // example re-uses buffer B, but a more sophisticated application could // use a rotating set of buffers to increase the amount of time that // the main thread has to process the data in the buffer before it is // reused. // uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG, (void *)(UART0_BASE + UART_O_DR), EEG_RXBufferB, sizeof(EEG_RXBufferA)); } // // If the UART0 DMA TX channel is disabled, that means the TX DMA transfer // is done. // if(!uDMAChannelIsEnabled(UDMA_CHANNEL_UART0TX)) { // // Start another DMA transfer to UART0 TX. // uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, EEG_TXBuffer, (void *)(UART0_BASE + UART_O_DR), sizeof(EEG_TXBuffer)); EEG_dataready = 1; // // The uDMA TX channel must be re-enabled. // uDMAChannelEnable(UDMA_CHANNEL_UART0TX); } } //***************************************************************************** // // Configure SSI0 in master Freescale (SPI) mode. This example will send out // 3 bytes of data, then wait for 3 bytes of data to come in. This will all be // done using the polling method. // //***************************************************************************** int main(void) { #if defined(TARGET_IS_TM4C129_RA0) || \ defined(TARGET_IS_TM4C129_RA1) || \ defined(TARGET_IS_TM4C129_RA2) uint32_t ui32SysClock; #endif uint32_t pui32DataTx[NUM_SSI_DATA]; uint32_t pui32DataRx[NUM_SSI_DATA]; uint32_t ui32Index; // // Set the clocking to run directly from the external crystal/oscillator. // TODO: The SYSCTL_XTAL_ value must be changed to match the value of the // crystal on your board. // #if defined(TARGET_IS_TM4C129_RA0) || \ defined(TARGET_IS_TM4C129_RA1) || \ defined(TARGET_IS_TM4C129_RA2) ui32SysClock = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_OSC), 25000000); #else SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ); #endif // // The SSI0 peripheral must be enabled for use. // SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0); SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0); // // For this example SSI0 is used with PortA[5:2]. The actual port and pins // used may be different on your part, consult the data sheet for more // information. GPIO port A needs to be enabled so these pins can be used. // TODO: change this to whichever GPIO port you are using. // SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); // // Configure the pin muxing for SSI0 functions on port A2, A3, A4, and A5. // This step is not necessary if your part does not support pin muxing. // TODO: change this to select the port/pin you are using. // GPIOPinConfigure(GPIO_PA2_SSI0CLK); GPIOPinConfigure(GPIO_PA3_SSI0FSS); #if defined(TARGET_IS_TM4C129_RA0) || \ defined(TARGET_IS_TM4C129_RA1) || \ defined(TARGET_IS_TM4C129_RA2) GPIOPinConfigure(GPIO_PA4_SSI0XDAT0); GPIOPinConfigure(GPIO_PA5_SSI0XDAT1); #else GPIOPinConfigure(GPIO_PA4_SSI0RX); GPIOPinConfigure(GPIO_PA5_SSI0TX); #endif // // PA0-1 are used for UART0. // GPIOPinConfigure(GPIO_PA0_U0RX); GPIOPinConfigure(GPIO_PA1_U0TX); GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1); // // Configure the GPIO settings for the SSI pins. This function also gives // control of these pins to the SSI hardware. Consult the data sheet to // see which functions are allocated per pin. // The pins are assigned as follows: // PA5 - SSI0Tx (TM4C123x) / SSI0XDAT1 (TM4C129x) // PA4 - SSI0Rx (TM4C123x) / SSI0XDAT0 (TM4C129x) // PA3 - SSI0Fss // PA2 - SSI0CLK // TODO: change this to select the port/pin you are using. // GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_2); // // Configure and enable the SSI port for SPI master mode. Use SSI0, // system clock supply, idle clock level low and active low clock in // freescale SPI mode, master mode, 1MHz SSI frequency, and 8-bit data. // For SPI mode, you can set the polarity of the SSI clock when the SSI // unit is idle. You can also configure what clock edge you want to // capture data on. Please reference the datasheet for more information on // the different SPI modes. // #if defined(TARGET_IS_TM4C129_RA0) || \ defined(TARGET_IS_TM4C129_RA1) || \ defined(TARGET_IS_TM4C129_RA2) SSIConfigSetExpClk(SSI0_BASE, ui32SysClock, SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 1000000, 8); #else SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 1000000, 8); #endif UARTConfigSetExpClk(UART0_BASE, ui32SysClock, 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); // // Enable peripherals to operate when CPU is in sleep. // SysCtlPeripheralClockGating(true); // // // // Enable the UDMA peripheral // SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); // // Wait for the UDMA module to be ready. // while(!SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA)) { } // // Enable the uDMA controller. // uDMAEnable(); // // Set the base for the channel control table. // uDMAControlBaseSet(&ui8ControlTable[0]); // // Set both the TX and RX trigger thresholds to 4. This will be used by // the uDMA controller to signal when more data should be transferred. The // uDMA TX and RX channels will be configured so that it can transfer 4 // bytes in a burst when the UART is ready to transfer more data. // UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8); // // Enable the UART for operation, and enable the uDMA interface for both TX // and RX channels. // UARTEnable(UART0_BASE); UARTDMAEnable(UART0_BASE, UART_DMA_TX | UART_DMA_RX); // // This register write will set the UART to operate in loopback mode. Any // data sent on the TX output will be received on the RX input. // HWREG(UART0_BASE + UART_O_CTL) |= UART_CTL_LBE; // // Put the attributes in a known state for the uDMA UART0RX channel. These // should already be disabled by default. // uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0RX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK); // // Configure the control parameters for the primary control structure for // the UART RX channel. The primary contol structure is used for the "A" // part of the ping-pong receive. The transfer data size is 8 bits, the // source address does not increment since it will be reading from a // register. The destination address increment is byte 8-bit bytes. The // arbitration size is set to 4 to match the RX FIFO trigger threshold. // The uDMA controller will use a 4 byte burst transfer if possible. This // will be somewhat more effecient that single byte transfers. // uDMAChannelControlSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_4); // // Configure the control parameters for the alternate control structure for // the UART RX channel. The alternate contol structure is used for the "B" // part of the ping-pong receive. The configuration is identical to the // primary/A control structure. // uDMAChannelControlSet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_4); // // Set up the transfer parameters for the UART RX primary control // structure. The mode is set to ping-pong, the transfer source is the // UART data register, and the destination is the receive "A" buffer. The // transfer size is set to match the size of the buffer. // uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG, (void *)(UART0_BASE + UART_O_DR), EEG_RXBufferA, sizeof(EEG_RXBufferA)); // // Set up the transfer parameters for the UART RX alternate control // structure. The mode is set to ping-pong, the transfer source is the // UART data register, and the destination is the receive "B" buffer. The // transfer size is set to match the size of the buffer. // uDMAChannelTransferSet(UDMA_CHANNEL_UART0RX | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG, (void *)(UART0_BASE + UART_O_DR), EEG_RXBufferB, sizeof(EEG_RXBufferB)); // // Put the attributes in a known state for the uDMA UART0TX channel. These // should already be disabled by default. // uDMAChannelAttributeDisable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK); // // Set the USEBURST attribute for the uDMA UART TX channel. This will // force the controller to always use a burst when transferring data from // the TX buffer to the UART. This is somewhat more effecient bus usage // than the default which allows single or burst transfers. // uDMAChannelAttributeEnable(UDMA_CHANNEL_UART0TX, UDMA_ATTR_USEBURST); // // Configure the control parameters for the UART TX. The uDMA UART TX // channel is used to transfer a block of data from a buffer to the UART. // The data size is 8 bits. The source address increment is 8-bit bytes // since the data is coming from a buffer. The destination increment is // none since the data is to be written to the UART data register. The // arbitration size is set to 4, which matches the UART TX FIFO trigger // threshold. // uDMAChannelControlSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_4); // // Set up the transfer parameters for the uDMA UART TX channel. This will // configure the transfer source and destination and the transfer size. // Basic mode is used because the peripheral is making the uDMA transfer // request. The source is the TX buffer and the destination is the UART // data register. // uDMAChannelTransferSet(UDMA_CHANNEL_UART0TX | UDMA_PRI_SELECT, UDMA_MODE_BASIC, EEG_TXBuffer, (void *)(UART0_BASE + UART_O_DR), sizeof(EEG_TXBuffer)); // // Now both the uDMA UART TX and RX channels are primed to start a // transfer. As soon as the channels are enabled, the peripheral will // issue a transfer request and the data transfers will begin. // uDMAChannelEnable(UDMA_CHANNEL_UART0TX); uDMAChannelEnable(UDMA_CHANNEL_UART0RX); // // Enable the UART DMA TX/RX interrupts. // UARTIntEnable(UART0_BASE, UART_INT_DMATX | UART_INT_DMARX); // // Enable the UART peripheral interrupts. // IntEnable(INT_UART0); while(1) { if(EEG_dataready) { // EEG_dataready = 0; //SysCtlDelay(240); uint32_t i = 0; for(i = 0; i < 132; i++) { // EEG_TXBuffer[i] = 0x55; EEG_RXBufferA[i] = 0x00; EEG_RXBufferB[i] = 0x00; } uDMAChannelEnable(UDMA_CHANNEL_UART0TX); uDMAChannelEnable(UDMA_CHANNEL_UART0RX); } } // // Return no errors // return(0); }