Mostrando las entradas con la etiqueta Computación física. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Computación física. Mostrar todas las entradas

31 de julio de 2017

pyboard: Python en un microchip

A finales del año 2013, el físico y programador australiano Damien George arrancó desde el Reino Unido una campaña en Kickstarter para recaudar fondos con el fin de desarrollar una plataforma de hardware libre basada en una placa con un microcontrolador que soportara directamente el lenguaje Python. Esencialmente quería diseñar algo parecido a un Arduino, pero que pudiera programarse de manera eficiente en Python en lugar de los lenguajes comúnmente disponibles para programar microcontroladores (C/C++ y ensamblador). La meta inicial de George era recaudar 15,000 GBP (aproximadamente equivalente a 25,000 USD de aquel entonces) para elaborar su proyecto, pero al finalizar la campaña había recaudado 97,803 GBP (más de 160,000 USD). Y así fue como surgió el pyboard.

Damien P. George, creador del pyboard.
Fuente: dpgeorge.net

El software

Central en el proyecto del pyboard fue el desarrollo de MicroPython, un subconjunto de Python 3 elaborado también por Damien George. Al re-escribir este intérprete desde cero, George logró que fuera suficientemente pequeño para poder caber y correr en un microcontrolador.


MicroPython utiliza muy poca memoria RAM e incluye una pequeña fracción de la biblioteca estándar de Python. Aún así, MicroPython soporta características avanzadas como son: enteros de tamaño arbitrario, cerraduras léxicas (closures), listas por comprensión, generadores, manejo de excepciones e incluseo un shell interactivo. Al correr directamente sobre los fierros, MicroPython funge efectivamente como el sistema operativo del pyboard.

Teóricamente, los requisitos mínimos de memoria para que un sistema pueda soportar MicroPython son: 128 kibibytes de ROM y 8 kibibytes de RAM. Gracias a eso ha sido migrado exitosamente a otras plataformas. Además del pyboard, el sitio oficial ofrece el firmware requerido para correr MicroPython en los microchips WiPy, ESP8266, ESP32, Espruino Pico, STM32F4, NUCLEO-L476RG y micro:bit.

El hardware

La placa del pyboard contiene un microcontrolador STM32F405RG de STMicroelectronics con las siguientes características:
  • Procesador RISC de 32 bits ARM Cortex-M4F.
  • Una velocidad de reloj de 168 MHz.
  • Registros de 32 bits: 
    • 16 para valores enteros.
    • 32 para valores de punto flotante de precisión sencilla.
  • 192 kibibytes de memoria RAM para almacenar datos volátiles.
  • 1024 kibibytes de memoria flash ROM para almacenar el código del programa.
  • 13 temporizadores independientes.
  • Soporte de protocolos UART, I2C, SPI y CAN para comunicación con periféricos.
  • Hardware generador de números aleatorios (TRNG, true random number generator).

El pyboard v1.1.
Fuente: MicroPython Store

La placa del pyboard tiene un tamaño de 4.1 cm × 3.2 cm × 0.2 cm y cuenta adicionalmente con los siguientes elementos:
  • Conector micro-USB: sirve como fuente de alimentación y también permite establecer comunicación serial con una computadora anfitriona.
  • Ranura para tarjeta microSD de capacidad estándar (SDSC) y alta capacidad (SDHC).
  • Acelerómetro de tres ejes.
  • Reloj en tiempo real (oscilador de cristal) a 32 kHz.
  • Dos botones: RST (hard reset) y USR (funcionalidad definida por el usuario).
  • Cuatro ledes (colores: rojo, verde, amarillo y azul).
  • 30 pines GPIO (general-purpose input/output).  

Comparando pyboard con Arduino

