Saltar al contenido

Control PID en PIC Motor-Generador

En esta entrada veremos como implementar un control PID en PIC sobre un sistema conformado por dos motores DC utilizando el compilador CCS C y explicando en detalle los conceptos del Controlador Digital PID.

Esta entrada contiene dos videos adicionales que explican en detalle el procedimiento y si te interesan estos temas, te invito a suscribirte al canal de YouTube para que no te pierdas los nuevos videos:

Si te interesa, puedes ver otras entradas donde implementamos el Control PID en PIC o Arduino para otros procesos:

Planta de Motores DC: Motor – Generador

Antes de realizar el control pid para motor dc con pic debemos inicialmente entender el comportamiento del sistema.

El proceso escogido para implementar el control PID en un microcontrolador PIC es conocido como Motor-Generador conformado por dos motores de corriente directa DC de 12v y es mostrado en la siguiente fotografía:

Motor-Generador

El funcionamiento del proceso es el siguiente:

Este proceso consta de dos motores DC de 12 voltios los cuales están acoplados en sus ejes por medio de poleas.

Uno de estos motores funciona propiamente como motor, entonces cuando dicho motor es excitado con un voltaje de alimentación comienza a girar a una velocidad proporcional al voltaje de entrada.

El giro del motor hace que el eje del segundo motor (Conocido como generador) comience a girar debido al acople entre ejes.

Cuando el eje del generador está en movimiento, este comienza a generar voltaje directo el cual puede ser tomado de los dos terminales del motor.

Aquí tenemos un sistema a ser Controlado:

  • Como variable manipulada tendremos el Voltaje de Entrada del Motor
  • Como variable controlada tendremos el Voltaje generado por el Generador.

Adicionalmente puede ser observado en la fotografía, que el sistema cuenta con dos bombillos que simularán la carga del sistema, esto seria la perturbación que constantemente afectan los sistemas de control.

Nuestro control debe ser lo suficientemente robusto para rechazar esas perturbaciones y llevar la variable controlada de nuevo para el Set-Point.

DESCARGAR DIAGRAMA DEL CIRCUITO

El circuito básico de este proceso MOTOR-GENERADOR puede ser descargado dando click al siguiente enlace

Para este ejemplo inicialmente debemos obtener el modelo matemático del proceso (Identificar la planta) y posteriormente con la función de transferencia identificada haremos la implementación de PID con PIC. Estos fueron los mismos pasos que utilizamos en la entrada anterior del PID del horno.

Identificación del Proceso Motor – Generador

Mostraré un procedimiento para realizar la identificación del proceso, pero aquí les dejaré su primera tarea.

Lo ideal es que lo siguiente que vamos a ver ustedes lo implementen con una comunicación serial (Click aca para ver entrada de comunicación serial), enviando los datos al computador para poder obtener la curva de reacción del proceso y posteriormente obtener la función de transferencia.

Si desean tener un ejemplo de como hacer eso, pueden ver como se hizo la identificación de un horno de temperatura con PIC enviando los datos al computador y realizando la identificación con el software de Matlab.

puede interesarte alguna de estas entradas del sitio web:

Como en este momento no poseo un FT232 para poder comunicar el PIC con mi Computador, decidí por hacer una identificación un poco más manual, utilizando un LCD 4×20.

El circuito para realizar la identificación del sistema con los Motores DC es el siguiente:

PID con PIC

NOTA: En este circuito se aplica lo aprendido en la entrada del Teclado Matricial 4×4 y como se está utilizando un LCD4x20, ya habíamos visto que el compilador CCS C Compiler ya trae lista la librería para trabajar con este teclado, pero solo sirve para el PUERTO B, por eso en el video de la identificación de Youtube, se muestra como hacer la modificación de la libreria para que trabaje con el PUERTO D.

La idea para el modelado de los motores DC a través de una identificación del procesos consiste en lo siguiente:

Tenemos nuestro proceso MOTOR-GENERADOR, el cual no tenemos idea de cual puede ser su representación matemática para poder sintonizar nuestro controlador PID, pero la idea entonces es obtener el modelo del motor de corriente directa acoplado al motor que sirve como generador.

Procedimiento de la identificación del sistema

Para el modelado matemático de los motores DC vamos a obtener su Función de Transferencia en el dominio de Laplace, es decir que utilizaremos la teoría de Control Lineal.

Todos los procesos en la industria y en la vida real en si, son procesos NO LINEALES, recayendo entonces en la teoría no lineal que es mucho mas compleja.

Ahora, dado que en la industria en la mayoría de los casos, los procesos siempre están trabajando sombre un punto de operación (Un mismo setpoint) y raramente son movidos para otro punto de operación, podemos aproximar ese proceso no lineal en un modelo Lineal por medio de una función de transferencia.

Entonces realizaremos el siguiente procedimiento:

  1. Exitaremos el sistema motor – generador con un set-point o un escalón del mismo valor al punto de operación donde queremos operar el proceso, para este ejemplo yo exité el sistema con un escalón del 40%, colocando entonces un PWM en la salida del CCP1 un ancho de pulso proporcional al 40% (Ver entrada del PWM con PIC).
  2. Establezco el tiempo de muestreo en que quiero tomar datos del MOTOR-GENERADOR (Dado que este sistema es sumamente Rápido, coloque un periodo de muestreo de 10ms).
  3. De esa manera el PIC exita el motor por causa de los 40% del PWM y comienza a guardar los valores de voltaje generados por el motor generador cada 10ms.
  4. Estos datos los guarda en un vector de 45 posiciones.
  5. Una vez las 45 posiciones son llenadas, el PIC coloca el PWM en Cero y muestra todos los datos en la pantalla del LCD. Este código de identificación se encuentra al Final de este Post o también en el Video de Youtube, donde se explica en detalle.

Convertir Señal PWM a Voltaje Continuo

Como la Planta MOTOR-GENERADOR solo acepta en su entrada voltaje continuo DC de 0 a 5 voltios, y nosotros estamos generando un PWM, es necesario transformar o convertir ese PWM en un valor de voltaje continuo que dependa del ancho de pulso.

Para eso se implementa el siguiente filtro pasa bajos que hace esa función.

