ADC DMA en STM32, Nucleo F334R8 detiene el ciclo principal mientras se ejecuta el bucle

1

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

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

Comprendo que las interrupciones de DMA pueden estar ocurriendo tan a menudo que no permiten que ocurran otras interrupciones, por lo que he cambiado la prioridad para que el DMA sea lo más bajo 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, enviar y recibir mensajes a través de un puerto COM
  • TEMPORIZADOR, alternar un LED cada 500 ms
  • PULSE EL BOTÓN, encienda un LED con solo presionar un botón.

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

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

Aquí está la configuración de 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á el 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();
        }
    }
}
    
pregunta notransients

1 respuesta

3
  

Comprendo que las interrupciones de DMA pueden estar ocurriendo tan a menudo que no permiten que ocurran otras interrupciones, por lo que he cambiado la prioridad para que el DMA sea lo más bajo posible

[...]

  

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

Si las interrupciones de DMA se produjeran tan a menudo, que evitarían que se ejecuten otros manejadores de interrupciones, tampoco permitirían que se ejecute el código principal.

Es posible configurar el programa principal para que se ejecute con una prioridad más alta que ciertas interrupciones, con la función __set_PRIMASK() CMSIS, que se traduciría en una instrucción MSR PRIMASK, Rx , pero esto desactivaría todas las interrupciones de prioridad más baja, porque el programa principal nunca termina (a menos que se produzca un error irrecuperable).

En mi opinión, deberías

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

Aumentar el tiempo de muestreo

El tiempo de muestreo para cada canal se puede ajustar en los registros ADC->SMPR1 y ADC->SMPR2 , o configurando el campo SamplingTime 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 sincronizar desde el reloj AHB, opcionalmente dividido por 2 o 4. Esto se controla mediante los campos CKMODE del registro ADC->CCR , o mediante el campo Init.ClockPrescaler de la estructura de inicialización ADC.

Alternativamente, el ADC se puede sincronizar desde el PLL principal, opcionalmente dividido por 2,4,6,8,10,12,16,32,64,128 o 256. El CKMODE bits o Init.ClockPrescaler debe ser 0 ( ADC_CLOCK_ASYNC_DIV1 ), y el divisor se puede seleccionar en RCC->CFGR2 , o establecer por HAL_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 deberían muestrearse en un corto intervalo de tiempo, para obtener resultados más o menos sincronizados.

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, el modo de conversión continua debe estar deshabilitado y debe seleccionarse un origen de evento en los bits EXTEN y EXTSEL del registro ADC->CFGR . Las posibles fuentes de eventos se enumeran en Capítulo 13.3.18 Conversión en disparador externo y polaridad de disparo en Manual de referencia . Los temporizadores ofrecen una gran flexibilidad para seleccionar una frecuencia de muestreo apropiada, pero son un recurso limitado. Si una frecuencia de muestreo de 1 kHz o inferior sería suficiente, entonces la forma más sencilla sería comenzar la conversión cada vez desde el controlador de interrupciones periódicas, si tiene uno.

    
respondido por el berendi

Lea otras preguntas en las etiquetas