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.

CCS/TM4C123GH6PM: SSI2 SG causing uDMA bus error.

Part Number: TM4C123GH6PM
Other Parts Discussed in Thread: EK-TM4C123GXL

Tool/software: Code Composer Studio

For some reason this is causing a bus error.

//*****************************************************************************
//
// udma_demo.c - uDMA example.
//
// Copyright (c) 2012-2017 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
// This is part of revision 2.1.4.178 of the EK-TM4C123GXL Firmware Package.
//
//*****************************************************************************

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_uart.h"
#include "inc/hw_ssi.h"
#include "driverlib/fpu.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/systick.h"
#include "driverlib/uart.h"
#include "driverlib/udma.h"
#include "driverlib/ssi.h"
#include "utils/cpu_usage.h"
#include "utils/uartstdio.h"
#include "utils/ustdlib.h"

//*****************************************************************************
//
//! \addtogroup example_list
//! <h1>uDMA (udma_demo)</h1>
//!
//! This example application demonstrates the use of the uDMA controller to
//! transfer data between memory buffers, and to transfer data to and from a
//! UART.  The test runs for 10 seconds before exiting.
//!
//! UART0, connected to the FTDI virtual COM port and running at 115,200,
//! 8-N-1, is used to display messages from this application.
//
//*****************************************************************************

//*****************************************************************************
//
// The number of SysTick ticks per second used for the SysTick interrupt.
//
//*****************************************************************************
#define SYSTICKS_PER_SECOND     100

//*****************************************************************************
//
// The size of the memory transfer source and destination buffers (in words).
//
//*****************************************************************************
#define MEM_BUFFER_SIZE         1024

//*****************************************************************************
//
// The size of the UART transmit and receive buffers.  They do not need to be
// the same size.
//
//*****************************************************************************
#define UART_TXBUF_SIZE         256
#define UART_RXBUF_SIZE         256

//*****************************************************************************
//
// The source and destination buffers used for memory transfers.
//
//*****************************************************************************
static uint32_t g_ui32SrcBuf[MEM_BUFFER_SIZE];
static uint32_t g_ui32DstBuf[MEM_BUFFER_SIZE];


//*****************************************************************************
//
// The count of uDMA errors.  This value is incremented by the uDMA error
// handler.
//
//*****************************************************************************
static uint32_t g_ui32uDMAErrCount = 0;

//*****************************************************************************
//
// The number of seconds elapsed since the start of the program.  This value is
// maintained by the SysTick interrupt handler.
//
//*****************************************************************************
static uint32_t g_ui32Seconds = 0;

//*****************************************************************************
//
// The control table used by the uDMA controller.  This table must be aligned
// to a 1024 byte boundary.
//
//*****************************************************************************
#if defined(ewarm)
#pragma data_alignment=1024
uint8_t ui8ControlTable[1024];
#elif defined(ccs)
#pragma DATA_ALIGN(ui8ControlTable, 1024)
uint8_t ui8ControlTable[1024];
#else
uint8_t ui8ControlTable[1024] __attribute__ ((aligned(1024)));
#endif

//*****************************************************************************
//
// The error routine that is called if the driver library encounters an error.
//
//*****************************************************************************
#ifdef DEBUG
void
__error__(char *pcFilename, uint32_t ui32Line)
{
    while(1)
    {
        //
        // Hang on runtime error.
        //
    }
}
#endif

//*****************************************************************************
//
// The interrupt handler for the SysTick timer.  This handler will increment a
// seconds counter whenever the appropriate number of ticks has occurred.  It
// will also call the CPU usage tick function to find the CPU usage percent.
//
//*****************************************************************************
void
SysTickHandler(void)
{
    static uint32_t ui32TickCount = 0;

    //
    // Increment the tick counter.
    //
    ui32TickCount++;

    //
    // If the number of ticks per second has occurred, then increment the
    // seconds counter.
    //
    if(!(ui32TickCount % SYSTICKS_PER_SECOND))
    {
        g_ui32Seconds++;
    }
}

//*****************************************************************************
//
// The interrupt handler for uDMA errors.  This interrupt will occur if the
// uDMA encounters a bus error while trying to perform a transfer.  This
// handler just increments a counter if an error occurs.
//
//*****************************************************************************
void
uDMAErrorHandler(void)
{
    uint32_t ui32Status;

    //
    // Check for uDMA error bit
    //
    ui32Status = ROM_uDMAErrorStatusGet();

    //
    // If there is a uDMA error, then clear the error and increment
    // the error counter.
    //
    if(ui32Status)
    {
        ROM_uDMAErrorStatusClear();
        g_ui32uDMAErrCount++;
    }
}

