icono de la entrada de PYTHON

Control Arduino con PYTHON.

Hola técnicos. En otras entradas del curso y el blog de electrónica hemos visto como montar un display de 7 segmentos en ARDUINO UNO y cómo se configura dicho display:

Comprobar display de 7 segmentos en ARDUINO.
Configurar display de 7 segmentos.

Esto es muy parecido con la práctica del curso de VB.NET en la que hacíamos un conector de Arduino en VB.NET para controlar nuestra placa desde el ordenador:

En esta entrada vamos a crear una aplicación en PYTHON, con TKINTER que nos servirá para controlar la placa ARDUINO y su display conectado, a través del puerto serial de ARDUINO.

Crea un nuevo archivo PYTHON en tu directorio laboratorio para PYTHON. Yo le voy a llamar directamente controlDisplay.py y lo voy a guardar junto al proyecto sketch en formato .ino.

Para empezar necesitaremos tener instalado PYTHON (obvio), y la librería que vamos a utilizar que es PYSERIAL.

Si tienes el instalador PIP (si tienes PYTHON instalado, deberías de tenerlo), descargateló con esta instrucción directamente desde la consola de comandos de tu máquina:

pip install pyserial

NOTA: por defecto siempre te bajarás la última versión estable con el gestor de dependencias PIP.

cuando descargas contenido con el gestor PIP, te bajas lo último

Funcionamiento de Serial de Arduino.

Serial es un conjunto de librerías que te permite la conexión con diferentes máquinas a través de puerto paralelo seleccionando automáticamente la máquina de destino, ya sea Windows, Linux, OS, Andriod, etc. entre las características de Serial están:

● La misma interfaz basada en clases en todas las plataformas compatibles.
● Acceso a la configuración del puerto a través de las propiedades de Python.
● Soporte para diferentes tamaños de bytes, bits de parada, paridad y control de flujo con RTS/CTS y/o Xon/Xoff.
● Trabajar con o sin tiempo de espera de recepción.
● Archivo tipo API con lectura y escritura.
● Los archivos de este paquete son 100% Python puro.
● Cliente RFC 2217 (experimental), servidor proporcionado en los ejemplos.
● El puerto está configurado para transmisión binaria. Sin eliminación de bytes NULL, traducción CR-LF, etc. (que muchas veces están habilitadas para POSIX). Esto hace que este módulo sea universalmente útil.

Te recomiendo que visites la documentación de pyserial para más información sobre conexión y métodos de la librería, ya que en este tutorial no vamos a utilizar toda la funcionalidad de esta gran librería.

Empezando por el .ino de ARDUINO.

La comunicación serial de Arduino UNO implica que, desde dispositivos externos se pueda "atacar" a la placa y actuar sobre el programa creado. El problema es que hay que hacerlo bit a bit, es decir enviar la información desde el dispositivo externo y hasta que no se envie dicho paquete no se puede enviar otro (porque el puerto estará ocupado entregando ese paquete).

Lo primero que hay que hacer y sabiendo que según el sistema actual de nuestro circuito montado previamente en otra entrada, vamos a poner los puertos utilizados como salidas. Eso lo haremos dentro del SETUP:

void setup() {
Serial.begin(9600);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
}

Recuerda que los puertos deben de estar bien conectados al decodificador 74LS47N, y este al display de 7 segmentos. La imagen del diagrama muestra el esquema del circuito como deben de quedar los puertos del Arduino y del decodificador conectados.

Circuito interno de conexión de puertos Arduino a decodificador y display de 7 segmentos

Sin tener en cuenta el control externo, con el circuito montado, para que mostremos un número específico en el display de 7 segmentos con el decodificador 74LS47N conectado al circuito, lo que tendriamos que hacer es poner los puertos de salida correspondientes del Arduino UNO según la tabla de la verdad de la hoja de caracteristicas del 74LS47N.

Tabla de verdad del 74LS47N

Así por ejemplo, según la tabla anterior, para que nos muestre el número 0 (cero), deberiamos de poner los 4 puertos de salida a LOW. Si en el loop del sketch, escribimos lo siguiente:

void loop() {
Serial.println("Número 0");
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
}

Al subir el programa, obtenemos en el display el número cero:

Representación del número cero con Arduino

