2 de julio de 2014

¿Dónde quedó el switch-case?

Hace tiempo escribí sobre la manera de darle la vuelta a la ausencia de los ciclos do-while en Python. Dicha entrada se convirtió rápidamente en una de las más visitadas del blog de EduPython. Debido a ello, en esta ocasión comentaré sobre la otra instrucción que mucha gente echa de menos en Python: la condicional switch-case de la familia de lenguajes basados en C (C++, C#, Java, JavaScript, etc.). Otros lenguajes como Pascal, Ada, Perl, Ruby y diversos dialectos de Lisp cuentan también con alguna instrucción parecida. Sin embargo Python carece de esta instrucción, que de forma más general se le puede llamar “ramificación condicional con múltiples caminos”.

El switch-case es una ramificación condicional
con múltiples caminos.

Una instrucción switch-case permite seleccionar, por medio de una expresión, el siguiente bloque de instrucciones a ejecutar de entre varios posibles. Veamos un ejemplo del switch-case en JavaScript:
// Código de JavaScript
function imprimeRangoCarta(n) {
    switch (n) {
        case 1:
            console.log('As');
            break;
        case 2: case 3: case 4: case 5:
        case 6: case 7: case 8: case 9:
        case 10:
            console.log(n);
            break;
        case 11:
            console.log('Jota');
            break;
        case 12:
            console.log('Reina');
            break;
        case 13:
            console.log('Rey');
            break;
        default:
            console.log('Inválido');
            break
    }
}
La intención de esta función es imprimir en la consola el rango de una carta de una baraja a partir de su valor numérico n, el cual se recibe como parámetro. Si n vale 1, 11, 12 o 13 la función imprime “As”, “Jota”, “Reina” o “Rey”, respectivamente; imprime el valor de n si dicha variable tiene un valor entre 2 y 10. Por último, imprime “Inválido” si n tiene algún valor diferente a los ya mencionados.

Las instrucciones break se requieren para concluir el switch. Si se llega a omitir un break, el programa continúa ejecutando las instrucciones asociadas al case (o default) que continúa inmediatamente; normalmente esto no es lo que se quiere.

La forma más directa de traducir nuestro código de JavaScript a Python es usando una secuencia de instrucciones if-elif-else:
# Código de Python 3
def imprime_rango_carta(n):
    if n == 1:
        print('As')
    elif 2 <= n <= 10:
        print(n)
    elif n == 11:
        print('Jota')
    elif n == 12:
        print('Reina')
    elif n == 13:
        print('Rey')
    else:
        print('Inválido')
La instrucción elif es una contracción de las instrucciones else e if, con la ventaja de que no es necesario añadir un nivel de indentación al momento de utilizarla. Si no existiera la instrucción elif, el código de arriba se tendría que escribir de una forma un tanto menos conveniente:
# Código de Python 3
def imprime_rango_carta(n):
    if n == 1:
        print('As')
    else:
        if 2 <= n <= 10:
            print(n)
        else:
            if n == 11:
                print('Jota')
            else:
                if n == 12:
                    print('Reina')
                else:
                    if n == 13:
                        print('Rey')
                    else:
                        print('Inválido')
Vale la pena también notar que para determinar si n está entre 2 y 10 usamos la expresión:
2 <= n <= 10
En otros lenguajes de programación tendríamos que escribir una expresión equivalente a la siguiente:
2 <= n and n <= 10
Para las situaciones más comunes, se considera que el switch-case tiene al menos dos ventajas sobre una serie equivalente de ifs anidados:
  1. Su sintaxis favorece la legibilidad y rapidez de escritura gracias a que el código generalmente es más breve y compacto.
  2. Corre más rápido debido a que usualmente, de manera interna, el ambiente de ejecución utiliza una tabla de acceso rápido para seleccionar el código a ejecutar, en lugar de ir haciendo una por una cada una de las comparaciones que requieren una serie de ifs anidados.
No hay nada que podamos hacer en Python respecto al punto 1, ya que no hay soporte sintáctico para la instrucción switch-case en el lenguaje. Sin embargo sí podemos aprovechar el punto 2, utilizando una tabla en Python para evitar múltiples comparaciones. La implementación de dicha tabla se puede hacer mediante una lista o un diccionario. En nuestro programa usaremos un diccionario. En la entrada titulada Código morse introduje el uso de diccionarios. Dado que los diccionarios en Python están implementados usando tablas hash, las búsquedas y accesos se ejecutan de manera muy veloz.

Ahora bien, para la discusión que sigue es importante distinguir entre “invocar una función” y “hacer referencia al objeto que representa una función”. Esto queda más claro con un ejemplo. Sea fun una función sin parámetros que devuelve siempre el mismo valor:
def fun():
    return 42
Podemos usar fun básicamente de dos maneras:
a = fun
b = fun()
Hay que notar la presencia o ausencia de los paréntesis () después del identificador fun. En el código anterior, a contiene una referencia al objeto que representa la función fun, mientras que b contiene el resultado de invocar a la función fun. Si imprimimos los valores de a y b con estas instrucciones:
print('a =', a)
print('b =', b)
veremos una salida como la siguiente:
a = <function fun at 0x4815162342>
b = 42
Esta salida extraña de a quiere decir que es un objeto función. De manera más precisa, a es una referencia al objeto función llamado fun. Dicho de otra forma, fun y a son alias, pues se refieren al mismo objeto. Dado que podemos decir que a es una función, entonces la podemos invocar usando los paréntesis correspondientes y hacer algo con el resultado:
c = a()
Como es de esperarse, c queda con 42.

Lo que hay que recordar es esto:
  • Si en una expresión viene el nombre de una función seguido de un par de paréntesis (), con o sin argumentos, significa que la función se está invocando. El resultado de esa parte de la expresión es lo que devuelva la función.
  • Si en una expresión viene el nombre de una función sin venir seguido de un par de paréntesis (), significa que se está obteniendo una referencia al objeto función siendo nombrado. El resultado de esa parte de la expresión es el objeto función correspondiente.
Integrando todo lo que hemos discutido, nos queda ahora el siguiente programa:
# Código de Python 3
def imprime_rango_carta(n):

    def caso_as():
        print('As')

    def caso_num():
        print(n)

    def caso_jota():
        print('Jota')

    def caso_reina():
        print('Reina')

    def caso_rey():
        print('Rey')

    def caso_invalido():
        print('Inválido')

    tabla = { 1: caso_as,   2: caso_num,   3: caso_num,
              4: caso_num,  5: caso_num,   6: caso_num,
              7: caso_num,  8: caso_num,   9: caso_num,
             10: caso_num, 11: caso_jota, 12: caso_reina,
             13: caso_rey }

    f = tabla.get(n, caso_invalido)
    f()
Hay que notar varias cosas en el código anterior:
  1. El código de cada “cláusula case” se incorpora dentro de una función local.
  2. La función local caso_num utiliza en su cuerpo el parámetro n de la función en la que está anidada. Esto es totalmente válido, y demuestra que Python soporta cerraduras léxicas (lexical closures). Esto último significa que al crear una función, todas las variables que son visibles en ese punto también lo son dentro del cuerpo de dicha función. 
  3. El objeto tabla se construye usando un diccionario. Las llaves son los valores de cada “cláusula case”, y éstas se asocian a sus correspondientes objetos función.
  4. El método get() de la penúltima línea permite buscar una llave (el primer argumento: n) en el diccionario receptor (tabla) y regresar el valor asociado a dicha llave en caso de encontrarlo; si la llave no existe, regresa un valor por omisión (el segundo argumento: el objeto función caso_invalido).
  5. A la variable f se le asigna el resultado del método get(), el cual en todos los casos es un objeto función.
  6. La última línea invoca la función obtenida en la instrucción anterior.
Esto código ya funciona como es debido, sin embargo si lo revisamos de manera más detenida podemos simplificarlo de manera significativa. Dado que el propósito de la función imprime_rango_carta es imprimir un valor en particular a partir de lo que contenga n, podemos hacer que el diccionario contenga solamente los valores que varían en cada uno de los distintos casos:
# Código de Python 3

__tabla = { 1: 'As', 2: 2, 3: 3, 4: 4, 5: 5, 6: 6,
            7: 7, 8: 8, 9: 9, 10: 10, 11: 'Jota',
            12: 'Reina', 13: 'Rey' }

def imprime_rango_carta(n):
    print(__tabla.get(n, 'Inválido'))
Definimos la variable __tabla afuera de la función para que sea inicializada una vez y no cada vez que ésta sea invocada. Los dos subguiones al inicio de un nombre son una convención para indicar que la variable es privada a este módulo. También hay que notar que solamente usamos una instrucción print() para abarcar todos los casos. Como se puede ver, el código queda más compacto y fácil de modificar que el switch-case original.

Moraleja: la instrucción switch-case es innecesaria en Python si sabemos usar diccionarios de manera correcta.

25 de junio de 2014

Cómo programar a tu Arduino

En esta entrada explicaremos cómo se programa un Arduino usando el lenguaje de programación Python.

Arte conceptual de Toby Shelton para la película
Cómo entrenar a tu dragón de DreamWorks Animation.

Opciones para programar al Arduino

Partimos primero del supuesto de que tenemos una computadora anfitriona (con Windows, Linux o MacOS) conectada a un Arduino a través de un cable USB tipo A-B (como el que usamos comúnmente para conectar una computadora a una impresora), tal como se muestra en la siguiente figura:


Arduino conectado a la computadora 
anfitriona a través del cable USB.

A partir de esta configuración, hay dos lugares en donde un programa puede ser ejecutado:
  1. Directamente en el Arduino.
  2. En la computadora anfitriona. 
En la opción 1, la computadora anfitriona sirve para desarrollar el programa y compilarlo para producir un archivo ejecutable que se descarga posteriormente al Arduino a través de la conexión USB. Dicho archivo no debe sobrepasar el espacio de la memoria flash del microcontrolador (32 Kbytes en el caso del Arduino Uno).

En la opción 2, por otro lado, se instala en el Arduino un firmware que permite que un programa corriendo en la computadora anfitriona controle las acciones del Arduino a través del intercambio de datos vía la conexión USB. El firmware que usualmente se utiliza con este fin se llama Firmata.

Para la opción 1 comúnmente se utiliza el lenguaje de programación C++ junto con el ambiente de desarrollo integrado (IDE) de Arduino. Una ventaja de este esquema es que, una vez cargado el programa en el Arduino, es posible retirar la computadora anfitriona por completo. Dado que la conexión USB sirve también como fuente de alimentación, es necesario suplir al Arduino de una fuente alterna. Por ejemplo, es posible conectar al Arduino a una pila de nueve voltios.

Arduino con pila de 9V como fuente de alimentación
y sin conexión a la computadora anfitriona.

En cuanto a la opción 2, ésta tiene la desventaja fundamental de que nunca podemos retirar la computadora anfitriona; sin embargo, tiene dos ventajas significativas:
  • Ya que nuestros programas corren en la computadora anfitriona y no directamente en el Arduino, no estamos restringidos a tener que usar C++. Podemos usar cualquier lenguaje de programación en la que exista una interfaz con Firmata: Processing, Pascal, Perl, C#, PHP, Java, JavaScript, Clojure, Ruby y por su puesto Python, por mencionar algunos.
  • El código de nuestros programas no está limitado al tamaño de la memoria flash del microcontrolador, sino al tamaño de la memoria de la computadora anfitriona (kilobytes vs. gigabytes).
Dado que deseamos programar el Arduino usando Python, estamos obligados a utilizar la opción 2.

Instalación del software

Hay que seguir estos tres pasos:
  1. Descargar e instalar el software de Arduino, según el sistema operativo de la computadora anfitriona, tal como se describe en la página Comienza a usar Arduino. Al momento de configurar el IDE, hay que anotar el puerto serie (serial port) al que se conecta el Arduino, ya que más adelante haremos referencia a éste. Según el sistema operativo, el puerto serie puede ser algo así como COM3 (Windows), /dev/ttyACM0 (Linux) o /dev/tty.usbserial (MacOS).
  2. Descargar Firmata en el Arduino. En el IDE de Arduino seleccionar File del menú principal. De ahí seleccionar: Examples/Firmata/StandardFirmata.



    Posteriormente, presionar Ctrl-U para compilar y copiar el código de Firmata al Arduino. Después de un momento debe aparecer el texto “Done uploading” en la barra de mensajes del IDE.
  3. Descargar e instalar la biblioteca pyFirmata en la computadora anfitriona. Esta biblioteca permite que un programa de Python, corriendo en una computadora anfitriona, se comunique vía USB con un Arduino corriendo Firmata. Desde una terminal (posiblemente con privilegios de administrador), teclear:
    pip install pyfirmata
    Si el sistema de administración de paquetes de Python pip no está instalado, referirse a la siguiente liga: http://pip.readthedocs.org/en/latest/installing.html. En algunos sistemas en necesario usar el comando pip3 en lugar de pip para instalar bibliotecas de Python 3.

Resumen del API de pyFirmata

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

Para comenzar a usar pyFirmata debemos importar el paquete correspondiente al inicio de nuestro programa:
import pyfirmata
Podemos determinar qué versión de pyFirmata estamos usando:
print('pyFirmata version:', pyfirmata.__version__)
A continuación creamos un objeto que representa nuestra placa (board) de Arduino. Para ello necesitamos indicar como cadena de caracteres el puerto serie que anotamos durante la configuración del IDE ('/dev/ttyACM0' en mi caso, ya que estoy usando Linux):
placa = pyfirmata.Arduino('/dev/ttyACM0')
Con el objeto referido por la variable placa podemos determinar la versión de Firmata que está usando nuestro Arduino. El método get_firmata_version() devuelve una tupla con dos valores: la versión mayor y la versión menor. Podemos utilizar una asignación paralela para extraer los valores de la tupla en dos variables:
v_mayor, v_menor = placa.get_firmata_version()
print('Firmata version mayor:', v_mayor)
print('Firmata version menor:', v_menor)
Al correr estas instrucciones, la salida en mi sistema me indica que la versión mayor es 2 y la versión menor es 3, por lo que el número completo de versión es 2.3.

El método get_pin() sirve para activar un pin en el Arduino, configurándolo según la cadena que se le envía como argumento. Dicha cadena está compuesta de a o d (para indicar que el pin es analógico o digital), el número de pin, y el modo (i para entrada, o para salida, p para PWM). Estos tres elementos se separan entre sí usando un carácter de dos punto (:). Por ejemplo d:12:o indica que el pin 12 es una salida digital. Este es un ejemplo de cómo se utiliza el método:
salida = placa.get_pin('d:12:o')
El siguiente ejemplo activa el pin 8 del Arduino como una entrada digital.
entrada = placa.get_pin('d:8:i')
Después del código de arriba, es necesario invocar el método enable_reporting() sobre el objeto que representa el pin de entrada para poder leer las señales recibidas:
entrada.enable_reporting()
Si se utilizan uno o más pines 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()
Para escribir a un pin de salida digital, usamos el método write() enviando como argumento False (0V) o True (5V), según la cantidad de voltaje que deseemos producir como salida. Ejemplo:
salida.write(True)
En lugar de False y True se puede usar 0 y 1, respectivamente.

Usamos el método read() para leer de un pin de entrada digital. Este método devuelve 0 si el voltaje de entrada es menor a 2.5V, o 1 si es mayor a 2.5V. Por ejemplo:
x = entrada.read()
Posteriormente, podemos usar la variable x en una instrucción condicional (if) para determinar qué valor efectivamente se leyó.

Para pausar nuestro programa por una cierta cantidad de tiempo se recomienda usar el método pass_time() sobre el objeto que representa nuestra placa de Arduino. Se le envía como argumento el número de segundos que deseamos que dure la pausa. La siguiente porción de código detiene el programa en curso durante medio segundo:
placa.pass_time(0.5)
Por último, para terminar nuestro programa de manera limpia, se debe invocar el método exit() así:
placa.exit()

Un ejemplo completo

Realicemos ahora un pequeño proyecto que nos permita integrar todo lo que describimos en la sección anterior. Vamos a usar un botón para controlar el prendido y apagado de un LED.

Además del Arduino Uno conectado vía USB a la computadora anfitriona, vamos a requerir el siguiente hardware:
  • Un protoboard.
  • Un botón o pulsador (push button).
  • Un LED (diodo emisor de luz).
  • Una resistencia de 330Ω (bandas naranja, naranja, café).
  • Una resistencia de 10KΩ (bandas café, negro, naranja).
  • Cinco cables conectores.
La siguiente figura, elaborada con Fritzing, muestra la manera de conectar todos los componentes:

Vista de protoboard Fritzing para
el LED controlado por un botón.

Usando un par de cables, conectamos un pin de tierra (GND) y un de pin de 5V del Arduino a los buses respectivos de alimentación negativa (línea azul) y positiva (línea roja) del protoboard.

El ánodo del LED (la patita larga) se conecta al pin 12 del Arduino, mientras que el cátodo (la patita corta) se conecta a la resistencia de 330Ω. El otro extremo de esta resistencia se conecta a tierra (bus negativo).

El push button 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 inferiores del botón al bus positivo. Una de las patitas superiores del botón se conecta a una resistencia de 10KΩ, mientras el otro extremo de la resistencia se conecta a tierra (bus negativo). La patita superior que queda del botón se conecta al pin 8 del Arduino. Dada esta disposición, se dice que la resistencia es de tipo pull-down. En términos prácticos esto significa que el valor que va leer el pin 8 por omisión (cuando el botón no está presionado) es 0V, y 5V cuando el botón está presionado.

Físicamente las conexiones de nuestros componentes electrónicos se ven como se muestra en la siguiente foto:

Vista física del LED controlado
por un botón.

El programa completo en Python 3.4 se muestra a continuación:
# Archivo: led_boton.py

import pyfirmata

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

print('Firmata version: %d.%d' % placa.get_firmata_version())
print('pyFirmata version:', pyfirmata.__version__)

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

entrada = placa.get_pin('d:8:i')
entrada.enable_reporting()
salida = placa.get_pin('d:12:o')

try:
    encendido = False
    while True:
        if entrada.read():
            encendido = not encendido
            salida.write(encendido)
            placa.pass_time(0.2)

finally:
    salida.write(False)
    placa.exit()
Para correr el programa, requerimos ejecutar el siguiente comando desde una terminal en el mismo directorio donde radica el archivo led_boton.py:
python3 led_boton.py
Cuando corremos el programa, el LED se prende cuando presionamos el botón y se apaga cuando lo volvemos a presionar, y así indefinidamente hasta que presionemos Ctrl-C para terminar el programa.

Más información

18 de junio de 2014

Arduino

El Arduino es una plataforma de hardware libre. Consiste de un microcontrolador y un ambiente de programación que permite la creación de sistemas que interactúan con el mundo físico a través de sensores y otros componentes electrónicos. A este tipo de tecnología, que también incluye a la Raspberry Pi, se le conoce usualmente como computación física.


El proyecto Arduino comenzó en el año 2005. Su objetivo era proveer de dispositivos económicos y fáciles de usar a los estudiantes del Instituto Ivrea de Diseño Interactivo (IDII), en Ivrea, Italia. Massimo Banzi, uno de los principales fundadores del proyecto, daba clases en IDII.

Massimo Banzi

Se puede utilizar al Arduino para elaborar una infinidad de cosas, por ejemplo: redes de sensores, alarmas, sistemas de comunicaciones, robots sencillos, proyectos de arte digital, etc. En general se puede usar un Arduino para construir casi cualquier tipo de objeto interactivo, limitado solo por nuestra imaginación.

El hardware del Arduino consiste de una tarjeta de circuito impreso o PCB (printed circuit board) en la que reside el microcontrolador junto con otros conectores y elementos electrónicos que permiten al usuario acoplar componentes de entrada y salida.

El Arduino Uno

Un microcontrolador es una pequeña computadora contenida en solo circuito integrado. Está conformado por un procesador, memoria y periféricos programables de entrada y salida.

Existen varios modelos del Arduino. El más conocido es el Arduino Uno, que cuenta con un microcontrolador ATmega328 de Atmel. Dicho microcontrolador tiene las siguientes características:
  • Procesador RISC de 8 bits AVR.
  • Una velocidad de reloj de 16 MHz.
  • 32 registros de propósito general.
  • 2 Kbytes de memoria RAM para almacenar datos volátiles.
  • 1 Kbyte de memoria EEPROM para almacenar datos no volátiles
  • 32 Kbytes de memoria flash (memoria no volátil) para almacenar el código del programa.

Microcontrolador ATmega328 de Atmel.

El número de bits, la velocidad y la memoria disponible de este microcontrolador parecen ser los de una configuración de alguna micro-computadora de inicios de los años ochenta. Sin embargo, no hay que perder de vista que los microcontroladores se utilizan principalmente en sistemas embebidos, por ejemplo dentro de electrodomésticos, automóviles, aparatos médicos y juguetes. Típicamente, estos sistemas no necesitan la velocidad ni la cantidad de memoria de una computadora convencional contemporánea.

El AVR es un procesador con arquitectura Harvard. Esto quiere decir que los dispositivos de almacenamiento para instrucciones y datos están físicamente separados. Esto contrasta con la arquitectura de von Neumann, en donde se utiliza el mismo hardware de memoria principal para almacenar datos e instrucciones. El término “arquitectura Harvard” proviene de la computadora Harvard Mark I, que almacenaba las instrucciones en cintas perforadas y los datos en interruptores.

El Arduino Uno tiene un costo aproximado de treinta dólares. Cuenta con 16 pines de entrada/salida digital y 6 pines de entrada analógica. De los pines digitales, 6 se pueden usar también como salidas de modulación por ancho de pulsos (PWM por sus siglas en inglés), que de forma práctica son como si fueran salidas analógicas.

El puerto USB del Arduino sirve como fuente de alimentación y también permite establecer comunicación serial con una computadora anfitriona. Es precisamente por este medio que podemos programar al Arduino; éste será el tema central de una entrada futura del blog de EduPython.

Para concluir, recomiendo a las personas interesadas ver el video Arduino the Documentary (2011) (incluye subtítulos en español). Este documental presenta en media hora un contexto más amplio de qué es y de dónde viene el Arduino, así como la manera en que surge el movimiento de hardware de open-source y las implicaciones que tiene todo esto en la educación. En palabras de Juan Carlos de Mena, maestro de educación media superior en Madrid, España:
Los alumnos aprenden que hay otro lado aparte del lado del consumidor, que hay formas de conocer los cacharros por dentro y tener el control sobre ellos. El Arduino es muy divertido y sirve para enseñar electrónica. Sirve para enseñar a pensar. Sirve para enseñar a tener proyectos a medio plazo y trabajar en equipo, para participar en una comunidad y documentarse. 

Más información

  • El sitio oficial de Arduino: http://www.arduino.cc/es/
  • ¿Dónde puedo adquirir un Arduino? Eso depende del lugar donde te encuentres. En México se puede adquirir a través de distribuidores autorizados tales como ElectronicaEstudio.com, o en algunos comercios de electrónica de la calle de República del Salvador en el Centro de la Ciudad de México, solo por mencionar algunos lugares. Adafruit.com y Amazon.com atienden pedidos internacionales, pero siempre hay que considerar los costos de envío, impuestos y restricciones aduanales de cada país. Otra opción para países de América Latina son los sitios de venta entre particulares, por ejemplo MercadoLibre.com.

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).

