Electronic – ADC DMA on STM32, Nucleo F334R8 stops main while loop from executing

adcdmanucleostm32

I am am reading ADC via DMA on the STM32 Nucleo F334R8. However, after I start the DMA using the code below, the main while loop fails to execute

HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADC_Raw, 4);

I understand that the DMA interrupts may be occurring so often that it may not allow other interrupts to occur, so I have changed the priority for the DMA to be low as possible:

HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 15);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

I know that other interrupts are being handled as the following modules are operational:

  • UART, send and receive message through a COM port
  • TIMER, toggle an LED every 500ms
  • PUSH BUTTON, turn on a LED based on a push button press.

I don't understand why the main while loop will not execute if the ADC DMA is enabled. If I comment out the code below, the the main while loop will execute

HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADC_Raw, 4);

Here is the ADC configuration:

void MX_ADC1_Init(void)
{
    ADC_MultiModeTypeDef multimode;
    ADC_ChannelConfTypeDef sConfig;

    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 4;
    hadc1.Init.DMAContinuousRequests = ENABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;

    if(HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    if(HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }
    /**Configure the ADC multi-mode
    */
  multimode.Mode = ADC_MODE_INDEPENDENT;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Configure Regular Channel
     */
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank = 1;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.SamplingTime = ADC_SAMPLETIME_19CYCLES_5;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if(HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    /**Configure Regular Channel
     */
    sConfig.Channel = ADC_CHANNEL_2;
    sConfig.Rank = 2;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.SamplingTime = ADC_SAMPLETIME_19CYCLES_5;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if(HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    /**Configure Regular Channel
     */
    sConfig.Channel = ADC_CHANNEL_6;
    sConfig.Rank = 3;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.SamplingTime = ADC_SAMPLETIME_19CYCLES_5;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if(HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    } 

    /**Configure Regular Channel
     */
    sConfig.Channel = ADC_CHANNEL_7;
    sConfig.Rank = 4;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.SamplingTime = ADC_SAMPLETIME_19CYCLES_5;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if(HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
       _Error_Handler(__FILE__, __LINE__);
    }
}

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{

    GPIO_InitTypeDef GPIO_InitStruct;
    if(adcHandle->Instance == ADC1)
    {
        /* USER CODE BEGIN ADC1_MspInit 0 */

        /* USER CODE END ADC1_MspInit 0 */
        /* ADC1 clock enable */
        __HAL_RCC_ADC12_CLK_ENABLE()
        ;

        /**ADC1 GPIO Configuration
         PC0     ------> ADC1_IN6
         PC1     ------> ADC1_IN7
         PA0     ------> ADC1_IN1
         PA1     ------> ADC1_IN2
         */
        GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        /* ADC1 DMA Init */
        /* ADC1 Init */
        hdma_adc1.Instance = DMA1_Channel1;
        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_LOW;
        if(HAL_DMA_Init(&hdma_adc1) != HAL_OK)
        {
            _Error_Handler(__FILE__, __LINE__);
        }

        __HAL_LINKDMA(adcHandle, DMA_Handle, hdma_adc1);

        /* ADC1 interrupt Init */
        HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 14);
        HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
        /* USER CODE BEGIN ADC1_MspInit 1 */

        /* USER CODE END ADC1_MspInit 1 */

        /* USER CODE BEGIN ADC1_MspInit 1 */

        /* USER CODE END ADC1_MspInit 1 */
    }
}

Here is the DMA configuration:

void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

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

}

Here is the main:

int main(void)
{

    /* USER CODE BEGIN 1 */

    /* USER CODE END 1 */

    /* MCU Configuration----------------------------------------------------------*/
    CPU_CACHE_Enable();
    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* USER CODE BEGIN Init */

    /* USER CODE END Init */

    /* Configure the system clock */
    SystemClock_Config();

    /* USER CODE BEGIN SysInit */

    /* USER CODE END SysInit */

    /* Initialize all configured peripherals */
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_ADC1_Init();
    MX_I2C1_Init();
    MX_SPI1_Init();
    MX_TIM1_Init();
    MX_TIM3_Init();
    MX_TIM6_Init();
    MX_USART2_UART_Init(); 

    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&ADC_Raw, 4); 
    my_printf("Version 1.0\n");
    InitializeControllerCommand();


    while (1)
    {
        if(start_pwm == true)
        {
            fade_LED();
        }

        if(readInput == 1)
        {
            readInputPin();
        }
    }
}

Best Answer

I understand that the DMA interrupts may be occurring so often that it may not allow other interrupts to occur, so I have changed the priority for the DMA to be low as possible

[...]

I don't understand why the main while loop will not execute if the ADC DMA is enabled.

If DMA interrupts were occuring so oft, that they would prevent other interrupt handlers from running, they wouldn't let the main code run as well.

It is possible to set the main program to run at a higher priority than certain interrupts, with the __set_PRIMASK() CMSIS function, which would translate to a MSR PRIMASK, Rx instruction, but this would effectively disable all lower priority interrupts, because the main program never finishes (unless it hits an unrecoverable fault).

In my opinion, you should

  • think it over, and find a solution that doesn't need an interrupt after every conversion sequence (and halfway throgh),
  • not use HAL for any time-critical code.
  • or slow down the measurements

Increasing the sampling time

Sample time for each channel can be adjusted in the ADC->SMPR1 and ADC->SMPR2 registers, or by setting the SamplingTime field in the channel initialization structure, from 1.5 to 601.5 ADC clock cycles.

Reducing the ADC clock frequency

The ADC clock can be clocked from the AHB clock, optionally divided by 2 or 4. This is controlled by the CKMODE fields of the ADC->CCR register, or by the Init.ClockPrescaler field of the ADC initialization structure.

Alternatively, the ADC can be clocked from the main PLL, optionally divided by 2,4,6,8,10,12,16,32,64,128 or 256. The CKMODE bits or Init.ClockPrescaler must be 0 (ADC_CLOCK_ASYNC_DIV1), and the divisor can be selected in RCC->CFGR2, or set by HAL_RCCEx_PeriphCLKConfig()

The above methods rely on increasing the time needed for each conversion in the sequence, this might not be suitable for some applications, where the channels should be sampled in a short time interval, to get more or less synchronous results.

Increasing the interval between conversion sequences

The ADC conversion sequence can be started by a timer event as well. In this case, continuous conversion mode should be disabled, and an event source must be selected in the EXTEN and EXTSEL bits of the ADC->CFGR register. Possible event sources are listed in Chapter 13.3.18 Conversion on external trigger and trigger polarity in the Reference Manual. Timers offer great flexibility in selecting an appropriate sampling frequency, but they are a limited resource. If a sampling frequency of 1 kHz or lower would be sufficient, then the simplest way would be to just start the conversion each time from the periodic interrupt handler, if you have one.