cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L4 Continous ADC DMA Multi-Channel Data Processing

abotha
Associate III
Posted on January 31, 2018 at 06:57

Good Day

I have an ADC Configured for Continous conversion with DMA. I Periodically stop it and quickly process the data before starting it again.

I would like to know how to process the data if I enable more than one channel, as the data is then interleaved in the ADC DMA Array 'getADCBuffer[2000]' - how can I split this into a separate array for each channel in the most efficient way? To use a 'for' loop and copy every nth byte into another array takes too much processing time and my ADC task can't keep up with what it should be measuring.

Is it possible to set up a Memory to Memory DMA Stream and increment the source address by the number of channels while only incrementing the destination address by one?

I am using an STM32L433 which only has one ADC.

Some Code to illustrate what I am currently doing while only having the 1 channel configured:

ADC Config: 
void MX_ADC1_Init(void)
{
 ADC_ChannelConfTypeDef sConfig;
 /**Common config 
 */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_12B;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = ENABLE;
 hadc1.Init.NbrOfConversion = 1;
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.NbrOfDiscConversion = 1;
 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
 hadc1.Init.DMAContinuousRequests = ENABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
 hadc1.Init.OversamplingMode = DISABLE;
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 /**Configure Regular Channel 
 */
 sConfig.Channel = ADC_CHANNEL_4;
 sConfig.Rank = ADC_REGULAR_RANK_1;
 sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
}
ADC DMA Config: 
 /* ADC1 DMA Init */
 /* ADC1 Init */
 hdma_adc1.Instance = DMA1_Channel1;
 hdma_adc1.Init.Request = DMA_REQUEST_0;
 hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
 hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
 hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
 hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
 hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
 hdma_adc1.Init.Mode = DMA_CIRCULAR;
 hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
 if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
 {
 _Error_Handler(__FILE__, __LINE__);
 }
 __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
//I then start the ADC:
HAL_ADC_Start_DMA(&hadc1, (uint16_t*)getADCBuffer, adcBufferLength); \\ADC BuffLength = 2000
//And stop it when I want to process the data, giving a Binary Semaphore to a high priority processing task:
else if (ICEventCount == 6)
{
HAL_ADC_Stop_DMA(&hadc1);
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
xSemaphoreGiveFromISR(processADC_BinSemHandle, &xHigherPriorityTaskWoken);
if(xHigherPriorityTaskWoken == pdTRUE)
{
portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
}
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus ); // Done with the critical section
}
//I am only interested in the Maximum and Minimum value withing the array for this particular channel, I use the arm DSP Lib to get these:
if(xSemaphoreTake(processADC_BinSemHandle, portMAX_DELAY))
{
dmaWritePtr = (adcBufferLength - (uint16_t)(DMA1_Channel1->CNDTR));
dataCounter = dmaWritePtr - dmaReadPtr;
if (dataCounter == 0)
{
}
else if (dataCounter > 0)
{
adcSampleLength = (uint32_t)dataCounter;
arm_max_q15(getADCBuffer, adcSampleLength, &adcMaxSample, &sampleMaxIndex);
arm_min_q15(getADCBuffer, adcSampleLength, &adcMinSample, &sampleMinIndex);
}
else
{
adcSampleLength = (uint32_t)(adcBufferLength + dataCounter);
arm_max_q15(getADCBuffer, adcSampleLength, &adcMaxSample, &sampleMaxIndex);
arm_min_q15(getADCBuffer, adcSampleLength, &adcMinSample, &sampleMinIndex);
}
ADC_HighWaterMark = uxTaskGetStackHighWaterMark( NULL );
}
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

#dma #multichannel-adc #adc
2 REPLIES 2
T J
Lead
Posted on January 31, 2018 at 12:20

hi,

that looks so difficult.

// this way, you dont have to stop the DMA.

I use the cube, so the config is all done, see my other posts today for my DMA cube config

I use this to get 16 rows of all the 14 channels, automatically filled under the ADC-DMA Callback

#define ADC_ChannelCount 14   // 14 analog inputs all passed by DMA

#define ADC_RowCount  16      // 16 rows of results to be summed and averaged for each analog input

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {

    if(!ADC_ChannelsCounter--) {            // every 14 channels it copies the whole set to the next row

        ADC_ChannelsCounter = ADC_ChannelCount;

        HAL_ADC_CompleteCycle = true;         // signal foreground to process

        // copy 15 results away now

                        for(int i = 0 ; i < ADC_ChannelCount ; i++) {

            //here add 2nd dimension to the array 16 rows of 14 results.

            HAL_ADC_Channel_Buffer[ADC_RowCounter][i] = ADC_DMABuffer[i];  // AVEColumnSum += HAL_ADC_Channel_Buffer [Ave_ADC_RowCounter][Ave_ADC_ChannelCounter];

        }

        ADC_RowCounter++;                             // for next time through the loop      // next row for next time

        if(ADC_RowCounter == ADC_RowCount) ADC_RowCounter = 0;        // 16 rows.

    }

}

in the foreground;

   seeing the flag;

    if (HAL_ADC_CompleteCycle) {

        HAL_ADC_CompleteCycle = false;

        ADCcompletedcounter++;

        Ave_next_ADC_Column();

    }

void Ave_next_ADC_Column(void) {      // 16x oversampled result.

    uint32_t AVEColumnSum = 0;

    for (int Ave_ADC_RowCounter = 0; Ave_ADC_RowCounter < ADC_RowCount; Ave_ADC_RowCounter++)

        AVEColumnSum += HAL_ADC_Channel_Buffer[Ave_ADC_RowCounter][Ave_ADC_ChannelCounter];

    ADC_Channel_AVE[Ave_ADC_ChannelCounter] = AVEColumnSum;  // we take 4 extra bits of resolution here.....  we should divide by 16.

    Ave_ADC_ChannelCounter++;                                             // for next time, its the next column

        if(Ave_ADC_ChannelCounter >= ADC_ChannelCount)

          {

              Ave_ADC_ChannelCounter = 0;                                  // for next time, its the next column

              AllADC_AveragesUpdatedFlag = true;                     // All averages updated

          }

}
abotha
Associate III
Posted on February 01, 2018 at 11:40

Hi TJ

That is unfortunately a little interrupt heavy for my implementation, which only has 5 active channels. 

I have ended up doing a DMA Memory to Memory Transfer, I will post my solution to the problem if no-one can point me towards a better implementation.