15 de diciembre de 2015

Listas por comprensión

Las listas por comprensión (list comprehensions en inglés) son una característica interesante pero sobre todo muy útil que fue incorporada al lenguaje Python en su versión 2.0 en el año 2000 (ver: PEP 202). Las listas por comprensión debutaron por vez primera en un lenguaje de programación en 1977, cuando Rod Burstall y John Darlington diseñaron el lenguaje funcional NPL. Hoy en día un número importante de lenguajes de programación soportan listas por comprensión, incluyendo: Haskell, JavaScript 1.7, CoffeeScript, Erlang, F#, Scala, Clojure y Racket.

Conjuntos

Para entender qué son las listas por comprensión tomémonos primero un momento para responder a las siguientes dos preguntas:
¿Qué es un conjunto?
¿Cómo se define un conjunto?
En matemáticas, un conjunto es simplemente una colección de elementos bien definidos. Los elementos pueden ser cualquier cosa: números, letras, nombres, figuras, etc.

Un conjunto de figuras geométricas.

Hay dos manera de definir los elementos que pertenecen a un conjunto: por extensión o por comprensión. Cuando se define un conjunto por extensión cada elemento se enumera de manera explícita. Tomando un ejemplo inspirado en el Señor de los Anillos de J. R. R. Tolkien:
A = { Frodo, Sam, Pippin, Merry, Legolas,
          Gimli, Aragorn, Boromir, Gandalf }
La expresión anterior se lee así: A es el conjunto formado por los elementos: Frodo, Sam, Pippin, Merry, LegolasGimli, Aragorn, Boromir y Gandalf.

Por otro lado, cuando un conjunto se define por comprensión no se mencionan los elementos uno por uno sino que se indica una propiedad que todos éstos cumplen, por ejemplo:
B = { x | x ∈ Comunidad del Anillo }
La expresión de arriba se lee así: B es el conjunto de elementos x tales que x  pertenece a la Comunidad del Anillo.

Los miembros de la Comunidad del Anillo.
Imagen de haleyhss
.

En los ejemplos anteriores podemos decir que el conjunto A es igual al conjunto B debido a que ambos tienen exactamente los mismos elementos. Esto es cierto aún a pesar de que el conjunto A se definió por extensión y B se definió por comprensión.

Listas en Python

En Python se utilizan listas para representar colecciones de elementos (a decir verdad, Python cuenta también con conjuntos [sets] y otros tipos de datos, pero con el fin de simplificar la discusión usaremos aquí solo listas). Ahora bien, así como matemáticamente se pueden definir los conjuntos por extensión y por comprensión, en Python se pueden definir las listas también por extensión y por comprensión.

Supongamos, como ejemplo, que deseamos tener una lista con las primeras diez potencias de dos. Una potencia de dos es cualquiera de los números obtenidos al elevar el número dos a una potencia entera no negativa. El siguiente código de Python cumple con este cometido usando una lista por extensión (enumerando todos los elementos de manera explícita):
c = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
Matemáticamente, un conjunto equivalente a la lista anterior se puede definir por comprensión así:
C = { 2x | x ∈ ℤ ∧ 0 ≤ x < 10 }
Esta expresión se lee así: C es el conjunto formado por los elementos “dos elevado a la x”, tales que x pertenece al conjunto de los números enteros y además x es mayor o igual a 0 pero menor que 10. Dicha expresión se puede traducir a Python directamente usando la notación de listas por comprensión:
c = [2 ** x for x in range(10)]
La sintaxis de listas por comprensión consiste en colocar entre corchetes una expresión (2 ** x) seguida de una cláusula for. Dicha cláusula es muy similar en intención a un ciclo for convencional. En este caso estamos indicando que la variable x tomará los valores devueltos por la función range(10) (los enteros del 0 al 9). A partir de cada valor que toma x se calcula el resultado de la expresión 2 ** x y con eso se determinan los valores finales de la lista resultante. Esencialmente es como si ejecutáramos el siguiente código:
c = []
for x in range(10):
    c.append(2 ** x)
