29 de junio de 2015

Termómetro digital

El proyecto que en esta ocasión construiremos integra un par de temas que discutimos anteriormente en este blog: “Midiendo los fríos y calores” y “Visualizador de siete segmentos”. Usaremos nuestro Arduino Uno para construir el prototipo de termómetro ambiental digital que se muestra en la siguiente foto:

Termómetro ambiental digital construido
a partir de: Arduino Uno, sensor de temperatura y
visualizador de tres dígitos de siete segmentos.

Utilizaremos un visualizador multiplexado de tres dígitos de siete segmentos para desplegar en grados Celsius la temperatura ambiente obtenida a partir de las lecturas de un sensor TMP36. Este proyecto contrasta con nuestro proyecto anterior en el que la información del sensor de temperatura solo se imprimía en una terminal en la pantalla de la computadora.

Visualizador multiplexado

Para controlar un visualizador de siete segmentos (SSD por sus siglas en inglés) se necesitan conectar nueve pines: siete pines para controlar los segmentos, un pin para el punto decimal, y otro pin que sirve de ánodo o cátodo común.

Si queremos desplegar n dígitos usando n SSDs individuales, necesitaríamos un total de 9 × n conexiones. Sin embargo, si usamos un visualizador multiplexado, también conocido como MD por sus siglas en inglés (multiplexed display), solo requerimos 8 + n pines: siete pines para los segmentos, un pin para el punto decimal, y n pines de ánodo o cátodo común (un pin por cada dígito). En nuestro proyecto queremos usar tres dígitos, por lo que bajamos de 27 conexiones (9 × 3)  a tan solo 11 (8 + 3) al momento de utilizar multiplexado.

Visualizador multiplexado o MD (multiplexed display)
de tres dígitos, de siete segmentos por cada dígito.

La técnica de multiplexado se basa en una característica que tiene la vista humana conocida como persistencia de la retina, en donde conservamos la sensación de ver una imagen aún cuando ésta ya despareció. Si creamos una intermitencia encendiendo y apagando un led, al momento en que ésta sea lo suficientemente rápida ya no apreciamos que el led llega a apagarse sino que nos da la sensación de que está permanentemente encendido.

Para el MD de n dígitos el multiplexado consiste en encender los segmentos deseados del dígito a desplegar en la primera posición, pausar por Δ segundos y apagar después todos los segmentos. Lo anterior lo repetimos secuencialmente con el dígito de la segunda posición, después con el dígito de la tercera posición y así hasta llegar al dígito de la n-ésima posición para luego comenzar nuevamente con el dígito de la primera posición y continuar así sucesivamente. El valor de Δ debe ser suficientemente breve para dar la impresión de un encendido continuo. A base de prueba y error determinamos que 0.001 segundos (una milésima de segundo) genera el efecto deseado en nuestro programa de Python.

El MD para nuestro proyecto es uno de la serie KW3-561 el cual puede verse de manera esquemática así:

Pines y segmentos de un visualizador
multiplexado de tres dígitos.

El MD tiene doce pines numerados del 1 al 12 con la siguiente correspondencia (notar que el pin 6 no se utiliza):
  • Pin 1: Segmento E
  • Pin 2: Segmento D
  • Pin 3: Segmento P (punto decimal)
  • Pin 4: Segmento C
  • Pin 5: Segmento G
  • Pin 6: No se utiliza
  • Pin 7: Segmento B
  • Pin 8: Ánodo o cátodo común de DIG_3 (dígito de la tercera posición)
  • Pin 9: Ánodo o cátodo común de DIG_2 (dígito de la segunda posición)
  • Pin 10: Segmento F
  • Pin 11: Segmento A
  • Pin 12: Ánodo o cátodo común de DIG_1 (dígito de la primera posición)
Solo uno de DIG_1, DIG_2 o DIG_3 debe estar encendido en un momento dado. Esto quiere decir que, por ejemplo, si enciendo DIG_1, debo apagar DIG_2 y DIG_3; cualquier segmento que encienda a partir de ese momento solo se verá reflejado en la posición correspondiente a DIG_1.

