Erwin.Ried.cl / Desarrollos / Hardware /
Capturando temperaturas con Arduino

Veamos paso a paso como capturar valores de un sensor de temperatura en una aplicación de Visual Studio, graficándolas elegantemente escribiendo un sencillo firmware para el Microprocesador AVR Atmega168, utilizado por Arduino (24/11/2008 06:57 PM)


1. Materiales

Un arduino decimilla. Es una plataforma de desarrollo bastante económica y sencilla de utilizar que te permitirá crear hardware de una forma muy rápida.



Adicionalmente yo compré un Sparkfun Protoshield y un mini protoboard para trabajar más cómodamente pero alternativamente se podría utilizar una led con una resistencia apropiada (http://led.linear1.org/1led.wiz) y un pushbutton en una protoboard tradicional.



Finalmente necesitamos el sensor de temperatura, en este caso es un ZX-TEMP:



Para los lectores de Chile, todas estas piezas se encuentran en Olimex.


2. Armando el circuito

El circuito de nuestro sistema de sensor es bastante sencillo. Los estados se informarán primitivamente por medio de un led y un pushbutton permitirá interactuar con pequeñas funcionalidades que añadiré.

Así tenemos un circuito similar al de la siguiente figura (ya nombré las entradas y salidas digitales en función de la disposición de componentes de la ProtoShield):



Naturalmente el circuito no estaría completo si nos falta el sensor de temperaturas, pero el circuito superior es suficiente si compras el sensor de temperaturas que nombré anteriormente en vez de sólo el termistor (en este caso un TTC 103):



Utilizaremos finalmente una entrada digital, una análoga y una salida digital.

Una sección interesante fue armar la Sparkfun Protoshield. No tiene mucha dificultad, pero me recordó tiempos en que jugaba con mis Tente. Aunque no viene con instrucciones, la siguiente página es de extremada ayuda.



Las terminaciones no fueron de extremada pulcritud, pero la placa funciona excelente, en perfecta armonía integrada con Arduino. Una recomendación es conseguir un cautín económico, de baja potencia y de punta fina.


3. Escribiendo el firmware

El lenguaje (Wiring) utilizado por Arduino es idéntico al tradicional C, C++ pero con ciertas palabras claves para funciones propias de arduino.

Entorno de programación
Para escribir y compilar el código de arduino es recomendable utilizar el IDE de Arduino.

Lamentablemente, hasta la versión 0012 del mismo no se incluye un instalador ni soporte para Aero Glass, en Windows Vista.

La solución fue hacer un sencillo paquete auto-instalable que funciona perfectamente en Windows Vista, creando adicionalmente un acceso directo tal como cualquier aplicación:
http://servicios.ried.cl/arduino-live_setup.exe



Bajas la aplicación y presionas el botón para iniciar el proceso. Al cabo de unos minutos se te solicitará una ruta de instalación.


Debemos considerar que técnicamente tendremos sólo un hilo de ejecución por lo que si nos detenemos mientras mantenemos encendida un LED estamos imposibilitando la detección de otros eventos como pasaría al trabajar en C#, con eventos e hilos de ejecución "simultáneos".

Así debemos pensar bien en los estados de nuestro programa. Para esto, nada mejor que un clásico diagrama de estados:



Primero implementaré las funciones básicas, en particular una de salida visual que utilizaré. Primero partamos con la función de parpadeo (el cual será bloqueante):

Código:
void blink(int miliseconds)
{
  digitalWrite(ledPin, HIGH);
  delay(miliseconds/2);
  digitalWrite(ledPin, LOW); 
  delay(miliseconds/2);
}


Aprovechando el soporte de funciones polimórficas, la misma función soportando múltiples repeticiones:

Código:
void blink(int miliseconds, int repeat)
{
  for(int i=0; i<repeat; i++)
    blink(miliseconds);
}


Para la conversión del entero proporcionado por el sensor a un valor de temperatura, tomaré este valor y lo ajustaré a nuevos márgenes (utilizando la macro "map"). El único detalle de esta función es que para soportar decimales multiplico, trabajo y divido el valor inicial:

Código:
float convertToCelsius(long sensorVal) 
{
  return map((int)(sensorVal*1000),
    438000,573000,26000,37000)/1000;
}


Los valores son experimentales y si pueden notarlo los calcule usando la temperatura ambiente (26 grados, en donde el valor entregado por el sensor era 438) y una referencia (temperatura corporal).

Para enviar la temperatura al equipo usaremos un formato en particular, especificado en el primer comentario de la función:

Código:
void printTemp(float tempCelcius)
{
  // Formato TEMP_:000000:
  Serial.print("TEMP_:");

  if(tempCelcius<100) // Tiene menos de 3 dígitos
    Serial.print("0");
  
  if(tempCelcius<10) // Tiene menos de 2 dígitos
    Serial.print("0");
    
  int c = (int)tempCelcius;
  float r = tempCelcius-c;
  Serial.print(c);
  
  long rem = r*1000;
  Serial.print(rem); 

  if(rem<100) // Tiene menos de 3 dígitos
    Serial.print("0");
  
  if(rem<10) // Tiene menos de 2 dígitos
    Serial.print("0");  
    
  Serial.print(":");
}


Con estas funciones, enviar una temperatura es tan sencillo como:

Código:
void doTempRead()
{
  printTemp(convertToCelsius(analogRead(sensorPin)));
  delay(100);
  blink(30,10);
}


Posteriormente debemos trabajar en la interacción del usuario con el botón, tal como define el diagrama superior.

Código:
void loop()
{
  val = digitalRead(buttonPin); 
  if (val == HIGH)
  {       
    digitalWrite(ledPin, LOW); 
  } else {
    blink(100);
    
    // Lo mantiene presionado por 2 segundos

    digitalWrite(ledPin, LOW);

    if(digitalRead(buttonPin) == LOW && buttonPressed)
    {
      // Sigue el boton presionado, pasar a modo continuo
    }
    else
      do_temp_read();
  }
}


Para verificar si el usuario mantuvo presionado efectivamente el botón por los dos segundos requeridos, iteraré con pequeñas pausas revisando el estado del botón:

Código:
// Lo mantiene presionado por 2 segundos
boolean buttonPressed = true;
if(digitalRead(buttonPin) == LOW)
{
  for(int z = 0; z < 40; z++)
  {
    if(z>10)
      digitalWrite(ledPin, HIGH);
      
    if(digitalRead(buttonPin) == HIGH)
    {
      buttonPressed = false;
      break;
    }
    else
      delay(50);
  }
}


Así luego de que sabemos que mantuvo el botón efectivamente presionado, pasamos al modo de captura continua, en donde cada dos segundos (siempre y cuando no vuelva a presionarse el botón) enviaremos los datos del sensor:

Código:
// Modo de captura continua
blink(500,2);
delay(200);

unsigned long start = millis();
boolean ledActive = false;
do
{
  if(millis()-start>=2000)
  {
    digitalWrite(ledPin, LOW);
    ledActive = false;
    do_temp_read();
    start = millis();
  }
  else
    if(!ledActive){
      digitalWrite(ledPin, HIGH);
      ledActive = true;
    }

}while(digitalRead(buttonPin)== HIGH);
delay(500);


Nótese que aquí aparece un problema potencial. La función de tiempo transcurrido (millis) tiene un límite dado por su tipo de variable. Así cuando el valor puede ser como máximo de 32 bits, tenemos que 4294967296 será el máximo de milisegundos a medir, es decir cerca de 49,72 días.

Ese límite puede parecer bastante para un proyecto pequeño, pero para una estación de trabajo continuo (aunque Arduino no está pensado para este propósito)


4. Escribiendo la aplicación del escritorio

Para los gráficos utilicé ZedGraph. Esta componente permite disponer de un cuadro de gráficos totalmente personalizable:



Como nos conectamos por un puerto serie provisto por el chip FTDI de arduino, entonces lo más sencillo es hacer un menú de conexión:



La finalidad es lograr un resultado similar al siguiente:



Con el control del puerto serie proporcionado por Visual Studio es bastante natural recibir los datos sobre la temperatura:

Código:
private void serialPortArduino_DataReceived(object sender, 
  System.IO.Ports.SerialDataReceivedEventArgs e)
{
  int bytesToRead = serialPortArduino.BytesToRead;

  if (bytesToRead >= 13)
  {
    char[] z = new char[13];
    serialPortArduino.Read(z, 0, 13);

    string n = new string(z);
    ProcessReceivedData(n);
  }
}


Nada muy complejo, menos la función que procesa la información recibida:

Código:
delegate void SetTextCallback(string text);

private void ProcessReceivedData(string text)
{
  if (this.zedGraphControlTemperature.InvokeRequired)
  {
    SetTextCallback d = new SetTextCallback(ProcessReceivedData);
    this.Invoke(d, new object[] { text });
  }
  else
  {
    temperatureData.Add(GetActualTimeInDouble(), 
      ConvertToCelsius(Convert.ToDouble(text.Split(':')[1])));
    zedGraphControlTemperature.AxisChange();
    zedGraphControlTemperature.PerformAutoScale();
    zedGraphControlTemperature.Refresh();
  }
}


El único detalle del código anterior, es la incorporación de una condición para el soporte de múltiples hilos.

Las nuevas versiones de Visual Studio nos advierten sobre esto cuando relacionamos cosas que involucran hilos. Así debemos usar InvokeRequired en conjunto con la función delegate de más arriba. Esto es más que nada porque al utilizar algo de la interfaz (como lo es el control zedGraph de gráficos), podemos tener un instante de tiempo en que dos secuencias recibidas alcanzan a ser procesadas tan rápidamente (gatilladas asincrónicamente) que pueden provocar un bloqueo actualizando (enviando mensajes) al componente visual.

Todo el otro código del programa se explica por si solo, incluyendo las rutinas de conexión, redibujado y otras.

Algo rápido que se me ocurrió para poder darle versatilidad al programa fue añadir un filtro por medio de expresiones regulares para los puertos disponibles en el equipo. Claramente hubiera sido más limpio buscar al "Arduino" leyendo los descriptores de hardware pero hubiera tomado más tiempo y en la práctica no hubiese sido necesario:

Código:
private void UpdateSerialPorts()
{
  toolStripComboBoxPort.Items.Clear();

  // Añadir solo los puertos válidos
  try
  {
    foreach (String d in SerialPort.GetPortNames())
    {
      if (Regex.IsMatch(d, @Settings.Default.configPortMatch, RegexOptions.IgnoreCase))
        toolStripComboBoxPort.Items.Add(d);
    }
  }
  catch
  {
    MessageBox.Show(Resources.AppFilterErrorText,Resources.AppGenericErrorTitle,
      MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
    Settings.Default.configPortMatch = "";
    UpdateSerialPorts();
    return;
  }

  toolStripComboBoxPort.SelectedIndex =toolStripComboBoxPort.Items.Count-1;
}


Como se puede ver en el código, cada puerto se compara con una expresión regular. Así podríamos correr esta aplicación en Unix utilizando Mono ajustando nuestro filtro de nombre de puertos correspondientemente.

Por defecto el filtro es para los puertos COM de Windows:

\ACOM\d{1,3}\Z



5. Pruebas

Un ejemplo de uso del sensor puede verse en el siguiente video:



Como se puede apreciar es bastante sencillo iniciar una captura de temperaturas.


6. Descarga

El código de la aplicación C# está disponible en el SVN de Google Code.

También subí el código fuente del proyecto completo:
TemperatureLogger v0.1 (Código fuente)

El ejecutable de la aplicación:
TemperatureLogger v0.1

Adicionalmente los fuentes para el código de Arduino:
TemperatureLogger v0.1 (Archivo fuente para el IDE de Arduino)



7. Conclusiones

Como futuro ingeniero en computación, siempre me interesó extender mis habilidades en términos de hardware, automatización y arquitectura de computadoras, ideales que la placa Arduino sustentó perfectamente.

Luego de malas e incomodas experiencias en otras placas (en particular una basada en PIC) puedo afirmar con certeza que Arduino es deliciosamente ideal para aquellas personas con pequeños proyectos que no quieren involucrarse en demasía ni perder mucho tiempo en cosas triviales y básicas del desarrollo en hardware.

El único gran contratiempo que puedo destacar en arduino es su implementación estricta, casi arbitraria de la función de conteo, "millis", como se vio con más detalles en el cuerpo del artículo.

Así, extiendo la invitación para todos los interesados en crear sus pequeños proyectos de manera extremadamente sencilla.

Haga clic sobre una de las estrellas para calificar este artículo.

Opiniones y comentarios (Escribir un nuevo comentario)
Muchas gracias por este gran trabajo. Estoy recopilando informacion para ver si puedo crear una estacion de soldadura BGA (para reballing) y que arduino sea el encargado de controlar la rampa de temperatura con un termopar y reles o potenciometros ...pero estoy empezando con arduino y se me hace bastante complicado... no se si seria muy complicado llegar a controlar esos parametros con la placa arduino ..... Graciassss x todo y seria de gran ayuda algun comentario, aporte y demas.
Escrito por apitraco (22/08/2010 02:17 AM)
Muy buen trabajo, quisiera hacerte algunas consultas: 1.- Que versión de Visual Studio estás utilizando en este proyecto? 2.- No tuviste problemas al leer los datos del puerto serial sin entrar en conflicto con el Arduino, me refiero a que Arduino o Visual te diga que el puerto ya está siendo utilizado?. Gracias
Escrito por C.Salas (16/06/2010 02:24 AM)
1) Visual Studio C# Express
2) Claro, no puedes usar el mismo puerto a la vez. Hay aplicaciones que pueden replicar un puerto, pero en este caso la aplicación sólo lanza una excepción
Escrito por Erwin Ried (16/06/2010 05:18 PM)
Buen proyecto y muy buen artículo. Buenas presentaciones. Sí señor. Saludos.
Escrito por Edu (07/02/2010 04:46 PM)
Te felicito, pero aun pude ser mas facil para nosotros que no le pegamops mucho al sunto. ¿Has visto Niple? Se hacen los programas a partir de un diagrama de flujo. Apretas una tecla y lo conviertes en codigo ASM. Seria interesante hacer algo así con el Arduino.
Escrito por Magdy (17/06/2009 12:44 PM)
No he visto, aunque mi sueño sería hacer una forma fácil de programarlo como por ejemplo un lenguaje de más alto nivel
Escrito por Erwin Ried (18/06/2009 08:42 PM)
Excelente trabajo, muy generoso de tu parte compartirlo. Muy bien presentado además. OldBeaver
Escrito por OldBeaver (09/06/2009 10:22 PM)
conoces la ecuación de Steinhart? no la vi en tu programa saludos
Escrito por rafel (21/05/2009 07:58 PM)
No la conozco, sin embargo el sensor de temperaturas utilizado define un segmento de temperaturas estable lineal respecto al valor analógico recibido, en este caso por arduino
Escrito por Erwin Ried (21/05/2009 09:27 PM)
Muy bueno, me gustaria que expliques mas articulos de esta tipo ya que hay gente que desea aplicar estos conocimientos en optmizar,medir y que sea economica rentable poder hacer esto.te felicito
Escrito por Elcholo (26/11/2008 11:51 AM)

Copyright © 2013 por Erwin Ried.