Filtro Pasa Bajos

Análisis de los Datos de la Planta

Los datos de voltaje mostrados en el LCD, los copie en un archivo de excel junto con los datos de tiempo (periodo de muestreo) y los datos de entrada o escalón (que son los 40%). Estos datos los muestro tanto en Voltaje como en Porcentaje.

La respuesta obtenida en porcentaje se muestra a continuación:

Identificacion del Motor-Generador

Tipicamente, todo proceso industrial es representado por funciones de transferencia de primer o segundo orden.

A continuación es expuesto un modelo de primer orden con retardo sobre el cual vamos a trabajar para poder modelar el comportamiento de nuestra planta.

P(s)=\dfrac{Ke^{-\theta s}}{\tau s +1}

El modelo está representado en su forma continua, es decir en el dominio de Laplace. donde K es la ganancia del sistema, \tau es la constante de tiempo del proceso y \theta es el retardo del proceso.

Vemos que la gráfica el voltaje generado (linea azul) comienza en 0% y llega hasta 32% y que para conseguir esta respuesta tuvimos que alimentar el motor con una exitación del 40% (linea naranja). Así podemos obtener la ganancia del proceso con la siguiente formula:

K=\dfrac{Y_{final}-Y_{inicial}}{U_{final}-U_{inicial}}

K=\dfrac{32-0}{40-0}=0.8

Notemos que el voltaje generado (linea zul), al comienzo ella se demora en comenzar a subir, ese tiempo que ella se queda en cero, es conocido como el retardo del sistema y en este caso equivale a 0,03 segundos o 30ms.

\theta=30

Observemos que el voltaje generado en la gráfica, se comienza a estabilizar mas o menos a los 0.26 segundos, pero debemos restarle los 0,03 segundos del retardo, entonces el tiempo de establecimiento seria 0,23 segundos, así podemos obtener la constante de tiempo de la variable temperatura con la siguiente formula:

T_{establecimieto}=4\tau

O sea que el \tau=57,5.

Así que en términos generales, nuestro proceso del MOTOR GENERADOR está representado por la siguiente función de transferencia:

P(s)=\dfrac{0,8e^{-30 s}}{57,5 s +1}

Con nuestro proceso de los motores DC identificado podemos calcular los parámetros del control pid para motor dc con pic (k_p,t_i,t_d), para eso utilizamos cualquiera de las tres técnicas vistas en la entrada el PID del horno.

Suscríbete a este sitio WEB para estar enterado de las nuevas entradas!

Comprar Componentes en Amazon

Los motores DC de 6V-12V son una excelente opción para proyectos de automatización casera debido a su alto torque y velocidad variable. Poseen un diseño compacto y una gran eficiencia energética para proyectos de bricolaje y robótica. Tal como el montaje mostrado aquí del Motor-Generador, adquiere tus motores directamente en Amazon.

Algoritmo de Control PID

En este sitio web hemos hablado várias veces de los algoritmos de control, en especial del control PID.

PID sintonia

El concepto que veremos a continuación puedes aplicarlo a cualquier otro sistema como por ejemplo realizar un control de velocidad pid con pic.

Una ves tenemos nuestro modelo matemático que representa nuestro proceso MOTOR-GENERADOR en el escalón del 40%, podremos calcular nuestro control PID en PIC para que controle adecuadamente el proceso en ese punto de operación.

Para este caso PARTICULAR de mi MOTOR-GENERADOR, es un proceso de muy baja NO LINEALIDAD, es decir que tiene un comportamiento casi lineal, entonces voy a poder controlar con facilidad el proceso en cualquier punto de operación diferente a el 40%. Esto en la mayoría de los procesos NO sucede.

Sistema de Control con PIC

Vamos a detallar como implementar el control PID digital con PIC paso a paso, para eso vamos a utilizar el control discreto PID que vimos en la entrada pasada  dado por:

C(z^{-1})=\dfrac{u(k)}{e(k)}=\dfrac{q_0+q_1z^{-1}+q_2z^{-2}}{1-z^{-1}}

donde:

q_0=k_p\left [ 1+\dfrac{T}{2t_i}+\dfrac{t_d}{T} \right ]

q_1=-k_p\left [ 1-\dfrac{T}{2t_i}+\dfrac{2t_d}{T} \right ]

q_2=\dfrac{k_pt_d}{T}

Con esto, la ley de control que vamos a ingresar a nuestro PIC sale del control PID discreto (Despejando u)

u(k)(1-z^{-1})=q_0e(k)+q_1z^{-1}e(k)+q_2z^{-2}e(k)

u(k)-u(k)z^{-1}=q_0e(k)+q_1z^{-1}e(k)+q_2z^{-2}e(k)

u(k)=u(k)z^{-1}+q_0e(k)+q_1z^{-1}e(k)+q_2z^{-2}e(k)

aplicando transformada inversa Z obtenemos la ecuacion en diferencias:

u(k)=u(k-1)+q_0e(k)+q_1e(k-1)+q_2e(k-2)

Como tiempo de muestreo para el control PID utilizamos el criterio:

T=\dfrac{\tau}{20}=\dfrac{57,5}{20}=2,87\approx 3ms

NOTA: Como vemos el tiempo de muestreo debe ser de 3ms, o sea que realmente debe ser rápido nuestro microcontrolador para poder alcanzar tal velocidad, para ello por la alta velocidad lo más recomendable es usar un cristal externo de 20.000Mhz. En mi caso personal, no cuento con este cristal, entonces voy a tener que implementarlo con un cristal de 4.000Mhz, por lo que no voy a poder cumplir el criterio del tiempo de muestreo. Esto me puede traer serios problemas de estabilidad, pero al hacer las pruebas, mi periodo de muestreo alcanzado fue de 11ms y el sistema consiguió responder adecuadamente, principalmente porque todavía es 5 veces menor que  la constante de tiempo. Si se hubieran presentado problemas de estabilidad por causa que no puedo respetar el periodo de muestreo, lo que hubiera hecho es ponderar un poco el error del controlador PID, es decir multiplicar el error por valores entre (0 – 1).

Código del Control PID en PIC Motor Generador

