ARM: ADC

El ADC se encarga de leer valores analógicos externos en un rango de tensiones determinado y guardar dicho valor como un valor digital.

Éste valor digital viene dado por los parámetros tensión de entrada, tensión de referencia y resolución en bits. El ADC de la STM32F4 Discovery tiene una resolución de 12 bits, es decir, en decimal, devolverá un valor entre 0 y 4096.

Valor digital = (Tensión de entrada / Tensión de referencia)*4096

El ADC es muy importante de cara al uso de sensores, ya que no estamos utilizando ya un sistema todo/nada, de forma que nos abre un rango de posibilidades bastante amplio. Por ejemplo será útil para la lectura de temperatura, de una tensión que está controlando la placa, digitalización de señales...

Para éste ejemplo usaremos la lectura de temperatura de un LM35, conocido porque a 0ºC hay 0 voltios a la salida y tiene una sensibilidad de 0.01 V/ºC (es decir, aumenta 0.01 V por cada grado), aunque con éste no será posible leer temperaturas negativas.

Una de las dificultades que nos podemos encontrar a la hora de usar el ADC de la STM32F4 es su tensión de referencia. Ésta placa funciona a 3.3 V, y tendremos ésta tensión como referencia, algo más o algo menos dependiendo de la calidad de la fuente y el uso que se le esté dando. Es recomendable medir Vdd para conocer la tensión y poder calibrar el software. Por lo general habrá un máximo de 3.3 V y un mínimo de 2.9 V. Con éste rango para el LM35 tendremos una temperatura máxima entre 330 y 290ºC, bastante más de lo que en realidad es capaz de soportar el integrado.

Para medir temperaturas negativas deberíamos añadir una resistencia con una referencia negativa, e igualmente adaptar el circuito para que dé valores positivos, ya que con los negativos el ADC no funciona, es más, puede que se estropee.

En éste caso sería más recomendable usar un LM335, que da la temperatura en grados Kelvin, es decir, a 0ºC, 272.15ºK, habría una tensión de salida de 2.7215 V. Éste sensor nos permitiría medir supuestamente hasta el cero absoluto (cosa que no vais a hacer y además el mínimo es -55ºC) y un máximo determinado por la referencia del ADC que va a ser de 57.85 o 17.85ºC en los peores casos. Se puede usar un circuito de adaptación con amplificadores operacionales para solucionar éstos problemas.

Bueno, vamos a ello. En primer lugar, el pin que vamos a usar para la lectura analógica será el pin 1 de GPIOC, PC1. El modo en el que lo tendremos que configurar será AN. Más adelante viene la configuración específica con la librería analógica, "stm32f4xx_adc". Veamos una función típica de configuración:

void temperatura_inicializar(void) {
    
    //Se declaran las variables tipo
    GPIO_InitTypeDef        GPIO_InitStructure;
    ADC_InitTypeDef         ADC_InitStructure;
    ADC_CommonInitTypeDef   ADC_CommonInitStructure;
   
    /* Puerto C -------------------------------------------------------------*/
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);

    /* Se configura el pin PC1 como entrada analógica */
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin     = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode    = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd    = GPIO_PuPd_NOPULL ;
    GPIO_Init(GPIOC, &GPIO_InitStructure); 

    /* Activar ADC1 dandole reloj*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    /* ADC Common Init*/
    ADC_CommonStructInit(&ADC_CommonInitStructure);
    //Configurar el ADC para trabajar independiente o multi
    ADC_CommonInitStructure.ADC_Mode                = ADC_Mode_Independent;
    //Selección de la frecuencia del ADC
    ADC_CommonInitStructure.ADC_Prescaler           = ADC_Prescaler_Div4;
    // max 30 MHz segun datasheet
    //Configura el modo de acceso a la memoria para modo multi
    ADC_CommonInitStructure.ADC_DMAAccessMode       = ADC_DMAAccessMode_Disabled;
    //Configurar el retraso entre 2 muestreos
    ADC_CommonInitStructure.ADC_TwoSamplingDelay    = ADC_TwoSamplingDelay_5Cycles;
    //Aplicar la configuración
    ADC_CommonInit(&ADC_CommonInitStructure);

    /* ADC Init ---------------------------------------------------------------*/
    ADC_StructInit (&ADC_InitStructure);
    //Se puede seleccionar una resolucion de 6,8,10 o 12 bits
    ADC_InitStructure.ADC_Resolution             = ADC_Resolution_12b;
    //Número de conversiones que se hacen en varios canales
    //Sólo es interesante para el modo multicanal
    ADC_InitStructure.ADC_ScanConvMode           = DISABLE;
    //Se especifica si la conversión va a realizarse todo el tiempo
    //O sólo una vez cuando se le diga
    ADC_InitStructure.ADC_ContinuousConvMode     = DISABLE;
    //Se puede hacer que empiece la conversión con un disparador externo
    //a modo de interrupción
    ADC_InitStructure.ADC_ExternalTrigConvEdge   = ADC_ExternalTrigConvEdge_None;
    //Dónde va el LSB del byte más vacío
    ADC_InitStructure.ADC_DataAlign              = ADC_DataAlign_Right;
    //Número de conversiones que se llevan a cabo al activarse
    ADC_InitStructure.ADC_NbrOfConversion        = 1;
    //Aplicación de la configuración
    ADC_Init(ADC1, &ADC_InitStructure);

    /* Establecer la configuración de conversión ------------------------------*/
    //Configurar la longitud del secuenciador
    ADC_InjectedSequencerLengthConfig(ADC1, 1);
    //Se le puede añadir un offset al valor de la conversión
    ADC_SetInjectedOffset(ADC1, ADC_InjectedChannel_1, 0);
    //Para los canales seleccionados se les da una posición
    //y un tiempo de muestreo
    ADC_InjectedChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_480Cycles);

    /* Poner en marcha ADC ----------------------------------------------------*/
    ADC_Cmd(ADC1, ENABLE);   
}

Y ahora veremos cómo se hace una función que lea el valor del ADC, lo pase a un valor digital (que podemos representar en decimal) y más adelante a un valor de temperatura. Se debe definir MVOLT_REF como la tensión Vdd que recomendaba medir, en éste caso se ha definido en milivoltios.

#define MVOLT_REF 2950

float temperatura_leer_celsius_v2(void) {
   
    uint32_t valor_adc;
    float grados, milivoltios;
   
    ADC_ClearFlag(ADC1,ADC_FLAG_JEOC);      // borrar flag de fin conversion
    
    ADC_SoftwareStartInjectedConv(ADC1);    // iniciar conversion

    while (ADC_GetFlagStatus(ADC1,ADC_FLAG_JEOC) == RESET); // esperar conversion
    //Cuando termine, la flag se pondrá a 1 y saldrá del bucle

    //Se guarda el valor del ADC en una variable
    valor_adc = ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1); 

    //podemos pasar el valor decimal a un valor de tensión en mV
    milivoltios = (valor_adc * ((float)MVOLT_REF))/0xFFF;   // 12 bits ADC   
    //Y de ahí a ºC
    grados  = (milivoltios/10.0F);
   
    //Esta función devuelve grados, que luego se podrán imprimir por pantalla
    return grados;
}

Recordad si vais a usar el ejemplo en Keil que tendréis que desactivar la FPU en las opciones para que no se atasque.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *