5 de junio de 2014

Contador binario

En esta entrada del blog de EduPython elaboraremos un nuevo proyecto para la Raspberry Pi (RPi). En esta ocasión vamos a hacer un contador binario. Utilizaremos cuatro LEDs para representar cuatro bits que nos permitirán contar del 0000 al 1111 en binario (0 al 15 en decimal). Tendremos también un botón, y cada vez que lo presionemos se incrementará en uno el contador.

La siguiente tabla muestra los valores que pueden tener los cuatro bits junto con su valor correspondiente en decimal:

Valores decimales y
sus equivalentes en binario.

Comencemos por escribir una función en Python que dado un entero n del 0 al 15, devuelva una lista con cuatro enteros que correspondan a los cuatro bits de la representación binaria de n:
def binario(n):
    resultado = []
    for i in range(4):
        resultado.append(n & 1)
        n >>= 1
    return resultado
Internamente en la computadora, los números están almacenados en binario, así que podemos inspeccionar los bits individuales de n para crear la lista con los valores que necesitamos. Los operadores & y >>= del código de arriba sirven para tal efecto en dos pasos dentro del ciclo for, el cual se repite un total de cuatro veces (una vez por cada bit que nos interesa):
  • La expresión n & 1 realiza un and (conjunción) a nivel de bits entre n y 1, efectivamente obteniendo el cero o el uno contenido en el bit 0 (el bit que está más a la derecha) de la representación binaria de n, y almacenando ese valor al final de la lista resultado usando el método append()
  • La expresión n >>= 1 desplaza todos los bits de n una posición hacia la derecha. Es decir el bit 1 original se convierte en el nuevo bit 0, el bit 2 original se convierte en el nuevo bit 1, y así sucesivamente. El bit 0 original se pierde en este proceso, pero no importa, ya que fue inspeccionado y almacenado en el paso anterior.
Como ejemplo, si invocamos la función binario() con argumento 1, ésta nos devuelve la lista [1, 0, 0, 0]. La lista resultante pudiera dar la impresión de estar invertida. Sin embargo, por simplicidad nos conviene que el bit 0 se encuentre en la primera posición de la lista, el bit 1 en la segunda, y así sucesivamente.

Vale la pena notar que la función binario() solo considera los cuatro bits menos significativos (los bits que están más a la derecha) de su entrada n. Esto significa, en términos prácticos, que la función devuelve una lista con la representación en binario de n módulo 16. Lo anterior es útil para saber interpretar el resultado para los casos en los que n esté fuera del rango del 0 al 15.

El hardware

Además de la RPi, para este proyecto requerimos los siguientes componentes:
  • Un protoboard.
  • Un botón o pulsador (push button).
  • Cuatro LEDs (diodos emisores de luz).
  • Cuatro resistencias de 330Ω (bandas naranja, naranja, café).
  • Siete cables conectores.
La siguiente figura, elaborada con Fritzing, muestra la manera de conectar los componentes:

Vista de protoboard Fritzing para el contador binario.

Usando un cable conectamos un pin de tierra (GND) de la RPi a uno de los buses para alimentación negativa (línea azul) del protoboard. El botón se coloca de tal forma que un par de patitas quede a la izquierda y el otro par a la derecha del canal central del protoboard. Conectamos una de las patitas superiores del botón a tierra (bus negativo), y una de las patitas inferiores la conectamos al pin 22 de la RPi. Colocamos los cuatro LEDs en el protoboard, en donde el cátodo (patita corta) y el ánodo (patita larga) de cada LED debe estar en su propia pista. Conectamos el cátodo de cada uno de los LEDs a una resistencia de 330Ω. El otro extremo de cada resistencia lo conectamos a tierra (bus negativo). Ahora conectamos el ánodo de cada LED a uno de los pines de la RPi:
  • Pin 18 para el LED correspondiente al bit 0 (LED inferior de la figura).
  • Pin 23 para el LED correspondiente al bit 1.
  • Pin 24 para el LED correspondiente al bit 2.
  • Pin 25 para el LED correspondiente al bit 3 (LED superior de la figura).
Con nuestros componentes ya en su lugar, estamos listos para escribir lo que falta de nuestro programa.

El software

Para controlar los LEDs, requerimos básicamente hacer lo mismo que hicimos en la entrada del LED parpadeante. Dado que son cuatro LEDs a controlar en lugar de uno, usamos una lista con los respectivos números de pin de salida. El siguiente código se encarga de establecer a los pines 18, 23, 24 y 25 de la RPi como pines de salida y además se asegura de que inicialmente se encuentren apagados:
PIN_LEDS = [18, 23, 24, 25]
for i in PIN_LEDS:
    GPIO.setup(i, GPIO.OUT, initial=GPIO.LOW)