30 de mayo de 2014

Código morse

En una entrada anterior elaboramos un proyecto relativamente sencillo en el que usamos la Raspberry Pi (RPi) para prender y apagar un LED dentro de un ciclo infinito. En esta ocasión vamos a utilizar la misma configuración de hardware de dicho proyecto, pero ahora con un programa más interesante que permita enviar mensajes utilizando código morse. Este es otro proyecto clásico para los recién iniciados en la RPi y otras plataformas electrónicas afines.

Creadores del código morse:
Samuel Morse y Alfred Vail

La Wikipedia explica los detalles que necesitamos conocer sobre el código morse:
Fue desarrollado por Alfred Vail mientras colaboraba en 1830 con Samuel Morse en la invención del telégrafo eléctrico. Vail creó un método según el cual cada letra o número era transmitido de forma individual con un código consistente en rayas y puntos, es decir, señales telegráficas que se diferencian en el tiempo de duración de la señal activa. La duración del punto es la mínima posible. Una raya tiene una duración de aproximadamente tres veces la del punto. Entre cada par de símbolos de una misma letra existe una ausencia de señal con duración aproximada a la de un punto. Entre las letras de una misma palabra, la ausencia es de aproximadamente tres puntos. Para la separación de palabras transmitidas el tiempo es de aproximadamente tres veces el de la raya.

