AnsweredAssumed Answered

KV30 ADC DMA scan inconsistent

Question asked by Christian Lees on Apr 10, 2018
Latest reply on Apr 18, 2018 by Jing Pan

I've implemented an ADC scan using two DMA controllers as per the application notes.  What I'm seeing is inconsistent performance.  There is a lot of jitter but the main issue is the results can end up in the wrong places.  The results get written to the incorrect memory locations, they slip by one.

 

The DMA is set up to do each ADC channel three times, ie. 1 2 3 1 2 3 1 2 3 and then I use a median filter on the values when I use them as the inputs for a compensator.

 

What I am seeing is that sometimes it is 1 2 3 1 2 3 1 2 3 but then it can slip and be 2 3 1 2 3 1 2 3 1.

 

What have I missed.  There is another loop that calls the median filter and scaling and compenstation routines, the ADC loop is meant to run in the background with little CPU interaction.

 

/*
* adc.c
*
*  Created on: 26 Mar 2018
*      Author: clees
*/

#include "adc.h"


#define ADC_TEST

#define ADC_PERIOD 10000U     // in uS

#define ADC16_BASE ADC0
#define ADC16_CHANNEL_GROUP 0U
#define ADC_VIN 2
#define ADC_VBATT 0
#define ADC_IMON 1

#define B_SIZE           9u               // Internal Buffer size, 3 channels by 3 samples
#define CHANNELS         3u               // Number of ADC channels

// ADC Channels array
uint8_t ADC_mux[CHANNELS] =
     { ADC_VIN, ADC_VBATT, ADC_IMON };
/* Internal ADC buffer */
uint16_t ADC0_resultBuffer[B_SIZE] = { 0 };

#define DMAChannel_0      0u               /*DMA Channels*/
#define DMAChannel_1      1u

/* edma handler for channels 0 and 1 */
edma_handle_t g_EDMA_Handle_0;
edma_handle_t g_EDMA_Handle_1;

static inline uint16_t median_ADC_sample(uint16_t a, uint16_t b, uint16_t c)
{
    /* a minimalistic whilst transparent algorithm used. There is an alternate
     * version: (credits: https:/goo.gl/tgDU0g ) which has approx. same
     * execution time but bigger duration jitter due to asym. branching */

    uint16_t min, max;
    /* ~700ns */
    min = MIN(a, b);
    min = MIN(min, c);
    max = MAX(a, b);
    max = MAX(max, c);
    return (a + b + c - min - max);
}

void get_ADC_raw(ctrl_param_t *ctrl)
{
     // Apply a Median filter to the ADC results
     ctrl->meas.IMON_raw = median_ADC_sample(ADC0_resultBuffer[0], ADC0_resultBuffer[3], ADC0_resultBuffer[6]);
     ctrl->meas.VBus_raw = median_ADC_sample(ADC0_resultBuffer[1], ADC0_resultBuffer[4], ADC0_resultBuffer[7]);
     ctrl->meas.VBatt_raw = median_ADC_sample(ADC0_resultBuffer[2], ADC0_resultBuffer[5], ADC0_resultBuffer[8]);

}

volatile uint8_t toggle = 0;


void EDMA_Callback_1(edma_handle_t *handle, void *param, bool transferDone, uint32_t tcds)
{
     if (transferDone)
     {
          EDMA_StartTransfer(&g_EDMA_Handle_1);
     }


}

// Used for testing the ADC speed for loop calculation, toggle the diode control pin
#ifdef ADC_TEST
void ADC0_IRQHandler(void)
{
     //ADC16_ClearStatusFlags(ADC0, kADC16_ChannelConversionDoneFlag);
     if(toggle == 0)
     {
          toggle = 1;
     }
     else
     {
          toggle = 0;
     }
     GPIO_PinWrite(BOARD_DIODE_CONTROL_GPIO, BOARD_DIODE_CONTROL_GPIO_PIN, toggle);

}
#endif


