STM32F303 ADC+DMA Promedio de mediciones

¿Cuál es el mejor método para tomar múltiples medidas usando interrupciones ADC+DMA y promediarlas? Actualmente tengo un STM32F303 con ADC2 inicializado con los canales 3 y 18 (Vrefint). Mi objetivo es tomar 16 medidas y luego promediar el resultado. Necesito tomar estas medidas con una frecuencia relativamente baja y para esta prueba configuré main para activar la conversión ADC DMA cada 250 ms. Los problemas que tengo son:

  • ¿Cuál es el mejor método para compartir datos entre el controlador de interrupciones y el bucle principal?
  • Cómo asegurarse de que la interrupción no se active después de 16 veces

Parte de mi código está debajo. Se supone que la función de devolución de llamada ADC/DMA activa la siguiente conversión 16 veces y luego envía una señal al bucle principal a través de una bandera de que la conversión está completa. Puedo ver que se llama a la devolución de llamada y se realiza la conversión de ADC; las medidas son correctas

// ADC Data structure, also accessed by ADC callback
typedef struct ADC_Data {
  volatile uint32_t adc_value_channel_3;
  volatile uint32_t adc_value_vrefint_channel;
  volatile bool adc_conversion_complete;
  uint16_t adc_value_vrefint_register;
}
ADC_Data;

int main(void) {

  // Initialize peripherals
  HAL_Init();
  SystemClock_Config();
  MX_DMA_Init();
  MX_ADC2_Init();
  MX_OPAMP2_Init();
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  HAL_OPAMP_Start( & hopamp2);

  adc_data.adc_conversion_complete = false;
  adc_data.adc_value_channel_3 = 0;
  adc_data.adc_value_vrefint_channel = 0;
  adc_data.adc_value_vrefint_register = * VREFINT_CAL_ADDR;

  HAL_ADC_Start_DMA( & hadc2, (uint32_t * ) adc_buffer, 2);

  while (1) {

    HAL_Delay(250);

    if (adc_data.adc_conversion_complete == true) {
        double adc_value = get_adc_value(&adc_data);
        HAL_ADC_Start_IT(&hadc2);
    }

  }
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef * hadc) {
  adc_data.adc_conversion_complete = false;
  static uint32_t conv_count = 0;
  static uint32_t temp_adc_value = 0;
  static uint32_t temp_vrefint_value = 0;

  temp_adc_value += adc_buffer[0];
  temp_vrefint_value += adc_buffer[1];

  if (++conv_count == 16) {
    conv_count = 0;
    adc_data.adc_value_channel_3 = temp_adc_value >> 4;
    adc_data.adc_value_vrefint_channel = temp_vrefint_value >> 4;
    adc_data.adc_conversion_complete = true;
    temp_adc_value = 0;
    temp_vrefint_value = 0;
  } else {
    HAL_ADC_Start_IT(hadc);
  }

}


double get_adc_value(ADC_Data * data) {
  return (3.3 * data - > adc_value_vrefint_register * data - > adc_value_channel_3) / (data - > adc_value_vrefint_channel * 4095);
}

***********************************EDITAR************** ****************

Modifiqué la inicialización de ADC para permitir la conversión continua (para 3 canales) y activar una interrupción al final de la conversión de secuencia: static void MX_ADC2_Init(void) { ADC_ChannelConfTypeDef sConfig = {0};

  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc2.Init.Resolution = ADC_RESOLUTION_12B;
  hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc2.Init.ContinuousConvMode = ENABLE;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.NbrOfConversion = 3;
  hadc2.Init.DMAContinuousRequests = DISABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc2.Init.LowPowerAutoWait = DISABLE;
  hadc2.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;

  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_3;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.SamplingTime = ADC_SAMPLETIME_181CYCLES_5;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_VREFINT;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  sConfig.SamplingTime = ADC_SAMPLETIME_181CYCLES_5;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = ADC_REGULAR_RANK_3;
  sConfig.SamplingTime = ADC_SAMPLETIME_181CYCLES_5;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  // Calibration
  ADC2->CR &= ~ADC_CR_ADEN;         // Disable ADC
  ADC2->CR |= ADC_CR_ADCAL;         // Start calibration
  while ( (ADC2->CR & ADC_CR_ADCAL) != 0);
}

La conversión se inicia con HAL_ADC_Start_DMA(&hadc2, (uint32_t*)adc_buffer, 48);. La devolución de llamada de interrupción se llama cuando el búfer se llena con 48 muestras (3 muestras de cada canal), y establece un indicador, que luego se sondea y se restablece desde otra función:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    adc_data.adc_conversion_complete = true;
}

Luego, en la función de sondeo, recorro el búfer lleno y hago un promedio de su contenido para cada uno de los tres canales:

adc_data.adc_value_channel_3 = 0;       
adc_data.adc_vrefint_data = 0;              
adc_data.adc_value_channel4 = 0;
uint32_t *tempbuf = adc_buffer;             

for (int x = 0; x < 16; x++) {                          
    adc_data.adc_value_channel_3 += *tempbuf++;
    adc_data.adc_vrefint_data += *tempbuf++;
    adc_data.adc_value_channel4+= *tempbuf++;
}

// Averaging by shifting each value to the right by 4 places...
Bueno, no hagas tus cálculos promedio en ISR. La familia OTTOMH STM32 empuja/abre 4 registros automáticamente, pero tiene un código que probablemente requiere más de 4, por lo que para 15 de 16 interrupciones se empujan/abre registros adicionales. Usa tu bandera para calcular el promedio.
Como sugiere la respuesta, use DMA para transferir datos desde ADC al búfer de memoria. Finaliza DMA, genera interrupción. Interrumpir, hacer matemáticas, reprogramar DMA.
La mejor manera es usar RTOS. Tiene todas las características necesarias de sincronización y comunicaciones entre procesos integradas.

Respuestas (1)

Dado que ya tiene DMA inicializado, ¿por qué no lo usa? Pase un puntero de memoria, sondee la conversión realizada o señale desde su interrupción:

en función principal:

while (data_count-- > 0) {
    HAL_ADC_Start_DMA( & hadc2, (uint32_t * ) adc_buffer, 2);
    while(!adc_done) {}
    <do your math here>
    adc_done = 0;
}

uint8_t adc_hecho = 0; es global, que se asigna en la interrupción DMA.