La principal ventaja de un pyboard sobre un Arduino tiene que ver con la forma en que soportan Python. En diversas entradas de este blog hemos programado el Arduino usando Python, pero no debemos perder de vista que el Arduino debe estar siempre conectado a una computadora anfitriona a través de un cable USB. Esto es así porque el programa de Python corre en el sistema anfitrión y no en el Arduino. El Arduino funge como esclavo que corre un ejecutable (típicamente escrito en C/C++) que implementa el protocolo de Firmata y que le permite establecer una comunicación serial en dos sentidos con el programa que lo controla y que corre en la PC anfitriona. El pyboard, por otro lado, puede correr programas en Python sin necesidad de estar conectado en todo momento a una computadora anfitriona. El pyboard tiene instalado localmente el intérprete de MicroPython y solo basta transferir (vía la conexión USB) a su memoria flash el programa *.py que deseamos correr. Una vez hecho esto, podemos desconectar el pyboard del sistema anfitrión y solo requerimos proveerle a la placa un suministro eléctrico alternativo, por ejemplo un banco de energía (como los que se usan para recargar las pilas de los teléfonos celulares). A partir de ese momento nuestro programa en Python se ejecuta automáticamente al encender o reiniciar el pyboard.

Un pyboard (en su estuche protector)
conectado a un banco de energía.

Cuando el pyboard está conectado a la computadora anfitriona podemos llevar a cabo una sesión interactiva con el shell de MicroPython corriendo directamente sobre la placa. Para lograr lo anterior tenemos que abrir una ventana de línea de comando en el sistema anfitrión y luego correr algún emulador de terminal tonta (por ejemplo screen o picocom) que permita conectarse al pyboard. Cuando corremos el shell podemos jugar y experimentar directamente con todas las facilidades provistas por la placa. El shell también puede ser bastante útil durante el proceso de depuración de nuestros programas. No existe una funcionalidad similar a ésta cuando se programa el Arduino usando C/C++.

El shell de MicroPython corriendo sobre el
pyboard y siendo visualizado en una
ventana de línea de comando en la
computadora anfitriona.

Otra diferencia entre el Arduino y el pyboard son las herramientas de desarrollo que se utilizan. Arduino necesita su propio IDE (ambiente de desarrollo integrado) para editar archivos, compilarlos y transferir los ejecutables a la placa. Para usar el pyboard no necesitamos ningún software especial, salvo el editor de nuestra preferencia. Resulta trivial la transferencia de archivos gracias a que la placa se comporta como una memoria USB al momento de conectarla a la máquina anfitriona, la cual puede estar corriendo Windows, macOS o Linux.

El pyboard tiene otra ventaja sobre Arduino que puede ser benéfica particularmente para principiantes. Al incorporar de fábrica cuatro ledes, un botón y un acelerómetro, es posible elaborar diversos proyectos interesantes en el pyboard sin requerir hardware adicional. Es posible empezar a hacer experimentos sencillos de computación física sin tener la necesidad de conseguir y conectar actuadores, sensores, resistencias y cables a un protoboard.

Caos resultado de un exceso de componentes electrónicos.
Fuente:
Harvard Business Review

Dado que el hardware del pyboard es de mayor capacidad y más sofisticado que la mayoría de los modelos existentes de Arduino, resulta también ser un tanto más costoso. Irónicamente, puede resultar que un programa escrito en C/C++ para Arduino se ejecute más rápido que su equivalente en Python para el pyboard. Esto se debe a que en estas plataformas el código de C/C++ se compila mientras que el de Python se interpreta. Como es bien sabido, los programas interpretados son usualmente más lentos y requieren más memoria que los compilados debido a la necesidad de traducir el código fuente mientras se corre. En la práctica, el impacto en los tiempos de ejecución normalmente no repercute de forma significativa en la mayoría de los programas escritos para el pyboard. Y si esto llegara a ser un problema, existe la opción de escribir porciones limitadas de código en C/C++/ensamblador que pueden ser invocadas desde el código de MicroPython para optimizar los cuellos de botella y obtener así un mejor desempeño pero sin reducir sustancialmente la productividad que se consigue al programar primordialmente en Python.

Comparando pyboard con Raspberry Pi