En la RPi usaremos el pin 22 para inspeccionar el estado de nuestro botón. Esto quiere decir que tenemos que configurar dicho pin como un pin de entrada para poder leer una señal LOW (0 voltios) o HIGH (3.3 voltios), según el estado del botón.

De acuerdo a la manera en que conectamos nuestros componentes, el pin 22 recibe la señal de tierra (0 voltios) solo cuando se cierra el circuito (el botón se encuentra presionado). Pero, ¿qué pasa cuando no es así? En ese caso se dice que el pin está “flotando”, lo que puede producir resultados inesperados. Para evitar este problema podemos utilizar una resistencia pull-up, con lo que efectivamente proveemos de un valor por omisión al pin cuando el circuito no está cerrado (el botón no está presionado). Lo anterior se puede lograr vía hardware (colocando una resistencia de 10KΩ entre el pin de entrada y una alimentación de 3.3 voltios) o por software configurando el pin para que utilice la resistencia pull-up interna provista por la misma RPi. El siguiente código se encarga de este último caso:
PIN_BOTON = 22
GPIO.setup(PIN_BOTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
Con esta configuración podemos usar la función input() indicando el número de pin de entrada como argumento. En este contexto, la función devuelve LOW (0) para indicar que el botón está presionado, o HIGH (1) en caso contrario. Este comportamiento es inverso a lo que alguien podría inicialmente suponer.

El siguiente ciclo infinito verifica en cada iteración si el botón está siendo presionado en ese preciso instante. En caso afirmativo, incrementa el contador y usa su representación binaria para prender o apagar los LEDs correspondientes:
contador = 0

while True:
    if GPIO.input(PIN_BOTON) == GPIO.LOW:
        contador = (contador + 1) % 16
        for pin, bit in zip(PIN_LEDS, binario(contador)):
            GPIO.output(pin, bit)
        time.sleep(0.2)
Dentro del cuerpo del if la instrucción:
contador = (contador + 1) % 16
se utiliza para incrementar en uno a la variable contador. La operación de módulo (%) 16 sirve para reiniciar contador a cero una vez que haya llegado a su máximo valor (15). El siguiente código es equivalente, aunque menos compacto:
contador += 1
if contador == 16:
    contador = 0
La función zip() que se usa dentro de la instrucción for permite iterar simultáneamente sobre dos o más colecciones. Por ejemplo, si PIN_LEDS es [18, 23, 24, 25] y el resultado de binario(1) es [1, 0, 0, 0], entonces la función zip() hace que las variables pin y bit sean 18 y 1 en la primera iteración del for, 23 y 0 en la segunda, y así sucesivamente.

Al final del cuerpo del if usamos la función sleep() para hacer una breve pausa antes de intentar leer nuevamente el estado del botón. De no hacerlo podríamos incrementar más de una vez a contador debido al tiempo que transcurre entre que presionamos el botón y el momento en que lo soltamos.

El programa completo se muestra a continuación:
# Archivo: contador_binario.py

import RPi.GPIO as GPIO
import time

# Usar el esquema de numeracion de pines de Broadcom SoC.
GPIO.setmode(GPIO.BCM)

# Inicializar pines de salida.
PIN_LEDS = [18, 23, 24, 25]
for i in PIN_LEDS:
    GPIO.setup(i, GPIO.OUT, initial=GPIO.LOW)

# Inicializar pin de entrada.
PIN_BOTON = 22
GPIO.setup(PIN_BOTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)

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

    La lista resultante es de la forma:

       [bit0, bit1, bit2, bit3]

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

contador = 0
try:
    while True:
        if GPIO.input(PIN_BOTON) == GPIO.LOW:
            contador = (contador + 1) % 16
            for pin, bit in zip(PIN_LEDS, binario(contador)):
                GPIO.output(pin, bit)
            time.sleep(0.2)
finally:
    GPIO.cleanup()

Desde una terminal de la RPi, debemos ejecutar el siguiente comando para correr el programa:
sudo python contador_binario.py
La siguiente foto muestra cómo se ve nuestro proyecto después de correr el programa y presionar once veces el botón:

Contador binario mostrando
1011 binario (11 decimal).

4 comentarios:

  1. hermano!!! que tuto!! super completo.. deberias hacer mas sobre el manejo de los GPIO de la raspberry, se nota el taento.

    ResponderBorrar
    Respuestas
    1. Muchas gracias. Espero escribir más entradas sobre la Raspberry Pi próximamante.

      Borrar
  2. el código no funciona, no encuentro el error.

    ResponderBorrar
  3. Ayuda con el codigo, no funciona

    ResponderBorrar