El telégrafo original de Samuel Morse.
De la colección del museo Nacional Smithsoniano
de Historia de los Estados Unidos.

El alfabeto morse (junto con símbolos de numeración y puntuación) se muestra en la siguiente tabla:

Código morse

Si deseamos, por ejemplo, enviar el mensaje “hola” usando el código morse, tendríamos la siguiente secuencia de rayas y puntos:
····  −−−  ·−··  ·−
Comúnmente, los puntos y rayas son generados a partir de señales audibles o el encendido y apagado de una fuente de luz. En nuestro proyecto usaremos nuestro LED para parpadear los puntos y rayas del mensaje que deseamos enviar. En las siguientes secciones iremos construyendo nuestro programa poco a poco.

Usando diccionarios

La tabla de código morse puede ser convertida a un diccionario de Python. Un diccionario es una estructura de datos que permite asociar una cierta llave a un valor determinado. Por ejemplo:
d = { 'pollito': 'chicken',
      'gallina': 'hen',
      'lapiz':   'pencil',
      'pluma':   'pen'
    }
En este caso, las cadenas 'pollito', 'gallina', 'lapiz' y 'pluma' son las llaves del diccionario contenido en la variable d. Decimos que la llave 'pollito' está asociada al valor 'chicken', y así con cada una de las llaves. Es importante notar en la sintaxis del diccionario el uso del carácter de dos puntos (:). A la izquierda de este carácter va la llave, y a la derecha está su valor asociado. Las parejas de llave-valor están delimitadas entre sí usando comas. Otro detalle importante a notar es que las llaves deben ser siempre objetos inmutables (por ejemplo números o cadenas de caracteres).