La Raspberry Pi (RPi) y el pyboard pertenecen a dos categorías diferentes de placas electrónicas. La RPi es una computadora monoplaca (single-board computer) que para fines prácticos es casi una computadora completa. Tiene su propio sistema operativo (usualmente alguna distribución de Linux) y para usarla necesitamos (generalmente) conectarle mouse, teclado y monitor. El modelo 3 de la RPi ya cuenta incluso con Bluetooth y Wi-Fi. Por otro lado, el pyboard (al igual que el Arduino) es un microcontrolador monoplaca (single-board microcontroller). Esto significa que un pyboard es más sencillo y tiene típicamente un propósito más restringido que una RPi. Conviene mencionar que una RPi puede servir de sistema anfitrión para programar a un pyboard.

Por sus características de hardware, una RPi modelo 3 resulta una buena opción para realizar proyectos de IoT (internet de las cosas). En contraste, el pyboard no cuenta con un esquema de comunicación inalámbrica, por lo que no resulta adecuado para este fin.

Al igual que el pyboard y el Arduino (y a diferencia de una PC común y corriente), la RPi cuenta con múltiples pines GPIO (entrada/salida de propósito general) que podemos usar para conectar y controlar diversos componentes, tales como actuadores y sensores. Al momento de programar la RPi podemos usar cualquier lenguaje soportado por su sistema operativo, por ejemplo C, C++, Java, Ruby, Python, Perl, JavaScript, etc. Como ya lo hemos mencionado varias veces, el pyboard está diseñado para programarse en Python.

Fuente: Day Break Tecnologies

Comentarios finales

El pyboard es una opción interesante en el mundo de la computación física. En primera instancia se parece al Arduino pero con el atractivo de que se programa directamente en Python. Lamentablemente es aún poco conocido, y por lo mismo existe menos documentación disponible (comparado con otras plataformas) y resulta complicado adquirirlo en países como México. En Estados Unidos se puede conseguir a través de Amazon.com o Adafruit. La empresa Adafruit realiza envíos de componentes electrónicos a México, pero hay que considerar los impuestos aduanales que se deben pagar al momento en que un producto de este tipo llega al país. En Europa y otras partes del mundo los interesados pueden comprar el pyboard en la tienda oficial de MicroPython. El costo de un pyboard v1.1 varía bastante según el proveedor, pero usualmente comienza en los 35 USD.

3 de julio de 2016

Midiendo distancias: el sensor ultrasónico

En esta entrada del blog veremos como usar la Raspberry Pi (RPi) junto con un sensor ultrasónico para medir la distancia a la que se encuentra algún objeto posicionado en frente de nuestro dispositivo.

El sensor ultrasónico que utilizaremos es el HC-SR04:

Sensor de distancia ultrasónico HC-SR04.

Este sensor es económico (tiene un precio comercial que oscila entre dos y cuatro dólares estadounidenses) pero bastante potente. Permite calcular distancias en un rango de 2 a 400 cm. y resulta relativamente fácil programarlo.

Un sensor ultrasónico funciona de manera similar a la capacidad de ecolocación que tienen ciertos animales tales como los murciélagos y delfines: el animal emite un sonido que rebota al encontrar un obstáculo; la distancia se estima a partir del tiempo que tarda en regresar el eco de la señal. El sonar que utilizan los submarinos para navegar en el océano está basado también en este mismo principio.

Ecolocación: el murciélago A genera un echo E que rebota
en la presa B generando una onda reflejada R. Con eso el
murciélago puede determinar la distancia d a su presa.
Imagen recuperada de: Wikimedia Commons.
 

Se dice que un sonido es ultrasónico cuando su onda tiene una frecuencia que se halla por arriba del espectro audible por el oído del ser humano, generalmente arriba de los veinte mil hertz.

El hardware

Además de un sensor HC-SR04 y una RPi (de cualquier generación) adecuadamente equipada y configurada, requeriremos los siguientes componentes de hardware:
  • Un protoboard
  • Dos resistencias R1 y R2 (ver discusión posterior)
  • Cables conectores

Vista esquemática del sensor HC-SR04.

