Interfaz Gráfica en Python «Recepción», 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 recibir los valores de dos señales analógicas para mostrarlos en una etiqueta y una barra de progreso.

En el siguiente link encontraras lo necesario para instalar los programas y librerías necesarias.

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
import threading

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


# Crea la ventana de la interfaz grafica
ventana = tk.Tk(className=" Recepcion")
ventana.geometry("380x300")
ventana.config(bg="#405158")

Colocamos una etiqueta como titulo de la ventana.


# Etiqueta para mostrar el titulo del proyecto
etiqueta = tk.Label(ventana, text="Sensores", 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 y los muestra el en «ComboBox».


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 de la conexión establecida con el pureto COM seleccionado.


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

La función «recibir_datos» se ejecuta en segundo plano para recibir constantemente los datos enviados por el microcontroalador y cuando se reciben los datos se envían a la función «mostrar».


def recibir_datos():
    global conectar
    while conectar:
        try:
            datos_recibidos = conectar.readline().decode("utf-8")
            mostrar(datos_recibidos)           
        except serial.SerialException:
            conectar = None
            btn_conectar.config(text="Conectar")
            break

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 botón «btn_conectar» se cambiara a «desconectar» y se utiliza un una función «threading.Thread» para  ejecutar la función «recibir_datos» en segundo plano, en caso contrario, 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")
            recibir_datos_thread = threading.Thread(target=recibir_datos)
            recibir_datos_thread.start()                    
    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 recibir los datos del microcontrolador y mostrar los valores en una etiqueta y en una barra de progreso.

Se colocan dos etiquetas para indicar el sensor correspondiente.


# Etiqueta para mostrar nombre del sensor1
label_sen1 = tk.Label(ventana, text="SEN01:", font=("Arial", 14), bg="#5C6B72", fg="white")
label_sen1.place(x=10, y=80)

# Etiqueta para mostrar nombre del sensor2
label_sen2 = tk.Label(ventana, text="SEN02:", font=("Arial", 14), bg="#5C6B72", fg="white")
label_sen2.place(x=10, y=120)

Para mostrar el valor correspondiente a cada sensor se utiliza una etiqueta.


# Etiqueta para mostrar el valor del sensor1
label_val_sen1= tk.Label(ventana, text="0000", font=("Arial", 14), bg="white", fg="Black", anchor=tk.NW, width=4, height=1)
label_val_sen1.place(x=110, y=80)

# Etiqueta para mostrar el valor del sensor2
label_val_sen2 = tk.Label(ventana, text="0000", font=("Arial", 14), bg="white", fg="Black", anchor=tk.NW, width=4, height=1)
label_val_sen2.place(x=110, y=120)

Otra forma de mostrar representar el valor de los sensores es utilizando una barra de progreso donde se especifica, el largo de la barra «length», el valor máximo de la barra «maximum»,  la orientación de la barra «orient» , el modo para establecer que se puede modificar el valor «mode» y se utiliza «style» para configurar otros parámetros personalizados en la configuración de la clase «ttk.style».


# Crear una barra de progreso para el sensor1
pbar_sen1 = ttk.Progressbar(ventana, style="Pbar1.Vertical.TProgressbar", length=200, maximum=1023, orient="vertical", 
                            mode="determinate")
pbar_sen1.place(x=220, y=80)

# Crear una barra de progreso para el sensor2
pbar_sen2 = ttk.Progressbar(ventana, style="Pbar2.Vertical.TProgressbar", length=200, maximum=1023, orient="vertical", 
                            mode="determinate")
pbar_sen2.place(x=300, y=80)

En la clase «ttk.style» se establece un estilo para los diferentes componentes, con «theme.use» se establece un tema para los componentes, se utiliza «configure» para establecer una configuración personalizada para cada objeto colocando un nombre, por ejemplo «Pbar1», seguido de «.Vertical.Tprogressbar», en este caso se asigna un estilo para la barra de progreso donde se cambia el color de fondo «background», el color de la barra «troughcolor», el el ancho de la barra «thickness».


style = ttk.Style()
style.theme_use('default')
style.configure("Pbar1.Vertical.TProgressbar", troughcolor='white', background='red', thickness=20)
style.configure("Pbar2.Vertical.TProgressbar", troughcolor='white', background='#00A4FF', thickness=60)

En la función «mostrar» se reciben los datos, se sabe que la cadena de caracteres recibida contiene en los primeros 5 caracteres el identificador «id_sen», y los siguientes 4 caracteres corresponden al valor «numero» por lo que se guardan en variables separadas, lo siguiente es utilizar una sentencia «if» para encontrar el identificador recibido y mostrar el valor en la etiqueta y la barra de progreso correspondiente.


def mostrar(datos_recibidos):
    id_sen = datos_recibidos[0:5] # obtiene los caracteres del 0 al 4
    numero = int(datos_recibidos[5:9]) # obtiene los caracteres del 5 al 8
    if id_sen == "SEN01":
        label_val_sen1.config(text="%d" %(numero)) # Muestra el valor en la etiqueta
        pbar_sen1["value"] = numero        # Muestra el valor en la barra
    if id_sen == "SEN02":
        label_val_sen2.config(text="%d" %(numero)) 
        pbar_sen2["value"] = numero

Código completo Python 


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


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 recibir_datos():
    global conectar
    while conectar:
        try:
            datos_recibidos = conectar.readline().decode("utf-8")
            mostrar(datos_recibidos)
        except serial.SerialException:
            conectar = None
            btn_conectar.config(text="Conectar")
            break

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")
            recibir_datos_thread = threading.Thread(target=recibir_datos)
            recibir_datos_thread.start()
    else:
        desconectar_puerto(conectar)
        conectar = None
        btn_conectar.config(text="Conectar")



# Crea la ventana de la interfaz grafica
ventana = tk.Tk(className=" Recepcion")
ventana.geometry("380x300")
ventana.config(bg="#405158")

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

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

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

style = ttk.Style()
style.theme_use('default')
style.configure("Pbar1.Vertical.TProgressbar", troughcolor='white', background='red', thickness=20)
style.configure("Pbar2.Vertical.TProgressbar", troughcolor='white', background='#00A4FF', thickness=60)


def mostrar(datos_recibidos):
    id_sen = datos_recibidos[0:5] # obtiene los caracteres del 0 al 4
    numero = int(datos_recibidos[5:9]) # obtiene los caracteres del 5 al 8
    if id_sen == "SEN01":
        label_val_sen1.config(text="%d" %(numero)) # Muestra el valor en la etiqueta
        pbar_sen1["value"] = numero        # Muestra el valor en la barra
    if id_sen == "SEN02":
        label_val_sen2.config(text="%d" %(numero))
        pbar_sen2["value"] = numero


# Etiqueta para mostrar nombre del sensor1
label_sen1 = tk.Label(ventana, text="SEN01:", font=("Arial", 14), bg="#5C6B72", fg="white")
label_sen1.place(x=10, y=80)

# Etiqueta para mostrar nombre del sensor2
label_sen2 = tk.Label(ventana, text="SEN02:", font=("Arial", 14), bg="#5C6B72", fg="white")
label_sen2.place(x=10, y=120)

# Etiqueta para mostrar el valor del sensor1
label_val_sen1= tk.Label(ventana, text="0000", font=("Arial", 14), bg="white", fg="Black", anchor=tk.NW, width=4, height=1)
label_val_sen1.place(x=110, y=80)

# Etiqueta para mostrar el valor del sensor2
label_val_sen2 = tk.Label(ventana, text="0000", font=("Arial", 14), bg="white", fg="Black", anchor=tk.NW, width=4, height=1)
label_val_sen2.place(x=110, y=120)

# Crear una barra de progreso para el sensor1
pbar_sen1 = ttk.Progressbar(ventana, style="Pbar1.Vertical.TProgressbar", length=200, maximum=1023, orient="vertical",
                            mode="determinate")
pbar_sen1.place(x=220, y=80)

# Crear una barra de progreso para el sensor2
pbar_sen2 = ttk.Progressbar(ventana, style="Pbar2.Vertical.TProgressbar", length=200, maximum=1023, orient="vertical",
                            mode="determinate")
pbar_sen2.place(x=300, y=80)

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

ventana.mainloop()

Circuito de conexió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();

Lo siguiente es enviar los datos a la computadora, en este caso se envía el valor de los pines analógicos.

Se habilita la conversión analógica digital.


setup_adc(ADC_CLOCK_INTERNAL); 

Para obtener el valor de los pines analógicos se habilita la lectura del canal analógico con «set_adc_channel()» y después se realiza la lectura del valor con «read_adc()», para finalmente guardarlo en la variable declarada.

Para enviar datos por el puerto USB, se utiliza la función «printf()» para generar una cadena de caracteres y utilizando la función «usb_cdc_putc» se enviara la cadena de caracteres por el puerto USB, colocando un identificador para el valor «SEN01», el valor «%04lu» y «\r\n» para establecer el final de los datos.


void enviar_sensor1()
{
   int16 sensor1;

   set_adc_channel (0);//Lectura Vout del potenciometro en PIN AN0
   delay_us(10);
   sensor1 = read_adc();// lectura del valor

   printf(usb_cdc_putc,"SEN01%04lu\r\n",sensor1);
}

Para enviar el valor del pin «AN1» se realiza el mismo procedimiento y en la cadena de caracteres se coloca un identificador para el valor «SEN02», el valor «%04lu» y «\r\n» para establecer el final de los datos.


void enviar_sensor2()
{
   int16 sensor2;

   set_adc_channel (1);//Lectura Vout del potenciometro en PIN AN1
   delay_us(10);
   sensor2 = read_adc();// lectura del valor

   printf(usb_cdc_putc,"SEN02%04lu\r\n",sensor2);
}

En la función «main» se coloca una sentencia «if» donde se ejecutar cada subrutina para enviar los datos de cada pin analógico cuando se asigne un puerto COM al microcontrolador.


      if(usb_enumerated()) //esta asignado a un puerto COM?
      {
         enviar_sensor1();
         delay_ms(50);
         enviar_sensor2();
         delay_ms(50);
      }

Código completo USB


#FUSES NOMCLR

#define USB_CONFIG_BUS_POWER 500 //500ma corriente de entrada

#include "usb_cdc.h"

void enviar_sensor1()
{
   int16 sensor1;

   set_adc_channel (0);//Lectura Vout del potenciometro en PIN AN0
   delay_us(10);
   sensor1 = read_adc();// lectura del valor

   printf(usb_cdc_putc,"SEN01%04lu\r\n",sensor1);
}

void enviar_sensor2()
{
   int16 sensor2;

   set_adc_channel (1);//Lectura Vout del potenciometro en PIN AN0
   delay_us(10);
   sensor2 = read_adc();// lectura del valor

   printf(usb_cdc_putc,"SEN02%04lu\r\n",sensor2);
}

void main()
{
   usb_init();

   setup_adc(ADC_CLOCK_INTERNAL);

   while(TRUE)
   {
      if(usb_enumerated()) //esta asignado a un puerto COM?
      {
         enviar_sensor1();
         delay_ms(50);
         enviar_sensor2();
         delay_ms(50);
      }
   }
}

Circuito de conexió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)

