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:
- Set the 8-bit address of the pixel column (8 GPIO pins with BSRR)
- wait 40ns before reading the analog output (time delay due to calculations, if necessary add some NOPs)
- 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:
- 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:
- 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.
- 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!