ADC DMA en STM32, Nucleo F334R8 detiene la ejecución del bucle mientras principal

Estoy leyendo ADC a través de DMA en el STM32 Nucleo F334R8. Sin embargo, después de iniciar el DMA con el siguiente código, el ciclo while principal no se ejecuta

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

Entiendo que las interrupciones de DMA pueden ocurrir con tanta frecuencia que es posible que no permitan que ocurran otras interrupciones, por lo que he cambiado la prioridad de DMA para que sea lo más baja posible:

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

Sé que se están manejando otras interrupciones ya que los siguientes módulos están operativos:

  • UART, envía y recibe mensajes a través de un puerto COM
  • TEMPORIZADOR, cambia un LED cada 500ms
  • PULSADOR, enciende un LED basado en presionar un botón.

No entiendo por qué el ciclo while principal no se ejecutará si el ADC DMA está habilitado. Si comento el código a continuación, se ejecutará el ciclo while principal

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

Aquí está la configuración del ADC:

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 */
    }
}

Aquí está la configuración de DMA:

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);

}

Aquí está lo principal:

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();
        }
    }
}

Respuestas (1)

Entiendo que las interrupciones de DMA pueden ocurrir con tanta frecuencia que es posible que no permitan que ocurran otras interrupciones, por lo que he cambiado la prioridad de DMA para que sea lo más baja posible.

[...]

No entiendo por qué el ciclo while principal no se ejecutará si el ADC DMA está habilitado.

Si las interrupciones de DMA ocurrieran con tanta frecuencia que evitarían que se ejecutaran otros controladores de interrupciones, no permitirían que el código principal se ejecutara también.

Es posible configurar el programa principal para que se ejecute con una prioridad más alta que ciertas interrupciones, con la __set_PRIMASK()función CMSIS, que se traduciría en una MSR PRIMASK, Rxinstrucción, pero esto deshabilitaría efectivamente todas las interrupciones de menor prioridad, porque el programa principal nunca termina (a menos que llegue una falla irrecuperable).

En mi opinión, deberías

  • piénselo bien y encuentre una solución que no necesite una interrupción después de cada secuencia de conversión (y a mitad de camino),
  • No use HAL para ningún código de tiempo crítico.
  • o ralentizar las mediciones

Aumento del tiempo de muestreo

El tiempo de muestra para cada canal se puede ajustar en los registros ADC->SMPR1y ADC->SMPR2, o configurando el SamplingTimecampo en la estructura de inicialización del canal, de 1,5 a 601,5 ciclos de reloj ADC.

Reducir la frecuencia del reloj ADC

El reloj ADC se puede cronometrar desde el reloj AHB, opcionalmente dividido por 2 o 4. Esto está controlado por los CKMODEcampos del ADC->CCRregistro, o por el Init.ClockPrescalercampo de la estructura de inicialización ADC.

Alternativamente, el ADC puede sincronizarse desde el PLL principal, opcionalmente dividido por 2, 4, 6, 8, 10, 12, 16, 32, 64, 128 o 256. Los CKMODEbits o Init.ClockPrescalerdeben ser 0 ( ADC_CLOCK_ASYNC_DIV1), y el divisor puede seleccionarse en RCC->CFGR2, o establecido porHAL_RCCEx_PeriphCLKConfig()

Los métodos anteriores se basan en aumentar el tiempo necesario para cada conversión en la secuencia, esto podría no ser adecuado para algunas aplicaciones, donde los canales deben muestrearse en un intervalo de tiempo corto, para obtener resultados más o menos sincrónicos.

Aumentar el intervalo entre secuencias de conversión

La secuencia de conversión de ADC también puede iniciarse mediante un evento de temporizador. En este caso, se debe desactivar el modo de conversión continua y se debe seleccionar una fuente de eventos en los bits EXTENy EXTSELdel ADC->CFGRregistro. Las posibles fuentes de eventos se enumeran en el Capítulo 13.3.18 Conversión en disparo externo y polaridad de disparo en el Manual de referencia . Los temporizadores ofrecen una gran flexibilidad para seleccionar una frecuencia de muestreo adecuada, pero son un recurso limitado. Si una frecuencia de muestreo de 1 kHz o inferior fuera suficiente, entonces la forma más sencilla sería iniciar la conversión cada vez desde el controlador de interrupción periódica, si tiene uno.

¿Es posible que este problema esté relacionado con la velocidad del reloj? Estoy ejecutando el mismo código en el Nucleo-F722ZE, que funciona a 200 MHz, el bucle principal funciona correctamente.
Sí. El ADC del F3 puede funcionar a 72 MHz, lo mismo que el núcleo del procesador, mientras que en el F7 funciona como máximo a 36 MHz.
Cambié el escalador previo del reloj para que el ADC lo ralentizara, y ahora se ejecuta el ciclo principal. Si edita su respuesta, lo haré con comentarios sobre la velocidad del reloj, lo aceptaré como la respuesta correcta.
@notransients he ampliado la respuesta
confirmó que esto era un problema para la placa STM32F746G-DISCO. Si ralentizo el ADC configurando APB2 a 25Mhz (/8 a 200Mhz), entonces la placa vuelve a responder