La expresión:
d['lapiz']
devuelve la cadena 'pencil', ya que la llave 'lapiz' está asociada al valor 'pencil' dentro del diccionario d.

Por otro lado, la expresión:
d['ventana']
produce un error (arroja la excepción KeyError) ya que la llave 'ventana' no existe en el diccionario d.

Si deseamos determinar si una llave existe en el diccionario podemos utilizar el operador in:
'lapiz' in d
En este caso, la expresión anterior devolvería True. Si la llave no hubiera existido, el resultado devuelto habría sido False.

Hay muchas más operaciones que se pueden efectuar sobre un diccionario. La documentación oficial describe todos los detalles de este tipo de dato. Cabe también mencionar que los diccionarios de Python están implementados usando tablas de hash, por lo que los tiempos de acceso e inserción son bastante rápidos.

El diccionario que necesitamos para nuestro proyecto quedaría así:
codigo = {
    'A': '.-',     'B': '-...',    'C': '-.-.',
    'D': '-..',    'E': '.',       'F': '..-.',
    'G': '--.',    'H': '....',    'I': '..',
    'J': '.---',   'K': '-.-',     'L': '.-..',
    'M': '--',     'N': '-.',      'O': '---',
    'P': '.--.',   'Q': '--.-',    'R': '.-.',
    'S': '...',    'T': '-',       'U': '..-',
    'V': '...-',   'W': '.--',     'X': '-..-',
    'Y': '-.--',   'Z': '--..',    '1': '.----',
    '2': '..---',  '3': '...--',   '4': '....-',
    '5': '.....',  '6': '-....',   '7': '--...',
    '8': '---..',  '9': '----.',   '0': '-----',
    '.': '.-.-.-', ',': '--..--',  ':': '---...',
    ';': '-.-.-.', '?': '..--..',  '!': '-.-.--',
    '"': '.-..-.', "'": '.----.',  '+': '.-.-.',
    '-': '-....-', '/': '-..-.',   '=': '-...-',
    '_': '..--.-', '$': '...-..-', '@': '.--.-.',
    '&': '.-...',  '(': '-.--.',   ')': '-.--.-'
}

