Interfaz Gráfica en Python «Com USB y RS232», Pic C Compiler

Interfaz gráfica de usuario (GUI), programada en Python y VS Code para establecer comunicación USB mediante la computadora y el microcontrolador PIC, el objetivo es enviar comandos al micrcontrolador para encender y apagar LEDs.

VS code

Visual Studio Code (VS Code) es un editor de código fuente desarrollado por Microsoft. Es una herramienta muy popular entre desarrolladores debido a su amplia funcionalidad, extensibilidad y facilidad de uso. Aunque es parte de la familia de productos «Visual Studio», es importante destacar que VS Code es un editor de código ligero y no una versión completa del entorno de desarrollo integrado (IDE) como Visual Studio.

Visual Studio Code es ampliamente utilizado por programadores para una variedad de tareas de desarrollo, desde proyectos pequeños hasta aplicaciones y sistemas complejos. Su comunidad activa de desarrolladores ha llevado a la creación de numerosas extensiones que hacen que la experiencia de desarrollo sea aún más poderosa y personalizada según las necesidades individuales de cada programador.

Instala VS Code

Python

Python es un lenguaje de programación de alto nivel, interpretado, de propósito general y de código abierto. Su diseño se centra en la legibilidad del código y en la simplicidad sintáctica, lo que lo hace muy accesible para programadores principiantes y expertos por igual.

Python se utiliza en una amplia variedad de campos, incluyendo desarrollo web, análisis de datos, inteligencia artificial, aprendizaje automático, automatización de tareas, desarrollo de videojuegos y más. Debido a su versatilidad y facilidad de uso, se ha convertido en uno de los lenguajes de programación más populares en la actualidad.

Instala Python

pyserial

PySerial es una biblioteca de Python que proporciona una interfaz para trabajar con dispositivos seriales. Permite la comunicación con dispositivos externos, como microcontroladores, sensores, módulos GPS, entre otros, que se conectan a través de puertos seriales (también conocidos como puertos COM o puertos UART).

Esta biblioteca es útil cuando se necesita comunicarse con dispositivos que utilizan una conexión serial, ya que PySerial facilita la transmisión y recepción de datos a través de estos puertos. Es compatible con una variedad de sistemas operativos, como Windows, Linux y macOS.

Instala PyInstaller
  1. Abre una terminal o línea de comandos.
  2. Ejecuta el siguiente comando para instalar la biblioteca:

pip install pyserial

Interfaz Gráfica 

Procedimiento

Inicialmente se importan las librerías y módulos necesarios.


import tkinter as tk
from tkinter import ttk
import serial
from serial.tools import list_ports

Se crea la ventana principal de la interfaz gráfica, se especifica el nombre de la ventana, en tamaño y el color de fondo.


# Crea la ventana de la interfaz grafica
ventana = tk.Tk(className=" LEDs")
ventana.geometry("305x350")
ventana.config(bg="#405158")

Colocamos una etiqueta como titulo de la ventana.


# Etiqueta para mostrar el titulo del proyecto
etiqueta = tk.Label(ventana, text="Encender LEDs", font=("Arial", 14), bg="#5C6B72", fg="white")
etiqueta.pack(side=tk.TOP, fill="both")

Se coloca un «ComboBox» para mostrar los puertos COM conectados a la computadora.


# Crear ComboBox para mostrar los puertos COM disponibles
combo_puertos = ttk.Combobox(ventana, state='readonly')
combo_puertos.place(x=5, y=35)

Se crea un botón para actualizar los puertos COM conectados, ejecutando la función «mostrar_puertos_com».


# Botón para actualizar la lista de puertos COM disponibles
btn_actualizar = tk.Button(ventana, text="Actualizar", command=mostrar_puertos_com)
btn_actualizar.place(x=235, y=30)

Se crea un botón para conectarse o desconectarse del puerto COM seleccionado en el «ComboBox», ejecutando la función  «conectar_o_desconectar».


# Botón para conectarse al puerto COM seleccionado
btn_conectar = tk.Button(ventana, text="Conectar", command=conectar_o_desconectar)
btn_conectar.place(x=155, y=30)