El sensor HC-SR04 cuenta con cuatro pines:
  • VCC: Alimentación de 5 voltios
  • TRIG: Trigger o activador (entrada) del sensor
  • ECHO: Eco (salida) del sensor
  • GND: Tierra
Es importante mencionar que la señal de salida ECHO del HC-SR04 es de 5V. Esto hace que el sensor sea compatible con Arduino y otras plataformas de microcontroladores. Sin embargo, para la RPi los pines GPIO de entrada son de 3.3V. Si se envía una señal de 5V a un puerto desprotegido de entrada de 3.3V, éste puede resultar dañado, y eso es algo que definitivamente queremos evitar. Necesitaremos usar un circuito divisor resistivo para disminuir el voltaje de salida del sensor y así permitir que la RPi reciba la señal correspondiente sin problemas.

Un divisor resistivo consiste de dos resistencias (R1 y R2) en serie conectadas a un voltaje de entrada (Vin), el cual debe ser reducido a un voltaje de salida (Vout). En nuestro circuito, Vin corresponderá al pin ECHO, el cual requiere disminuir de 5V al Vout que necesitamos de 3.3V. El siguiente esquema muestra la disposición que deben tener las resistencias:


Un divisor resistivo se define a partir de la siguiente fórmula:


Sabemos que Vin = 5V y Vout = 3.3V. Por tanto lo que nos interesa calcular es:


Despejando tenemos:


Si conocemos el valor de la resistencia R2 podemos calcular directamente la resistencia R1. Por ejemplo, si R2 = 2kΩ, R1 tendría que ser igual a 1030.303Ω o algún otro valor cercano (por ejemplo 1kΩ).

La siguiente figura, elaborada en Fritzing, muestra la manera de conectar todos nuestros componentes:

Vista de protoboard Fritzing de un sensor HC-SR04 conectado a la RPi.

Las conexiones son las siguientes:
  • El pin VCC del sensor se conecta a cualquier pin de 5V de la RPi.
  • El pin TRIG del sensor se conecta al pin GPIO 23 de la RPi.
  • El pin GPIO 24 de la RPi se conecta a una pista disponible del protoboard. Un extremo de la resistencia R2 se conecta a esta pista, y el otro extremo se conecta a tierra (cualquiera de los pines GND de la RPi). Un extremo de la resistencia R1 se conecta también a esta pista, y el otro extremo se conecta al pin ECHO del sensor.
  • El pin GND del sensor se conecta a tierra (cualquiera de los pines GND de la RPi).

Vista física del sensor ultrasónico de distancias
colocado en el protoboard con sus respectivos
cables conectores.

El software

Para controlar el sensor y realizar las mediciones debemos efectuar lo siguiente:
  1. Poner TRIG en bajo durante dos segundos con el fin de estabilizarlo.
  2. Poner TRIG en alto por 10µs (diez microsegundos) y ponerlo inmediatamente después en bajo. En este momento el sensor envía 8 pulsos ultrasónicos de 40kHz y coloca ECHO en alto. Es necesario detectar cuando ocurre esto último para comenzar a medir el tiempo en ese preciso instante.
  3. ECHO se mantiene en alto hasta que reciba el eco reflejado por algún obstáculo. En ese momento ECHO se pone en bajo y significa que hay que terminar la medición del tiempo.
  4. A partir de la medición de tiempo obtenida ya podemos calcular la distancia como se discute a continuación.
Sabemos que la velocidad v es igual a distancia d entre tiempo t:


Despejando la distancia d, tenemos que ésta es igual a la velocidad v multiplicada por el tiempo t:


La velocidad que usaremos aquí es la del sonido, que es de 34,300 centímetros por segundo. Al final necesitamos dividir a la mitad el valor resultante debido a que nuestra medición corresponde al tiempo que tarda el pulso ultrasónico en llegar al obstáculo y regresar nuevamente al sensor.


En esta última fórmula, el tiempo t debe estar en segundos y la distancia resultante d en centímetros.

