¿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:
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...
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.
Rata de acero inoxidable
Rata de acero inoxidable
0___________