La función «obtener_puerto_com» obtiene la lista de los puertos COM conectados a la computadora.


def obtener_puertos_com():
    puertos_com = [port.device for port in list_ports.comports()]
    return puertos_com

La función «mostrar_puertos_com», obtienen los puertos COM de la función «obtener_puerto_com» y los muestra en el «ComboBox» de la ventana.


def mostrar_puertos_com():
    combo_puertos['values'] = obtener_puertos_com()
    combo_puertos.set("")

La función «conectar_puerto» se conecta al puerto COM seleccionado en el «ComboBox», y retorna la comunicación serial, si no se establece la conexión, retorna «None».


def conectar_puerto(puerto):
    try:
        conectar = serial.Serial(puerto, baudrate=9600, stopbits=1, parity='N', bytesize=8)
        return conectar
    except serial.SerialException as e:
        return None

La función «desconectar_puerto» se desconecta al puerto COM seleccionado en el «ComboBox».


def desconectar_puerto(conectar):
    if conectar:
        conectar.close()

La función «conectar_o_desconectar», simplemente se conecta o desconecta del puerto COM seleccionado ejecutando las diferentes funciones. Si no existe comunicación serial con un puerto «COM», la computadora se conectara al puerto COM seleccionado y el texto del boton «btn_conectar» se cambiara a «desconectar», si existe una comunicación serial con un puerto COM, la computadora se desconectara del puerto COM y el texto del botón «btn_conectar» se cambiara a «conectar».


def conectar_o_desconectar():
    global conectar
    if conectar is None:
        puerto_seleccionado = combo_puertos.get()
        conectar = conectar_puerto(puerto_seleccionado)
        if conectar:
            btn_conectar.config(text="Desconectar")
    else:
        desconectar_puerto(conectar)
        conectar = None
        btn_conectar.config(text="Conectar")

Una vez colocado el código correspondiente a la comunicación serial entre la computadora y el microcontrolador, se realiza el código, en este caso para enviar diferentes comandos al microcontrolador para encender o a pagar diferentes LEDs mediante botones de encendido y apagado.

Inicialmente se colocan una etiqueta para identificar el LED.


# Etiqueta para mostrar led1
etiqueta_led1 = tk.Label(ventana, text="LED1", font=("Arial", 20), padx=1, pady=10, bg="#49646F", fg="white")
etiqueta_led1.place(x=10, y=80)

Se crea un botón de encendido y un botón de apagado para enviar el comando correspondiente para encender o apagar el LED, cada boton ejecutara una función que enviara el comando por el puerto COM seleccionado «command=led1_on».


# Botón para encender el led1
boton1_ON = tk.Button(ventana, text="ON", font=("Arial", 24), padx=1, pady=1, bg="green4", fg="white",
                      activebackground="green2", activeforeground="black", command=led1_on)
boton1_ON.place(x=110, y=80)

# Botón para apagar el led1
boton1_OFF = tk.Button(ventana, text="OFF", font=("Arial", 24), padx=1, pady=1, bg="red4", fg="white",
                       activebackground="red2", activeforeground="black", command=led1_off)
boton1_OFF.place(x=200, y=80)

Se crean las funciones para enviar el comando por el puerto COM seleccionado al microcontrolador cuado se pulsa el botón correspondiente, «conectar.write()» indica que enviara los datos por la comunicación serial establecida, se coloca «b» que convierte los datos en Bytes y entre comillas «datos» se colocan los datos que se desean enviar, en este caso se envía el comando seguido del retorno de carro «\r» y el salto de linea «\n».


# Envia el comando por el puerto seleccionado
def led1_on():
    conectar.write(b"LED1_ON\r\n")

def led1_off():
    conectar.write(b"LED1_OFF\r\n")

Código completo Python 


import tkinter as tk
from tkinter import ttk
import serial
from serial.tools import list_ports

conectar = None

def obtener_puertos_com():
    puertos_com = [port.device for port in list_ports.comports()]
    return puertos_com

