Interfaz Gráfica en Python «Transmisió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 enviar el valor de un Slider al micrcontrolador para controlar el ciclo de trabajo de una señal PWM.

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

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=" Transmisión")
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="Control PWM", 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 «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 el valor de cada Slider al microcontrolador para controlar en ciclo de trabajo dos señales PWM

Se crea un Slider con un rango de valor de 0 a 255 y con la función «Slider1.bind» ejecuta la función «valor_slider1» solo cuando se deja de pulsar el botón del «Slider1».


# Crear un slider
slider1 = tk.Scale(ventana, label="Slider 1", from_=0, to=255, length=270, width=30, font=("Arial", 16), orient="horizontal")
slider1.place(x=10, y=90)
slider1.bind("&ltButtonRelease&gt", valor_slider1) #Ejecuta la funcion cuando se suelta el botón del mouse en el slider

La función «valor_slider1» envía los datos al microcontrolador. Inicialmente obtiene el valor del «slider1» y para poder enviar ese valor se formatea para convertirlo en 3 caracteres, ejemplo: «1 = 001». Se crea una cadena de caracteres colocando un comando identificador del Slider «val01», seguido del número «%s» y «\r\n», finalmente para enviar los datos, la cadena de caracteres se convierte en bytes y con la función «conectar.write()» se envían los datos por la comunicación serial al microcontrolador.


def valor_slider1(event):
        # Esta función se ejecutará cuando el usuario suelte el slider después de moverlo
        value = slider1.get()  # Obtener el valor actual del slider
        value_format = "{:03d}".format(value) # Convierte el valor en cadena con 3 caracteres "001"
        comando_str = "val01%s\r\n" %(value_format) # Datos a enviar
        comando_byte = comando_str.encode() #Convierte la cadena en Bytes
        conectar.write(comando_byte) #envía los datos por la comunicación serial  

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=" Transmisión")
ventana.geometry("305x350")
ventana.config(bg="#405158")

# Etiqueta para mostrar el titulo del proyecto
etiqueta = tk.Label(ventana, text="Control PWM", 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()

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

def valor_slider1(event):
        # Esta función se ejecutará cuando el usuario suelte el slider después de moverlo
        value = slider1.get()  # Obtener el valor actual del slider
        value_format = "{:03d}".format(value) # Convierte el valor en cadena con 3 caracteres "001"
        comando_str = "val01%s\r\n" %(value_format) # Datos a enviar
        comando_byte = comando_str.encode() #Convierte la cadena en Bytes
        conectar.write(comando_byte) #envía los datos por la comunicación serial  

def valor_slider2(event):
        # Esta función se ejecutará cuando el usuario suelte el slider después de moverlo
        value = slider2.get()  # Obtener el valor actual del slider
        value_format = "{:03d}".format(value) # Convierte el valor en cadena con 3 caracteres "001"
        comando_str = "val02%s\r\n" %(value_format) # Datos a enviar
        comando_byte = comando_str.encode() #Convierte la cadena en Bytes
        conectar.write(comando_byte) #envía los datos por la comunicación serial  


# Crear un slider
slider1 = tk.Scale(ventana, label="Slider 1", from_=0, to=255, length=270, width=30, font=("Arial", 16), orient="horizontal")
slider1.place(x=10, y=90)
slider1.bind("&ltButtonRelease&gt", valor_slider1) #Ejecuta la funcion cuando se suelta el botón del mouse en el slider


# Crear un slider
slider2 = tk.Scale(ventana, label="Slider 2", from_=0, to=255, length=270, width=30, font=("Arial", 16), orient="horizontal")
slider2.place(x=10, y=200)
slider2.bind("&ltButtonRelease&gt", valor_slider2) #Ejecuta la funcion cuando se suelta el botón del mouse en el slider

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

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

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 «datos()».


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
      {
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)

         datos();
         return;
      }
      i++;
   }
}

En la subrutina «datos()» se identificara el comando y el valor correspondiente, para ello se declaran dos variables para separa el comando del valor.


   char ID_val[6];
   char num[4];

   char val01[6] = "val01";
   char val02[6] = "val02";

La variable «recibido[]» contendrá el comando y el valor recibido, por lo que, en el primer ciclo «for()» se obtienen los primeros 5 caracteres correspondientes al comando, sea «val01» o «val02» y se guarda en la variable «ID_val[]», en el segundo ciclo «for()» se obtienen los 3 caracteres siguientes correspondientes al valor y se guarda en la variable «num[]».


       for (int i=0; i<=4; ++i)
      {
         ID_val[i] = recibido[i]; //identifica la variable
      }

      for (int i=0; i<=2; ++i)
      {
         num[i] = recibido[i+5]; //guarda el valor de la variable
      }

Los 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()» donde se utiliza la función «atoi» para convertir el valor en «num» que es una variable de caracteres a una variable de 8 bits, finalmente el valor se guarda en la variable correspondiente al comando sea «val01_num» o «val02_num».


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

      if(result==0)
      {
         val01_num = (int8)atoi(num);  //convierte la cadena en un numero de 8bits
         return;
      }

      result = strcmp(ID_val, val02);

      if(result==0)
      {
         val02_num = (int8)atoi(num); //convierte la cadena en un numero de 8bits
         return;
      }

En este ejemplo se utilizara el valor recibido para controlar el ciclo de trabajo de dos señales PWM de salida en los pines CCP1 y CCP2.

Se habilita el timer2 para generar la señal de salida.


   setup_timer_2(T2_DIV_BY_16,63,1);

   setup_ccp1(CCP_PWM);//habilita el pin ccp1
   setup_ccp2(CCP_PWM);//habilita el pin ccp2

