lunes, 7 de marzo de 2011

Enrutamiento avanzado automático en Linux.

Una situación que se va volviendo más común es tener más de una conexión a internet en los servidores. El problema que se presenta, entonces, es que cuando nos llega un paquete proveniente de internet por una conexión, éste debe ser contestado por la misma por donde llegó y no simplemente por la ruta por defecto.

El comportamiento normal sería el siguiente:

Red local, eth0
ip 192.168.1.1
mascara 255.255.255.0

Primera salida a internet, eth1
ip 1.2.3.4
mascara 255.255.255.0
ruta por defecto 1.2.3.254

Primera salida a internet, eth2
ip2.3.4.5
mascara 255.255.255.0
ruta por defecto 2.3.4.254

Segun este esquema, las tablas de ruteo quedarían así:


Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
192.168.1.0     0.0.0.0         255.255.255.0   U         0 0          0 eth0

1.2.3.0         0.0.0.0         255.255.255.0   U         0 0          0 eth1
2.3.4.0         0.0.0.0         255.255.255.0   U         0 0          0 eth2

0.0.0.0         1.2.3.254       0.0.0.0         UG        0 0          0 eth1
0.0.0.0         2.3.4.254       0.0.0.0         UG        0 0          0 eth2 



Como se puede ver, aparecen dos rutas por defecto. Sin embargo el sistema utilizará siempre la primera que haya a no ser que la interfaz esté caída, o sea que falte la letra "U" (de up).

En qué nos afecta esto? Supongamos que nos viene un paquete destinado a la ip 1.2.3.4 proveniente de algún lugar de internet, digamos 5.5.5.5. Cuando el sistema mande la respuesta, esta saldrá por la placa eth1, porque es la primera ruta que sirve para alcanzar la ip 5.5.5.5 y todo funciona bien.

Pero qué pasa si viene un paquete proveniente, digamos, de 6.6.6.6, destinado a 2.3.4.5. El paquete llegará sin problemas pero cuando haya que enviar la respuesta, esta saldrá, de nuevo, por la eth1. Pero resulta que la IP de origen del paquete de respueta tiene que ser, obviamente, 2.3.4.5 y el proveedor de internet al que llegamos por la eth1 no querrá rutear paquetes con esa ip de origen. El paquete debería haber salido por la eth2, dirigido a su router correspondiente, el 2.3.4.254.

Lo que necesitamos, es alguna manera de decirle al sistema qué hacer en esta situación, y ahí es donde aparece la capacidad de ruteo avanzado que tiene Linux.

El documento  Linux Advanced Routing and Traffic Control how-to
 explica claramente este problema y la manera de solucionarlo. Lo que yo les presento ahora es un script que se encarga de hacer todo automáticamente.


La situación es  que tenemos dos rutas por defecto y debemos utilizar cada una según corresponda.

ATENCION: esto no se trata de balancear cargas por las dos conexiones. Se trata simplemente de que ambas sean utilizables desde internet.




Para solucionar esta situación, lo que hace es crear una tabla para cada placa de red que haya. En cada una tiene rutas para llegar a las demás redes, y la ruta por defecto que corresponda a esa placa. De esa forma, se puede configurar en cada placa de red una ruta por defecto diferente.

Para seleccionar cuál de todas estas tablas de ruteo es la que debemos utilizar en cada momento, se debe crear reglas que seleccionan la tabla correspondiente en función de la IP de origen del paquete. La IP de origen es lo que distingue una placa de red de otra, así que nos sirve para saber por qué placa está saliendo el paquete.


Para su funcinamiento, el script utiliza la información de los archivos de configuración de la red, que se encuentran en /etc/sysconfig/network-scripts. Al menos en los sistemas que siguen la Linux Standard Base, este directorio contiene archivos con el nombre ifcfg-<nombre de la placa de red>. Por ejemplo /etc/sysconfig/network-scripts/ifcfg-eth1 contendría:

DEVICE=eth
BOOTPROTO=static
IPADDR=1.2.3.4
NETMASK=255.255.255.0
GATEWAY=1.2.3.254
ONBOOT=yes
METRIC=10
MII_NOT_SUPPORTED=no
USERCTL=no
DNS1=127.0.0.1
RESOLV_MODS=no
IPV6INIT=no
IPV6TO4INIT=no
ACCOUNTING=no


Lo que nos interesa de este archivo son la IPADDR, NETMASK y GATEWAY. Con estos datos podemos generar automáticamente lo que necesitamos.

A continuación el script:

#limpiar reglas anteriores por si lo ejecutamos de nuevo

ip rule show | awk 'BEGIN{FS=":"}{if(index($2,"lookup main")==0 && index($2,"lookup default")==0 && index($2,"lookup local")==0) system ("ip rule del " $2) }'