A partir de lo ya discutido estamos en condiciones de esbozar un algoritmo un poco más formal para realizar el multiplexado de un MD de tres dígitos de la forma DD.D:
  • Sea n un número en base decimal (0 ≤ n < 100) conformado por tres dígitos d1, d2 y d3, en donde n = d1×10 + d2 + d3×0.1; es decir, d1 son las decenas, d2 son las unidades y d3 son las décimas de unidad. Repetir lo siguiente mientras se desee desplegar n en el MD:
    • Para i de 1 hasta 3:
      • Mandar una señal de encendido a la posición de DIG_i, y una señal de apagado a todas las demás posiciones.
      • Encender o apagar los segmentos A, B, C, D, E, F y G correspondientes al valor del dígito di (la entrada del “Visualizador de siete segmentos” detalla puntalmente la manera de hacer esto). Adicionalmente, si i = 2, encender el segmento P (punto decimal), de lo contrario apagarlo.
      • Pausar por Δ segundos.
      • Apagar todos los segmentos correspondientes al dígito di

Armando el proyecto

Esta es la lista de componentes necesarios para nuestro proyecto:
  • Arduino Uno.
  • MD de ánodo común KW3-561ASN (dígitos rojos de 14.2 mm de altura).
  • Sensor de temperatura TMP36.
  • Protoboard.
  • Ocho resistencias de 330Ω (bandas naranja, naranja, café). 
  • Cables conectores.
La siguiente figura, elaborada con Fritzing, muestra la manera de conectar estos componentes:

Vista de protoboard Fritzing para
el termómetro ambiental digital.

Cada pin asociado a un segmento del MD debe quedar conectado, con una resistencia de 330Ω de por medio, a un pin digital del Arduino. Estas son las conexiones que se muestran en la figura anterior:

  • El pin digital 2 del Arduino se conecta al pin 1 (SEG_E) del MD.
  • El pin digital 3 del Arduino se conecta al pin 2 (SEG_D) del MD.
  • El pin digital 4 del Arduino se conecta al pin 3 (SEG_P) del MD.
  • El pin digital 5 del Arduino se conecta al pin 4 (SEG_C) del MD.
  • El pin digital 6 del Arduino se conecta al pin 5 (SEG_G) del MD.
  • El pin digital 7 del Arduino se conecta al pin 11 (SEG A) del MD.
  • El pin digital 8 del Arduino se conecta al pin 10 (SEG_F) del MD.
  • El pin digital 9 del Arduino se conecta al pin 7 (SEG_B) del MD.
Por otro lado, los pines de ánodo común deben conectarse directamente de la siguiente manera:
  • El pin digital 10 del Arduino se conecta al pin 12 (DIG_1) del MD.
  • El pin digital 11 del Arduino se conecta al pin 9 (DIG_2) del MD.
  • El pin digital 12 del Arduino se conecta al pin 8 (DIG_3) del MD.
Recordemos que el sensor de temperatura TMP36 tiene tres pines. Viendo este componente de frente a su parte plana:
  • El pin de 5V del Arduino se conecta al pin Vs (pin izquierdo) del TMP36.
  • El pin analógico A0 del Arduino se conecta al pin Vout (pin central) del TMP36.
  • Algún pin GND del Arduino se conecta al pin GND (pin derecho) del TMP36.

El software

El código fuente completo en Python 3 del proyecto se muestra a continuación. Contiene diversos comentarios con el fin de ayudar a su comprensión.
#!/usr/bin/env python3
# Archivo: termometro_digital.py

import pyfirmata
from time import time

# Valor de delta para el multiplexado.
PAUSA_MULTIPLEXADO = 0.001

# Leer la temperatura después de este número de segundos.
PAUSA_LECTURAS = 60

# Iniciar la placa de Arduino.
# IMPORTANTE: Cambiar la cadena de caracteres que indica
# el puerto serie que utiliza nuestra plataforma.
placa = pyfirmata.Arduino('/dev/ttyACM0')

# Crear un thread iterador, de lo contrario la placa puede
# seguir enviando datos por la conexión serial hasta
# producir un desbordamiento.
pyfirmata.util.Iterator(placa).start()

# Inicializar el pin de entrada analógica 0 del Arduino
# para conectarlo al pin Vout del TMP36.
entrada_temperatura = placa.get_pin('a:0:i')
entrada_temperatura.enable_reporting()

