lunes, 7 de febrero de 2011

Capacímetro con Arduino

En la página de Arduino hay un ejemplo de un capacímetro digital (. El diseño es muy sencillo pero tiene algunas limitaciones, así que me vamos a hacer algunas mejoras.

El funcionamiento es simple: se conecta una resistencia conocida y el capacitor a una salida digital del Arduino. Por otro lado, con una entrada analógica se mide la tensión sobre el capacitor.


Cuando se envía un 1 por la salida digital, se aplican 5Volts en R1 y el capacitor comienza a cargarse. En la entrada analógica el Arduino puede ir midiendo la tensión que debería tener una forma así:

Así que para conocer el valor del capacitor, sólo hay que medir el tiempo que tarda en cargarse. Más concretamente el valor de la resistencia (en ohms) multiplicado por el del capacitor (en faradios) da el tiempo que tarda la tensión en llegar al 63,2%, así que es fácil hacer un programa que haga el cálculo.

Inconveniente: si el capacitor es demasiado chico se carga demasiado rápido como para que la medición sea exacta. Si es demasiado grande, tarda mucho en cargarse. Dependiendo de la resistencia podría tardar en cargarse varios segundos.

Esto, lógicamente, se puede evitar eligiendo adecuadamente el valor de la resistencia. Sin embargo los valores de los capacitores pueden irdesde pocos picofaradios hasta cientos de microfaradios. O sea una variación de unas cien millones de veces más grande uno que otro.

Para solucionar ese problema la única alternativa es cambiar la resistencia. Con tres diferentes es suficiente. Con una de 1KΩ se pueden medir capacitores de cientos de microfaradios, y con una de 1MΩ los tiempos para capacitores de picofaradios son razonables. Conectamos las tres resistencias a diferentes salidas del Arduino y programamos como entradas las que no estemos usando en cada momento.


El programa comenzará entonces poniendo como entrada las salidas 2 y 3, y como salida la 1, enviando un 1 para aplicar 5V al capacitor.

Hecho esto irá midiendo la entrada analógica hasta llegar a un 63,2% del valor máximo. Si el tiempo ha sido muy corto, realizará el mismo procedimiento con la salida 2 y luego con la 3.

El programa es bastante sencillo. Para empezar, en la función setup de Arduino nos aseguramos de que como referencia use la misma tension de fuente, o sea 5V:

void setup(){
  analogReference(DEFAULT); 
  }

Después creamos una función que se encargue de hacer la medición y le pasaremos como parámetro cuál es el la patita por la que enviaremos la tensión.

En el programa principal necesitaremos agregar unas cuantas constantes para poder cambiar después fácilmente las patitas a usar. Así que definiremos estas:

#define analogPin 0
#define pinR1   12   
#define pinR2   11
#define pinR3   10

O sea, que en la patita analógica 0 (A0) conectaremos el capacitor a medir. En la 12,11 y 10 conectaremos las tres resistencias. Para usar otras patitas basta con cambiar los valores de las constantes y el resto del programa quedará igual.

La rutina que hace la medición, para empezar, debe asegurarse de que el capacitor está descargado. Para eso lo que haremos será poner las 3 patitas de medición como salidas:

  pinMode(pinR1, OUTPUT);
  pinMode(pinR2, OUTPUT);
  pinMode(pinR3, OUTPUT);

Y a continuación enviaremos un cero por todas ellas:

  digitalWrite(pinR1, LOW);
  digitalWrite(pinR2, LOW);
  digitalWrite(pinR3, LOW);

 El siguiente paso es esperar a que haya 0 volts en la entrada digital.

  while(analogRead(analogPin) > 0){}

En este punto, ya sabemos que el capacitor está descargado así que pondremos como entradas todas las patitas de las resistencias para no enviar ninguna tensión al capacitor.

  pinMode(pinR1, INPUT);
  pinMode(pinR2, INPUT);
  pinMode(pinR3, INPUT);

Terminada la descarga, comienza el proceso de medición. Dado que la función recibe parámetro llamado "pin" que indica cuál es la patita por la que enviaremos tensión, programaremos esa patita como salida:

  pinMode(pin, OUTPUT);

Y enviaremos un 1 por ella para que entrege 5 volts:

  digitalWrite(pin, HIGH);

Inmediatamente después copiamos el contador de microsegundos del Arduino a una variable temporal que llamaremos StartTime:

  startTime = micros();

Una de las rutinas que incluye Arduino automáticamente en los programas tiene un contador de interrupciones que cuenta cuántos microsegundos lleva encendido el procesador. Claro que no es tan rápido para contar los microsegundos de uno en uno. Dependiendo de la velocidad del cristal que lleve podrá dar resultados más o menos exactos. En el caso de losque tienen cristal de 16Mhz el resultado salta de 4 en 4 microsegundos. Este contador, obviamente, no puede incrementarse para siempre. El límite es el máximo de un entero largo y vuelve a cero cada unos 70 minutos... pero no nos preocuparemos por eso porque no afecta al uso que le daremos.

El siguiente paso es quedarnos esperando a que la tensión del capacitor llegue al 63,2%:

   while(analogRead(analogPin) < 648 ){}

¿Por qué 648? Porque el conversor analógico-digital del Arduino devuelve un valor entre 0 y 1023, siendo 0=0Volts y  1023=5volts (puede ser 3,3 si el circuito está alimentado con eta tensión). El 63,2% de 1023 es 647, así que el lazo se repite mientras el valor sea menor que 648.

Lo que falta hacer ahora es calcular el tiempo transcurrido que será la cantidad de microsegundos actual menos los microsegundos al inicio y devolver ese resultado:

   elapsedTime= micros() - startTime;
   return elapsedTime;

En definitiva, la función completa queda así:

unsigned long medir_tiempo(byte pin,byte lr){
  pinMode(pinR1, OUTPUT);
  pinMode(pinR2, OUTPUT);
  pinMode(pinR3, OUTPUT);
  digitalWrite(pinR1, LOW);
  digitalWrite(pinR2, LOW);
  digitalWrite(pinR3, LOW);
  while(analogRead(analogPin) > 0){}
  pinMode(pinR1, INPUT);
  pinMode(pinR2, INPUT);
  pinMode(pinR3, INPUT);

  pinMode(pin, OUTPUT);
  digitalWrite(pin, HIGH);
  startTime = micros();

  while(analogRead(analogPin) < 648 ){}// 647 = 63.2% de 1023
  elapsedTime= micros() - startTime;
  return elapsedTime;
}

Ya tenemos la función que mide el tiempo así que no nos tenemos que preocupar más de cómo lo hace. Ahora solo falta el programa que haga uso de estos datos. Crearemos una función llamada medir_capacidad.

void medir_capacidad(){

Par hacer los cálculos a demás del tiempo de carga del capacitor, nos hace falta saber el valor de la resistencia. Así que definiremos como constantes los valores de R1, R2 y R3. Para tener mayor exactitud, lo mejor es medir las resistencias con un tester y poner el valor exacto que tengan. En mi caso quedó así:

#define R1  990.0F
#define R2  9820.0F
#define R3  988000.0F

La F al final de los números le dice al compilador que los tome como números de coma flotante en lugar de enteros.

Ahora comenzamos con la medición.Para empezar, vamos usar la resistencia más chica (R1) y si el tiempo es muy chico probaremos con otra. ¿Cuánto es chico? Bueno, yo elegí que si tarda menos de 100000 microsegundos (o sea una décima de segundo) podemos usar una resistencia más grande. Así que para empezar:

  resistorValue=R1;
  t=medir_tiempo(pinR1,0);

¿Por qué copio el valor de R1 a la variable resistorValue? Porque si tengo que hacer varios intentos con diferentes resistencias, quiero una variable donde tenga el valor de la resistencia usada en el último intento que hicimos.

Ahora vamos a ver si el tiempo que tardó es muy corto:

  if (t<1e5) {

Si el tiempo es corto, lo que hacemos es probar con R2:

      resistorValue=R2;
      t=medir_tiempo(pinR2,0);
      }
Si todavía es poco tiempo, volvemos a hacer el mismo proceso pero con R3:


  if (t<1e5) {
      resistorValue=R3;
      t=medir_tiempo(pinR3,0);
      }

Como se ve,sería posible cambiar de resistencia todas las veces que haga falta. Lo ideal sería que la medición tomara entre 0,1 y 0,5 segundos. Cuanto más tiempo tarde más exacto será el resultado. Pero tampoco tiene que ser demasiado lento para que no tarde en dar el resultado. Medio segundo como máximo es razonable. Se podría poner muchas más resistencias y probar para obtener el valor más adecuado posible pero en mis pruebas con estas tres funcionaba aceptablemente así que así lo dejé.

Finalmente, lo único que nos queda es hacer el cálculo que es bastante sencillo: el tiempo en microsegundos dividido por el valor de la resistencia da el valor en microfaradios.

  microFarads = ((float)t / resistorValue);

Hecho esto sólo falta alguna manera de mostrar los resultados. Como es un experimento, podemos enviarlo por el puerto serie para verlo desde la PC pero en otro momento haremos que muestre los datos en un display.

Eso sí, para poder usar el puerto serie, deberemos agregar a la función setup() el comando Serial.begin(9600);

Vamos a hacer que muestre microfaradios o nanofaradios según el valor. Si la medición es mayor que 1, mostraremos el resultado como está y agregaremos uF al final:

  if (microFarads > 1){
     Serial.print((long)microFarads); 
     Serial.println(" uF");  
     }

De lo contrario mostraremos el resultado en nanofaradios multiplicando el valor de microfaradios por 1000:

  else
     {
     nanoFarads = microFarads * 1000.0;
     Serial.print((long)nanoFarads);
     Serial.println(" nF");
     }

Finalmente, sólo faltaría llamar a esta función. La podemos poner en la función loop para que se repita constantemente, y agregar un retardo por ejemplo de 1 segundo entre una medición y otra.

void loop(){
  medir_capacidad();
  delay(500);
  }

El programa completo quedaría así:

#define analogPin 0                                                                                                                    
                                                                                                                                            
#define pinR1   12                                                                                                                          
#define pinR2   11                                                                                                                          
#define pinR3   10                                                                                                                          
                                                                                                                                            
#define R1  990.0F                                                                                                                          
#define R2  9820.0F                                                                                                                         
#define R3  988000.0F                                                                                                                       
                                                                                                                                            
unsigned long startTime;
unsigned long elapsedTime,maximo,t;

unsigned long resistorValue;

float microFarads;
float nanoFarads;

void setup(){ 
  analogReference(DEFAULT);  
  Serial.begin(9600);
  }

///////////////////////////////////////////////////////////////////

unsigned long medir_tiempo(byte pin){  
  pinMode(pinR1, OUTPUT);
  pinMode(pinR2, OUTPUT);
  pinMode(pinR3, OUTPUT);
  digitalWrite(pinR1, LOW);
  digitalWrite(pinR2, LOW);
  digitalWrite(pinR3, LOW);
  while(analogRead(analogPin) > 0){}
  pinMode(pinR1, INPUT);
  pinMode(pinR2, INPUT);
  pinMode(pinR3, INPUT);

  pinMode(pin, OUTPUT);  
  digitalWrite(pin, HIGH);
  startTime = micros();
  
  
  while(analogRead(analogPin) < 648 ){}// 647 = 63.2% de 1023
  elapsedTime= micros() - startTime;
  return elapsedTime;  

}


///////////////////////////////////////////////////////////////////


void medir_capacidad(){
  resistorValue=R1;  
  t=medir_tiempo(pinR1,0);
  
  if (t<1e5) { 
      resistorValue=R2;  
      t=medir_tiempo(pinR2,0);
      }
  if (t<1e5) {
      resistorValue=R3;  
      t=medir_tiempo(pinR3,0);
      }
      
  microFarads = ((float)t / resistorValue);  
  
  if (microFarads > 1){
     Serial.print((long)microFarads);
     Serial.println(" uF");
     }
  else
     {
     nanoFarads = microFarads * 1000.0;
     Serial.print((long)nanoFarads);
     Serial.println(" nF");
     }     

}

//////////////////////////////////////////////////////////
void loop(){
  medir_capacidad();
  delay(500);  
  }