#recorrer las intefaces de red
#para cada una crear una tabla

cuenta=0
for ifcfg in /etc/sysconfig/network-scripts/ifcfg-*
do
      cuenta=$(( cuenta + 1 ))
      TABLE=100$cuenta

      . $ifcfg
      echo "DEVICE=$DEVICE"

      export `ipcalc -n $IPADDR $NETMASK`
      NET=$NETWORK/$NETMASK

      ip route flush table $TABLE

      ip route add $NET dev $DEVICE src $IPADDR table $TABLE
      if [ "$GATEWAY" != "" ]
      then
         ip route add default via $GATEWAY table $TABLE
      fi

      ip route add $NETWORK dev $DEVICE src $IPADDR


      ip rule add from $IPADDR table $TABLE
      ip rule add fwmark $TABLE table $TABLE



      tables="$tables $TABLE"
      if [ "$GATEWAY" != "" ]
      then
         ip route add default via $GATEWAY table $TABLE
      fi

      echo 
      echo 
      echo 
      echo 
done



for TABLE in $tables 
do
   for ifcfg in /etc/sysconfig/network-scripts/ifcfg-*
   do
      . $ifcfg
      export `ipcalc -n $IPADDR $NETMASK`
      NET=$NETWORK/$NETMASK

      ip route add $NET dev $DEVICE table $TABLE
   done

done


echo 0 > /proc/sys/net/ipv4/conf/eth1/rp_filter
ip route flush cache

domingo, 6 de marzo de 2011

Termómetro graficador con LM35 y display HD44780 compatible

El circuito integrado LM35 es muy interesante. Se comporta como un regulador de tensión controlado por temperatura. El resultado es que por su salida entrega 10 milivolts por grado celsius.

En la documentación figuran varios circuitos de aplicación pero para este experimento vamos a hacerlo lo más básico posible. Lo conectaremos así:



Para la medición, lo único que tenemos que hacer es leer la entrada analógica y calcular cuantos milivolts son. Lo dividimos por 10 y listo.

Un detalle con respecto al montaje físico del LM35: no conviene conectarlo directamente sobre el Arduino. Aunque no lo parezca la placa genera bastante calor como para aumentar la temperatura un par de grados. Es mejor ponerle un calbe y que esté lejos.

Con temperaturas normales, rara vez mediremos más de 100 grados, o sea que rara vez tendremos más de 1 volt en la salida. Así que para obtener mejor precisión en la medida podríamos usar como referencia del conversor analógico digital, la tensión interna de 1,1 Volt.

  analogReference(INTERNAL); //referencia de 1.1V 

¿Por qué usar una referencia de 1,1V en lugar de dejarlo en la normal que son 5V?  Para tener mejor precisión en la medida. Aunque tampoco sería tan importante ya que el LM35 tampoco es super exacto. Puede tener un error de medio grado, lo cual parece mucho pero en comparación con otras alternativas está muy bien. Otros tienen tolerancias de un grado o más.

El conversor analógico digital mide con una precisión de 10 bits, o sea que la función analogRead nos devuelve un valor entre 0 y 1023. Dado que 1023 corresponde a 1.1 volt, lo que son 110 grados, la fórmula para convertir la información en grados es sencilla. Si a es el valor medido, la temperatura es:

t=a*110/1023.0;

Dado que la entrada está sujeta a múltiples interferencias externas, el valor de entrada no es totalmente estable. Entonces, para conseguir una medición más exacta lo que vamos a hacer es promediar todos los valores medidos durante una fracción de segundo. Los vamos sumando en la variable s y contando con c. Al final dividimos s por c y tenemos la temperatura (variable a):

m=millis();
s=0;
c=0;
while(millis()-m <300) {
     s+=analogRead(A1);
     c++;
     } 
a=s/c;


Ahora bien. Usando la rutina del post anterior, tenemos 24 barritas para mostrar datos. Así que podríamos mostrar una barrita por hora y así graficar las últimas 24 horas. Crearemos una variable llamada m10m donde guardaremos los milisegundos de la última barra mostrada, y lo compararemos con los milisegundos actuales. Eso nos dará los milisegundos que han transcurrido. Si lo dividimos por 1000 lo tendremos en segundos, y cuando esto de 3600 será que ha pasado una hora. Algo así:


