1 votos

STM32: ¿Por qué no puedo utilizar el modo de exploración en el ADC controlado por interrupción?

Estoy usando un STM32F103C8 para leer 3 canales ADC, y he utilizado CubeMX + HAL para configurar el ADC para lanzar los valores ADC en un búfer.

He podido conseguirlo con DMA y polling: He visto ambos como formas generalmente aceptables para hacer esto en línea. Sin embargo, no he sido capaz de encontrar una configuración que me permita escanear a través de los canales iniciando manualmente las conversiones en la interrupción EOC. En todos mis intentos, o bien no avanza canales, o la interrupción no se dispara en absoluto.

Preferiría utilizar interrupciones porque mi único canal DMA en el dispositivo se utiliza para almacenar en búfer algunos datos de audio de frecuencia bastante alta, pero también estoy preocupado porque no puedo entender la forma en que las interrupciones ADC trabajan en conjunto con el modo de exploración. He utilizado los siguientes enfoques:

  • DMA : Esta parece ser la forma autorizada de escanear varios canales y almacenar sus respectivos resultados. En particular, el manual del usuario en §11.3.8 ¶3 dice:

    Cuando se utiliza el modo scan, el bit DMA debe estar activado y el controlador de acceso directo a memoria se utiliza para transferir los datos convertidos de los canales de grupo regulares a la SRAM después de cada actualización del registro ADC_DR.

    He conseguido que funcione con los ajustes intuitivos de CubeMX:

    • ADC_Settings:
      • Modo de conversión de escáner: Activado
      • Modo continuo: Activado
      • Modo discontinuo: Desactivado
    • ADC_Regular_Conversion_Mode
      • Activar conversiones regulares: Activar
      • Número de conversiones: 3
      • Fuente de conversión de disparo externo: Conversión regular lanzada por software
      • <configuraciones de canales y rangos...>

    más un DMA circular alineado por media palabra, y una llamada directa a HAL_ADC_Start_DMA() en la fuente.

  • Sondeo : He intentado lo siguiente esta respuesta que desactiva los modos continuo y discontinuo, y es capaz de recorrer los canales con sucesivas llamadas a HAL_ADC_PollForConversion solo. Descubrí que necesitaba activar el modo discontinuo con tamaños de grupo de 1, es decir:

    hadc1.Init.DiscontinuousConvMode = ENABLE;
    hadc1.Init.NbrOfDiscConversion = 1;

    A continuación, recorriendo los canales con HAL_ADC_PollForConversion funcionó sin problemas.

  • Interrupciones: He probado todas las permutaciones de modo de exploración, modo discontinuo y número de conversiones discontinuas, y ninguna de ellas me permite recorrer los canales en el HAL_ADC_ConvCpltCallback rutina de interrupción. Aquí está la rutina que estoy usando:

    #define NUM_ADC_BUF 8
    #define NUM_ADC_CH 3
    volatile uint16_t adc_buf[NUM_ADC_BUF][NUM_ADC_CH];
    void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
      if(hadc->Instance == ADC1) {
        adc_buf[adc_buf_idx & (NUM_ADC_BUF - 1)][adc_ch++] = HAL_ADC_GetValue(hadc);
        if(adc_ch == NUM_ADC_CH) {
          adc_ch = 0;
          adc_buf_idx++;
        }
    
        HAL_ADC_Start_IT(hadc);
      }
    }

    Dentro del ADC_Settings en CubeMX, aquí están mis intentos y sus resultados:

    +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
    | (Continuous mode) | Scan mode | Discontinuous mode | Number of           |                      Outcome                         |
    |                   |           |                    | discontinuous conv. |                                                      |
    +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+
    | DISABLED          | ENABLED   | ENABLED            | 3                   | Only highest-rank-# (rank 3) channel result is given |
    | DISABLED          | ENABLED   | ENABLED            | 1                   | Interrupt never fires: EOC never set                 |
    | DISABLED          | ENABLED   | DISABLED           | N/A                 | Only highest-rank-# (rank 3) channel result is given |
    | DISABLED          | DISABLED  | ENABLED            | 3                   | Only lowest-rank-# (rank 1) channel result is given  |
    | DISABLED          | DISABLED  | ENABLED            | 1                   | Only lowest-rank-# (rank 1) channel result is given  |
    | DISABLED          | DISABLED  | DISABLED           | N/A                 | Only lowest-rank-# (rank 1) channel result is given  |
    +-------------------+-----------+--------------------+---------------------+------------------------------------------------------+

    Como puede ver, ninguna combinación funciona correctamente. ¿Es simplemente imposible? Supongo que puedo tomar el extracto que he citado del manual de usuario como que la única forma de usar el escaneo es el DMA, y que el sondeo funciona como una característica no soportada formalmente. ¿Es esto cierto?

    Como referencia, aquí está mi auto-generado sin tocar adc.c de CubeMX:

    /* Includes ------------------------------------------------------------------*/
    #include "adc.h"
    
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    ADC_HandleTypeDef hadc1;
    
    /* ADC1 init function */
    void MX_ADC1_Init(void)
    {
      ADC_ChannelConfTypeDef sConfig = {0};
    
      /** Common config 
      */
      hadc1.Instance = ADC1;
      hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
      hadc1.Init.ContinuousConvMode = DISABLE;
      hadc1.Init.DiscontinuousConvMode = ENABLE;
      hadc1.Init.NbrOfDiscConversion = 3;
      hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
      hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
      hadc1.Init.NbrOfConversion = 3;
      if (HAL_ADC_Init(&hadc1) != HAL_OK)
      {
        Error_Handler();
      }
      /** Configure Regular Channel 
      */
      sConfig.Channel = ADC_CHANNEL_4;
      sConfig.Rank = ADC_REGULAR_RANK_1;
      sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /** Configure Regular Channel 
      */
      sConfig.Channel = ADC_CHANNEL_8;
      sConfig.Rank = ADC_REGULAR_RANK_2;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /** Configure Regular Channel 
      */
      sConfig.Channel = ADC_CHANNEL_9;
      sConfig.Rank = ADC_REGULAR_RANK_3;
      if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
      {
        Error_Handler();
      }
    
    }
    
    void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      if(adcHandle->Instance==ADC1)
      {
      /* USER CODE BEGIN ADC1_MspInit 0 */
    
      /* USER CODE END ADC1_MspInit 0 */
     /* ADC1 clock enable */
     __HAL_RCC_ADC1_CLK_ENABLE();
    
     __HAL_RCC_GPIOA_CLK_ENABLE();
     __HAL_RCC_GPIOB_CLK_ENABLE();
     /**ADC1 GPIO Configuration    
     PA4     ------> ADC1_IN4
     PB0     ------> ADC1_IN8
     PB1     ------> ADC1_IN9 
     */
     GPIO_InitStruct.Pin = Xin_Pin;
     GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
     HAL_GPIO_Init(Xin_GPIO_Port, &GPIO_InitStruct);
    
     GPIO_InitStruct.Pin = Zin_Pin|Yin_Pin;
     GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    
     /* ADC1 interrupt Init */
     HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
     HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
      /* USER CODE BEGIN ADC1_MspInit 1 */
    
      /* USER CODE END ADC1_MspInit 1 */
      }
    }
    
    void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
    {
    
      if(adcHandle->Instance==ADC1)
      {
      /* USER CODE BEGIN ADC1_MspDeInit 0 */
    
      /* USER CODE END ADC1_MspDeInit 0 */
     /* Peripheral clock disable */
     __HAL_RCC_ADC1_CLK_DISABLE();
    
     /**ADC1 GPIO Configuration    
     PA4     ------> ADC1_IN4
     PB0     ------> ADC1_IN8
     PB1     ------> ADC1_IN9 
     */
     HAL_GPIO_DeInit(Xin_GPIO_Port, Xin_Pin);
    
     HAL_GPIO_DeInit(GPIOB, Zin_Pin|Yin_Pin);
    
     /* ADC1 interrupt Deinit */
     HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
      /* USER CODE BEGIN ADC1_MspDeInit 1 */
    
      /* USER CODE END ADC1_MspDeInit 1 */
      }
    } 
    
    /* USER CODE BEGIN 1 */
    
    /* USER CODE END 1 */