Filtrando el mensaje

El mensaje que deseamos enviar usando código morse estará contenido en una cadena de caracteres (string) de Python. Sin embargo dicha cadena puede tener algunos problemas. Por ejemplo, la cadena “¡Adiós Niños!” contiene caracteres que no están en nuestro diccionario. Específicamente aparecen tres casos que tenemos que considerar:
  1. Letras minúsculas.
  2. Letras acentuadas (ó) o con tilde (ñ).
  3. Símbolos especiales (¡).
Para el caso 2 definiremos la siguiente función, la cual elimina los acentos, tildes y diéresis que hay en ciertas letras que usamos en el idioma español:
import unicodedata

def convierte_ascii(c):
    return (unicodedata.normalize('NFD', c.decode('utf8'))
            .encode('ascii', 'ignore'))
Si invocamos esta función así:
convierte_ascii('¡Adiós Niños!')
nos devuelve la cadena 'Adios Ninos!'. Como se puede ver, la función se encarga parcialmente también del caso 3.  Cualquier carácter que no sea ASCII se elimina, como es el caso del signo de exclamación invertido (¡). Sin embargo, hay algunos caracteres ASCII (por ejemplo el asterisco o el signo de por ciento) que no son eliminados, por lo que tendrían que estar presentes como llaves en nuestro diccionario de código morse, pero no es así. Nos encargaremos de estos caracteres en la siguiente sección.