El método append() se encarga de ir añadiendo un nuevo elemento (2 ** x) al final de la lista c (inicialmente vacía) en cada iteración del ciclo for.

Las listas por comprensión también pueden incluir una expresión condicional que nos permite quedarnos con ciertos elementos y eliminar los restantes. Para ello se debe utilizar una cláusula if después de la cláusula for. Por ejemplo, si queremos una lista con todos los números entre 1 y 100 que sean múltiplos de 7 o que terminen con el dígito 7, podemos escribir la siguiente lista por comprensión:
[n for n in range(1, 101) if n % 7 == 0 or n % 10 == 7]
El resultado es exactamente lo esperado:
[ 7, 14, 17, 21, 27, 28, 35, 37, 42, 47, 
 49, 56, 57, 63, 67, 70, 77, 84, 87, 91,
 97, 98]
Puede haber más de una cláusula for en una lista por comprensión (también puede haber cero o más cláusulas if). Como ejemplo, supongamos que tengo cuatro camisas (de color rojo, amarillo, azul y verde) y dos pantalones (de color negro y blanco). Quiero saber de qué manera puedo combinar mi ropa. Hay ocho formas distintas (4 camisas × 2 pantalones) de hacerlo:
  • Camisa roja y pantalón negro.
  • Camisa roja y pantalón blanco.
  • Camisa amarilla y pantalón negro.
  • Camisa amarilla y pantalón blanco.
  • Camisa azul y pantalón negro.
  • Camisa azul y pantalón blanco.
  • Camisa verde y pantalón negro.
  • Camisa verde y pantalón blanco.
Podemos dejar que Python calcule lo anterior usando una lista por comprensión con dos cláusulas for:
[(camisa, pantalon) 
    for camisa in ['rojo', 'amarillo', 'azul', 'verde']
    for pantalon in ['negro', 'blanco']]
El resultado es:
[('rojo', 'negro'), ('rojo', 'blanco'),
 ('amarillo', 'negro'), ('amarillo', 'blanco'),
 ('azul', 'negro'), ('azul', 'blanco'),
 ('verde', 'negro'), ('verde', 'blanco')]
Como se puede observar, el ejemplo anterior calcula efectivamente el producto cartesiano de dos conjuntos (los colores de las camisas por los colores de los pantalones).

A continuación presento un par de ejemplos que demuestran el uso de listas por comprensión en contextos más complejos.

Ternas pitagóricas

Una terna pitagórica es un conjunto de tres números naturales (a, b, c) que cumplen con la siguiente ecuación:
 a2 + b2 = c2
El nombre deriva del teorema de Pitágoras, el cual establece que el cuadrado de la hipotenusa de un triángulo rectángulo es igual a la suma de los cuadrados de sus catetos.

Por ejemplo, si a = 3, b = 4 y c = 5, tenemos:
32 + 42 = 52
9 + 16 = 25
Queremos calcular todos los valores posibles de a, b y c que sean menores a 20. Traduciendo directamente los requisitos planteados a una lista por comprensión, tenemos:
[(a, b, c)
    for a in range(1, 20)
    for b in range(1, 20)
    for c in range(1, 20)
    if a ** 2 + b ** 2 == c ** 2]
El resultado correspondiente es:
[(3, 4, 5), (4, 3, 5), (5, 12, 13), (6, 8, 10),
 (8, 6, 10), (8, 15, 17), (9, 12, 15), (12, 5, 13),
 (12, 9, 15), (15, 8, 17)]
Si observamos con atención, hay el doble de ternas deseables en esta solución, ya que se incluyen todas las permutaciones de los valores de a y b. Para corregir esta situación podemos cambiar el valor de inicio de los rangos de las variables b y c para que inicien en un valor posterior al contenido en la variable que está a su izquierda inmediata y con ello garantizamos que: a < b < c. El código quedaría así:
[(a, b, c)
    for a in range(1, 20)
    for b in range(a + 1, 20)
    for c in range(b + 1, 20)
    if a ** 2 + b ** 2 == c ** 2]