2voto

Tagli Puntos 81

A diferencia de la serie F4, el ADC de la serie F1 sólo puede generar una (única) interrupción al final de toda la secuencia de exploración. Por eso DMA es una necesidad cuando se utiliza el modo de escaneo.

Sin embargo, creo que todavía es posible configurar el ADC para la exploración basada en interrupciones, pero hay que hacerlo manualmente:

  1. Desactiva el escaneo y el modo continuo. Configure el ADC para una sola conversión.
  2. Seleccione el canal ADC mediante ADC->SQR3 : SQ1 bits (1ª conversión en secuencia regular)
  3. Activar la interrupción EOC mediante ADC->CR1 : EOCIE bit
  4. En la rutina de servicio de interrupción (ISR), recoge el resultado de ADC->DR .
  5. En ISR, repita el paso 2 para configurar el ADC para el siguiente canal.
  6. El CAD debe activarse manualmente o mediante un evento externo.

Para el muestreo periódico de varios canales, probablemente necesitará 2 módulos TIM y algunas interrupciones TIM. Un temporizador (Esclavo) controla la temporización entre cada canal de un grupo de escaneo y dispara el módulo ADC. Este temporizador necesita ser consciente de su número de desbordamientos (utilizando una interrupción) para que pueda desactivarse a sí mismo después de que todos los canales de la secuencia hayan sido muestreados y convertidos. Otro temporizador (Maestro) controla el tiempo entre las secuencias de escaneo, y activa el temporizador esclavo (y probablemente el primer escaneo de la secuencia).

No sé cómo de ocupado está tu DMA, pero definitivamente intentaría usar DMA antes de probar el método que he descrito antes.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X