El caso 1 se resuelve de manera muy sencilla. Después de transformar las letras con acentos, diéresis y tildes, solo necesitamos convertir las letras resultantes a mayúsculas. Para ello usamos el método upper() sobre la cadena.

Traduciendo a código morse

Una vez que tenemos el diccionario de codigo morse y el mecanismo de filtrado, solo falta tomar cada carácter del mensaje de entrada y convertirlo a su equivalente en morse. Supongamos que la variable mensaje tiene el texto a traducir. El siguiente código se encarga de hacer la parte principal de la conversión:
resultado = []
for c in convierte_ascii(mensaje).upper():
    if c in codigo:
        resultado.append(codigo[c])
    elif c == ' ':
        resultado.append('  ')
Usamos la lista resultado para ir acumulando las cadenas resultantes de la traducción. El for trabaja sobre el mensaje filtrado, el cual solo contiene caracteres ASCII y letras mayúsculas. Para cada carácter del mensaje, si es un carácter que existe como llave en el diccionario, entonces se coloca su valor asociado (una cadena con la secuencia de puntos y rayas correspondiente) en la lista resultante. Como caso especial, si hay un espacio en el mensaje, éste se denota con dos espacios que representan un separador entre palabras. Cualquier otro carácter del mensaje de entrada es ignorado, y con ello nos encargamos completamente del caso 3 de la sección anterior.

Si el valor de la variable mensaje es '¡Adiós Niños!', después de ejecutar el ciclo for la variable resultado contiene lo siguiente:
['.-', '-..', '..', '---', '...', '  ',
 '-.', '..', '-.', '---', '...', '-.-.--']
Ahora, usaremos el método join() para convertir el contenido de esta lista en una gran cadena. A continuación tenemos un ejemplo del uso de este método:
'#'.join(['uno', 'dos', 'tres', 'cuatro'])
El método recibe una lista de cadenas y produce una sola cadena que resulta de concatenar todas las cadenas pero delimitándolas entre sí usando la cadena correspondiente al objeto receptor del método (la cadena '#' en el ejemplo). La expresión anterior produciría 'uno#dos#tres#cuatro'.

Regresando a nuestro proyecto, aquí usaremos un espacio para delimitar todas las cadenas contenidas en la lista resultado:
' '.join(resultado)
Con el mensaje '¡Adiós Niños!', la cadena de código morse después del proceso completo de  traducción sería la siguiente:
'.- -.. .. --- ...    -. .. -. --- ... -.-.--'
Hay que notar que quedó un espacio en blanco entre letras y cuatro espacios entre palabras.

Controlando el LED

Para controlar el LED desde nuestra RPi, definiremos dos constantes simbólicas que representan las duraciones en segundos de un punto y una raya:
TIEMPO_PUNTO = 0.2
TIEMPO_RAYA  = TIEMPO_PUNTO * 3
Ahora procesaremos con un for cada carácter del mensaje traducido a código morse. Los caracteres solo pueden ser puntos, rayas o espacios. Si encontramos un punto, prendemos el LED por la duración de un punto. Si encontramos una raya, prendemos el LED por la duración de una raya. Si encontramos un espacio, apagamos el LED por la duración de un punto. Al final de cada iteración del ciclo, sin importar el carácter que hayamos encontrado anteriormente, apagamos el LED por la duración de un punto.
for c in convierte_morse(mensaje):
    if c == '.':
        GPIO.output(PIN_LED, GPIO.HIGH)
        time.sleep(TIEMPO_PUNTO)
    elif c == '-':
        GPIO.output(PIN_LED, GPIO.HIGH)
        time.sleep(TIEMPO_RAYA)
    elif c == ' ':
        GPIO.output(PIN_LED, GPIO.LOW)
        time.sleep(TIEMPO_PUNTO)

    GPIO.output(PIN_LED, GPIO.LOW)
    time.sleep(TIEMPO_PUNTO)