Se colocan las variables en las funciones para controlar el ciclo de trabajo.


      set_pwm1_duty((int16)val01_num);//100% duty clicle = numero 255
      set_pwm2_duty((int16)val02_num);

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


#include "stdlib.h"

#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"

char recibido[15];

int8 val01_num = 0;
int8 val02_num = 0;

void datos();

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
      {
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)

         datos();
         return;
      }
      i++;
   }
}

void datos()
{
   signed int8 result;

   char ID_val[6];
   char num[4];

   char val01[6] = "val01";
   char val02[6] = "val02";

   memset(ID_val, 0, sizeof(ID_val)); //limpia los registros de la varible ID_val
   memset(num, 0, sizeof(num)); //limpia los registros de la varible num

       for (int i=0; i<=4; ++i)
      {
         ID_val[i] = recibido[i]; //identifica la variable
      }

      for (int i=0; i<=2; ++i)
      {
         num[i] = recibido[i+5]; //guarda el valor de la variable
      }

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

      if(result==0)
      {
         val01_num = (int8)atoi(num);  //convierte la cadena en un numero de 8bits
         return;
      }

      result = strcmp(ID_val, val02);

      if(result==0)
      {
         val02_num = (int8)atoi(num); //convierte la cadena en un numero de 8bits
         return;
      }
}

void main()
{
   usb_init(); 
   
   setup_timer_2(T2_DIV_BY_16,63,1);

   setup_ccp1(CCP_PWM);//habilita el pin ccp1
   setup_ccp2(CCP_PWM);//habilita el pin ccp2
   
   while(TRUE)
   {
      set_pwm1_duty((int16)val01_num);//100% duty clicle = numero 255
      set_pwm2_duty((int16)val02_num);

      //TODO: User Code
   }

}

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)

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 «datos()».


#int_rda
void recibe()
{
   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
      {
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)

         datos();
         return;
      }
      i++;
   }
}

En la subrutina «datos()» se identificara el comando y el valor correspondiente, para ello se declaran dos variables para separa el comando del valor.


   char ID_val[6];
   char num[4];

   char val01[6] = "val01";
   char val02[6] = "val02";

La variable «recibido[]» contendrá el comando y el valor recibido, por lo que, en el primer ciclo «for()» se obtienen los primeros 5 caracteres correspondientes al comando, sea «val01» o «val02» y se guarda en la variable «ID_val[]», en el segundo ciclo «for()» se obtienen los 3 caracteres siguientes correspondientes al valor y se guarda en la variable «num[]».


       for (int i=0; i<=4; ++i)
      {
         ID_val[i] = recibido[i]; //identifica la variable
      }

      for (int i=0; i<=2; ++i)
      {
         num[i] = recibido[i+5]; //guarda el valor de la variable
      }

Los 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()» donde se utiliza la función «atoi» para convertir el valor en «num» que es una variable de caracteres a una variable de 8 bits, finalmente el valor se guarda en la variable correspondiente al comando sea «val01_num» o «val02_num».


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

      if(result==0)
      {
         val01_num = (int8)atoi(num);  //convierte la cadena en un numero de 8bits
         return;
      }

      result = strcmp(ID_val, val02);

      if(result==0)
      {
         val02_num = (int8)atoi(num); //convierte la cadena en un numero de 8bits
         return;
      }

En este ejemplo se utilizara el valor recibido para controlar el ciclo de trabajo de dos señales PWM de salida en los pines CCP1 y CCP2.

Se habilita el timer2 para generar la señal de salida.


   setup_timer_2(T2_DIV_BY_16,63,1);

   setup_ccp1(CCP_PWM);//habilita el pin ccp1
   setup_ccp2(CCP_PWM);//habilita el pin ccp2

Se colocan las variables en las funciones para controlar el ciclo de trabajo.


      set_pwm1_duty((int16)val01_num);//100% duty clicle = numero 255
      set_pwm2_duty((int16)val02_num);

Código completo RS232


#include "stdlib.h"

#FUSES NOMCLR

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

char recibido[15];

int8 val01_num = 0;
int8 val02_num = 0;

void datos();

#int_rda
void recibe()
{
   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
      {
         recibido[i-1] = '\0';  //  \r = \0 (caracter nulo)
         recibido[i] = '\0';   //  \n = \0 (caracter nulo)

         datos();
         return;
      }
      i++;
   }
}

void datos()
{
   signed int8 result;

   char ID_val[6];
   char num[4];

   char val01[6] = "val01";
   char val02[6] = "val02";

   memset(ID_val, 0, sizeof(ID_val)); //limpia los registros de la varible ID_val
   memset(num, 0, sizeof(num)); //limpia los registros de la varible num

       for (int i=0; i<=4; ++i)
      {
         ID_val[i] = recibido[i]; //identifica la variable
      }

      for (int i=0; i<=2; ++i)
      {
         num[i] = recibido[i+5]; //guarda el valor de la variable
      }

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

      if(result==0)
      {
         val01_num = (int8)atoi(num);  //convierte la cadena en un numero de 8bits
         return;
      }

      result = strcmp(ID_val, val02);

      if(result==0)
      {
         val02_num = (int8)atoi(num); //convierte la cadena en un numero de 8bits
         return;
      }
}

void main()
{

   enable_interrupts(int_rda);
   enable_interrupts(global);

   setup_timer_2(T2_DIV_BY_16,63,1);

   setup_ccp1(CCP_PWM);//habilita el pin ccp1
   setup_ccp2(CCP_PWM);//habilita el pin ccp2   

   while(TRUE)
   {
      set_pwm1_duty((int16)val01_num);//100% duty clicle = numero 255
      set_pwm2_duty((int16)val02_num);
   }
}
Scroll al inicio