El resultado final es el buscado:
[(3, 4, 5), (5, 12, 13), (6, 8, 10), (8, 15, 17),
 (9, 12, 15)]
La lista por comprensión anterior es equivalente al resultado que queda en la variable r del siguiente código:
r = []
for a in range(1, 20):
    for b in range(a + 1, 20):
        for c in range(b + 1, 20):
            if a ** 2 + b ** 2 == c ** 2:
                r.append((a, b, c))
Vale la pena notar que se preserva el orden de los fors y el if en ambos códigos.

Creando una matriz identidad

Una matriz identidad de tamaño n es una matriz cuadrada de n renglones por n columnas en donde cada elemento de la diagonal principal es 1 y los elementos restantes son 0. Por ejemplo, la siguiente es una matriz identidad de tamaño n = 4:


En álgebra lineal una matriz identidad sirve como elemento neutro en la multiplicación de matrices. Esto quiere decir que si I es la matriz identidad y A es otra matriz de dimensiones compatibles, entonces A I = A.

La siguiente función de Python crea un matriz identidad de tamaño n usando listas por comprensión:
def matriz_identidad(n):
    """Devuelve una lista de listas que representa una
       matriz identidad de tamaño n."""
    return [[(1 if ren == col else 0) for col in range(n)]
            for ren in range(n)]
En el código anterior hay una lista por comprensión anidada dentro de otra lista por comprensión. La comprensión externa controla que los n renglones sean generados. La comprensión interna crea un renglón particular conformado por n columnas. Cada elemento individual de la matriz es 1 si su número de renglón es igual a su número de columna, de otra forma es 0.

Probando el código:
>>> matriz_identidad(5)
[[1, 0, 0, 0, 0], 
 [0, 1, 0, 0, 0], 
 [0, 0, 1, 0, 0], 
 [0, 0, 0, 1, 0], 
 [0, 0, 0, 0, 1]]
>>> matriz_identidad(1)
[[1]]

Conclusión

Las listas por comprensión en Python se basan en la notación matemática de conjuntos por comprensión. La notación no es muy compleja, y una vez que la entendemos podemos escribir programas más compactos y expresivos.

7 de julio de 2015

5° aniversario de EduPython

El día de hoy, 7 de julio del 2015, se cumplen cinco años de la publicación de la primera entrada del blog EduPython.


Resulta interesante revisar las estadísticas recopiladas por el sitio de blogger.com que hospeda a EduPython:

Visitantes al blog de EduPython por país.

Por otro lado, las siguientes cinco entradas del blog son mis favoritas personales:
  1. La curva de dragón: el fractal de Parque Jurásico
  2. La sección de oro
  3. Visualizador de siete segmentos 
  4. Pensando en Pi
  5. Código morse

Cinco años impulsando Python