Lo siguiente es enviar los datos a la computadora, en este caso se envía el valor de los pines analógicos.

Se habilita la conversión analógica digital.


setup_adc(ADC_CLOCK_INTERNAL); 

Para obtener el valor de los pines analógicos se habilita la lectura del canal analógico con «set_adc_channel()» y después se realiza la lectura del valor con «read_adc()», para finalmente guardarlo en la variable asignada.

Para enviar datos por el puerto RS232, se utiliza la función «printf()», colocando en la cadena de caracteres un identificador para el valor «SEN01», el valor «%04lu» y «\r\n» para establecer el final de los datos.


 void enviar_sensor1()
{
   int16 sensor1;

   set_adc_channel (0);//Lectura Vout del potenciometro en PIN AN0
   delay_us(10);
   sensor1 = read_adc();// lectura del valor

   printf("SEN01%04lu\r\n",sensor1);
}

Para enviar el valor del pin «AN1» se realiza el mismo procedimiento y en la cadena de caracteres se coloca un identificador para el valor «SEN02», el valor «%04lu» y «\r\n» para establecer el final de los datos.


void enviar_sensor2()
{
   int16 sensor2;

   set_adc_channel (1);//Lectura Vout del potenciometro en PIN AN1
   delay_us(10);
   sensor2 = read_adc();// lectura del valor

   printf("SEN02%04lu\r\n",sensor2);
}

En la función «main» se coloca una sentencia «if» donde se ejecutara cada subrutina para enviar los datos por el puerto RS232 a la computadora.


      enviar_sensor1();
      delay_ms(50);
      enviar_sensor2();
      delay_ms(50);

Código completo RS232


#FUSES NOMCLR

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

void enviar_sensor1()
{
   int16 sensor1;

   set_adc_channel (0);//Lectura Vout del potenciometro en PIN AN0
   delay_us(10);
   sensor1 = read_adc();// lectura del valor

   printf("SEN01%04lu\r\n",sensor1);
}

void enviar_sensor2()
{
   int16 sensor2;

   set_adc_channel (1);//Lectura Vout del potenciometro en PIN AN1
   delay_us(10);
   sensor2 = read_adc();// lectura del valor

   printf("SEN02%04lu\r\n",sensor2);
}

void main()
{  
   setup_adc(ADC_CLOCK_INTERNAL); 
   while(TRUE)
   {
      enviar_sensor1();
      delay_ms(50);
      enviar_sensor2();
      delay_ms(50);
   }
}
Scroll al inicio