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.