A continuación se presenta el código de ejemplo del control PID con PIC para que lo copies y lo pegues en tu compilador y puedas reproducirlo.

Puedes realizar este control PID en PIC en un 16F887, 16F877A, 18F4550 o cualquier otro microcontrolador que dispongas.

Recuerda que para ver el código debes compartir el contenido de este blog para que más personas se beneficien de esta información.

Descargar librería del LCD4x20 para puertoD:

Código de la Identificación del Sistema Motor Generador

#INCLUDE <16F887.h>
#DEVICE ADC=10
#USE DELAY(CLOCK=4000000)
#FUSES XT,NOPROTECT,NOWDT,NOBROWNOUT,PUT,NOLVP
#DEFINE USE_PORTB_KBD   //Por defecto el teclado se conecta al puerto D,
                        //como el microcontrolador que se esta usando
                        //no tiene puerto D se conecta al puerto B.*/
#INCLUDE <LCD420D.C>
#INCLUDE <KBD4x4.C>  //Incluir en el encabezado el driver para
                      //manejar el teclado telefónico MODIFICADO
#include <stdlib.h>
#include <string.h>

#use     standard_io(b) 
#define  KEYHIT_DELAY   200    //Tiempo de espera del teclado en milisegundos
#byte PORTB= 6
#byte PORTC= 7
#BYTE PORTA= 5
#BYTE PORTD= 8

int16 adc,control=0;
long T=100; //100 ms
float R=0;
char c;
int opcion=0;