Si analizamos con cuidado la lógica de todo nuestro código podremos percatarnos que el LED efectivamente queda apagado por la duración de un punto entre símbolos (punto y raya) de una misma letra, por la duración de tres puntos entre dos letras de una misma palabra, y por la duración de nueve puntos entre dos palabras del mismo mensaje, tal como lo especifica el artículo citado de Wikipedia.

Así queda el programa completo:
# coding: utf8

# Archivo: morse.py

# La primera línea del archivo especifica que estamos
# usando una codificación UTF-8 en nuestro archivo
# fuente. Es indispensable hacer esto en Python 2 con
# el fin de poder incluir en el archivo caracteres que
# no sean ASCII (por ejemplo letras con acentos y eñes).

import RPi.GPIO as GPIO
import time
import unicodedata

# Duración de un punto y una raya.
TIEMPO_PUNTO = 0.2
TIEMPO_RAYA  = TIEMPO_PUNTO * 3

# Pin donde está conectado el ánodo del LED.
PIN_LED = 24

GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_LED, GPIO.OUT)

codigo = {
    'A': '.-',     'B': '-...',    'C': '-.-.',
    'D': '-..',    'E': '.',       'F': '..-.',
    'G': '--.',    'H': '....',    'I': '..',
    'J': '.---',   'K': '-.-',     'L': '.-..',
    'M': '--',     'N': '-.',      'O': '---',
    'P': '.--.',   'Q': '--.-',    'R': '.-.',
    'S': '...',    'T': '-',       'U': '..-',
    'V': '...-',   'W': '.--',     'X': '-..-',
    'Y': '-.--',   'Z': '--..',    '1': '.----',
    '2': '..---',  '3': '...--',   '4': '....-',
    '5': '.....',  '6': '-....',   '7': '--...',
    '8': '---..',  '9': '----.',   '0': '-----',
    '.': '.-.-.-', ',': '--..--',  ':': '---...',
    ';': '-.-.-.', '?': '..--..',  '!': '-.-.--',
    '"': '.-..-.', "'": '.----.',  '+': '.-.-.',
    '-': '-....-', '/': '-..-.',   '=': '-...-',
    '_': '..--.-', '$': '...-..-', '@': '.--.-.',
    '&': '.-...',  '(': '-.--.',   ')': '-.--.-'
}

def convierte_ascii(c):
    """Convierte c a una cadena que únicamente contiene
    caracteres ASCII.
    
    Convierte caracteres con acento, diéresis o tilde al
    carácter simple correspondiente. Elimina cualquier otro
    carácter que no sea ASCII.
    """
    return (unicodedata.normalize('NFD', c.decode('utf8'))
            .encode('ascii', 'ignore'))

def convierte_morse(mensaje):
    """Convierte mensaje a una cadena de símbolos morse.
    
    Las letras se delimitan entre sí con un espacio. Las
    palabras se delimitan con cuatro espacios.
    """
    resultado = []
    for c in convierte_ascii(mensaje).upper():
        if c in codigo:
            resultado.append(codigo[c])
        elif c == ' ':
            resultado.append('  ')
    return ' '.join(resultado)

def parpadea_morse(mensaje):
    """Prende y apaga un LED para enviar mensaje usando
    código morse.
    """
    try:
        for c in convierte_morse(mensaje):
            if c == '.':
                GPIO.output(PIN_LED, GPIO.HIGH)
                time.sleep(TIEMPO_PUNTO)
            elif c == '-':
                GPIO.output(PIN_LED, GPIO.HIGH)
                time.sleep(TIEMPO_RAYA)
            elif c == ' ':
                GPIO.output(PIN_LED, GPIO.LOW)
                time.sleep(TIEMPO_PUNTO)

            GPIO.output(PIN_LED, GPIO.LOW)
            time.sleep(TIEMPO_PUNTO)
    finally:
        GPIO.cleanup()

# Coloca aquí tu mensaje.
parpadea_morse('Este es un mensaje en código morse.')
Para correr el programa, teclear en la terminal de la RPi:
sudo python morse.py
Si todo salió bien, se debe ver el LED parpadeando el mensaje indicado en código morse.

26 de mayo de 2014

El LED parpadeante

En la entrada anterior introduje brevemente a la Raspberry Pi (RPi). Ahora toca elaborar nuestro primer proyecto usando esta plataforma. Vamos a programar en Python uno de los pines de GPIO (Entrada/Salida de Propósito General) para controlar el parpadeo de un LED. Este proyecto es considerado el “¡Hola Mundo!” de la electrónica.

El hardware

Además de la RPi correctamente configurada y funcional, requeriremos los siguientes componentes:
  • Un protoboard.
  • Un LED (diodo emisor de luz).
  • Una resistencia de 330Ω (bandas naranja, naranja, café).
  • Dos cables conectores.
Es muy importante conectar los componentes de forma correcta para evitar todo tipo de problemas. El ánodo del LED (la patita larga) se conecta al pin GPIO 24 de la RPi, mientras que el cátodo (la patita corta) se conecta a la resistencia. El otro extremo de la resistencia se conecta a tierra (cualquiera de los pines GND de la RPi). La siguiente figura, elaborada con Fritzing, muestra cómo se ven los componentes ya conectados:

Vista de protoboard Fritzing de un LED conectado a la RPi.

