• Resolved

CCS/TM4C123BH6PZ: ADC capture and DMA question

Part Number: TM4C123BH6PZ

Tool/software: Code Composer Studio

I am using CCS 7.0.  Essentially, that is the simplest form of what I am trying to do.  We have a somewhat fast 38Khz signal in which I want to sample the data at.  The interrupt time is taking longer than I want.  I figured a DMA would work well to dump the results into a buffer, generate an interrupt once the buffer is full and then process the data. There isn’t any ADC data in the buffer however. 

Also, the ADC is supposed to be triggered off a falling edge GPIO but it seems to go whenever it wants to.  I don’t know that the issues are related since I could read the ADC values before implement the DMA.

I have a file of snippet code to look at if you want.  It is just the latest in a long number of attempts.  Most of it has come from scouring the TI forums and example code while also trying to understand what the datasheet is trying to explain.  

Looked at the following post where a user finally got ADC DMA working, but this did not get the problem resolved:

I have all the items set up as listed there and still there is no data in the buffer.  From what I read in the datasheet, the DMA should fire the ADC interrupt handler when the buffer is half full. (Page 585)  I register the ADC interrupt handler but it never goes off.  The only time it trips is when I have the  ADCIntEnable( ADC1_BASE, 0 ); function call in but this isn’t the behavior I need as it ignores the DMA.  Even pausing the code shows the buffer is empty so it isn’t just an interrupt problem.

Is it not possible to have the ADC trigger and load the value into the buffer via DMA?  Then get an interrupt when the DMA buffer is half full to process that data?