int init_ADC_DMA()
{

     adc16_config_t adc16ConfigStruct;
     adc16_channel_config_t adc16ChannelConfigStruct;
     ADC16_GetDefaultConfig(&adc16ConfigStruct);

     adc16ConfigStruct.referenceVoltageSource = kADC16_ReferenceVoltageSourceVref;
     adc16ConfigStruct.clockSource = kADC16_ClockSourceAlt0; //kADC16_ClockSourceAsynchronousClock; //
     adc16ConfigStruct.enableAsynchronousClock = false;
     adc16ConfigStruct.clockDivider = kADC16_ClockDivider2;
     adc16ConfigStruct.resolution = kADC16_ResolutionSE12Bit;
     adc16ConfigStruct.longSampleMode = kADC16_LongSampleDisabled;
     adc16ConfigStruct.enableHighSpeed = true;
     adc16ConfigStruct.enableLowPower = false;
     adc16ConfigStruct.enableContinuousConversion = false;

     ADC16_Init( ADC0, &adc16ConfigStruct);

     ADC16_DoAutoCalibration( ADC0 );

     ADC16_EnableHardwareTrigger( ADC0, true);
     ADC16_EnableDMA( ADC0, true);

     adc16ChannelConfigStruct.channelNumber = ADC_mux[1];
     adc16ChannelConfigStruct.enableDifferentialConversion = false;



#ifdef ADC_TEST
     EnableIRQ(ADC0_IRQn);
     adc16ChannelConfigStruct.enableInterruptOnConversionCompleted = true; // Enable the interrupt.
#else
     adc16ChannelConfigStruct.enableInterruptOnConversionCompleted = false; // Enable the interrupt.
#endif

     ADC16_SetChannelConfig( ADC0, 0, &adc16ChannelConfigStruct);
     ADC16_SetHardwareAverage(ADC0, kADC16_HardwareAverageDisabled);

     SIM->SOPT7  = 0X84;

     pit_config_t pitConfig;
     PIT_GetDefaultConfig(&pitConfig);
     PIT_Init(PIT, &pitConfig);
     PIT_SetTimerPeriod(PIT, kPIT_Chnl_0, USEC_TO_COUNT(200U, CLOCK_GetFreq(kCLOCK_BusClk)));

     edma_transfer_config_t transferConfig_ch0;
     edma_transfer_config_t transferConfig_ch1;
     edma_config_t userConfig;

     // Configure DMAMUX
     DMAMUX_Init(DMAMUX0);

     DMAMUX_SetSource(DMAMUX0, DMAChannel_0, 60); // Channel 0 Source 60: DMA always enabled
     DMAMUX_EnableChannel(DMAMUX0, DMAChannel_0);

     DMAMUX_SetSource(DMAMUX0, DMAChannel_1, 40); // Channel 1 Source 40: ADC COCO trigger
     DMAMUX_EnableChannel(DMAMUX0, DMAChannel_1);


     EDMA_GetDefaultConfig(&userConfig);
     userConfig.enableHaltOnError = false;

     EDMA_Init(DMA0, &userConfig);

     EDMA_CreateHandle(&g_EDMA_Handle_1, DMA0, DMAChannel_1);
     EDMA_SetCallback(&g_EDMA_Handle_1, EDMA_Callback_1, NULL);

     EDMA_PrepareTransfer(&transferConfig_ch1,      // Prepare TCD for CH1
                               (uint32_t*) (ADC0->R), // Source Address (ADC0_RA)
                               sizeof(uint16_t),          // Source width (2 bytes)
                               ADC0_resultBuffer,     // Destination Address (Internal buffer)
                               sizeof(ADC0_resultBuffer[0]), // Destination width (2 bytes)
                               sizeof(uint16_t),          // Bytes to transfer each minor loop (2 bytes)
                               B_SIZE * 2,                // Total of bytes to transfer (12*2 bytes)
                               kEDMA_PeripheralToMemory); // From ADC to Memory
     // Push TCD for CH1 into hardware TCD Register
     EDMA_SubmitTransfer(&g_EDMA_Handle_1, &transferConfig_ch1);

     //Link channel 1 to channel 0
     // when channel 1 transfer ADC data,
     // then channel 0 will change ADC channel
     EDMA_SetChannelLink(DMA0, DMAChannel_1, kEDMA_MinorLink, DMAChannel_0);
     EDMA_SetChannelLink(DMA0, DMAChannel_1, kEDMA_MajorLink, DMAChannel_0);

     EDMA_CreateHandle(&g_EDMA_Handle_0, DMA0, DMAChannel_0);
     //EDMA_SetCallback(&g_EDMA_Handle_0, EDMA_Callback_0, NULL);

     EDMA_PrepareTransfer(&transferConfig_ch0,     // Prepare TCD for CH0
                               &ADC_mux[0],               // Source Address (ADC channels array)
                               sizeof(ADC_mux[0]),     // Source width (1 bytes)
                               (uint32_t*)(ADC0->SC1),// Destination Address (ADC_SC1A_ADCH)
                               sizeof(uint8_t),          // Destination width (1 bytes)
                               sizeof(uint8_t),          // Bytes to transfer each minor loop (1 bytes)
                               CHANNELS,                    // Total of bytes to transfer (3*1 bytes)
                               kEDMA_MemoryToPeripheral);// From ADC channels array to ADCH register
     // Push TCD for CH0 into hardware TCD Register
     EDMA_SubmitTransfer(&g_EDMA_Handle_0, &transferConfig_ch0);

     // If transfer will continue it will need to adjust last source and destination
     DMA0->TCD[0].SLAST = -1 * CHANNELS;
     DMA0->TCD[1].DLAST_SGA = -2 * B_SIZE;

     EDMA_StartTransfer(&g_EDMA_Handle_1);

     // Start the sample clock
     NVIC_SetPriority(DMA0_IRQn, 0);
     PIT_StartTimer(PIT, kPIT_Chnl_0);

     return 1;

}


Outcomes