El objetivo original del blog EduPython era promover el uso del lenguaje Python en los primeros cursos del programación de las carreras profesionales del Tecnológico de Monterrey. Lamentablemente pocos campus de esta institución adoptaron nuestra propuesta (solo supe de los casos de Guadalajara, Puebla y Estado de México); los demás continuaron usando los lenguajes que veníamos usando en el pasado (C, C++, C#, Java) o comenzaron a usar herramientas con un mayor enfoque hacia cómputo numérico (MATLAB, Visual Basic for Applications).

Me queda la satisfacción de que al menos en el Campus Estado de México seguimos usando Python desde el año 2011 y hasta la fecha tanto para el curso de Fundamentos de Programación (para alumnos de las carreras de Ingeniero en Sistemas Computacionales, Ingeniero en Sistemas Digitales y Robótica, Licenciado en Animación y Arte Digital y Licenciado en Comunicación y Medios Digitales) como para el curso de Solución de Problemas con Programación (para alumnos de las carreras de Ingeniero en Biotecnología, Ingeniero Civil, Ingeniero en Diseño Automotriz, Ingeniero Industrial y de Sistemas, Ingeniero Mecánico Electricista, Ingeniero en Producción Musical Digital, Ingeniero en Mecatrónica e Ingeniero Químico Administrador).

Usualmente enseño cursos avanzados de la carrera de Ingeniero en Sistemas Computacionales. Sin embargo, el semestre pasado tuve la oportunidad de impartir por primera vez la clase de Solución de Problemas con Programación. Estas son algunas reflexiones que hicieron mis alumnos al concluir el curso:
Este curso me ayudó a desarrollar la habilidad de análisis de los problemas y la implementación de algoritmos para su solución. Creo que Python es un lenguaje con el que se puede aprender a programar de manera sencilla y obteniendo grandes beneficios, porque una vez que comienzas a trabajar con él te das cuenta de que puede resultar realmente útil más allá de los fines didácticos que se le puedan dar. Por esta razón, espero en un futuro seguir aprendiendo más sobre esta herramienta para seguir utilizándola durante mi vida estudiantil y profesional.
— María Fernanda Téllez Ponce, estudiante de la carrera de
Ingeniera Química Administradora.

Sin duda alguna puedo decir que este semestre aprendí más de programación de lo que jamás había aprendido en ningún otro curso de computación, y como estudiante estoy sumamente satisfecho con la experiencia que tuve con Python todo el semestre. Llegamos a niveles de programación de un buen nivel de complejidad que de no haber sido por el lenguaje utilizado, hubiera sido imposible alcanzarlos. Debo admitir que programación es para mí en lo personal la materia más complicada y siempre le he tenido miedo, pero después de este semestre comprendí aún mejor los usos y beneficios que me pueden traer estos conocimientos. Sin duda alguna concuerdo con que Python es la mejor elección como lenguaje para principiantes y como primer contacto con la programación, ya que sí incentiva a los estudiantes a buscar aún más aplicaciones para esta herramienta y despierta nuestro interés en lugar de ahuyentarnos de la materia.
— Bruno González Soria, estudiante de la carrera de
Ingeniero en Biotecnología.

Durante preparatoria tuve la oportunidad de experimentar mi clase de programación con lenguajes como Pascal y C++. Este semestre, al utilizar Python pude enfocarme en el objetivo principal del problema que se deseaba resolver, en vez de invertir tiempo verificando que todas las llaves y puntos y comas fueran utilizados adecuadamente. Así mismo, al ser gratuito pude elaborar con mayor facilidad los ejercicios, ya que los podía hacer desde mi casa sin necesidad de comprar una licencia o quedarme en la escuela para poder realizarlos. Python tiene una sintaxis y una semántica sencilla, lo cual hizo mucho más clara mi comprensión. Justamente, en preparatoria, se me dificultaba mucho programar porque no entendía, debido a la complejidad y las complicaciones que utilizar Pascal y C++ conllevan. El aprender a utilizar como herramienta básica Python, me dio tanto un panorama completamente diferente de lo que es la programación, de la importancia y repercusión que tiene la misma en nuestras vidas diarias, como mayor seguridad y facilidad para poder resolver problemas de una manera mucho más rápida y eficiente. Así mismo, cada error que cometí solo me aseguraba de que una computadora nunca va a hacer lo que nosotros pensamos sino lo que le decimos que haga. Por eso, es de suma importancia aprender a saber decirle qué hacer y cómo hacerlo. Es ahí donde nuestra tarea como ingenieros y/o programadores empieza. Dicha tarea se vuelve complicada porque en la mayoría de las situaciones sucede que, una vez que entendemos cuál es el problema que se busca resolver, no sabemos cómo hacerlo. Esto se debe a que lo más difícil es tener la lógica adecuada para resolverlos. Sin embargo, utilizar Python lo facilitó totalmente y nos brindó una mayor comprensión analítica. Pues lo que en verdad pesa aquí es desarrollar la capacidad para resolver problemas. Necesitamos promover el uso de herramientas que sean parte de la solución, y no parte del problema.
— Daniela Jeannette Ponce Aparicio, estudiante de la carrera de
Ingeniera Industrial y de Sistemas.


Con mis alumnos de uno de mis grupos de
Solución de Problemas con Programación
del semestre enero-mayo del 2015.

No me queda duda de que haber seleccionado el lenguaje Python para estas materias fue una muy buena decisión.

Pasado, presente y futuro de EduPython

La siguiente gráfica muestra el comportamiento de las visitas que ha tenido EduPython desde julio del 2010 hasta la fecha:

Historial de visitas al blog de EduPython.

En los primeros dos mes de arranque de EduPython hubo más de 1,300 visitas, pero después hubo un decaimiento importante. Durante cerca de tres años muy poca gente visitó el blog, menos de 200 visitas al mes en promedio. En julio del 2013 hubo un repunte y comenzó a ser frecuente tener meses con más de mil visitas. A partir de marzo del 2015 el blog ha tenido más de 2,000 visitas al mes y veo muy probable que esta tendencia continúe así debido a que EduPython se encuentra bien posicionado en los buscadores más populares de Internet. Sin haber realizado jamás alguna optimización en motores de búsqueda el blog de EduPython aparece el día hoy (2015-07-07) como primer resultado de Google al realizar cualquiera de las siguientes búsquedas:
Tal como ya lo mencioné, originalmente el objetivo de este blog fue promover el uso de Python como primer lenguaje de programación en la universidad donde trabajo y las entradas de los primeros meses reflejan precisamente esto (por ejemplo: Python como primer lenguaje de programación, ¿Qué sigue después de Python?, Primera capacitación para profesores en la enseñanza del lenguaje Python). Después de un periodo de dos años en los que casi no publiqué nada opté por transformar EduPython en un espacio para presentar tópicos que pudieran ser de interés general a la comunidad de Python de habla hispana (por ejemplo: Cómo programar a tu tortuga, Los números de Fibonacci, ¿Dónde quedó el do-while?). El último año el tema central del blog ha sido computación física mediante el uso de Raspberry Pi y Arduino (por ejemplo: Contador binario, Visualizador de siete segmentos, Midiendo los fríos y calores).

Plataformas para computación física: Arduino Uno y Raspbery Pi 2.

Ahora bien, ¿cuál es el futuro del blog de EduPython? Mi intención es cubrir temas de interés general de nivel un poco más avanzado así como continuar con tópicos relacionados con Arduino y RPi. Me encantaría también tener autores invitados que nos puedan presentar sus experiencias y otros puntos de vista. Considero que hay EduPython todavía para un buen rato.

Y tú, mi amigo lector, ¿te ha dejado algo positivo este blog en los últimos cinco años? ¿Cuál ha sido la entrada del blog que más te ha gustado y/o servido? Te animo a que compartas tus opiniones y experiencias en la sección de comentarios.

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.

3 de abril de 2015

Visualizador de siete segmentos

Un visualizador de siete segmentos, también conocido como SSD por sus siglas en inglés (seven-segment display), es un componente electrónico constituido por una serie de ledes. Cada led es un segmento en forma de una pequeña raya la cual se puede prender y apagar de manera independiente. Los siete segmentos están dispuestos de tal manera que forman el número “8”. Usualmente hay también un octavo segmento que se utiliza para representar el punto decimal.

Visualizador de siete segmentos
o SSD (seven-segment display).

Los visualizadores de siete segmentos sirven para desplegar números decimales en relojes digitales, medidores electrónicos, calculadoras básicas y otros dispositivos electrónicos que requieren mostrar información numérica.

Reloj digital con dígitos de siete segmentos.

En esta entrada discutiremos la manera de programar en Python un SSD conectado a un Arduino.

Ficha técnica del SSD

El proyecto que se presentará más adelante utiliza un SSD con número de parte LTS-4801G, el cual despliega dígitos de 10 mm de altura con segmentos en color verde. La siguiente imagen es una versión esquemática de este componente:

Segmentos y pines de un SSD.

Del esquema se puede observar que cada segmento está identificado por una letra de la A a la G, y P para el segmento correspondiente al punto decimal. El SSD tiene diez pines numerados del 1 al 10. Cada uno de los ocho segmentos está asociado a un pin en particular:
  • Pin 1: Segmento G
  • Pin 2: Segmento F
  • Pin 4: Segmento E
  • Pin 5: Segmento D
  • Pin 6: Segmento P
  • Pin 7: Segmento C
  • Pin 9: Segmento B
  • Pin 10: Segmento A
Los pines 3 y 8 sirven de ánodo común o de cátodo común. Según la descripción de Wikipedia:
En los de tipo de ánodo común, todos los ánodos de los segmentos están unidos internamente a un pin común que debe ser conectado a potencial positivo (valor lógico 1). El encendido de cada segmento individual se realiza aplicando potencial negativo (valor lógico 0) por el pin correspondiente a través de una resistencia que limite el paso de la corriente.

En los de tipo de cátodo común, todos los cátodos de los segmentos están unidos internamente a un pin común que debe ser conectado a potencial negativo (valor lógico 0). El encendido de cada segmento individual se realiza aplicando potencial positivo (valor lógico 1) por el pin correspondiente a través de una resistencia que limite el paso de la corriente.
El SSD para nuestro proyecto es de ánodo común.

Diseño de los dígitos

Cada uno de los siete segmentos puede estar encendido o apagado. Esto quiere decir que un SSD puede tener 27 = 128 estados diferentes (256 si consideramos el segmento del punto decimal). Sin embargo no todos estos estados son de nuestro interés. Para nuestro proyecto deseamos únicamente desplegar los dieciséis dígitos hexadecimales: del 0 al 9 y de la A a la F. La siguiente imagen muestra el diseño convencional de cada uno de estos dígitos. Los segmentos encendidos se muestran en negro, mientras que los apagados están en gris:

Diseño de los dígitos hexadecimales usando siete segmentos.

Hay que notar que los diseños de la b y la d se encuentran en minúsculas ya que no se pueden representar en mayúsculas sin que se confundan con algún otro dígito.

Una forma compacta de representar en Python estos diseños consiste en usar números binarios, en donde un bit 0 representa un segmento apagado y un bit 1 representa un segmento encendido. A cada bit de un total de ocho (numerados del 0 al 7) le asociamos un segmento específico del SSD, tal como se muestra en la siguiente figura:

Correspondencia entre bits y segmentos.

Por ejemplo, al dígito “4” le corresponde el número binario 0b01100110 (0b en Python 3 es el prefijo para las literales numéricas en base binaria) ya que los segmentos A, D, E y P deben estar apagados, mientras que los segmentos B, C, F y G deben estar encendidos.

A partir de la disposición anterior podemos codificar el diseño de todos los dígitos hexadecimales usando números binarios:
Dígito 0:0b11111100
Dígito 1:0b01100000
Dígito 2:0b11011010
Dígito 3:0b11110010
Dígito 4:0b01100110
Dígito 5:0b10110110
Dígito 6:0b10111110
Dígito 7:0b11100000
Dígito 8:0b11111110
Dígito 9:0b11110110
Dígito A:0b11101110
Dígito B:0b00111110
Dígito C:0b10011100
Dígito D:0b01111010
Dígito E:0b10011110
Dígito F:0b10001110
Al momento de escribir el software para desplegar estos diseños será necesario implementar un poco de lógica de manipulación de bits para decodificar estos números binarios.

Armando el proyecto

El proyecto a desarrollar consistirá de un contador hexadecimal conceptualmente similar al contador binario que construimos anteriormente usando la Raspberry Pi. Utilizaremos el SSD para desplegar el valor actual del contador. Tendremos también un botón, y cada vez que lo presionemos se incrementará en uno el contador. El contador estará inicializado en 0. Al llegar al valor máximo (F hexadecimal), se reiniciará nuevamente en 0.

Esta es la lista completa de componentes que vamos a requerir para nuestro proyecto:
  • Arduino Uno.
  • SSD de ánodo común.
  • Protoboard.
  • Botón o pulsador (push button).
  • Ocho resistencias de 330Ω (bandas naranja, naranja, café). 
  • Una resistencia de 10KΩ (bandas café, negro, naranja).
  • Cables conectores.
La siguiente figura, elaborada con Fritzing, muestra la manera de conectar todos los componentes:

Vista de protoboard Fritzing para
el contador hexadecimal.

El pin de 5V del Arduino se conecta al pin 3 o al pin 8 del SSD. Como ya mencionamos, los pines 3 y 8 corresponden al ánodo común del visualizador. Si en su lugar usáramos un SSD de cátodo común, entonces conectaríamos el pin 3 o el pin 8 del componente a un pin de tierra (GND) del Arduino.

Cada pin asociado a un segmento del SSD 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 del SSD.
  • El pin digital 3 del Arduino se conecta al pin 2 del SSD.
  • El pin digital 4 del Arduino se conecta al pin 4 del SSD.
  • El pin digital 5 del Arduino se conecta al pin 5 del SSD.
  • El pin digital 6 del Arduino se conecta al pin 6 del SSD.
  • El pin digital 7 del Arduino se conecta al pin 7 del SSD.
  • El pin digital 8 del Arduino se conecta al pin 9 del SSD.
  • El pin digital 9 del Arduino se conecta al pin 10 del SSD.
Es importante no utilizar los pines 0 y 1 del Arduino, ya que éstos son usados para la recepción y transmisión de datos seriales. Nuestros programas escritos en Python se comunican con el Arduino usando dichos pines. 

El botón y la resistencia de 10KΩ se conectan tal como se explicó en “Cómo programar a tu Arduino”. La única diferencia para nuestro proyecto es que la patita superior del botón se conecta al pin 10 del Arduino.

Una vez armado nuestro proyecto podemos proceder a programar su funcionalidad.

El software

En términos generales, debemos seguir las mismas indicaciones que se explicaron 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.

Para almacenar los números binarios que representan los diseños de los dieciséis dígitos hexadecimales definimos una lista llamada patron:
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
]
Reutilizaremos la función binario() de la entrada “Contador binario” para convertir los números anteriores a una lista de ocho bits con el fin de simplificar su posterior procesamiento:
def binario(n):
    resultado = []
    for i in range(8):
        resultado.append(n & 1)
        n >>= 1
    return resultado
