Using STM32L4 ADC with DMA for imaging sensor reading

Viewed 460

I was reading a lot how to do the multichannel ADC readings. My first success was with poll for conversion. Then I managed to switch to ADC with DMA but I got stuck with my new goal. I have analog output from image sensor I want to read. I will not bother you with the full loop, but basically most important is the loops for the columns:

  1. Set the 8-bit address of the pixel column (8 GPIO pins with BSRR)
  2. wait 40ns before reading the analog output (time delay due to calculations, if necessary add some NOPs)
  3. read two outputs (simultaneously 2 pixels are read) and do some calculations with them

Doing it with channel selection and poll for conversion is extremely slow and I want to have several frames per sec (the more the better). I tried to do it in Scan mode, 2 channels, contconv disabled, DMA set to normal mode. I still see there is a big overhead due to ADC start each time. Best idea would be to read the whole row of 320 values using ADC+DMA, then stop the ADC, do some calcs and continue with next row. I guess a big issue is the fact I try to check it in the debug mode through Truestudio so any advice how to proceed optimally will be appreciated. First, some clarifying questions:

  1. Would it be correct to set ADCbuffer[320] for the DMA and write HAL_ADC_Start_DMA(&hadc1, ADCBuffer, 320) although I have 2 channels? I expect the ADC to loop the sequence of 2 channels in contconv mode, so DMA would fill the buffer. My idea is to put a check for the column number and once final one is reached to stop the ADC. If this is not feasible, I will stick to ADCbuffer[2] and two readings for the DMA.

While I try to debug the case with contconv ADC with 2 channels and circular DMA, I notice a number of problems (some of them may be due to debugger mode). By the way, System clock is 100MHz (4 WS for Flash for my processor), ADC works at 80MHz:

  1. Regardless of the integration time I chose, the time between two HAL_ADC_ConvHalfCpltCallback remains about 150 ticks. Even in the case 640.5 cycles I get 200 ticks. I find it strange, but it may be due to debug mode.
  2. While HAL_ADC_ConvHalfCpltCallback is regularly called and I can insert the bus change for next pixel, HAL_ADC_ConvCpltCallback is not called or called very rarely randomly. Any idea why would that happen?

Considering the timing issues, I am curious if there is a way to use ADC with DMA, disabled contconv, normal mode DMA, but with some fast ADC start (not HAL) or some interruption for ADC start. Thus, I can be sure I read 2 values, send command to the sensor to read next pixel and so on, but with less overhead. I think my code is set correctly at the moment, but I will post it in case someone can find an issue fixing some of the problems. I am using STM32L4R5ZI:

static void MX_ADC1_Init(void)
{


  ADC_ChannelConfTypeDef sConfig = {0};

  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_ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.NbrOfConversion = 2;
  hadc1.Init.NbrOfDiscConversion = 0;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = ENABLE; 
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc1.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; 
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = 2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  HAL_NVIC_SetPriority(ADC1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(ADC1_IRQn);
}


static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMAMUX1_CLK_ENABLE();
  __HAL_RCC_DMA2_CLK_ENABLE();
  __DMA2_CLK_ENABLE();

  /* DMA interrupt init */
  hdma_adc1.Instance = DMA2_Channel3;
  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;
  //HAL_DMA_DeInit(&hdma_adc1);
  HAL_DMA_Init(&hdma_adc1);
  __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

  DMAMUX1_Channel9->CCR = 0x5;


  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Channel3_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Channel3_IRQn);
  /* DMAMUX1_OVR_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMAMUX1_OVR_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMAMUX1_OVR_IRQn);

}


//somewhere in main.c, the read out loop:
uint32_t ADCBuffer[2];

GPIOF -> BSRR = (col) | (((~col) & 0xFF) << (16)); //r_col case, F0:7
HAL_ADC_Start_DMA(&hadc1, ADCBuffer, 2);


//And the callbacks and handlers in STM32L4xx_it.c:

void DMA2_Channel3_IRQHandler(void)
{
  HAL_DMA_IRQHandler(&hdma_adc1);
  HAL_ADC_IRQHandler(&hadc1);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    if(vCol==159){
        HAL_ADC_Stop_DMA(hadc);
    }
}

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)
{
    vCol++;
    GPIOF -> BSRR = (vCol) | (((~vCol) & 0xFF) << (16)); //r_col case, F0:7
    
    rsum = rsum + ADCBuffer[0] + ADCBuffer[1];
}

void ADC_IRQHandler()
{
    HAL_ADC_IRQHandler(&hadc1);
}

Any help and ideas how to proceed are greatly appreciated!

0 Answers
Related