Es importante que entiendas que con los pines configurados como salida de la Arduino UNO, estamos actuando sobre las entradas del decodificador BCD 7447N (resumido de 74LS47N), y no estamos actuando directamente sobre el display con nuestro Arduino como vimos en esta ENTRADA.

La cuestión es que en el loop, podemos poner la estructura que queramos para mostrar el estado de los puertos y la entrada al decodificador. La opción más ajustable es hacer funciones que carguen el estado de los puertos, así podemos crear una funcion cero() que represente el estado actual del loop; otra función uno(), que represente la configuración de la tabla de verdad del 7447N para el número 1; la función dos(), para que muestre el número 2, etc., etc. Así hasta llegar a la función nueve().

Para terminar el programa de Arduino UNO, en el loop tenemos que llamar a las funciones, pero tenemos que tener en cuenta que vamos a utilizar el serial para comunicar la placa Arduino con un programa externo, por lo que la placa estará a la escucha. Eso se hace con el método read(), de Serial:

int datos = Serial.read();

Esta variable nos servirá para mantener a la Arduino en escucha. Serial.read() solo permite un único dato por petición, es decir que solo permite un parámetro que puede ser una letra, un número o un carácter. En nuestro caso vamos a utilizar números como símil para las funciones a utilizar. Y creamos un condicional para el caso de que el dato sea el escogido, se cargue dicha función:

void loop() {
int datos = Serial.read();
if (datos=='0'){
cero();
}
if (datos=='1'){
uno();
}
if (datos=='2'){
dos();
}
if (datos=='A'){
apagado();
}
delay(1000);
}

Y como funciones uno(), muestra el número 1 en el display; dos(), muestra el dos; apagado(), apaga el display:

void cero(){
Serial.println("Número 0");
digitalWrite(9, LOW);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
}
void uno(){
Serial.println("Número 1");
digitalWrite(9, HIGH);
digitalWrite(10, LOW);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
}
void dos(){
Serial.println("Número 2");
digitalWrite(9, LOW);
digitalWrite(10, HIGH);
digitalWrite(11, LOW);
digitalWrite(12, LOW);
}
void apagado() {
digitalWrite(9, HIGH);
digitalWrite(10, HIGH);
digitalWrite(11, HIGH);
digitalWrite(12, HIGH);
}

NOTA: Puedes crear las funciones que te faltan para representar los números siguientes. Pero no te preocupes si no das con ello, que te puedes bajar el proyecto en el menú inferior de esta entrada.

Creamos la aplicación en PYTHON.

Ya hemos comprobado que la aplicación funciona correctamente en Arduino UNO, por lo que podemos empezar a crear la estructura para PYTHON. Podriamos hacer un simple script de PYTHON para que nos encendiese o apagase el display y nos mostrase un número en concreto, pero prefiero hacer con TKINTER y tener una estructura gráfica para el control del display con Arduino UNO.

Estructura del programa en python

No nos vamos a complicar con florituras. Vamos a crear un canvas y dentro del mismo vamos a utilizar dos Frames tkinter (uno contenedor y otro para poner el título), y dentro del frame de título, pondremos los botones que serán los que ejecuten las funciones del programa.

El inicio de todas las APPs de tkinter es el mismo:

import tkinter as tk
from tkinter import ttk, Label, messagebox
import serial, time
root = tk.Tk()
root.title("Controlador de 7 segmentos")
root.geometry("410x200")
root.resizable(False,False)

#ICONO. FUENTE FLATICON REQUIERE MENCIÓN:
# <a href="https://www.flaticon.es/iconos-gratis/bloques-de-numeros" # title="bloques de números iconos"> # Bloques de números iconos creados por surang - # Flaticon</a>
icono = tk.PhotoImage(file="icono.png")
root.iconphoto(True, icono)

Hasta aquí no hay secretos. Salvo la mención del icono utilizado en la aplicación que es de una fuente externa. Si lo utilizas tendrás que referenciar la fuente.

Ahora necesitamos conectarnos a la placa Arduino con su sketch cargado. Para ello si nos vamos a la página de la librería serial, podemos ver la conexión rápida que nos muestran.

conexion = serial.Serial('COM3', 9600)
time.sleep(2)
conexion.write(b'A')
conexion.close()