//*****************************************************************************
//
// The interrupt handler for uDMA interrupts from the memory channel.  This
// interrupt will increment a counter, and then restart another memory
// transfer.
//
//*****************************************************************************
void
uDMAIntHandler(void)
{
}

#define NUM_SG 4

#pragma DATA_ALIGN(sg_tx_list, 16)
tDMAControlTable
sg_tx_list[NUM_SG] = {
    uDMATaskStructEntry(
        1024,
        UDMA_SIZE_8,
        UDMA_DST_INC_8, &(g_ui32SrcBuf[0]),
        UDMA_DST_INC_NONE, (void*)(SSI2_BASE + SSI_O_DR),
        UDMA_ARB_1,
        UDMA_MODE_BASIC),
};

#pragma DATA_ALIGN(sg_rx_list, 16)
tDMAControlTable
sg_rx_list[NUM_SG] = {
    uDMATaskStructEntry(
        1024,
        UDMA_SIZE_8,
        UDMA_SRC_INC_NONE, (uintptr_t)(SSI2_BASE + SSI_O_DR),
        UDMA_DST_INC_8, &(g_ui32SrcBuf[0]),
        UDMA_ARB_1,
        UDMA_MODE_BASIC),
};


//*****************************************************************************
//
// Initializes the uDMA software channel to perform a memory to memory uDMA
// transfer.
//
//*****************************************************************************
void
DispatchSSITransfer(void)
{

    uint_fast16_t ui16Idx;

    //
    // Fill the source memory buffer with a simple incrementing pattern.
    //
    for(ui16Idx = 0; ui16Idx < MEM_BUFFER_SIZE; ui16Idx++)
    {
        g_ui32SrcBuf[ui16Idx] = ui16Idx;
        g_ui32DstBuf[ui16Idx] = MEM_BUFFER_SIZE - ui16Idx;
    }

    //
    // Enable interrupts from the uDMA software channel.
    //
    ROM_IntEnable(INT_UDMA);

    MAP_SSIDisable(SSI2_BASE);

    MAP_SSIConfigSetExpClk(SSI2_BASE,
                           ROM_SysCtlClockGet(),
                           SSI_FRF_NMW,
                           SSI_MODE_MASTER,
                           ROM_SysCtlClockGet()>>4,
                           8);

    MAP_uDMAChannelDisable(UDMA_CH12_SSI2RX);
    MAP_uDMAChannelDisable(UDMA_CH13_SSI2TX);

    MAP_uDMAChannelAttributeDisable(UDMA_CH12_SSI2RX, UDMA_ATTR_ALL);
    MAP_uDMAChannelAttributeDisable(UDMA_CH13_SSI2TX, UDMA_ATTR_ALL);

    ROM_uDMAChannelScatterGatherSet(
            UDMA_CH12_SSI2RX,
            1, &(sg_rx_list[0]),
            1);

    ROM_uDMAChannelScatterGatherSet(
            UDMA_CH13_SSI2TX,
            1, &(sg_tx_list[0]),
            1);

    MAP_SSIIntDisable(SSI2_BASE, 0xFF);

    //MAP_SSIIntEnable(SSI2_BASE, SSI_DMATX);

    MAP_uDMAChannelAssign(UDMA_CH12_SSI2RX);
    MAP_uDMAChannelAssign(UDMA_CH13_SSI2TX);

    MAP_SSIDMAEnable(SSI2_BASE, SSI_DMA_TX);

    //MAP_IntEnable(INT_SSI2);

    MAP_SSIEnable(SSI2_BASE);

    //MAP_uDMAChannelEnable(UDMA_CH12_SSI2RX);
    MAP_uDMAChannelEnable(UDMA_CH13_SSI2TX);
}

//*****************************************************************************
//
// Configure the UART and its pins.  This must be called before UARTprintf().
//
//*****************************************************************************
void
ConfigureUART(void)
{
    //
    // Enable the GPIO Peripheral used by the UART.
    //
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

    //
    // Enable UART0
    //
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    ROM_SysCtlPeripheralSleepEnable(SYSCTL_PERIPH_UART0);

    //
    // Configure GPIO Pins for UART mode.
    //
    ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
    ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
    ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);

    //
    // Use the internal 16MHz oscillator as the UART clock source.
    //
    UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);

    //
    // Initialize the UART for console I/O.
    //
    UARTStdioConfig(0, 115200, 16000000);
}