Best Regards, Mark P

  • Mark,
    I suggest you use the Ping-Pong mode of the uDMA. The uDMA generates an interrupt when one buffer is filled and automatically starts putting data in the next buffer. You must process the data in the first buffer before the second buffer fills.

    If you check the ADC Sample Sequence FIFO Status register of the ADC sequence you are using, does it show that the FIFO is not empty?

    Best Regards,
    Bob Crosby

  • In reply to Bob Crosby:

    Bob,

    The mode is set up as Ping-Pong but still not getting any interrupt events nor data in the buffer.  I have looked at many postings and it seems all the functions for setup are included in the code but the events are not happening.  Below is the setup code that Mark is referring to.

    #include <stdint.h>
    #include <stdbool.h>
    #include <string.h>
    #include "inc/tm4c123bh6pz.h"
    #include "inc/hw_types.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_adc.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/adc.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/gpio.h"
    #include "driverlib/udma.h"
    #include "hardware/AtoD.h"
    
    typedef unsigned char       UInt8;
    typedef char                Int8;
    typedef unsigned short      UInt16;
    typedef signed short        Int16;
    typedef unsigned int        UInt32;
    typedef signed int          Int32;
    typedef unsigned long long  UInt64;
    typedef signed long long    Int64;
    
    #ifndef Boolean
      typedef enum
      {
        False,
        True
      } Boolean;
    #endif
    
    static UInt32 ADC1S0_DMA_Control_Table[256] = {0};
    #define NUMBER_OF_STEPS       (1)        // Number of conversions in ADC0 Sequence 0
    #define NUMBER_OF_SETS        (128)      // Number of sets of conversions which are stored in buffer
    
    //Buffers for storing ADC convertion results
    static UInt16 BufferA[NUMBER_OF_SETS][NUMBER_OF_STEPS];
    static UInt16 BufferB[NUMBER_OF_SETS][NUMBER_OF_STEPS];
    
    UInt32 DMAErrCount;
    
    
    /***********************************************************************************************//**
     * @details   Does everything required to initialize the ADC for a single sequence triggered by an
     *            external pin and then saved into a DMA.
     *
     * @return    The function will return 1 if it successful else it will return 0.
     **************************************************************************************************/
    Int32 hw_ADC1_Initialize_Sequence_GPIO(void)
    {
      // Enable ADC0 peripheral with dummy read to insert a few cycles.
      SysCtlPeripheralEnable( SYSCTL_PERIPH_ADC1 );
      SysCtlDelay(10);
    
      // Configure the ADC clock speed
      ADCClockConfigSet( ADC1_BASE, ADC_CLOCK_SRC_PIOSC | ADC_CLOCK_RATE_EIGHTH, 1 );
    
      // Set reference source
      ADCReferenceSet( ADC1_BASE, ADC_REF_EXT_3V );
    
      // Disable the hardware over-sampling
      ADCHardwareOversampleConfigure( ADC1_BASE, 0 );
      SysCtlDelay(10);
    
      // Clear any pending interrupt.
      ADCIntClear( ADC1_BASE, 0 );
    
      // Disable the sequencer so we can configure it
      ADCSequenceDisable( ADC1_BASE, 0 );
    
      // Set the external GPIO pin to trigger the ADC.
      GPIOIntTypeSet( GPIO_PORTH_BASE, GPIO_PIN_0, GPIO_RISING_EDGE );
      GPIOIntEnable( GPIO_PORTH_BASE, GPIO_PIN_0 );
    
      // Set up the ADC to trigger from GPIO pin interrupt.
      GPIOADCTriggerEnable( GPIO_PORTH_BASE, GPIO_PIN_0 );
    
      // Configure the port pin to be ADC alternate function
      GPIOPinTypeADC( GPIO_PORTE_BASE, GPIO_PIN_0 );
    
      // Assign the Sequencer to the PWM and set it's priority to the Seq #
      ADCSequenceConfigure( ADC1_BASE, 0, ADC_TRIGGER_EXTERNAL, 0 );
    
      // Configure the 0 sequence of ADC1 to read AIN3.
      ADCSequenceStepConfigure( ADC1_BASE, 0, 0, ADC_CTL_CH3 | ADC_CTL_IE | ADC_CTL_END );
    
      //Enables DMA for sample sequencers.
      ADCSequenceDMAEnable( ADC1_BASE, 0 );
    
      // Clear the interrupt status flag.  This is done to make sure the interrupt flag is cleared 
      //   before we sample.
      ADCIntClear( ADC1_BASE, 0 );
    
      // Power-up DMA
      SysCtlPeripheralReset( SYSCTL_PERIPH_UDMA );
      SysCtlPeripheralEnable( SYSCTL_PERIPH_UDMA );
    
      // Enables the uDMA controller for use.
      uDMAEnable();
    
      // Assign the memory location that is used for the DMA control values.
      uDMAControlBaseSet( ADC1S0_DMA_Control_Table );
    
      // Enables DMA for sample sequencers.
      ADCSequenceEnable( ADC1_BASE, 0 );
    
      // Put the attributes in a known state.  These should already be disabled by default.
      uDMAChannelAttributeDisable( UDMA_CHANNEL_ADC1, UDMA_ATTR_ALL );
    
      // Set the USEBURST attribute. This is somewhat more efficient bus usage than the default which 
      //   allows single or burst transfers.
      uDMAChannelAttributeEnable( UDMA_CHANNEL_ADC1, UDMA_ATTR_USEBURST );
    
      // Configure the control parameters for the primary control structure for
      uDMAChannelControlSet( UDMA_CHANNEL_ADC1 | UDMA_PRI_SELECT,
          UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_NEXT_USEBURST | UDMA_ARB_128 | UDMA_DST_INC_16 );
    
      uDMAChannelControlSet( UDMA_CHANNEL_ADC1 | UDMA_ALT_SELECT,
          UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_NEXT_USEBURST | UDMA_ARB_128 | UDMA_DST_INC_16 );
    
      // Set up the transfer parameters for the primary control structure.  The mode is set to 
      //   ping-pong.
      uDMAChannelTransferSet( UDMA_CHANNEL_ADC1 | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG,
          (void *) (ADC1_BASE + ADC_O_SSFIFO0), BufferA, 64 );
    
      uDMAChannelTransferSet( UDMA_CHANNEL_ADC1 | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG,
          (void *) (ADC1_BASE + ADC_O_SSFIFO0), BufferB, 64 );
    
      ADCIntRegister( ADC1_BASE, 0, ADC0S0IntHandler );
    
      // USed to test the ADC works outside of the DMA. It does trip at proper time and speed.
    //  ADCIntEnable( ADC1_BASE, 0 );
    
      // Register the DMA Error handler interrupt as maybe errors causing problem.
      uDMAIntRegister( UDMA_INT_ERR, uDMAErrorHandler );
    
      //Enable ADC Sequence 0 Interrupt on the processor (NVIC).
      IntEnable( INT_ADC1SS0 );
    
      // Enables a sample sequence interrupt.
      ADCIntEnableEx( ADC1_BASE, ADC_INT_DMA_SS0 );
    
      //  Enable the DMA Channel for ADC
      uDMAChannelEnable( UDMA_CHANNEL_ADC1 );
    
      return 1;
    }
    
    /***********************************************************************************************//**
     * @details  Interrupt handler for the ADC Sequence 0.  Code is almost mirror of this forum post:
     *           https://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/t/612982?tisearch=e2e-sitesearch&keymatch=ADC%20uDMA
     **************************************************************************************************/
    void ADC0S0IntHandler(void)
    {
      uint16_t i = 0, j = 0;
      UInt32 status = 0;
      uint32_t priChMode = 0, altChMode = 0;
    
      // Test pin to verify the interrupt is going off when buffer is half full. Remove once working.
      GPIOPinWrite( GPIO_PORTF_BASE, GPIO_PIN_6, GPIO_PIN_6 );
    
      //Get interrupt status
      status = ADCIntStatusEx( ADC1_BASE, 0 );
    
      //Get mode of primary DMA channel
      priChMode = uDMAChannelModeGet( UDMA_CHANNEL_ADC1 | UDMA_PRI_SELECT );
    
      //Get mode of alternative DMA channel
      altChMode = uDMAChannelModeGet( UDMA_CHANNEL_ADC1 | UDMA_ALT_SELECT );
    
      //Clear interrupt flag
      ADCIntClearEx( ADC1_BASE, ADC_INT_DMA_SS0 );
    
      //Clear and re-init primary DMA buffer if it's full
      if (priChMode == 0)
      {
        for (i = 0; i < NUMBER_OF_STEPS; i++)
        {
          for (j = 0; j < NUMBER_OF_SETS; j++)
          {
            BufferA[i][j] = 0;
          }
        }
        uDMAChannelTransferSet( UDMA_CHANNEL_ADC1 | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG,
            (void *) (ADC1_BASE + ADC_O_SSFIFO0), BufferA, NUMBER_OF_SETS * NUMBER_OF_STEPS );
      }
    
      //Clear and re-init alternative DMA buffer if it's full
      if (altChMode == 0)
      {
        for (i = 0; i < NUMBER_OF_STEPS; i++)
        {
          for (j = 0; j < NUMBER_OF_SETS; j++)
          {
            BufferB[i][j] = 0;
          }
        }
        uDMAChannelTransferSet( UDMA_CHANNEL_ADC1 | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG,
            (void *) (ADC1_BASE + ADC_O_SSFIFO0), BufferB, NUMBER_OF_SETS * NUMBER_OF_STEPS );
      }
      
      // Test pin to verify the interrupt is going off when buffer is half full. Remove once working.
      GPIOPinWrite( GPIO_PORTF_BASE, GPIO_PIN_6, 0 );
    }
    
    
    //*****************************************************************************
    // 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 = uDMAErrorStatusGet();
    
      // If there is a uDMA error, then clear the error and increment
      // the error counter.
      if (ui32Status)
      {
        uDMAErrorStatusClear();
        DMAErrCount++;
      }
    }
    

  • In reply to Tim49805:

    Hi Mark,
    I am working on an example. I don't have it completed yet, but on the TM4C123 devices the interrupt is generated by the ADC after the DMA transfer takes place. The DMA interrupts are for software initiated transfers and errors only.

    Best Regards,
    Bob Crosby

  • In reply to Bob Crosby:

    Hi Mark,

    Here is a TM4C123 example of a timer generating a periodic interrupt that triggers an AtoD. The uDMA reads the AtoD and ping-pongs between loading two buffers. One thing important is that the uDMA arbitration size should not be greater than the AtoD sequence. In this case, I only do a single conversion in the sequence, so I use a DMA arbitration size of 1.

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

    Best Regards,
    Bob Crosby

  • In reply to Bob Crosby:

    Bob,

    I looked through the code you sent and while it is not the same trigger, I did make some changes based on yours.  All of these items however did not fix the issue that the ADC interrupt still does not trigger when configured as DMA.  It does trigger directly.

    • Changed the ADC interrupt handler code to be identical to yours with exception of ADC1 instead of ADC0.
    • Removed the line: ADCIntEnableEx( ADC1_BASE, ADC_INT_DMA_SS0 );
    • Removed the line: ADCHardwareOversampleConfigure( ADC1_BASE, 0 );
    • Remvoed the option  UDMA_NEXT_USEBURST in the function uDMAChannelControlSet
    • Changed the Arbitration value to UDMA_ARB_1
    • Changed the buffer pointer address to be the address of the address (which seems odd to me but what you had in your example): &BufferA
    • Reordered the initializes to put the DMA first and then the ADC (match your order)
    • Change the option UDMA_ATTR_ALL to UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK in the function uDMAChannelAttributeDisable()

    I can re-post my code with the changes.  Could there be something with the trigger mechanism?  You used an example with a timer trigger (which is already in the forums) but I need it to trigger from an external interrupt.  If I don't use the DMA, the ADC interrupt triggers as expected so I believe the trigger is set up properly.

    Any other suggestions to make this function?

  • In reply to Tim49805:

    When the A to D buffer reaches the trigger point, you can have the A to D generate an interrupt, or you can have it generate a DMA burst request. It will not do both. If using the DMA, once the DMA transfer sequence completes, you will then get an A to D interrupt.

    Best Regards,
    Bob Crosby

  • In reply to Tim49805:

    Tim,

    If you change from ADC0 to ADC1, be careful how you map the uDMA channel. ADC1 sequence 0 is the second peripheral on uDMA channel 24. Note that the macro UDMA_CHANNEL_ADC1 is defined as channel 15, which is the primary channel for ADC0 sequence 1. That is not the correct macro to use. You must call uDMAChannelAssign, and then use the value 24 for the channel, or assign a more descriptive macro.

    #define uDMACHANNEL_ADC1_SEQ0   24
    
    void init_DMA()
    {
        uDMAEnable(); // Enables uDMA
        uDMAControlBaseSet(ucControlTable);
        uDMAChannelAssign(UDMA_CH24_ADC1_0);
        uDMAChannelAttributeDisable(uDMACHANNEL_ADC1_SEQ0, UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK);
    
        uDMAChannelAttributeEnable(uDMACHANNEL_ADC1_SEQ0, UDMA_ATTR_USEBURST);
        // Only allow burst transfers
    
        uDMAChannelControlSet(uDMACHANNEL_ADC1_SEQ0 | UDMA_PRI_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);
        uDMAChannelControlSet(uDMACHANNEL_ADC1_SEQ0 | UDMA_ALT_SELECT, UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);
    
        uDMAChannelTransferSet(uDMACHANNEL_ADC1_SEQ0 | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG, (void *)(ADC1_BASE + ADC_O_SSFIFO0), &ADC_Out1, ADC_SAMPLE_BUF_SIZE);
        uDMAChannelTransferSet(uDMACHANNEL_ADC1_SEQ0 | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG, (void *)(ADC1_BASE + ADC_O_SSFIFO0), &ADC_Out2, ADC_SAMPLE_BUF_SIZE);
    
        uDMAChannelEnable(uDMACHANNEL_ADC1_SEQ0); // Enables DMA channel so it can perform transfers
    
    }
    

    Best Regards,
    Bob Crosby

  • In reply to Bob Crosby:

    That was the issue all along.  It is interesting as this breaks from the Tiva implementation of the number 1 refers to module 1 just like SSI1 or UART1.  Thanks for getting to the root of the problem as it functions now as expected.

    Tim