Dado que deseamos usar ocho pines digitales de salida (los pines 2 al 9) en nuestro Arduino, debemos activarlos para tal fin. Lo más conveniente es colocar los objetos que representan los pines en una lista:
salida = [
    placa.get_pin('d:6:o'), # Seg_P/Ard_6/SSD_6
    placa.get_pin('d:2:o'), # Seg_G/Ard_2/SSD_1
    placa.get_pin('d:3:o'), # Seg_F/Ard_3/SSD_2
    placa.get_pin('d:4:o'), # Seg_E/Ard_4/SSD_4
    placa.get_pin('d:5:o'), # Seg_D/Ard_5/SSD_5
    placa.get_pin('d:7:o'), # Seg_C/Ard_7/SSD_7
    placa.get_pin('d:8:o'), # Seg_B/Ard_8/SSD_9
    placa.get_pin('d:9:o')  # Seg_A/Ard_9/SSD_10
]
El argumento para el método get_pin() establece que el pin del Arduino con el número indicado es digital (d) y de salida out (o). El comentario al final de las líneas identifica a cada objeto con su correspondiente segmento, número de pin de Arduino y número de pin del SSD.

Cada elemento de la lista salida está asociado por su posición a un bit particular de un número binario de ocho bits conforme a lo descrito en la sección “Diseño de los dígitos” de arriba. Por ejemplo, recordemos que el dígito “4” tiene codificado su diseño en el número binario 0b01100110. Esto quiere decir que sus bits 0, 3, 4 y 7 valen cero, mientras que sus bits 1, 2, 5 y 6 valen uno. Por tanto, para desplegar el dígito “4” en el SSD necesitamos mandar:
  • Una señal de apagado a los elementos salida[0], salida[3], salida[4] y salida[7].
  • Una señal de encendido a los elementos salida[1], salida[2], salida[5] y salida[6].