def mostrar_puertos_com():
    combo_puertos['values'] = obtener_puertos_com()
    combo_puertos.set("")

def conectar_puerto(puerto):
    try:
        conectar = serial.Serial(puerto, baudrate=9600, stopbits=1, parity='N', bytesize=8)
        return conectar
    except serial.SerialException as e:
        return None

def desconectar_puerto(conectar):
    if conectar:
        conectar.close()

def conectar_o_desconectar():
    global conectar
    if conectar is None:
        puerto_seleccionado = combo_puertos.get()
        conectar = conectar_puerto(puerto_seleccionado)
        if conectar:
            btn_conectar.config(text="Desconectar")
    else:
        desconectar_puerto(conectar)
        conectar = None
        btn_conectar.config(text="Conectar")


# Crea la ventana de la interfaz grafica
ventana = tk.Tk(className=" LEDs")
ventana.geometry("305x350")
ventana.config(bg="#405158")


# Etiqueta para mostrar el titulo del proyecto
etiqueta = tk.Label(ventana, text="Encender LEDs", font=("Arial", 14), bg="#5C6B72", fg="white")
etiqueta.pack(side=tk.TOP, fill="both")


# Crear ComboBox para mostrar los puertos COM disponibles
combo_puertos = ttk.Combobox(ventana, state='readonly')
combo_puertos.place(x=5, y=35)

# Botón para actualizar la lista de puertos COM disponibles
btn_actualizar = tk.Button(ventana, text="Actualizar", command=mostrar_puertos_com)
btn_actualizar.place(x=235, y=30)

# Botón para conectarse al puerto COM seleccionado
btn_conectar = tk.Button(ventana, text="Conectar", command=conectar_o_desconectar)
btn_conectar.place(x=155, y=30)


# Actualizar la lista de puertos COM disponibles al abrir la ventana
mostrar_puertos_com()

################################################################################################

# Etiqueta para mostrar led1
etiqueta_led1 = tk.Label(ventana, text="LED1", font=("Arial", 20), padx=1, pady=10, bg="#49646F", fg="white")
etiqueta_led1.place(x=10, y=80)

# Etiqueta para mostrar led2
etiqueta_led2 = tk.Label(ventana, text="LED2", font=("Arial", 20), padx=1, pady=10, bg="#49646F", fg="white")
etiqueta_led2.place(x=10, y=170)

# Etiqueta para mostrar led3
etiqueta_led3 = tk.Label(ventana, text="LED3", font=("Arial", 20), padx=1, pady=10, bg="#49646F", fg="white")
etiqueta_led3.place(x=10, y=260)

# Envia el comando por el puerto seleccionado
def led1_on():
    conectar.write(b"LED1_ON\r\n")

def led1_off():
    conectar.write(b"LED1_OFF\r\n")

def led2_on():
    conectar.write(b"LED2_ON\r\n")

def led2_off():
    conectar.write(b"LED2_OFF\r\n")

def led3_on():
    conectar.write(b"LED3_ON\r\n")

def led3_off():
    conectar.write(b"LED3_OFF\r\n")


# Botón para encender el led1
boton1_ON = tk.Button(ventana, text="ON", font=("Arial", 24), padx=1, pady=1, bg="green4", fg="white",
                      activebackground="green2", activeforeground="black", command=led1_on)
boton1_ON.place(x=110, y=80)

# Botón para apagar el led1
boton1_OFF = tk.Button(ventana, text="OFF", font=("Arial", 24), padx=1, pady=1, bg="red4", fg="white",
                       activebackground="red2", activeforeground="black", command=led1_off)
boton1_OFF.place(x=200, y=80)

# Botón para encender el led2
boton2_ON = tk.Button(ventana, text="ON", font=("Arial", 24), padx=1, pady=1, bg="green4", fg="white",
                      activebackground="green2", activeforeground="black", command=led2_on)
boton2_ON.place(x=110, y=170)

# Botón para apagar el led2
boton2_OFF = tk.Button(ventana, text="OFF", font=("Arial", 24), padx=1, pady=1, bg="red4", fg="white",
                       activebackground="red2", activeforeground="black", command=led2_off)