Con todo lo anterior tomado en cuenta, ya estamos en condiciones de escribir un programa en Python que controle un sensor ultrasónico HC-SR04. El código debe comenzar con las inicializaciones y configuraciones necesarias, similares a las que se explican detalladamente en la entrada titulada El LED parpadeante.

El programa completo en Python 2.7 se muestra a continuación:
# coding: utf-8

# Archivo: ultrasonico.py

# Importar las funciones necesarias de los módulos RPi,
# y time.
import RPi.GPIO as GPIO
import time

# Pin GPIO donde está conectado el activador (entrada) del
# sensor HC-SR04.
TRIG = 23

# Pin GPIO donde está conectado el eco (salida) del sensor
# HC-SR04.
ECHO = 24

# Indicar que se usa el esquema de numeración de pines
# de BCM (Broadcom SOC channel), es decir los números de
# pines GPIO (General-Purpose Input/Output).
GPIO.setmode(GPIO.BCM)

# Establecer que TRIG es un canal de salida.
GPIO.setup(TRIG, GPIO.OUT)

# Establecer que ECHO es un canal de entrada.
GPIO.setup(ECHO, GPIO.IN)

print "Medición de distancias en progreso"

try:
    # Ciclo infinito.
    # Para terminar el programa se debe presionar Ctrl-C.
    while True:

        # Apagar el pin activador y permitir un par de
        # segundos para que se estabilice.
        GPIO.output(TRIG, GPIO.LOW)
        print "Esperando a que el sensor se estabilice"
        time.sleep(2)

        # Prender el pin activador por 10 microsegundos
        # y después volverlo a apagar.
        GPIO.output(TRIG, GPIO.HIGH)
        time.sleep(0.00001)
        GPIO.output(TRIG, GPIO.LOW)

        # En este momento el sensor envía 8 pulsos
        # ultrasónicos de 40kHz y coloca su salida ECHO
        # en HIGH. Se debe detectar este evento e iniciar
        # la medición del tiempo.
        print "Iniciando eco"
        while True:
            pulso_inicio = time.time()
            if GPIO.input(ECHO) == GPIO.HIGH:
                break

        # La salida ECHO se mantendrá en HIGH hasta recibir
        # el eco reflejado por el obstáculo. En ese momento
        # el sensor pondrá ECHO en LOW y se debe terminar
        # la medición del tiempo.
        while True:
            pulso_fin = time.time()
            if GPIO.input(ECHO) == GPIO.LOW:
                break

        # La medición del tiempo es en segundos.
        duracion = pulso_fin - pulso_inicio

        # Calcular la distancia usando la velocidad del
        # sonido y considerando que la duración incluye
        # la ida y vuelta.
        distancia = (34300 * duracion) / 2

        # Imprimir resultado.
        print "Distancia: %.2f cm" % distancia

finally:
    # Reiniciar todos los canales de GPIO.
    GPIO.cleanup()
En una terminal necesitamos correr el siguiente comando para ejecutar nuestro programa:
sudo python ultrasonico.py
Mientras el programa se esté ejecutando se puede acercar y alejar algún objeto del sensor para ver si el programa funciona correctamente. Podemos usar una regla o cinta métrica para verificar los resultados. La salida del programa debe verse algo así:
Medición de distancias en progreso
Esperando a que el sensor se estabilice
Iniciando eco
Distancia: 79.30 cm
Esperando a que el sensor se estabilice
Iniciando eco
Distancia: 78.91 cm
Esperando a que el sensor se estabilice
Iniciando eco
Distancia: 79.70 cm
Esperando a que el sensor se estabilice
Iniciando eco
Distancia: 12.73 cm
Esperando a que el sensor se estabilice
Iniciando eco
Distancia: 12.49 cm
Esperando a que el sensor se estabilice
Iniciando eco
Distancia: 12.57 cm
Esperando a que el sensor se estabilice
Iniciando eco
Distancia: 78.82 cm
Esperando a que el sensor se estabilice
Es necesario presionar Ctrl-C para terminar el programa.

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.