Ves que creo una variable conexion, que va a servir para establecer el puerto de conexión a nuestra placa Arduino. Utiliza el método Serial(), de la librería serial para establecer la conexión y como requisito mínimo pide la ruta del puerto (o nombre si utilizas Windows). Como parámetro adicional puedes escoger el número de baudios a transmitir por cada conexión.

El método sleep(), de la librería time, es interesante mencionar el porqué se tiene que utilizar.

Conexión de datos entre diferentes máquinas

Con Serial.read(), establecido en el sketch de Arduino UNO, hacemos que la placa electrónica se ponga a la espera de recibir comunicación. Pero como la comunicación es a través del puerto serial, se requiere de una serie de estados antes de realizar la petición. El puerto serial funciona mediante protocolo RS232 lo que implica que tendrá una velocidad de transmisión fija (o tasa de baudios), que dependerá del tipo de conexión (siempre mediante cableado).

Los equipos que se comunican a través del protocolo RS232 se conectan a través de un conjunto de cables de por lo menos dos cables, uno para recibir señales y otro para enviarlas. Cuando se recibe señales se dice señales RX; cuando se transmiten señales, se dice señales TX. Por eso en nuestra Arduino UNO tenemos dos diodos LED SMD y dos puertos (0 y 1), uno para la recepción de señales RX, y otro para la transmisión TX.

puertos y dispositivos para indicar TX y RX

Según este protocolo, el transmisor transmitirá a una tensión de entre ±5V y ±25V para suplir la caída de tensión en el propio cableado; el receptor interpretará cualquier voltaje por encima de los +3V como si fuese un 0 (false); y cualquier tensión por debajo de los -3V como un 1 (true). Por lo que se puede ver que la naturaleza del protocolo es binaria.

Volviendo a lo que nos compete, una comunicación 232, tiene un proceso para el envío de información a través de los puertos de las máquinas afectadas.

Protocolo RS232 y las cabeceras de información

Ten en cuenta que los bits de información previa tienen que preparar a la máquina que recibe (o envía) la información. Al ser un protocolo asíncrono, la máquina debe de prepararse para recibir los bits. Por eso desde nuestro programa debemos de introducir un retraso en las comunicaciones, porque primero se envían los paquetes de conexión para preparar al Arduino UNO, y después se envía el mensaje.

Para terminar hay que cerrar la conexión y evitar mantenerla abierta, pues si se mantiene abierta, el puerto del dispositivo que recibe los datos (en este caso Arduino), se quedará abierto y no podrá utilizarse en otra conexión.

Volviendo al circuito empiezo a definir los contenedores de la aplicación que gobernará a la Arduino.

####CONTROLES####
mensaje = tk.Label(root, text="")
mensaje.pack(side="bottom")

#Frame de botones
frameBotones = tk.Frame(root)
frameBotones.pack(side="top")

frameLabelBotones = tk.LabelFrame(frameBotones, text="Control manual de Números",
      width="130", height="150")

frameLabelBotones.pack(ipadx="60", pady="10")
frameLabelCombobox = tk.LabelFrame(frameBotones, text="Selecciona un elemento.",
     width="130", height="150")
frameLabelCombobox.pack(side="top")

Y para la funcionalidad (y que no se ejecute al abrir la aplicación), lo que me parece más adecuado es crear dentro de funciones PYTHON que llamarán desde el atributo command de los botones establecidos en la APP a lo. De esta manera para cada botón ejecutaremos la función específica sin que se ejecute al inicio.

Por ejemplo, para la función cero(), y su botón quedaría de la siguiente forma:

def cero():
conexion = serial.Serial('COM3', 9600)
time.sleep(2)
conexion.write(b'0')
conexion.close()
mensaje.configure(text="Se está mostrando el número 0")
boton0 = tk.Button(frameLabelBotones, text="0",
bg="#CCEAFF", pady="8", padx="8", command=cero)
boton0.pack(side="left",ipadx="2", ipady="2")

Así que vamos a dejarlo por aquí, teniendo en cuenta que le puedes agregar más funciones personalizadas y los botones y estructura que creas conveniente.

Te dejo el enlace del vídeo en mi canal de YouTube y espero que te haya servido de algo este tutorial de conexión con serial.

Si aprecias el tiempo que he invertido en hacer este contenido, plantéate ayudarme o bien, compartiendo el contenido o bien con una donación económica. Tus aportes harán que siga creando contenido gratuito y de calidad.