dif=(millis()-m10m)/1000;
if (dif>3600){....

Para ver que tal funcionan las cosas en la pantalla mostraremos el valor devuelto por el conversor analógico-digital, la gráfica, la medición de temperatura, y el tiempo transcurrido.

Una vez conformes con el resultado los datos se pueden reubicar. Recordemos que la gráfica sale donde pongamos los caracteres 0 a 7.

Al final a mi me ha quedado así:



Y el programa que lo hace es este:

#include <LiquidCrystal.h>

LiquidCrystal lcd(8,9,4,5,6,7);



byte grafica[24]; //valores entre 0 y 8

void setup(){
  lcd.begin(16, 2);

  Serial.begin(9600);

  analogReference(INTERNAL); //referencia de 1.1V
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("OK");

  lcd.setCursor(8,0);
  lcd.print('\0');
  lcd.print('\1');
  lcd.print('\2');
  lcd.print('\3');
  lcd.print('\4');
  lcd.print('\5');
  lcd.print('\6');
  lcd.print('\7');

  memset(grafica,(byte)9,sizeof(grafica));  

  mostrar_grafica();  
  }

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


long a;
float t;
long m,s,c;

long s10m=0;
long c10m=0;
long m10m=millis();
int i,indice_registro=0;
long dif;
int k;
/////////////////////////////////////////////////////////////

void loop(){

m=millis();
s=0;
c=0;
while(millis()-m <300) {
     s+=analogRead(A1);
     c++;
     }

a=s/c;

lcd.setCursor(0,0);
lcd.print(a);
lcd.print("  ");

t=a*110/1023.0;
lcd.setCursor(0,1);
lcd.print(t);

//cada 10 minutos guardar el promedio

s10m+=a;
c10m++;;

dif=(millis()-m10m)/1000;
lcd.setCursor(8,1);
lcd.print(dif);
lcd.print("       ");
if (dif>3600){ 
   //guradar valor
   
   a=s10m/c10m;
   memcpy(grafica,grafica+1,23); //desplazar hacia la izquierda   
   grafica[23]=8 * (1.0*t-10.0)/12;

   s10m=0;
   c10m=0;
   m10m=millis();
   mostrar_grafica();
   }

}


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

void mostrar_grafica(){
int d,dp;
byte bm[8];
int c,columna,fila,posicion,x;
int i;
for ( c=0;c<8;c++){ 
    memset(bm,0,8);//llenamos el caracter con ceros
    
    // cada caracter representa 3 datos, que comienzan por el...
    
    //poner puntitos en la primera columna (entre 0 y 8)
    x=0;
    dp=c * 3;

    for (columna=0;columna<5;columna+=2){
        i=dp + 2-( columna/2 );
        d=grafica[i];
                   
        for (fila=0;fila<8;fila++){
            
            if (d>8) // dato invalido
              bm[fila] |= (fila & 1) <<columna;
            else 
              if (fila>=9-d) bm[fila] |= 1<<columna;
                 
            
            }//for fila
        x++;
        }//for columna
    lcd.createChar(c,bm);  
    }

} 
 

sábado, 5 de marzo de 2011

Graficos en display HD44780 (1602)

Existen en el mercado montones de displays compatibles con HD44780. Yo tengo este: http://www.yerobot.com/arduino-lcd-shield.html.

El display es de 16 columnas por dos filas y puede mostrar una amplia variedad de caracteres, pero tiene un inconveniente: no puede mostrar gráficos... pero podemos hacer alguna trampa para conseguir algo útil.

Para empezar, manejamos bien el display con la biblioteca standard incluída con el entorno Arduino: http://www.arduino.cc/en/Reference/LiquidCrystal. No hace falta utilizar la que provee el fabricante. De hecho, es poco aconsejable usarla porque es más lenta y no funciona bien con las versiones nuevas.
El controlador HD44780 muestra caracteres en una matriz de puntos, así que nada impediría que pudiera dibujar cualquier cosa sobre ella. Sin embargo no está pensado para eso. No tiene comandos que permitan enviar pixels individuales sino sólo caracteres alfanuméricos.

El truco que usaré para hacer algunos gráficos consiste en aprovechar la capacidad del HD44780 para mostrar hasta ocho caracteres definidos por el usuario. De esa forma, en principio, en la zona que ocuparían ocho letras podemos poner lo que nos de la gana. Simplemente escribiríamos el carácter 0, el 1, el 2 y así hasta el 8 y luego los redefinimos para que tengan los pixels que nos haga falta.

Sólo hay un inconveniente: que el display LCD no tiene todos los pixels sino que deja una separación entre los caracteres. La cosa se ve más o menos así:





Físicamente no existen los pixels necesarios para dibujar un gráfico completo. Así que tendremos que conformarnos con otro tipo de gráficos. Tenemos 8 cuadros de 8x5 pixels separados por 1 pixel, para hacer lo que queramos. Así que voy a hacer una rutina para dibujar histogramas.

Podemos suponer que cada carácter tiene seix pixels, uno de los cuales siempre tendrá que estar apagado. Así que tendremos que adaptarnos a eso y tratar de que no se note. Para eso dibujaremos barras verticales pero separadas por un píxel entre ellas. De esa forma parecerá que la separación fue dejada a propósito. 


De esta forma, hay 24 columnas disponibles para mostrar información. La rutina utilizará, entonces, un vector con 24 bytes. En ellos se usarán valores entre 0 y 8 para indicar la altura de la barra a mostrar. 

byte grafica[24];

La gráfica se podrá ubicar en cualquier parte de la pantalla, simplemente escribiendo los caracteres del 0 al 7, que son los que se pueden redefinir. Por ejemplo una manera sencilla de hacerlo sería esta:

void setup(){
  lcd.begin(16, 2);

  lcd.clear();

  lcd.setCursor(8,0);
  lcd.print('\0');
  lcd.print('\1');
  lcd.print('\2');
  lcd.print('\3');
  lcd.print('\4');
  lcd.print('\5');
  lcd.print('\6');
  lcd.print('\7');
  }

Por supuesto, también hace falta información para mostrar. Esta deberá ser generada por cualquier programa que hagamos. Después daré un ejemplo práctico. Por ahora llenemos el vector con algo de información para probar.

for (a=0;a<9;a++) grafica[a]=a;

for (a=8;a<16;a++) grafica[a]=8;
Y ahora la rutina que convierte el contenido del vector "gráfica" componiendo los caracteres que se ven en la pantalla.
void mostrar_grafica(){
int d,dp;
byte bm[8];
int c,columna,fila,posicion,x;
int i;
for ( c=0;c<8;c++){ 
    memset(bm,0,8); //llenamos el caracter con ceros
    
    // cada caracter representa 3 datos
    
    //poner puntitos en la primera columna (entre 0 y 8)
    x=0;
    dp=c * 3;

    for (columna=0;columna<5;columna+=2){
        i=dp + 2-( columna/2 );
        d=grafica[i];
                   
        for (fila=0;fila<8;fila++){
            
            if (d>8) // dato invalido
              bm[fila] |= (fila & 1) <<columna;
            else 
              if (fila>=9-d) bm[fila] |= 1<<columna;
                 
            
            }//for fila
        x++;
        }//for columna
    lcd.createChar(c,bm);  
    }

}

Nótese que no es necesario volver a escribir los caracteres en la pantalla cuando se modifica la gráfica. El controlador se encargará de irlos modificando cuando se hace el lcd.createChar.

Finalmente, al usar la rutina el resultado queda así:


En el siguiente artículo, un ejemplo práctico de uso de esta rutina.

lunes, 7 de febrero de 2011

Biblioteca para teclas del Arduino LCD+Keypad

El Arduino LCD+Keypad es un accesorio interesante y en realidad el LCD con precio más accesible que encontré. A demás de incluir un display de 16 caracteres por 2 líneas, también tiene unos cuantos botones que se leen mediante una entrada analógica.
El display se puede manejar sin problemas utilizando la biblioteca standard del entorno Arduino, pero para el teclado sólo viene un ejemplo muy rudimentario, así que me hice una pequeña biblioteca que no sólo evita el rebote de contactos (que puede interpretarse como si se presionara varias veces el botón) sino que maneja autorepetición si se mantiene presionada la tecla un rato.

Sólo hay que crear un drectorio llamado keyb donde están las bibliotecas de Arduino y en él los siguientes tres archivos. El uso es muy sencillo. Se incluye keyb.h y ya queda disponible la función int KEYB.getkey(n); donde n puede ser 0 o 1 para seleccionar si queremos autorepetición.

Para descargar, click aquí.

El archivo contiene el siguiente ejemplo de uso:

#include 
#include 

LiquidCrystal lcd(8,9,4,5,6,7);

int count,repeat;

void setup(){
  lcd.begin(16, 2);
  count=0;
  repeat=1;

  lcd.clear();
  lcd.print("repeat:");
  lcd.print(repeat);
  lcd.setCursor(1,1);
  lcd.print(count);
  }


/////////////////////////////////////////////////////////////
void loop(){

int k=KEYB.getkey(repeat);

if (k!=KEY_NONE){
  
    if (k==KEY_SELECT) {
       repeat= !repeat;
       }
       
    if (k==KEY_RIGHT) {
       count+=100;
       }   
    if (k==KEY_LEFT) {
       count-=100;
       }
       
    if (k==KEY_UP) {
       count+=1;
       }   
    if (k==KEY_DOWN) {
       count-=1;
       }
       
    lcd.clear();
    lcd.print("repeat:");
    lcd.print(repeat);
    lcd.setCursor(1,1);
    lcd.print(count);
    
    }
}

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);  
  }