boton2_OFF.place(x=200, y=170)

# Botón para encender el led3
boton3_ON = tk.Button(ventana, text="ON", font=("Arial", 24), padx=1, pady=1, bg="green4", fg="white",
                      activebackground="green2", activeforeground="black", command=led3_on)
boton3_ON.place(x=110, y=260)

# Botón para apagar el led3
boton3_OFF = tk.Button(ventana, text="OFF", font=("Arial", 24), padx=1, pady=1, bg="red4", fg="white",
                       activebackground="red2", activeforeground="black", command=led3_off)
boton3_OFF.place(x=200, y=260)


################################################################################################

ventana.mainloop()
Crear una aplicación ejecutable:
Instala PyInstaller
  1. Abre una terminal o línea de comandos.
  2. Ejecuta el siguiente comando para instalar la biblioteca:

pip install pyinstaller
  1. Coloca tu código y todos los archivos necesarios en una carpeta. Asegúrate de tener todos los archivos que necesitas para que tu aplicación funcione correctamente, como los archivos .py, imágenes, otros recursos, etc.
  2. Abre una terminal o línea de comandos en la carpeta donde se encuentran tus archivos.
  3. Ejecuta el siguiente comando para crear el archivo ejecutable:

pyinstaller --onefile tu_archivo_principal.py

Reemplaza tu_archivo_principal.py con el nombre del archivo que contiene el código principal de tu aplicación.

  1. PyInstaller generará una carpeta dist en la ubicación actual, y dentro de ella encontrarás el archivo ejecutable. El nombre del archivo ejecutable será el mismo que el nombre de tu archivo principal, pero sin la extensión .py.

Es importante tener en cuenta que la creación de un ejecutable puede depender del sistema operativo en el que te encuentres. Si estás en Windows, el archivo ejecutable generado tendrá una extensión .exe; si estás en Linux, no habrá extensión, pero el archivo estará marcado como ejecutable; y si estás en macOS, puede tener la extensión .app.

Circuito comunicación USB

Procedimiento

Inicialmente en el archivo .h se define el oscilador del microcontrolador, y se habilita la velocidad de la comunicación USB.


#use delay(clock=48MHz,crystal=20MHz,USB_FULL)

Se establece la corriente de funcionamiento necesaria para que el microcontrolador se conecte mediante USB.


#define USB_CONFIG_BUS_POWER 500 //500ma corriente de entrada

Para la comunicación USB se utiliza la siguiente librería.


#include "usb_cdc.h"

Se inicializa la comunicación USB.


   usb_init();

Cuando se reciben datos por el puerto USB en la subrutina «recibe()» se obtiene la cadena de caracteres con la función «usb_cdc_getc()» y se guardan en la variable de caracteres «recibido[]», cuando se recibe el carácter «\n» se termina la recepción de datos, después se borran los caracteres especiales «\r y \n» para conservar solo el comando recibido, finalmente el programa se dirige a la subrutina «leds()».


void recibe()
{
   memset(recibido, 0, sizeof(recibido)); //limpia los registros de la varible
   int8 i=0;
   while(true)
   {
      recibido[i] = usb_cdc_getc();//RECIBE EL CARACTER

      if(recibido[i] == '\n')//busca el salto de linea \r\n
      {
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)

         leds();
         return;
      }
      i++;
   }
}

En la subrutina «leds()» inicialmente se colocan los comandos que se pueden recibir para después hacer la comparación con los comandos recibidos.