static
void
ConfigureSSI2() {
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);
    MAP_SysCtlPeripheralReset(SYSCTL_PERIPH_SSI2);

    MAP_GPIOPinConfigure(GPIO_PB4_SSI2CLK);
    MAP_GPIOPinConfigure(GPIO_PB6_SSI2RX);
    MAP_GPIOPinConfigure(GPIO_PB7_SSI2TX);

    MAP_GPIOPinTypeSSI( GPIO_PORTB_BASE, GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7);
}


//*****************************************************************************
//
// This example demonstrates how to use the uDMA controller to transfer data
// between memory buffers and to and from a peripheral, in this case a UART.
// The uDMA controller is configured to repeatedly transfer a block of data
// from one memory buffer to another.  It is also set up to repeatedly copy a
// block of data from a buffer to the UART output.  The UART data is looped
// back so the same data is received, and the uDMA controlled is configured to
// continuously receive the UART data using ping-pong buffers.
//
// The processor is put to sleep when it is not doing anything, and this allows
// collection of CPU usage data to see how much CPU is being used while the
// data transfers are ongoing.
//
//*****************************************************************************
int
main(void)
{
    static uint32_t ui32PrevSeconds;
    static uint32_t ui32PrevXferCount;
    static uint32_t ui32PrevUARTCount = 0;
    uint32_t ui32XfersCompleted;
    uint32_t ui32BytesTransferred;

    //
    // Enable lazy stacking for interrupt handlers.  This allows floating-point
    // instructions to be used within interrupt handlers, but at the expense of
    // extra stack usage.
    //
    ROM_FPULazyStackingEnable();

    //
    // Set the clocking to run from the PLL at 50 MHz.
    //
    ROM_SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
                       SYSCTL_XTAL_16MHZ);

    //
    // Enable peripherals to operate when CPU is in sleep.
    //
    ROM_SysCtlPeripheralClockGating(true);

    //
    // Enable the GPIO port that is used for the on-board LED.
    //
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);

    //
    // Enable the GPIO pins for the LED (PF2).
    //
    ROM_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_2);

    //
    // Initialize the UART.
    //
    ConfigureUART();

    UARTprintf("\033[2JuDMA Example\n");

    //
    // Show the clock frequency on the display.
    //
    UARTprintf("Tiva C Series @ %u MHz\n\n", ROM_SysCtlClockGet() / 1000000);

    //
    // Show statistics headings.
    //
    UARTprintf("CPU    Memory     UART       Remaining\n");
    UARTprintf("Usage  Transfers  Transfers  Time\n");

    //
    // Configure SysTick to occur 100 times per second, to use as a time
    // reference.  Enable SysTick to generate interrupts.
    //
    ROM_SysTickPeriodSet(ROM_SysCtlClockGet() / SYSTICKS_PER_SECOND);
    ROM_SysTickIntEnable();
    ROM_SysTickEnable();

    //
    // Initialize the CPU usage measurement routine.
    //
    CPUUsageInit(ROM_SysCtlClockGet(), SYSTICKS_PER_SECOND, 2);

    //
    // Enable the uDMA controller at the system level.  Enable it to continue
    // to run while the processor is in sleep.
    //
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
    ROM_SysCtlPeripheralSleepEnable(SYSCTL_PERIPH_UDMA);

    //
    // Enable the uDMA controller error interrupt.  This interrupt will occur
    // if there is a bus error during a transfer.
    //
    ROM_IntEnable(INT_UDMAERR);

    //
    // Enable the uDMA controller.
    //
    ROM_uDMAEnable();

    //
    // Point at the control table to use for channel control structures.
    //
    ROM_uDMAControlBaseSet(ui8ControlTable);

    ConfigureSSI2();

    //
    // Initialize the uDMA memory to memory transfers.
    //
    DispatchSSITransfer();

    //
    // Loop until the button is pressed.  The processor is put to sleep
    // in this loop so that CPU utilization can be measured.
    //
    while(1)
    {
        __asm("\twfi\n");
    }
}

  • The most common reason for bus faults is accessing a peripheral register before that peripheral is enabled or accessing a location that is not in a valid memory range. Here is an application note that helps explain bus errors and how to diagnose them.

    http://www.ti.com/lit/an/spma043/spma043.pdf

  • Alas not the problem here. To be more specific I am getting a UDMA error, as in the UDMA Error interrupt fires and the bus error bit is set. Its a bit hard to debug as there is a total lack of info in this case as to what caused the error or even what channel the error occurred on. My example code has been reduced to only using one channel to eliminate any other possible sources.

    So some additional behavior, if you place a breakpoint at MAP_SSIEnable and step past it, everything works... Sorta. Even in loopback mode all I get back is zeros (the SSIDMAEnable in this case is modified to include a SSI_DMA_RX term) but it at least transmits bytes. However without a breakpoint at MAP_SSIEnable or if I just hit continue and don't single step the UDMA Error interrupt triggers and the error bit is set.

  • If you can reduce your project to something that can run on an EK-TM4C123GXL without additional hardware and still create the problem, export that project to a .zip file and attach it. I will try to reproduce your issue and look into it.

  • Bob,

    I'm not entirely sure how to attach something here. The file posted is just a modified udma_demo from the driver lib so it should be copy pasta. As for hardware, this just tires to do a loopback so you can either set the loopback flag or jumper PB6 and PB7 which is what I did. Either way the result is the same.

    If I figure out how to attach the zip of the project I suspect you will know how to find it here.

    Cheers,

    Michael McMinn

  • udma_demo.zip

    I figured out how to attach a file. See the attached zip for the project zip thing. Hopefully this works.

  • Your code did not make much sense to me. I went ahead and wrote an example using uDMA to send and receive on SSI2. It uses basic mode for the transmit and ping-pong mode for the receive. One trick is that for SSI2 you must use a secondary uDMA channel so you need the calls to uDMAChannelAssign().

    Here is a logic analyzer picture of the transfers:

    Use the Code Composer "File"->"Import" function to add the project in the .zip file to your workspace.

    /cfs-file/__key/communityserver-discussions-components-files/908/udma_5F00_demo_5F00_SSI.zip

  • Bob,

    I appreciate the help!

    Unfortunately it is specifically scatter gather I am trying to get working. This is a simplified example that demonstrates the same problem I am having with the driver I am writing. I really wish there were more examples of SG in use but it seems to be very unpopular. I feel like I missed a notification somewhere that SG is broken / flaky and you should not be using it.

    My current implementation just uses normal DMA transfers but it is really inefficient as the target devices need a lot of small transfers exchanged in sequence to multiple buffers all over the place. It is really a problem that SG is ideally suited to solve.

    I'm going to continue poking at this on my end and hope a solution eventually falls out of the sky. Worst case I'm stuck with the inefficient solution.

    Again, thanks for your help. It is more then I'd have expected.

    Cheers,

    Michael McMinn

  • What do you want to do in scatter/gather mode? I can work up an example, but what you started with may be too simple in that it only had a single uDMA task in the task list for each uDMA channel.

  • Well initially it had more. This was the minimum code to cause the error kind of thing. The target functionality is a bit complicated but I'll try to sum it up.

    The driver is provided essentially something similar iovec list and it is expected to run the list of transfers on the port. Each entry has an in and out pointer along with a size. 

    struct iovec {

    void *in, out;

    size_t size;

    };

    For each entry the out buffer is DMA written to the DR register and the in buffer is DMA read from the DR register. These two transfers happen at the same time with an ARB of 1 and an item size of 1.

    Since there is usually multiple iovec entries the driver uses SG lists to just run all the transfers in sequence without needing an interrupt for each iovec entry.

    I have no idea if that makes any sense but basically I want to send from one set of buffers and at the same time receive into another set of same sized buffers and I want all of these transfers to just happen using scatter gather and then I get an interrupt once all of the transfers are completed.

  • Sorry, this took me longer than expected. I have attached a simple example that uses uDMA and SSI(SPI) to read a serial EEPROM. The uDMA is in basic mode to write the SPI. It writes a command, an address and then two dummy bytes to generate clocks for reading the results. The uDMA is in Scatter/Gather mode to read the results. The first uDMA task reads the first two bytes, which are dummy bytes, and discards them. The second uDMA task reads the next two bytes and stores them in a 16-bit variable. Since the serial EEPROM requires that the chip select stay low during the entire transaction, it is emulated with GPIO. It is set low as the uDMA is started and set high in an interrupt routine after the last byte is received.

    Here is a logic analyzer picture of the results. (There was no serial EEPROM attached so the data returned is 0x0000.

    /cfs-file/__key/communityserver-discussions-components-files/908/udma_5F00_demo_5F00_SSI_5F00_SG.zip

  • Bob,

    Rock and roll! This works. I was able to change it to use SG for both transmit and receive and verified with a loopback jumper. Now with a clean working state I can fix whatever is broken with the driver! Thanks a ton! This should totally be included in an example package or something to demonstrate SG to SSI.

    Now I just need to figure out why my code is broken... But now I can do that with something to compare to.

    I owe you a beer. :)

    Thanks,

    Michael McMinn