La resistencia se necesita con el fin de evitar que el LED reciba demasiada corriente y se dañe. Para no complicar demasiado las cosas, se recomienda utilizar una resistencia entre 270Ω y 330Ω (si se requiere ser más precisos al respecto, se debe aplicar la Ley de Ohm y conocer la tensión del LED así como la corriente que pasa por éste).

La siguiente foto muestra físicamente la forma en que se ven las conexiones de nuestros componentes electrónicos:

Vista física de un LED conectado a la RPi.

Hay que notar que en la foto se aprecia un GPIO breakout (la tarjeta de interfaz negra) que facilita conectar la RPi al protoboard a través de un cable plano. En este caso, los cables conectores a utilizar deben ser macho-macho. Si no se cuenta con el breakout, se pueden usar cables conectores macho-hembra para conectar directamente los pines GPIO de la RPi al protoboard.

El software

Ahora veremos la manera de escribir nuestro programa en Python. Primeramente necesitamos importar el módulo RPi.GPIO:
import RPi.GPIO as GPIO
De esta forma podemos referirnos al módulo simplemente usando el identificador GPIO. Las distribuciones recientes de Raspian traen pre-instalado este módulo para Python 2.7.

Como segundo paso, necesitamos indicar la manera en que estaremos haciendo referencia a los números de pin de la RPi. El módulo RPi.GPIO permite hacerlo de dos formas:
  • BOARD: Los pines se numeran usando la distribución física de la cabecera de expansión P1 de 26 pines.
  • BCM: Los pines se numeran según la designación de canales de Broadcom SoC (System-on-a-chip). En otras palabras, se usan los números GPIO.
La siguiente figura (adaptada de elinux.org) muestra la correspondencia entre la numeración de los pines BOARD (fondo blanco) y los pines BCM (fondo naranja):

Numeración de pines BOARD (blanco) y BCM (naranja),
para la RPi modelo B, revisión 2.

Por ejemplo, el pin GPIO 24 corresponde al pin físico 18, mientras que el pin GPIO 17 corresponde al pin físico 11.

La función GPIO.setmode() recibe como argumento GPIO.BOARD o GPIO.BCM para indicar el esquema de numeración a utilizar:
GPIO.setmode(GPIO.BCM)
En nuestro proyecto usaremos la numeración BCM. Hay que notar que se debe tener algo de cuidado con este esquema, ya que hay algunas diferencias sutiles entre las revisiones 1 y 2 del modelo B de la RPi. Sin embargo, el proyecto que estamos construyendo debe funcionar sin problemas con cualquiera de las dos revisiones. En nuestro programa de Python podemos utilizar el valor de la constante simbólica GPIO.RPI_REVISION para averiguar el número de revisión.

El tercer paso consiste en indicar la dirección (entrada o salida, usando GPIO.IN o GPIO.OUT, respectivamente) que usaremos con el pin 24:
GPIO.setup(24, GPIO.OUT)
La documentación le llama a lo anterior “establecer un canal”.

Ahora, para controlar nuestro LED solo tenemos que usar la función GPIO.output(), indicando como argumentos el número de pin y GPIO.HIGH si queremos prender el LED o GPIO.LOW si lo queremos apagar. Por ejemplo:
GPIO.output(24, GPIO.HIGH)
Para producir un efecto de parpadeo necesitamos prender el LED, pausar por un tiempo, apagar el LED, volver a pausar, y repetir lo anterior cuantas veces queramos. Para pausar un momento nuestro programa en Python podemos usar la función sleep() del módulo time, la cual recibe como argumento la cantidad de segundos que se debe detener.

Finalmente, se recomienda invocar a la función GPIO.cleanup() para reiniciar todos los canales de GPIO y con esto asegurarnos que el LED quede apagado y también evitar algunos mensajes de advertencia que pudieran aparecer la siguiente vez que se corra nuestro programa. Dado que deseamos que siempre ocurra esto al final del programa (sin importar si éste terminó de forma normal o a raíz de una excepción), lo más conveniente es colocar la instrucción GPIO.cleanup() como parte de la cláusula finally de una instrucción try.

El siguiente programa completo integra lo que se discutió anteriormente:
# Archivo: parpadea.py

import RPi.GPIO as GPIO
import time

# Constantes
TIEMPO_PAUSA = 0.5     # Las pausas duran medio segundo.
PIN_LED = 24           # Pin donde esta conectado
                       # el anodo del LED.

GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_LED, GPIO.OUT)

try:
    # Ciclo infinito.
    # Para terminar el programa de debe presionar Ctrl-C.
    while True:
        GPIO.output(PIN_LED, GPIO.HIGH)
        time.sleep(TIEMPO_PAUSA)
        GPIO.output(PIN_LED, GPIO.LOW)
        time.sleep(TIEMPO_PAUSA)
finally:
    GPIO.cleanup()
Para correr el programa, requerimos ejecutar el siguiente comando desde una terminal en el mismo directorio donde radica el archivo parpadea.py:
sudo python parpadea.py
El comando debe empezar con sudo debido a que el programa de Python necesita privilegios de administrador para poder manipular los puertos de GPIO.

Cuando corremos el programa, el LED se prende por medio segundo, y luego se apaga durante otro medio segundo. Esto se repite indefinidamente hasta que  presionemos Ctrl-C para terminar el programa. Podemos cambiar el valor de la constante simbólica TIEMPO_PAUSA para hacer que el LED parpadee más lento o más rápido.

Usamos la constante simbólica PIN_LED para indicar el número de pin GPIO al que conectamos el ánodo del LED. Si después queremos usar otro número de pin, solo tenemos que cambiar este dato en un solo lugar de nuestro programa y no en múltiples sitios.

Si por fin logramos hacer funcionar este proyecto, entonces estamos listos para hacer proyectos más complicados e interesantes. ¡Yupi!