void leds()
{
   signed int8 result;

   char LED1_ON[8] = "LED1_ON";
   char LED1_OFF[9] = "LED1_OFF";

   char LED2_ON[8] = "LED2_ON";
   char LED2_OFF[9] = "LED2_OFF";

   char LED3_ON[8] = "LED3_ON";
   char LED3_OFF[9] = "LED3_OFF";
   .
   .
   .

Lo siguiente es utilizar la función «strcmp()» para comparar el comando recibido con los diferentes comandos declarados, cuando la comparación resulta igual, la función devuelve «0» y entrara a la sentencia «if()» para  encender o apagar el LED correspondiente al comando recibido.


      result = strcmp(recibido, LED1_ON);//compara, si son iguales (0), no (+-1)

      if(result==0)
      {
         OUTPUT_BIT(LED1,1);
         return;
      }//enciende el led1

      result = strcmp(recibido, LED1_OFF);

      if(result==0)
      {
         OUTPUT_BIT(LED1,0);
         return;
      }
      .
      .
      .

Finalmente antes de colocar la librería «usb_cdc.h», se define la interrupción por recepción de datos USB, colocando la subrutina en este caso «recibe()» y después de definirla se declara la subrutina «void recibe();»


#define USB_CDC_ISR() recibe() //Interrupcion por recepcion de datos

void recibe();//Subrutina de la interrupcion

Código completo USB


#FUSES NOMCLR

#define USB_CDC_ISR() recibe() //Interrupcion por recepcion de datos

void recibe();//Subrutina de la interrupcion

#define USB_CONFIG_BUS_POWER 500 //500ma corriente de entrada

#include "usb_cdc.h"

#define LED1   PIN_D0
#define LED2   PIN_D1
#define LED3   PIN_D2

char recibido[15];

void leds();

void recibe()
{
   memset(recibido, 0, sizeof(recibido)); //limpia los registros de la varible
   int8 i=0;
   while(true)
   {
      recibido[i] = usb_cdc_getc();//RECIBE EL CARACTER

      if(recibido[i] == '\n')//busca el salto de linea \r\n
      {
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)

         leds();
         return;
      }
      i++;
   }
}


void leds()
{
   signed int8 result;

   char LED1_ON[8] = "LED1_ON";
   char LED1_OFF[9] = "LED1_OFF";

   char LED2_ON[8] = "LED2_ON";
   char LED2_OFF[9] = "LED2_OFF";

   char LED3_ON[8] = "LED3_ON";
   char LED3_OFF[9] = "LED3_OFF";
   
   result = strcmp(recibido, LED1_ON);//compara, si son iguales (0), no (+-1)

   if(result==0)
   {
      OUTPUT_BIT(LED1,1);
      return;
   }//enciende el led1

   result = strcmp(recibido, LED1_OFF);

   if(result==0)
   {
      OUTPUT_BIT(LED1,0);
      return;
   }

   result = strcmp(recibido, LED2_ON);

   if(result==0)
   {
      OUTPUT_BIT(LED2,1);
      return;
   }

   result = strcmp(recibido, LED2_OFF);

   if(result==0)
   {
      OUTPUT_BIT(LED2,0);
      return;
   }

   result = strcmp(recibido, LED3_ON);

   if(result==0)
   {
      OUTPUT_BIT(LED3,1);
      return;
   }

   result = strcmp(recibido, LED3_OFF);

   if(result==0)
   {
      OUTPUT_BIT(LED3,0);
      return;
   }
}


void main()
{
   usb_init();
   while(TRUE)
   {


      //TODO: User Code
   }

}

Circuito comunicación RS232

Módulo convertidor de USB a RS232 FT232RL

Procedimiento

Inicialmente se establecen los parámetros de la comunicación RS232.

  • baud = Velocidad de transmisión.
  • stop = Bit de paro.
  • parity = Bit de paridad. 
  • xmit = Pin de transmisión.
  • rcv = Pin de recepción. 
  • bits = Datos de transmisión.
  • stream = identificación de transmisión.

#use rs232(baud=9600, stop=1, parity=N, xmit=PIN_C6, rcv=PIN_C7, bits=8)

Se definen los pines de salida para encender y apagar los LEDs.


#define LED1   PIN_D0
#define LED2   PIN_D1
#define LED3   PIN_D2

Se habilita la interrupción para la recepción de datos por el puerto rs232.


   enable_interrupts(global);
   enable_interrupts(int_rda);