# Incicializar los pines de salida digital para los
# segmentos del MD.
segmento = [
    placa.get_pin('d:4:o'), # SEG_P/Ard_4/MD_3
    placa.get_pin('d:6:o'), # SEG_G/Ard_6/MD_5
    placa.get_pin('d:8:o'), # SEG_F/Ard_8/MD_10
    placa.get_pin('d:2:o'), # SEG_E/Ard_2/MD_1
    placa.get_pin('d:3:o'), # SEG_D/Ard_3/MD_2
    placa.get_pin('d:5:o'), # SEG_C/Ard_5/MD_4
    placa.get_pin('d:9:o'), # SEG_B/Ard_9/MD_7
    placa.get_pin('d:7:o')  # SEG_A/Ard_7/MD_11
]

# Inicializar los pines de salida digital para las
# conexiones del ánodo común de las posiciones DIG_1,
# DIG_2 y DIG_3 del MD.
posicion = [
    placa.get_pin('d:10:o'), # DIG_1/Ard_10/MD_12
    placa.get_pin('d:11:o'), # DIG_2/Ard_11/MD_9
    placa.get_pin('d:12:o'), # DIG_3/Ard_12/MD_8
]

# Diseños de los dígitos individuales. Este programa no
# utiliza los diseños de la A a la F.
patron = [
    # ABCDEFGP <-- Segmentos
    # --------
    0b11111100, # 0
    0b01100000, # 1
    0b11011010, # 2
    0b11110010, # 3
    0b01100110, # 4
    0b10110110, # 5
    0b10111110, # 6
    0b11100000, # 7
    0b11111110, # 8
    0b11110110, # 9
    0b11101110, # A
    0b00111110, # b
    0b10011100, # C
    0b01111010, # d
    0b10011110, # E
    0b10001110  # F
]

def voltios_a_celsius(v):
    """Convierte un voltaje v obtenido de un sensor
       TMP36 a grados Celsius.
    """
    return 100 * (v - 0.5)

def binario(n):
    """Devuelve como lista el valor binario de n.

    La lista resultante es de la forma:

       [bit0, bit1, bit2, bit3, bit4, bit5, bit6, bit7]

    Los bit8 en adelante son ignorados.
    """
    resultado = []
    for i in range(8):
        resultado.append(n & 1)
        n >>= 1
    return resultado

def activa_posicion(n):
    """Enciende el pin correspondiente al dígito de la
    posición n de un MD, apagando los pines restantes.

        n = 0: DIG_1
        n = 1: DIG_2
        n = 2: DIG_3
    """
    for i, d in enumerate(posicion):
        d.write(i == n)

def despliega_digito(d, puntodec=False):
    """Despliega el digito d en la posición actualmente
    activa (DIG_1, DIG_2 o DIG_3) del MD.

    Si d es menor a 0 o mayor a 15 apaga todos los
    segmentos. Si puntodec es True, enciende el segmento
    correspondiente al punto decimal.
    """
    d = ((patron[d] if 0 <= d <= 15 else 0b00000000)
         | puntodec)
    for pin, bit in zip(segmento, binario(d)):
        # El operador 'not' de la siguiente instrucción se
        # necesita debido a que el MD es de ánodo común.
        # Dicho operador se debe omitir si el MD es de
        # cátodo común.
        pin.write(not bit)

def despliega_numero(n):
    """Despliega el número n en un visualizador
    multiplexado (MD) con tres dígitos de siete segmentos
    (SSD).

    El valor desplegado siempre tiene la forma:

        DD.D

    donde D es un dígito del 0 al 9. Despliega 88.8 si
    el valor de n es menor a cero o mayor o igual a 100.
    """
    if n < 0 or n >= 100:
        fmt = '888'
    else:
        fmt = '{:03}'.format(round(n * 10))
    for i, d in enumerate(fmt):
        activa_posicion(i)
        despliega_digito(int(d), i == 1)
        placa.pass_time(PAUSA_MULTIPLEXADO)
        despliega_digito(-1)

def lee_temperatura():
    """Realiza la lectura del voltaje de un sensor de
    temperatura TMP36 y devuelve el valor obtenido
    convertido a grados Celsius.
    """
    while True:
        v = entrada_temperatura.read()
        if v != None:
            v *= 5
            return voltios_a_celsius(v)

# El algoritmo de multiplexado comienza aquí.
try:
    tiempo = 0
    while True:

        # Tomar nueva lectura de temperatura después de
        # PAUSA_LECTURAS segundos.
        # Esta condición es verdadera en la primera
        # iteración (tiempo == 0).
        if time() > tiempo + PAUSA_LECTURAS:
            tiempo = time()
            temperatura = lee_temperatura()

        despliega_numero(temperatura)