/*===========================================================================*/
/*=======================       FUNCION TECLA         =======================*/
/*===========================================================================*/
//Funcion encargada de esperar a que se presione una tecla 
char tecla(void)
{
   char c;
   do{ //espera hasta que se presione una tecla
      c=kbd_getc(); //Captura valor del teclado
     }
   while(c=='
#INCLUDE <16F887.h>
#DEVICE ADC=10
#USE DELAY(CLOCK=4000000)
#FUSES XT,NOPROTECT,NOWDT,NOBROWNOUT,PUT,NOLVP
#DEFINE USE_PORTB_KBD   //Por defecto el teclado se conecta al puerto D,
//como el microcontrolador que se esta usando
//no tiene puerto D se conecta al puerto B.*/
#INCLUDE <LCD420D.C>
#INCLUDE <KBD4x4.C>  //Incluir en el encabezado el driver para
//manejar el teclado telefónico MODIFICADO
#include <stdlib.h>
#include <string.h>
#use     standard_io(b) 
#define  KEYHIT_DELAY   200    //Tiempo de espera del teclado en milisegundos
#byte PORTB= 6
#byte PORTC= 7
#BYTE PORTA= 5
#BYTE PORTD= 8
int16 adc,control=0;
long T=100; //100 ms
float R=0;
char c;
int opcion=0;
/*===========================================================================*/
/*=======================       FUNCION TECLA         =======================*/
/*===========================================================================*/
//Funcion encargada de esperar a que se presione una tecla 
char tecla(void)
{
char c;
do{ //espera hasta que se presione una tecla
c=kbd_getc(); //Captura valor del teclado
}
while(c=='\0'); 
return(c);
}
/*===========================================================================*/
/*=======================    FUNCION TECLA CON TIMER  =======================*/
/*===========================================================================*/
// Pregunta por una Tecla por un tiempo, si no hay actividad, deja de preguntar
// y deja que el PIC continue con su trabajo
char tecla_time(void) {
char c='\0';
unsigned int16 timeout;
timeout=0;
c=kbd_getc(); //Captura valor del teclado
while(c=='\0' && (++timeout< (KEYHIT_DELAY*100)))
{
delay_us(10);
c=kbd_getc(); //Captura valor del teclado
}
return(c);
}
/*===========================================================================*/
/*============   FUNCION PARA DIGITAR ESCALÓN/SETPOINT  =====================*/
/*===========================================================================*/
long escalon(int nd)
{
//Esta funcion captura el escalon desde el teclado, si el proceso está tomando
//datos el escalon sirve para exitar el sistema, por otro lado si el sistema
//está controlando, el escalo sirve para establecer el setpoint del sistema
long val;
int i;
char str[5]; //Variable tipo String
str[0]='0';
for(i=0;i<nd;i++)
{
c=tecla();  //Lee el valor del teclado y espera hasta que alguna tecla se pulse
if(c!='*'){
//Muestra el digito presionado en el LCD
lcd_gotoxy(5+i,4);
lcd_putc(c);
//Almacena el dato presionado en la variable String
str[i]=c;
}
else{i=nd;} //Si se presiona * sale del For      
}
val = atol(str); //Convierte el String en un valor numerico
return(val);
}
/*===========================================================================*/
/*======================= IDENTIFICACION DEL SISTEMA  =======================*/
/*===========================================================================*/
void identificacion(void)
{
int16 dat[45]; //Variable guarda 45 datos de la identificacion
float aux;
int i,ban=0;
LCD_PUTC("TOMANDO DATOS....");
control=R*10;
set_pwm1_duty(control); //Exita el sistema
for(i=0;i<45;i++)
{
//Lee los datos
dat[i]=read_adc(); //Leer ADC
//dat[i]=adc*5000.0/1024.0;
delay_ms(T);
}
set_pwm1_duty(0); //Desactiva el sistema
LCD_PUTC("\f");
//Muestra los datos en la pantalla del LCD utilizando la tecla A para subir
//y utilizando la tecla B para bajar, estos datos deben ser graficados (EXCEL)
//para poder montar la curva de reacción del sistema y poder determinar los
//Parametros de la Planta
i=0;
while(ban==0)
{
aux=dat[i]*5000.0/1024.0;
lcd_gotoxy(1,1);
printf(lcd_putc,"%d. %1.3f",i+1,aux/1000);
aux=dat[i+1]*5000.0/1024.0;
lcd_gotoxy(1,2);
printf(lcd_putc,"%d. %1.3f",i+2,aux/1000);
aux=dat[i+2]*5000.0/1024.0;
lcd_gotoxy(1,3);
printf(lcd_putc,"%d. %1.3f",i+3,aux/1000);
aux=dat[i+3]*5000.0/1024.0;
lcd_gotoxy(1,4);
printf(lcd_putc,"%d. %1.3f",i+4,aux/1000);
c=tecla();      //Lee el valor del teclado y espera hasta que alguna tecla se pulse
i =(c=='B' && i!=41) ? i+1:i; //Si es B incremente i
i =(c=='A' && i!=0) ? i-1:i; //Si es A decremente i
ban =(c=='*') ? 1:0;   //Si es * salga del while
}
}
/*===========================================================================*/
/*=======================    FUNCION DEL PRINCIPAL    =======================*/
/*===========================================================================*/
void main()
{
port_b_pullups (0xFF);  //Utiliza las resistencias PULL UP internas del puerto B
set_tris_c(0);
set_tris_d(0);
setup_timer_2(t2_div_by_4,249,1);   //Configuracion de Timer 2 para establecer frec. PWM a 1kHz
setup_ccp1(ccp_pwm);                //Configurar modulo CCP1 en modo PWM
set_pwm1_duty(0);                   //Dejo en cero la salida PWM
setup_adc_ports(sAN0);              //Configurar ADC (Lectura de temperatura)
setup_adc(adc_clock_internal);      //Reloj interno para la conversion analoga digital)
set_adc_channel(0);                 //Seleccionar Canal 0 para sensor de Temperatura
LCD_INIT();                         //Inicializo el LCD
LCD_PUTC("\f");                     //Limpio el LCD
while(1)
{
lcd_gotoxy(1,1);
LCD_PUTC("TOMAR DATOS PLANTA");
lcd_gotoxy(1,2);
LCD_PUTC("Escalon(0-100):");
lcd_gotoxy(1,3);
LCD_PUTC("y presione *");
lcd_gotoxy(1,4);
LCD_PUTC("SP:           ");
R=escalon(3); //Llama la funcion para digitar el escalon de exitacion
//Valida si R esta entre 0 y 100 (Esto es otra forma de usar el if - else)
R =(R > 100) ? 100:R;
//Muestra el SETPOINT en pantalla
lcd_gotoxy(1,4);
printf(lcd_putc,"SP: %3.1f",R);
delay_ms(2000);
//Aprovecho la funcion escalon para digitar el periodo de muestreo
lcd_gotoxy(1,2);
LCD_PUTC("Periodo de M(0-1000):");
lcd_gotoxy(1,3);
LCD_PUTC("y presione *");
lcd_gotoxy(1,4);
LCD_PUTC(" T:        ");
T=escalon(4);
//Muestra el periodo de muestreo en pantalla
lcd_gotoxy(1,4);
printf(lcd_putc,"T: %ld  ",T);
delay_ms(2000);
LCD_PUTC("\f");
identificacion();
opcion=0; //Regresa al menu principal
}
}
'); return(c); } /*===========================================================================*/ /*======================= FUNCION TECLA CON TIMER =======================*/ /*===========================================================================*/ // Pregunta por una Tecla por un tiempo, si no hay actividad, deja de preguntar // y deja que el PIC continue con su trabajo char tecla_time(void) { char c='
#INCLUDE <16F887.h>
#DEVICE ADC=10
#USE DELAY(CLOCK=4000000)
#FUSES XT,NOPROTECT,NOWDT,NOBROWNOUT,PUT,NOLVP
#DEFINE USE_PORTB_KBD   //Por defecto el teclado se conecta al puerto D,
//como el microcontrolador que se esta usando
//no tiene puerto D se conecta al puerto B.*/
#INCLUDE <LCD420D.C>
#INCLUDE <KBD4x4.C>  //Incluir en el encabezado el driver para
//manejar el teclado telefónico MODIFICADO
#include <stdlib.h>
#include <string.h>
#use     standard_io(b) 
#define  KEYHIT_DELAY   200    //Tiempo de espera del teclado en milisegundos
#byte PORTB= 6
#byte PORTC= 7
#BYTE PORTA= 5
#BYTE PORTD= 8
int16 adc,control=0;
long T=100; //100 ms
float R=0;
char c;
int opcion=0;
/*===========================================================================*/
/*=======================       FUNCION TECLA         =======================*/
/*===========================================================================*/
//Funcion encargada de esperar a que se presione una tecla 
char tecla(void)
{
char c;
do{ //espera hasta que se presione una tecla
c=kbd_getc(); //Captura valor del teclado
}
while(c=='\0'); 
return(c);
}
/*===========================================================================*/
/*=======================    FUNCION TECLA CON TIMER  =======================*/
/*===========================================================================*/
// Pregunta por una Tecla por un tiempo, si no hay actividad, deja de preguntar
// y deja que el PIC continue con su trabajo
char tecla_time(void) {
char c='\0';
unsigned int16 timeout;
timeout=0;
c=kbd_getc(); //Captura valor del teclado
while(c=='\0' && (++timeout< (KEYHIT_DELAY*100)))
{
delay_us(10);
c=kbd_getc(); //Captura valor del teclado
}
return(c);
}
/*===========================================================================*/
/*============   FUNCION PARA DIGITAR ESCALÓN/SETPOINT  =====================*/
/*===========================================================================*/
long escalon(int nd)
{
//Esta funcion captura el escalon desde el teclado, si el proceso está tomando
//datos el escalon sirve para exitar el sistema, por otro lado si el sistema
//está controlando, el escalo sirve para establecer el setpoint del sistema
long val;
int i;
char str[5]; //Variable tipo String
str[0]='0';
for(i=0;i<nd;i++)
{
c=tecla();  //Lee el valor del teclado y espera hasta que alguna tecla se pulse
if(c!='*'){
//Muestra el digito presionado en el LCD
lcd_gotoxy(5+i,4);
lcd_putc(c);
//Almacena el dato presionado en la variable String
str[i]=c;
}
else{i=nd;} //Si se presiona * sale del For      
}
val = atol(str); //Convierte el String en un valor numerico
return(val);
}
/*===========================================================================*/
/*======================= IDENTIFICACION DEL SISTEMA  =======================*/
/*===========================================================================*/
void identificacion(void)
{
int16 dat[45]; //Variable guarda 45 datos de la identificacion
float aux;
int i,ban=0;
LCD_PUTC("TOMANDO DATOS....");
control=R*10;
set_pwm1_duty(control); //Exita el sistema
for(i=0;i<45;i++)
{
//Lee los datos
dat[i]=read_adc(); //Leer ADC
//dat[i]=adc*5000.0/1024.0;
delay_ms(T);
}
set_pwm1_duty(0); //Desactiva el sistema
LCD_PUTC("\f");
//Muestra los datos en la pantalla del LCD utilizando la tecla A para subir
//y utilizando la tecla B para bajar, estos datos deben ser graficados (EXCEL)
//para poder montar la curva de reacción del sistema y poder determinar los
//Parametros de la Planta
i=0;
while(ban==0)
{
aux=dat[i]*5000.0/1024.0;
lcd_gotoxy(1,1);
printf(lcd_putc,"%d. %1.3f",i+1,aux/1000);
aux=dat[i+1]*5000.0/1024.0;
lcd_gotoxy(1,2);
printf(lcd_putc,"%d. %1.3f",i+2,aux/1000);
aux=dat[i+2]*5000.0/1024.0;
lcd_gotoxy(1,3);
printf(lcd_putc,"%d. %1.3f",i+3,aux/1000);
aux=dat[i+3]*5000.0/1024.0;
lcd_gotoxy(1,4);
printf(lcd_putc,"%d. %1.3f",i+4,aux/1000);
c=tecla();      //Lee el valor del teclado y espera hasta que alguna tecla se pulse
i =(c=='B' && i!=41) ? i+1:i; //Si es B incremente i
i =(c=='A' && i!=0) ? i-1:i; //Si es A decremente i
ban =(c=='*') ? 1:0;   //Si es * salga del while
}
}
/*===========================================================================*/
/*=======================    FUNCION DEL PRINCIPAL    =======================*/
/*===========================================================================*/
void main()
{
port_b_pullups (0xFF);  //Utiliza las resistencias PULL UP internas del puerto B
set_tris_c(0);
set_tris_d(0);
setup_timer_2(t2_div_by_4,249,1);   //Configuracion de Timer 2 para establecer frec. PWM a 1kHz
setup_ccp1(ccp_pwm);                //Configurar modulo CCP1 en modo PWM
set_pwm1_duty(0);                   //Dejo en cero la salida PWM
setup_adc_ports(sAN0);              //Configurar ADC (Lectura de temperatura)
setup_adc(adc_clock_internal);      //Reloj interno para la conversion analoga digital)
set_adc_channel(0);                 //Seleccionar Canal 0 para sensor de Temperatura
LCD_INIT();                         //Inicializo el LCD
LCD_PUTC("\f");                     //Limpio el LCD
while(1)
{
lcd_gotoxy(1,1);
LCD_PUTC("TOMAR DATOS PLANTA");
lcd_gotoxy(1,2);
LCD_PUTC("Escalon(0-100):");
lcd_gotoxy(1,3);
LCD_PUTC("y presione *");
lcd_gotoxy(1,4);
LCD_PUTC("SP:           ");
R=escalon(3); //Llama la funcion para digitar el escalon de exitacion
//Valida si R esta entre 0 y 100 (Esto es otra forma de usar el if - else)
R =(R > 100) ? 100:R;
//Muestra el SETPOINT en pantalla
lcd_gotoxy(1,4);
printf(lcd_putc,"SP: %3.1f",R);
delay_ms(2000);
//Aprovecho la funcion escalon para digitar el periodo de muestreo
lcd_gotoxy(1,2);
LCD_PUTC("Periodo de M(0-1000):");
lcd_gotoxy(1,3);
LCD_PUTC("y presione *");
lcd_gotoxy(1,4);
LCD_PUTC(" T:        ");
T=escalon(4);
//Muestra el periodo de muestreo en pantalla
lcd_gotoxy(1,4);
printf(lcd_putc,"T: %ld  ",T);
delay_ms(2000);
LCD_PUTC("\f");
identificacion();
opcion=0; //Regresa al menu principal
}
}
'; unsigned int16 timeout; timeout=0; c=kbd_getc(); //Captura valor del teclado while(c=='
#INCLUDE <16F887.h>
#DEVICE ADC=10
#USE DELAY(CLOCK=4000000)
#FUSES XT,NOPROTECT,NOWDT,NOBROWNOUT,PUT,NOLVP
#DEFINE USE_PORTB_KBD   //Por defecto el teclado se conecta al puerto D,
//como el microcontrolador que se esta usando
//no tiene puerto D se conecta al puerto B.*/
#INCLUDE <LCD420D.C>
#INCLUDE <KBD4x4.C>  //Incluir en el encabezado el driver para
//manejar el teclado telefónico MODIFICADO
#include <stdlib.h>
#include <string.h>
#use     standard_io(b) 
#define  KEYHIT_DELAY   200    //Tiempo de espera del teclado en milisegundos
#byte PORTB= 6
#byte PORTC= 7
#BYTE PORTA= 5
#BYTE PORTD= 8
int16 adc,control=0;
long T=100; //100 ms
float R=0;
char c;
int opcion=0;
/*===========================================================================*/
/*=======================       FUNCION TECLA         =======================*/
/*===========================================================================*/
//Funcion encargada de esperar a que se presione una tecla 
char tecla(void)
{
char c;
do{ //espera hasta que se presione una tecla
c=kbd_getc(); //Captura valor del teclado
}
while(c=='\0'); 
return(c);
}
/*===========================================================================*/
/*=======================    FUNCION TECLA CON TIMER  =======================*/
/*===========================================================================*/
// Pregunta por una Tecla por un tiempo, si no hay actividad, deja de preguntar
// y deja que el PIC continue con su trabajo
char tecla_time(void) {
char c='\0';
unsigned int16 timeout;
timeout=0;
c=kbd_getc(); //Captura valor del teclado
while(c=='\0' && (++timeout< (KEYHIT_DELAY*100)))
{
delay_us(10);
c=kbd_getc(); //Captura valor del teclado
}
return(c);
}
/*===========================================================================*/
/*============   FUNCION PARA DIGITAR ESCALÓN/SETPOINT  =====================*/
/*===========================================================================*/
long escalon(int nd)
{
//Esta funcion captura el escalon desde el teclado, si el proceso está tomando
//datos el escalon sirve para exitar el sistema, por otro lado si el sistema
//está controlando, el escalo sirve para establecer el setpoint del sistema
long val;
int i;
char str[5]; //Variable tipo String
str[0]='0';
for(i=0;i<nd;i++)
{
c=tecla();  //Lee el valor del teclado y espera hasta que alguna tecla se pulse
if(c!='*'){
//Muestra el digito presionado en el LCD
lcd_gotoxy(5+i,4);
lcd_putc(c);
//Almacena el dato presionado en la variable String
str[i]=c;
}
else{i=nd;} //Si se presiona * sale del For      
}
val = atol(str); //Convierte el String en un valor numerico
return(val);
}
/*===========================================================================*/
/*======================= IDENTIFICACION DEL SISTEMA  =======================*/
/*===========================================================================*/
void identificacion(void)
{
int16 dat[45]; //Variable guarda 45 datos de la identificacion
float aux;
int i,ban=0;
LCD_PUTC("TOMANDO DATOS....");
control=R*10;
set_pwm1_duty(control); //Exita el sistema
for(i=0;i<45;i++)
{
//Lee los datos
dat[i]=read_adc(); //Leer ADC
//dat[i]=adc*5000.0/1024.0;
delay_ms(T);
}
set_pwm1_duty(0); //Desactiva el sistema
LCD_PUTC("\f");
//Muestra los datos en la pantalla del LCD utilizando la tecla A para subir
//y utilizando la tecla B para bajar, estos datos deben ser graficados (EXCEL)
//para poder montar la curva de reacción del sistema y poder determinar los
//Parametros de la Planta
i=0;
while(ban==0)
{
aux=dat[i]*5000.0/1024.0;
lcd_gotoxy(1,1);
printf(lcd_putc,"%d. %1.3f",i+1,aux/1000);
aux=dat[i+1]*5000.0/1024.0;
lcd_gotoxy(1,2);
printf(lcd_putc,"%d. %1.3f",i+2,aux/1000);
aux=dat[i+2]*5000.0/1024.0;
lcd_gotoxy(1,3);
printf(lcd_putc,"%d. %1.3f",i+3,aux/1000);
aux=dat[i+3]*5000.0/1024.0;
lcd_gotoxy(1,4);
printf(lcd_putc,"%d. %1.3f",i+4,aux/1000);
c=tecla();      //Lee el valor del teclado y espera hasta que alguna tecla se pulse
i =(c=='B' && i!=41) ? i+1:i; //Si es B incremente i
i =(c=='A' && i!=0) ? i-1:i; //Si es A decremente i
ban =(c=='*') ? 1:0;   //Si es * salga del while
}
}
/*===========================================================================*/
/*=======================    FUNCION DEL PRINCIPAL    =======================*/
/*===========================================================================*/
void main()
{
port_b_pullups (0xFF);  //Utiliza las resistencias PULL UP internas del puerto B
set_tris_c(0);
set_tris_d(0);
setup_timer_2(t2_div_by_4,249,1);   //Configuracion de Timer 2 para establecer frec. PWM a 1kHz
setup_ccp1(ccp_pwm);                //Configurar modulo CCP1 en modo PWM
set_pwm1_duty(0);                   //Dejo en cero la salida PWM
setup_adc_ports(sAN0);              //Configurar ADC (Lectura de temperatura)
setup_adc(adc_clock_internal);      //Reloj interno para la conversion analoga digital)
set_adc_channel(0);                 //Seleccionar Canal 0 para sensor de Temperatura
LCD_INIT();                         //Inicializo el LCD
LCD_PUTC("\f");                     //Limpio el LCD
while(1)
{
lcd_gotoxy(1,1);
LCD_PUTC("TOMAR DATOS PLANTA");
lcd_gotoxy(1,2);
LCD_PUTC("Escalon(0-100):");
lcd_gotoxy(1,3);
LCD_PUTC("y presione *");
lcd_gotoxy(1,4);
LCD_PUTC("SP:           ");
R=escalon(3); //Llama la funcion para digitar el escalon de exitacion
//Valida si R esta entre 0 y 100 (Esto es otra forma de usar el if - else)
R =(R > 100) ? 100:R;
//Muestra el SETPOINT en pantalla
lcd_gotoxy(1,4);
printf(lcd_putc,"SP: %3.1f",R);
delay_ms(2000);
//Aprovecho la funcion escalon para digitar el periodo de muestreo
lcd_gotoxy(1,2);
LCD_PUTC("Periodo de M(0-1000):");
lcd_gotoxy(1,3);
LCD_PUTC("y presione *");
lcd_gotoxy(1,4);
LCD_PUTC(" T:        ");
T=escalon(4);
//Muestra el periodo de muestreo en pantalla
lcd_gotoxy(1,4);
printf(lcd_putc,"T: %ld  ",T);
delay_ms(2000);
LCD_PUTC("\f");
identificacion();
opcion=0; //Regresa al menu principal
}
}
' && (++timeout< (KEYHIT_DELAY*100))) { delay_us(10); c=kbd_getc(); //Captura valor del teclado } return(c); } /*===========================================================================*/ /*============ FUNCION PARA DIGITAR ESCALÓN/SETPOINT =====================*/ /*===========================================================================*/ long escalon(int nd) { //Esta funcion captura el escalon desde el teclado, si el proceso está tomando //datos el escalon sirve para exitar el sistema, por otro lado si el sistema //está controlando, el escalo sirve para establecer el setpoint del sistema long val; int i; char str[5]; //Variable tipo String str[0]='0'; for(i=0;i<nd;i++) { c=tecla(); //Lee el valor del teclado y espera hasta que alguna tecla se pulse if(c!='*'){ //Muestra el digito presionado en el LCD lcd_gotoxy(5+i,4); lcd_putc(c); //Almacena el dato presionado en la variable String str[i]=c; } else{i=nd;} //Si se presiona * sale del For } val = atol(str); //Convierte el String en un valor numerico return(val); } /*===========================================================================*/ /*======================= IDENTIFICACION DEL SISTEMA =======================*/ /*===========================================================================*/ void identificacion(void) { int16 dat[45]; //Variable guarda 45 datos de la identificacion float aux; int i,ban=0; LCD_PUTC("TOMANDO DATOS...."); control=R*10; set_pwm1_duty(control); //Exita el sistema for(i=0;i<45;i++) { //Lee los datos dat[i]=read_adc(); //Leer ADC //dat[i]=adc*5000.0/1024.0; delay_ms(T); } set_pwm1_duty(0); //Desactiva el sistema LCD_PUTC("\f"); //Muestra los datos en la pantalla del LCD utilizando la tecla A para subir //y utilizando la tecla B para bajar, estos datos deben ser graficados (EXCEL) //para poder montar la curva de reacción del sistema y poder determinar los //Parametros de la Planta i=0; while(ban==0) { aux=dat[i]*5000.0/1024.0; lcd_gotoxy(1,1); printf(lcd_putc,"%d. %1.3f",i+1,aux/1000); aux=dat[i+1]*5000.0/1024.0; lcd_gotoxy(1,2); printf(lcd_putc,"%d. %1.3f",i+2,aux/1000); aux=dat[i+2]*5000.0/1024.0; lcd_gotoxy(1,3); printf(lcd_putc,"%d. %1.3f",i+3,aux/1000); aux=dat[i+3]*5000.0/1024.0; lcd_gotoxy(1,4); printf(lcd_putc,"%d. %1.3f",i+4,aux/1000); c=tecla(); //Lee el valor del teclado y espera hasta que alguna tecla se pulse i =(c=='B' && i!=41) ? i+1:i; //Si es B incremente i i =(c=='A' && i!=0) ? i-1:i; //Si es A decremente i ban =(c=='*') ? 1:0; //Si es * salga del while } } /*===========================================================================*/ /*======================= FUNCION DEL PRINCIPAL =======================*/ /*===========================================================================*/ void main() { port_b_pullups (0xFF); //Utiliza las resistencias PULL UP internas del puerto B set_tris_c(0); set_tris_d(0); setup_timer_2(t2_div_by_4,249,1); //Configuracion de Timer 2 para establecer frec. PWM a 1kHz setup_ccp1(ccp_pwm); //Configurar modulo CCP1 en modo PWM set_pwm1_duty(0); //Dejo en cero la salida PWM setup_adc_ports(sAN0); //Configurar ADC (Lectura de temperatura) setup_adc(adc_clock_internal); //Reloj interno para la conversion analoga digital) set_adc_channel(0); //Seleccionar Canal 0 para sensor de Temperatura LCD_INIT(); //Inicializo el LCD LCD_PUTC("\f"); //Limpio el LCD while(1) { lcd_gotoxy(1,1); LCD_PUTC("TOMAR DATOS PLANTA"); lcd_gotoxy(1,2); LCD_PUTC("Escalon(0-100):"); lcd_gotoxy(1,3); LCD_PUTC("y presione *"); lcd_gotoxy(1,4); LCD_PUTC("SP: "); R=escalon(3); //Llama la funcion para digitar el escalon de exitacion //Valida si R esta entre 0 y 100 (Esto es otra forma de usar el if - else) R =(R > 100) ? 100:R; //Muestra el SETPOINT en pantalla lcd_gotoxy(1,4); printf(lcd_putc,"SP: %3.1f",R); delay_ms(2000); //Aprovecho la funcion escalon para digitar el periodo de muestreo lcd_gotoxy(1,2); LCD_PUTC("Periodo de M(0-1000):"); lcd_gotoxy(1,3); LCD_PUTC("y presione *"); lcd_gotoxy(1,4); LCD_PUTC(" T: "); T=escalon(4); //Muestra el periodo de muestreo en pantalla lcd_gotoxy(1,4); printf(lcd_putc,"T: %ld ",T); delay_ms(2000); LCD_PUTC("\f"); identificacion(); opcion=0; //Regresa al menu principal } }

Código en Lenguaje C del Controlador PID para MOTOR-GENERADOR

#INCLUDE <16F887.h>
#DEVICE ADC=10
#USE DELAY(CLOCK=4000000)
#FUSES XT,NOPROTECT,NOWDT,NOBROWNOUT,PUT,NOLVP
#DEFINE USE_PORTB_KBD   //Por defecto el teclado se conecta al puerto D,
//como el microcontrolador que se esta usando
//no tiene puerto D se conecta al puerto B.*/
#INCLUDE <LCD420D.C>
#INCLUDE <KBD4x4.C>  //Incluir en el encabezado el driver para
//manejar el teclado telefónico MODIFICADO
#include <stdlib.h>
#include <string.h>
#use     standard_io(b) 
#define  KEYHIT_DELAY   1    //Tiempo de espera del teclado en milisegundos
#byte PORTB= 6
#byte PORTC= 7
#BYTE PORTA= 5
#BYTE PORTD= 8
int16 adc,control=0;
float R=0;
float yM=0,e=0.0,e_1=0.0,e_2=0.0,u=0.0,u_1=0.0,T=11;
float kp,ti,td,q0,q1,q2;
float k=0.8,tao=57.5,theta=30.0;
float TsMA,Wn,P1,P2;
char c;
int cont=0;
/*===========================================================================*/
/*=======================       FUNCION TECLA         =======================*/
/*===========================================================================*/
//Funcion encargada de esperar a que se presione una tecla 
char tecla(void)
{
char c;
do{ //espera hasta que se presione una tecla
c=kbd_getc(); //Captura valor del teclado
}
while(c=='\0'); 
return(c);
}
/*===========================================================================*/
/*=======================    FUNCION TECLA CON TIMER  =======================*/
/*===========================================================================*/
// Pregunta por una Tecla por un tiempo, si no hay actividad, deja de preguntar
// y deja que el PIC continue con su trabajo
char tecla_time(void) {
char c='\0';
unsigned int16 timeout;
timeout=0;
c=kbd_getc(); //Captura valor del teclado
while(c=='\0' && (++timeout< (KEYHIT_DELAY*100)))
{
delay_us(10);
c=kbd_getc(); //Captura valor del teclado
}
return(c);
}
/*===========================================================================*/
/*=======================    FUNCION DEL CONTROL PID  =======================*/
/*===========================================================================*/
void PID(void)
{
//Comienzo la ley de control
float tp;
tp= yM*100.0/5000.0; //Convierte la referencia de porcentaje a milivoltaje
e=1*(R-tp);
// Controle PID
u = u_1 + q0*e + q1*e_1 + q2*e_2; //Ley del controlador PID discreto
if (u >= 100.0)        //Saturo la accion de control 'uT' en un tope maximo y minimo
u = 100.0;
if (u <= 0.0)
u = 0.0;
control=u*10;
//Retorno a los valores reales
e_2=e_1;
e_1=e;
u_1=u;
//La accion calculada la transformo en PWM
set_pwm1_duty(control);
}
/*===========================================================================*/
/*============   FUNCION PARA DIGITAR ESCALÓN/SETPOINT  =====================*/
/*===========================================================================*/
long escalon(int nd)
{
//Esta funcion captura el escalon desde el teclado, si el proceso está tomando
//datos el escalon sirve para exitar el sistema, por otro lado si el sistema
//está controlando, el escalo sirve para establecer el setpoint del sistema
long val;
int i;
char str[5]; //Variable tipo String
str[0]='0';
for(i=0;i<nd;i++)
{
c=tecla();  //Lee el valor del teclado y espera hasta que alguna tecla se pulse
if(c!='*'){
//Muestra el digito presionado en el LCD
lcd_gotoxy(5+i,4);
lcd_putc(c);
//Almacena el dato presionado en la variable String
str[i]=c;
}
else{i=nd;} //Si se presiona * sale del For      
}
val = atol(str); //Convierte el String en un valor numerico
}
/*===========================================================================*/
/*=======================    FUNCION DEL PRINCIPAL    =======================*/
/*===========================================================================*/
void main()
{
port_b_pullups (0xFF);  //Utiliza las resistencias PULL UP internas del puerto B
set_tris_c(0);
set_tris_d(0);
setup_timer_2(t2_div_by_4,249,1);   //Configuracion de Timer 2 para establecer frec. PWM a 1kHz
setup_ccp1(ccp_pwm);                //Configurar modulo CCP1 en modo PWM
set_pwm1_duty(0);                   //Dejo en cero la salida PWM
setup_adc_ports(sAN0);              //Configurar ADC (Lectura de temperatura)
setup_adc(adc_clock_internal);      //Reloj interno para la conversion analoga digital)
set_adc_channel(0);                 //Seleccionar Canal 0 para sensor de Temperatura
SET_RTCC(246);                               //Cargo el TIMER0 con 243
SETUP_COUNTERS(RTCC_INTERNAL, RTCC_DIV_256); //Configuro el TIMER0 con  prescaler 256
ENABLE_INTERRUPTS(INT_RTCC);  //Habilito interrupcion por TIMER0
enable_interrupts(GLOBAL);    //Habilito las interrupciones a nivel global
LCD_INIT();                         //Inicializo el LCD
LCD_PUTC("\f");                     //Limpio el LCD
lcd_gotoxy(1,1);
LCD_PUTC("CONTROL PID DE MOTOR");
//*************************************************************************//
//*************  DISEÑO POR ASIGNACIÓN DE 2 POLOS REALES   ****************//
//*************************************************************************//
TsMA=5*tao;                     //Tiempo deseado en Lazo Cerrado    
Wn=3/(TsMA);               //Frecuencia natural del sistema
//Ubicación de 2 Polos reales
P1=Wn+Wn;
P2=Wn*Wn;
kp=(P1*tao-1)/k;        //Calculo de Kc
ti=(k*kp)/(P2*tao);     //Calculo de ti
//*************************************************************************//
//*****************   DISEÑO POR CANCELACIÓN DE POLOS    *******************//
//*************************************************************************//
/*
TsMA=0.75*tao;                  //Tiempo deseado en Lazo Cerrado 
kp=(tao)/(TsMA*k);      //Calculo de Kc
ti=tao;                  //Calculo de Ti (Igual a la constante de tiempo)
td=0;
*/
//*************************************************************************//
//*****************   SINTONIA POR ZIEGLER y NICHOLS    *******************//
//*************************************************************************//
/*
kp=(1.2*tao)/(k*theta);
ti=2*theta;
td=0.5*theta;
*/
//*************************************************************************//
// Calculo do controle PID digital
q0=kp*(1+T/(2*ti)+td/T);
q1=-kp*(1-T/(2*ti)+(2*td)/T);
q2=(kp*td)/T;
while(1)
{
adc=read_adc(); //Leer ADC
yM=adc*5000.0/1024.0;
//Llama la funcion del controlador PID
PID();
//Muestra Resultados en LCD cada 50 veces el periodo de muestreo
if(cont==50)
{
cont=0;
lcd_gotoxy(1,2);
printf(lcd_putc,"V: %f  E:%f",yM*100.0/5000.0,e);
lcd_gotoxy(1,3);
printf(lcd_putc,"SP: %3.1f C   ",R);
lcd_gotoxy(1,4);
printf(lcd_putc,"U: %ld   ",control);
}
//tiempo de muestreo
//delay_ms(2); 
cont=cont+1; //Incrementa contador para visualizar en el LCD
c=tecla_time();   //Lee el valor del teclado pero solo espera un tiempo determinado
if(c=='D')
{
lcd_gotoxy(1,2);
LCD_PUTC("Escalon(0-100):");
lcd_gotoxy(1,3);
LCD_PUTC("y presione *");
lcd_gotoxy(1,4);
LCD_PUTC("SP:           ");
R=escalon(3); //Llama la funcion para digitar el escalon de exitacion
//Valida si R esta entre 0 y 100 (Esto es otra forma de usar el if - else)
R =(R > 100) ? 100:R;
//Muestra el SETPOINT en pantalla
lcd_gotoxy(1,4);
printf(lcd_putc,"SP: %3.1f",R);
delay_ms(2000);
LCD_PUTC("\f");
}
}
}

Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:

👉 Invitar a Sergio a un Café ☕️

Que esten muy bien, nos vemos en la siguiente entrada.