Cuando se reciben datos por el puerto rs232 se habilita la interrupción «#int_rda» donde se reciben los datos y se guardan en la variable de caracteres «recibido[]», cuando se recibe el carácter «\n» se termina la recepción de datos, después se borran los caracteres especiales «\r y \n» para conservar solo el comando recibido, finalmente el programa se dirige a la subrutina «leds()».


#int_rda
void rda_isr()
{
   memset(recibido, 0, sizeof(recibido)); //limpia los registros de la varible
   int8 i=0;
   while(true)
   {
      recibido[i] = getc();//RECIBE EL CARACTER
      
      if(recibido[i] == '\n')//busca el salto de linea \r\n
      {        
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)
         
         leds();
         return;
      }
      i++;
   }
}

En la subrutina «leds()» inicialmente se colocan los comandos que se pueden recibir para después hacer la comparación con los comandos recibidos.


void leds()
{
   signed int8 result;

   char LED1_ON[8] = "LED1_ON";
   char LED1_OFF[9] = "LED1_OFF";

   char LED2_ON[8] = "LED2_ON";
   char LED2_OFF[9] = "LED2_OFF";

   char LED3_ON[8] = "LED3_ON";
   char LED3_OFF[9] = "LED3_OFF";
   .
   .
   .

Lo siguiente es utilizar la función «strcmp()» para comparar el comando recibido con los diferentes comandos declarados, cuando la comparación resulta igual, la función devuelve «0» y entrara a la sentencia «if()» para  encender o apagar el LED correspondiente al comando recibido.


      result = strcmp(recibido, LED1_ON);//compara, si son iguales (0), no (+-1)

      if(result==0)
      {
         OUTPUT_BIT(LED1,1);
         return;
      }//enciende el led1

      result = strcmp(recibido, LED1_OFF);

      if(result==0)
      {
         OUTPUT_BIT(LED1,0);
         return;
      }
      .
      .
      .

Finalmente se retorna a la función «main()» para esperar a recibir un nuevo comando por el puerto rs232.

Código completo RS232


#include "string.h"

#FUSES NOMCLR

#use rs232(baud=9600, stop=1, parity=N, xmit=PIN_C6, rcv=PIN_C7, bits=8)

#define LED1   PIN_D0
#define LED2   PIN_D1
#define LED3   PIN_D2

char recibido[15];

void leds();

#int_rda
void rda_isr()
{
   memset(recibido, 0, sizeof(recibido)); //limpia los registros de la varible
   int8 i=0;
   while(true)
   {
      recibido[i] = getc();//RECIBE EL CARACTER

      if(recibido[i] == '\n')//busca el salto de linea \r\n
      {
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)

         leds();
         return;
      }
      i++;
   }
}

void leds()
{
   signed int8 result;

   char LED1_ON[8] = "LED1_ON";
   char LED1_OFF[9] = "LED1_OFF";

   char LED2_ON[8] = "LED2_ON";
   char LED2_OFF[9] = "LED2_OFF";

   char LED3_ON[8] = "LED3_ON";
   char LED3_OFF[9] = "LED3_OFF";

   result = strcmp(recibido, LED1_ON);//compara, si son iguales (0), no (+-1)

   if(result==0)
   {
      OUTPUT_BIT(LED1,1);
      return;
   }//enciende el led1

   result = strcmp(recibido, LED1_OFF);

   if(result==0)
   {
      OUTPUT_BIT(LED1,0);
      return;
   }

   result = strcmp(recibido, LED2_ON);

   if(result==0)
   {
      OUTPUT_BIT(LED2,1);
      return;
   }

   result = strcmp(recibido, LED2_OFF);

   if(result==0)
   {
      OUTPUT_BIT(LED2,0);
      return;
   }

   result = strcmp(recibido, LED3_ON);

   if(result==0)
   {
      OUTPUT_BIT(LED3,1);
      return;
   }

   result = strcmp(recibido, LED3_OFF);

   if(result==0)
   {
      OUTPUT_BIT(LED3,0);
      return;
   }

}

void main()
{
   enable_interrupts(global);
   enable_interrupts(int_rda);

   while(TRUE)
   {

      //TODO: User Code
   }

}
Scroll al inicio