except KeyboardInterrupt:
    # Terminar programa cuando se presione Ctrl-C.

    # Apagar los SSDs de las tres posiciones.
    for i in range(3):
        activa_posicion(i)
        despliega_digito(-1)

finally:
    placa.exit()
Para correr el programa se necesita tener el Arduino conectado por el puerto USB a la computadora anfitriona y ejecutar el siguiente comando desde una terminal en el mismo directorio donde se encuentra el archivo termometro_digital.py:
python3 termometro_digital.py
Si todo resulta bien se debe poder ver la temperatura ambiente en el visualizador. El programa corre hasta que presionemos Ctrl-C para terminar.

5 de junio de 2015

Midiendo los fríos y calores

En esta ocasión presentaremos una manera de medir la temperatura ambiente usando Python, Arduino, y un sensor.


El hardware

Para este proyecto usaremos el sensor TMP36. Esta es la descripción oficial de dicho componente:
El TMP36 es un sensor de precisión y bajo voltaje que permite medir la temperatura en grados centígrados. La salida de voltaje provista es linealmente proporcional a la temperatura en grados Celsius. No requiere ninguna calibración externa para proporcionar una precisión típica de ± 2 °C. Este sensor es muy fácil de usar, basta con conectarlo a tierra y a un voltaje de entre 2.7 y 5.5 voltios y se podrá comenzar a tomar lecturas por el correspondiente pin de salida. El factor de escala de salida es de 10 milivoltios por grado Celsius.
Este sensor es relativamente económico, tiene un costo aproximado de 2.00 USD. Físicamente se ve así:

Sensor de temperatura TMP36.

Tal como se pudo apreciar en la descripción de arriba, el TMP36 tiene tres pines. Viendo el componente de frente a su parte plana:
  • El pin de la izquierda (Vs) se conecta al voltaje.
  • El pin central (Vout) se conecta a un pin de entrada analógica del Arduino.
  • El pin de la derecha (GND) se conecta a tierra.
La siguiente figura muestra el TMP36 con los nombres de sus pines:


El diagrama de abajo elaborado con Fritzing muestra la manera de conectar el sensor con el Arduino:

Vista de protoboard Fritzing para
el sensor de temperatura.

La siguiente foto muestra como se ven físicamente las conexiones de nuestros componentes electrónicos:

Vista física del sensor de temperatura TMP36
conectado al Arduino.

El software

Para escribir el programa que controlará nuestro hardware tenemos primero que seguir las mismas instrucciones presentadas en “Cómo programar a tu Arduino”. Si aún no le hemos hecho, debemos descargar Firmata en el Arduino e instalar la biblioteca pyFirmata en la computadora anfitriona.

NOTA: Todo el código presentado aquí fue probado con Python 3.4.

Las instrucciones para importar el paquete pyfirmata y crear el objeto que representa nuestra placa (board) de Arduino son las usuales:
import pyfirmata

placa = pyfirmata.Arduino('/dev/ttyACM0')
No hay que olvidar ajustar la cadena de caracteres que indica el puerto serie que utiliza nuestra plataforma ('/dev/ttyACM0' en mi caso, ya que estoy usando Linux) para poder conectarse al Arduino.

Dado que vamos a utilizar un pin de entrada es necesario crear un thread iterador, de lo contrario la placa puede seguir enviando datos por la conexión serial hasta producir un desbordamiento. Para crear dicho thread usamos la siguiente instrucción:
pyfirmata.util.Iterator(placa).start()
A continuación asignamos a la variable entrada el objeto asociado al pin 0 analógico (a) de entrada in (i) del Arduino:
entrada = placa.get_pin('a:0:i')
Posteriormente debemos invocar el método enable_reporting() sobre la entrada para poder leer las señales recibidas:
entrada.enable_reporting()
Ahora solo queda leer el estado del sensor usando el método read() sobre el objeto entrada:
v = entrada.read()
El valor devuelto por el método read() depende del voltaje presente en el pin de entrada analógica. Este voltaje va de 0 a 5 voltios, sin embargo el método read() devuelve un valor ajustado entre 0.0 y 1.0. Multiplicaremos v por 5 para tener el valor de lectura en voltios, ya que así lo requiere una fórmula que usaremos más adelante.