La siguiente función logra el efecto deseado:
def despliega_digito(digito):
    d = patron[digito] if 0 <= digito <= 15 else 0b00000000
    for pin, bit in zip(salida, binario(d)):        
        pin.write(not bit)
A partir de la lista patron obtenemos d, que es el número binario correspondiente al diseño de digito, siempre y cuando este último tenga un valor entre el 0 y el 15. De lo contrario d queda con 0b00000000, que implica apagar todos los segmentos del SSD. Posteriormente, usando un for junto con la función zip(), recorremos simultáneamente la lista salida (que tiene los ocho objetos que representan nuestros pines de salida) y la secuencia resultante de convertir d a la lista de ocho bits devuelta por la función binario(). En cada iteración tomamos el valor de bit para decidir si encendemos o apagamos el segmento correspondiente al objeto pin de la iteración en curso.

Dado que estamos usando un SSD de ánodo común, para encender un segmento se debe enviar un 0 lógico (potencial negativo) a través del pin correspondiente, o un 1 lógico (potencial positivo) en caso de que se quiera apagar. Estos valores son justamente los opuestos a los que estamos manejando en nuestros diseños representados como números binarios. Por esta razón al argumento del método write() se le aplica el operador not, precisamente para invertir su valor. Dicho operador se debe omitir si el SSD fuera de cátodo común.

