DMA stops due to ADC Overrun
Hi,
I have a problem with the DMA seemingly being shut down due to ADC overrun.
What I want to achieve:
Three ADC channels should be read indefinitely and the values should be written to memory by DMA as soon as they are ready.
What's currently happening:
ADC and DMA work in the beginning, so my buffer fills up with values, but shortly after the start, the values in the buffer are not updated anymore and the DMA2_Stream0_IRQHandler is not called anymore either. I suspect the problem is an overrun, as the OVR bit is set in the ADC_ISR register (value is 0x101F) and ErrorCode is set to 2.
The ADC_DR registers value still changes, so the ADC itself seems to be operational, and only the DMA stopped working.
When the DMA does not work anymore, the DMA_CR register value alternates between 0x440 and 0x457. NDTR is some value over 140, and the addresses in M0AR and M1AR keep changing.
My code for the ADC/DMA is as follows:
DMA Initialization:
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMAMUX_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}ADC Config:
// ADC configuration:
AdcManager::AdcManager(ADC_TypeDef* adc, ADC_HandleTypeDef* hadc) {
adcHandle = hadc; // this is a pointer to ADC_HandleTypeDef hadc1;
/* auto-generated by IOC file - use IOC file to change config and copy-paste it here */
/** Common config
*/
adcHandle->Instance = adc; // this is ADC1
adcHandle->Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;
adcHandle->Init.Resolution = ADC_RESOLUTION_12B;
adcHandle->Init.ScanConvMode = ADC_SCAN_ENABLE;
adcHandle->Init.EOCSelection = ADC_EOC_SEQ_CONV;
adcHandle->Init.LowPowerAutoWait = DISABLE;
adcHandle->Init.ContinuousConvMode = ENABLE;
adcHandle->Init.NbrOfConversion = NUM_ADC_INSTANCES;
adcHandle->Init.DiscontinuousConvMode = DISABLE;
adcHandle->Init.NbrOfDiscConversion = 1;
adcHandle->Init.ExternalTrigConv = ADC_SOFTWARE_START;
adcHandle->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
adcHandle->Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
adcHandle->Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
adcHandle->Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
adcHandle->Init.OversamplingMode = DISABLE;
}
// this is called to add a new channel:
void AdcManager::configureChannel(uint32_t channel, uint32_t rank, uint32_t sampleTime)
{
/** Configure Regular Channel
*/
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = channel;
sConfig.Rank = rank;
sConfig.SamplingTime = sampleTime;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
HAL_ADC_ConfigChannel(adcHandle, &sConfig);
}
void AdcManager::init(void)
{
ASSERT(HAL_ADC_DeInit(adcHandle) == HAL_OK);
ASSERT(HAL_ADC_Init(adcHandle) == HAL_OK);
/** Configure the ADC multi-mode
*/
ADC_MultiModeTypeDef multimode = {0};
multimode.Mode = ADC_MODE_INDEPENDENT; // multi-mode disabled
ASSERT(HAL_ADCEx_MultiModeConfigChannel(adcHandle, &multimode) == HAL_OK);
/* Run the ADC calibration in single-ended mode */
ASSERT(HAL_ADCEx_Calibration_Start(adcHandle, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED) == HAL_OK);
SET_BIT(adcHandle->Instance->IER, ADC_IER_EOSIE);
}
void AdcManager::start()
{
ASSERT(HAL_ADC_Start_DMA(this->adcHandle, (uint32_t*) &samplingData[0], NUM_ADC_INSTANCES) == HAL_OK); // NUM_ADC_INSTANCES is the number of channels (3 in this case)
}
Msp:
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(hadc->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
if(IS_ENGINEERING_BOOT_MODE())
{
/** Initializes the peripherals clock
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLL4;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/* Peripheral clock enable */
__HAL_RCC_ADC12_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/**ADC1 GPIO Configuration
PC2 ------> ADC1_INP12
PC3 ------> ADC1_INP13
*/
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Request = DMA_REQUEST_ADC1;
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_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
/* ADC1 interrupt Init */
HAL_NVIC_SetPriority(ADC1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(ADC1_IRQn);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}What I also tried:
- Using a 1 kHz timer to trigger the ADC conversion, hoping it would be slow enough to prevent an overrun. This did not change the behavior though. I only changed these settings and left the rest as it was:
- adcHandle->Init.ExternalTrigConv = ADC_EXTERNALTRIG_T2_TRGO; adcHandle->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
- Setting adcHandle->Init.LowPowerAutoWait to ENABLE. With this setting, the OVR bit is not set, but the DMA stops working nevertheless. I know that it is advised not to use this setting with Circular Mode, but I wanted to try whether it makes a difference.
Any help that points me in the right direction would be highly appreciated.