Una cosa extraña que sucedió al estar realizando diversas pruebas fue que el método read() devuelve None la primera vez que se invoca dentro de un programa. La documentación no menciona razón alguna por lo que pudiera estar sucediendo esto. Para evitar problemas conviene entonces colocar un instrucción if para manejar este caso.

Según la ficha técnica del TMP36, la fórmula para convertir un voltaje V leído del pin Vout a una temperatura C en grados Celsius es:
C =  100 (V 0.5)
Conociendo el valor de C podemos usar la siguiente fórmula si nos interesa conocer la temperatura en grados Fahrenheit:
F = 1.8 (C) + 32
Para facilitar la legibilidad del programa colocaremos dentro de una función la implementación de cada una de las fórmulas de arriba. El siguiente programa completo realiza la lectura de la temperatura ambiente cada 10 segundos:
#!/usr/bin/env python3
# Archivo: temperatura.py

import pyfirmata
from datetime import datetime

PAUSA = 10 # Número de segundos entre tomas de lectura
           # de temperatura.

placa = pyfirmata.Arduino('/dev/ttyACM0')

pyfirmata.util.Iterator(placa).start()

entrada = placa.get_pin('a:0:i')
entrada.enable_reporting()

def volts_to_celsius(v):
    """Convierte un voltaje v obtenido de un sensor
       TMP36 a grados Celsius.
    """
    return 100 * (v - 0.5)

def celsius_to_fahrenheit(c):
    """Convierte c grados Celsius a grados Ffahrenheit.
    """
    return 1.8 * c + 32

try:
    while True:
        d = datetime.now()
        v = entrada.read()
        if v != None:
            v *= 5 # Convertir el valor devuelto por
                   # read() a voltios.

            c = volts_to_celsius(v)
            f = celsius_to_fahrenheit(c)
            print('{}, {:.3f} V, {:.2f} °C, {:.2f} °F'
                  .format(d, v, c, f))
            placa.pass_time(PAUSA)

except KeyboardInterrupt:
    # Terminar programa cuando se presione Ctrl-C.
    pass

finally:
    placa.exit()
Para correr el programa debemos tener el Arduino conectado por el puerto USB a la computadora anfitriona y ejecutar el siguiente comando desde una terminal en el mismo directorio donde se encuentra el archivo temperatura.py:
python3 temperatura.py
La salida del programa debe ser similar a lo siguiente:
2015-06-05 18:14:41.819863, 0.743 V, 24.30 °C, 75.74 °F
2015-06-05 18:14:51.819918, 0.743 V, 24.30 °C, 75.74 °F
2015-06-05 18:15:01.819973, 0.743 V, 24.30 °C, 75.74 °F
2015-06-05 18:15:11.820032, 0.748 V, 24.80 °C, 76.64 °F
2015-06-05 18:15:21.820090, 0.743 V, 24.30 °C, 75.74 °F
2015-06-05 18:15:31.820148, 0.743 V, 24.30 °C, 75.74 °F
2015-06-05 18:15:41.820206, 0.757 V, 25.75 °C, 78.35 °F
2015-06-05 18:15:51.820263, 0.762 V, 26.25 °C, 79.25 °F
2015-06-05 18:16:01.820322, 0.772 V, 27.20 °C, 80.96 °F
2015-06-05 18:16:11.820378, 0.772 V, 27.20 °C, 80.96 °F
2015-06-05 18:16:21.820432, 0.762 V, 26.25 °C, 79.25 °F
2015-06-05 18:16:31.820502, 0.752 V, 25.25 °C, 77.45 °F
2015-06-05 18:16:41.820568, 0.752 V, 25.25 °C, 77.45 °F
2015-06-05 18:16:51.820625, 0.748 V, 24.80 °C, 76.64 °F
2015-06-05 18:17:01.820692, 0.748 V, 24.80 °C, 76.64 °F
2015-06-05 18:17:11.820746, 0.748 V, 24.80 °C, 76.64 °F
...
Por ahí del séptimo renglón de la salida se nota un incremento importante en la temperatura ambiente. Esto se debe a que coloqué en ese momento mi dedo encima del sensor durante aproximadamente medio minuto. Una vez que retiré mi dedo se puede observar un decremento en la temperatura hasta llegar nuevamente a valores cercanos a los del inicio del programa.

El programa termina al presionar Ctrl-C.