El último pin del Arduino que necesitamos activar es el 10. Este es un pin digital (d) de entrada in (i):
entrada = placa.get_pin('d:10:i')
Finalmente, el código principal de nuestro programa es un ciclo infinito que se encarga de detectar cuando el usuario presiona el botón. Cuando eso ocurre, el contador se incrementa y se actualiza el valor desplegado en el SSD. Pero antes de ejecutar el ciclo, el contador se inicializa en cero y se despliega dicho valor en el visualizador.
contador = 0
despliega_digito(contador)
while True:
    if entrada.read():
        contador = (contador + 1) % 16
        despliega_digito(contador)
        placa.pass_time(0.2)
Así queda el programa completo después de integrar todo lo anterior:
#!/usr/bin/env python3
# Archivo: ssd.py

import pyfirmata

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

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

entrada = placa.get_pin('d:10:i')
entrada.enable_reporting()

salida = [
    placa.get_pin('d:6:o'), # Seg_P/Ard_6/SSD_6
    placa.get_pin('d:2:o'), # Seg_G/Ard_2/SSD_1
    placa.get_pin('d:3:o'), # Seg_F/Ard_3/SSD_2
    placa.get_pin('d:4:o'), # Seg_E/Ard_4/SSD_4
    placa.get_pin('d:5:o'), # Seg_D/Ard_5/SSD_5
    placa.get_pin('d:7:o'), # Seg_C/Ard_7/SSD_7
    placa.get_pin('d:8:o'), # Seg_B/Ard_8/SSD_9
    placa.get_pin('d:9:o')  # Seg_A/Ard_9/SSD_10
]

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 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 despliega_digito(digito):
    """Despliega en el SSD el digito indicado.

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

try:
    contador = 0
    despliega_digito(contador)
    while True:
        if entrada.read():
            contador = (contador + 1) % 16
            despliega_digito(contador)
            placa.pass_time(0.2)

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

finally:
    despliega_digito(-1)
    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 ssd.py:
python3 ssd.py
La siguiente foto muestra cómo se ve el proyecto después de correr el programa y presionar quince veces el botón:


El programa corre hasta que presionemos Ctrl-C para terminar.