Control Automático Educación

Predictor de Smith con PIC

Hola controleras y controleros, hoy vamos a aprender como implementar un Predictor de Smith en un microcontrolador PIC, utilizando como controlador primário un PID. Todo será implementado en el compilador CCS C.

Antes que nada te hago la invitación para 👉 Ver el Curso de PIC completo.

Predictor de Smith

El predictor de Smith es una estructura de control ampliamente utilizada en la industria para poder realizar control de procesos que presentan un retardo dominante. La estructura se presenta a continuación:

Predictor de Smith
5. Predictor de Smith

Donde P(s) es el proceso (Horno), C(S) será el controlador primário (En este caso usaremos un PID), Gn(s) es el modelo del proceso (función de transferencia del horno), Ln es el retardo del horno.

Mira la siguiente entrada para entender el funcionamiento del 👉 Predictor de Smith.

Predictor de Smith con PIC

Para la implementación de la estructura del predictor de Smith dentro del microcontrolador, tendremos que recurrir a la representación discreta de la estructura.

Y para eso vamos a tomar la función de transferencia que obtuvimos de la Identificación del Horno de Temperatura.

Vamos a discretizar esa función de transferencia con un tiempo de muestreo de T=10s usando el retenedor de orden cero ZOH. Donde obtendremos una función de transferencia discreta de la siguiente forma:

Implementando un controlador PID discreto, podemos proceder a montar nuestra estructura del predictor de Smith en un microcontrolador PIC.

Ecuaciones en diferencias

Para poder escribir la función de transferencia discreta dentro del microcontrolador PIC, se debe transformar dicha función en una ecuación en diferencias.

Se lleva a potencias negativas en z

Se resuelve para obtener la salida del modelo

Con eso ya podemos calcular el resultado de la salida del modelo (función de transferencia) del horno en lenguaje C.

//Salida del modelo nominal Pn(z)
yE= b0*u[kT-d]+b1*u[kT-1-d]-a1*yE_1;

Notemos que

Y que u es un vector. En este caso de 15 posiciones, donde la ultima posición corresponde al tiempo actual (presente) y las demás posiciones corresponden a muestras en el pasado.

Implementación del Predictor de Smith en un PIC 18F4550

El controlador primário del PIC, sera el controlador PID explicado en la entrada Control de Temperatura de un Horno.

El circuito a ser implementado, será el mismo empleado en la entrada anterior de la identificación del horno.

Donde se ve que se usa 2 XBEE para realizar una comunicación inálambrica.

La respuesta del controlador sobre el horno

El código del PIC en CCS C y el código en MATLAB para la interfaz gráfica pueden ser descargados a continuación, solo basta con que compartas el contenido de este post con cualquiera de los siguientes botonos, de esa forma ayudas a que más personas aprendan sobre esto y contribuyes a mejorar la educación.

Otros Controladores implementados en PIC

Si deseas saber como implementar otro tipo de controladores como diversas configuraciones del PID, logica Fuzzy, Espacio de estados, RST, etc, te invito a que te matricules a mi curso de Control en Sistemas Embebidos, y puedes seleccionar el micro que más te guste (PIC o Arduino):

  • Curso de Sistemas de Control en Dispositivos Microcontrolados en UDEMY (PIC y ARDUINO)
  • Certificado de Aprobación una vez finalices el Curso
  • DESCUENTO si accedes directamente con los siguientes botones de acceso.
  • NOTA: Si buscas el curso directamente en UDEMY o si lo adquieres en otra plataforma distintas a las mostradas anteriormente NO OBTENDRÁS NINGUN DESCUENTO sobre el valor final del Curso.

Código en CCS C (PIC C) Predictor de Smith

#include <18F4550.h>
#device ADC=10

#FUSES NOWDT                    //No Watch Dog Timer
#FUSES WDT128                   //Watch Dog Timer uses 1:128 Postscale
#FUSES NOBROWNOUT               //No brownout reset
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOXINST                  //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#FUSES HS

#use delay(crystal=20000000)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)

#define TC_CLK     PIN_B1  
#define TC_CS      PIN_C0 
#define TC_DATA    PIN_B0



#include "max6675.c"
#include <stdlib.h>
#include <string.h>

long deg=0;
int muest=0;
int16 control=0;
float R=0;//Referencia de 150 °C por defecto
float yM=0,e=0.0,e_1=0.0,e_2=0.0,T=10;
float kp,ti,td,q0,q1,q2;
float k=2.3553 ,tao=370 ,theta=30;
float TsMA,Wn,P1,P2;
float b0=0,b1=0.0628,a1=-0.9733; //Parametros de la Funcion de transferencia discreta
int d=3-1; //Retardo discreto (d-1)
float u[15]; //Ley de control (debe ser mayor que el retardo + el numero de ceros de la FT)
int kT=14; //Tiempo discreto actual (Ultima posición del vector u)
float yE=0,yE_1=0; //Salida del modelo interno
float ep=0; //Error de prediccion
float yR=0,yR_1=0; //Salida del modelo Rapido (sin retardo)
float yp=0; //Salida  predicha


//Define la interrupción por recepción Serial
#int_RDA
void RDA_isr()
{
 int i=0,ini=0,fin=0;
 char dat[5];
 char degC[5];
 
 // Almacena 10 datos leidos del serial
  for(i=0;i<5;i++){
     dat[i]=getc();
     putc(dat[i]);
    }



//Busco el valor del escalon en los datos recibidos
for(i=0;i<5;i++){
  if(dat[i]=='S'){
    ini=i+1;
    i=10;
  }
 }
 for(i=ini;i<5;i++){
  if(dat[i]=='$'){
    fin=i-1;
    i=10;
  }
 }
 if(ini!=0 && fin!=0){
    // salvo en degC el caracter con el escalon
    for(i=ini;i<=fin;i++){
     degC[i-ini]=dat[i];
    }
    
     deg = atol(degC); //Convierte el String en un valor numerico
     
     // valida que el escalón esté ente 0 y 300 grados celcius
     
     if(deg>1000)
         deg=1000;
     if(deg<0)
         deg=0;
    
       R=deg;
 }
}
//! 

/*===========================================================================*/
/*=======================    FUNCION DEL CONTROL PID  =======================*/
/*===========================================================================*/
void PID(void)
{
    //char msg[32];
    e=1*(R-yp);
    // Controle PID
      u[kT] = u[kT-1] + q0*e + q1*e_1 + q2*e_2; //Ley del controlador PID discreto
      
//!     sprintf(msg,"e=%3.2f u=%3.2f",e,u); 
//!      printf("%s",msg);
      
      
    if (u[kT] >= 1000.0)        //Saturo la accion de control 'uT' en un tope maximo y minimo
     u[kT] = 1000.0;
    
    if (u[kT] <= 0.0)
     u[kT] = 0.0;
     
     control=u[kT];
     
     //Actualizo los valores pasados
     e_2=e_1;
     e_1=e;
     
     //La accion calculada la transformo en PWM
     set_pwm1_duty(control);
     output_toggle(PIN_B2);  //LED verifica el tiempo de muestreo correcto
}


void main() 
{ 
   int i;
   char msg[32]; 
   delay_ms(50);      //allow oscillator to stabilise 
   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
   enable_interrupts(INT_RDA);
   enable_interrupts(GLOBAL);
   set_tris_b(0);
   
   
   //*************************************************************************//
   //*************  DISEÑO POR ASIGNACIÓN DE 2 POLOS REALES   ****************//
   //*************************************************************************//
   
   TsMA=600;                    //Tiempo deseado en Lazo Cerrado    
   Wn=4/(TsMA);               //Frecuencia natural del sistema
   
   //Ubicación de 2 Polos reales
   P1=2*Wn;
   P2=Wn*Wn;
   
   kp=(P1*tao-1)/k;        //Calculo de Kc
   ti=(k*kp)/(P2*tao);     //Calculo de ti
   td=0;
   
   //*************************************************************************//
   //*****************   DISEÑO POR CANCELACIÓN DE POLOS    *******************//
   //*************************************************************************//
   /*
   TsMA=7.5;                  //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=(0.9*tao)/(k*theta); //Z-N Rapido
//!   kp=kp/5;                //Z-N Lento
//!   ti=3.33*theta;
//!   td=0;
   
   //*************************************************************************//
   
  // 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;
   
   //Inicializa el vector de control
   for(i=0;i<=kT;i++){
      u[i]=0;
   }
   
   //Inicializa las variables que contienen la lectura de salida
   yM=do_everything();
   yE=yM;yE_1=yM;yR=yM;yR_1=yM;
   while(1){ 
      yM=do_everything();
      //sprintf(msg,"%3.2f%cC\r\n",do_everything(),0xB0); 
      sprintf(msg,"I%3.2fFI%3.2fFC%ldRC%ldR",yM,yM,control/10,control/10); 
      printf("%s",msg); 
     // printf(" Hola Mundo ");
     
     if(muest>6){
       muest=0;
 
      //Salida del modelo nominal Pn(z)
      yE= b0*u[kT-d]+b1*u[kT-1-d]-a1*yE_1;
      
      //Diferencia entre proceso real y proceso nominal (error de prediccion)
      ep=yM-yE;
      
      //Salida del modelo nominal Rapido (sin retardo)
      yR= b0*u[kT]+b1*u[kT-1]-a1*yR_1;
      
      //Suma entre error de prediccion y salida predicha
      yp=yR+ep;
      
      //Llama la funcion del controlador PID
      PID();
      
      
      //desplaza el vector de la ley de control
      for(i=1;i<=kT;i++){
         u[i-1]=u[i];
      }
      
      //Actualizo los valores pasados
      yE_1=yE;
      yR_1=yR;
     }
      
      //tiempo de muestreo
      delay_ms(1000);
      muest++;
         
   } 
} 

Código en Matlab

%% Ejemplo Monitoreo de señales en tiempo Real
function varargout=monitoreo(varargin)
parar=false;
fclose('all')
global tiempo salida escalon control
fig(1)=figure('name','Monitor','menubar','none','position',[200 200 800 700],'color',[0.9 0.6 0.3])
movegui(fig(1),'center');
axe(1)=axes('parent',fig(1),'units','pixels','position',[60 380 600 280],'xlim',[0 40],'ylim',[0 200],'xgrid','on','ygrid','on')
axe(2)=axes('parent',fig(1),'units','pixels','position',[60 50 600 280],'xlim',[0 40],'ylim',[0 100],'xgrid','on','ygrid','on')

set(get(axe(1),'XLabel'),'String','Tiempo (Seg)')
set(get(axe(1),'YLabel'),'String','Temperatura (°C)')

set(get(axe(2),'XLabel'),'String','Tiempo (Seg)')
set(get(axe(2),'YLabel'),'String','Control (%)')

lin(1)=line('parent',axe(1),'xdata',[],'ydata',[],'Color','r','LineWidth',2.5);
lin(2)=line('parent',axe(1),'xdata',[],'ydata',[],'Color','k','LineWidth',2);
lin(3)=line('parent',axe(2),'xdata',[],'ydata',[],'Color','r','LineWidth',2.5);


bot(1)=uicontrol('parent',fig(1),'style','pushbutton','string','Detener','position',[680 50 100 50],'callback',@stop,'fontsize',11)
bot(2)=uicontrol('parent',fig(1),'style','pushbutton','string','Enviar','position',[680 200 100 50],'callback',@enviar,'fontsize',11)

txbx(1)=uicontrol('parent',fig(1),'style','tex','string','Temp','position',[680 100 100 50],'fontsize',11)
txbx(2)=uicontrol('parent',fig(1),'style','edit','string','000','position',[680 250 100 50],'fontsize',11)



%% Funcion Pare
    function varargout=stop(hObject,evendata)
        parar=true;
        fclose(SerialP);
        delete(SerialP);
        clear SerialP;
        
    end

%% Funcion enviar
    function varargout=enviar(hObject,evendata)
        deg1=get(txbx(2),'string');
        deg=["S"+deg1+"$"];
        fwrite(SerialP,deg,'uchar');
    end
    %% funcion Graficar
   % function varargout=grafique(hObject,evendata)
        tiempo=[0];
        salida=[0];
        escalon=[0];
        control=[0];
        deg1="0";
   
        dt=1;
        limx=[0 40];
        limy=[0 200];
        set(axe(1),'xlim',limx,'ylim',limy);
        
        
    %% Configura el Puerto Serial
    
    SerialP=serial('COM8');
    set(SerialP,'Baudrate',9600); % se configura la velocidad a 9600 Baudios
    set(SerialP,'StopBits',1); % se configura bit de parada a uno
    set(SerialP,'DataBits',8); % se configura que el dato es de 8 bits, debe estar entre 5 y 8
    set(SerialP,'Parity','none'); % se configura sin paridad

    fopen(SerialP);
              
     %% Grafico
     k=5;nit = 10000;
        while(~parar)
%             if get(bot(3),'value')
%                drawnow(); % necesario para que actualice el grafico
%                continue % forza a salir del while para  la proxima interacion y de esta forma nop actualizo datos
%             
%             end


            % Lectura del Dato por Puerto Serial
            variable= (fread(SerialP,30,'uchar'));
            ini=find(variable==73); %Busca el I (Primer dato)
            ini=ini(1)+1;
            fin=find(variable==70); %Busca F (ultimo dato)
            fin= fin(find(fin>ini))-1;
            fin=fin(1);
            tempC=char(variable(ini:fin))';
            temp=str2num(tempC);
            
            %Lectura de la senal de control
            ini=find(variable==67); %Busca el C (Primer dato)
            ini=ini(1)+1;
            fin=find(variable==82); %Busca R (ultimo dato)
            fin= fin(find(fin>ini))-1;
            fin=fin(1);
            Con1=char(variable(ini:fin))';
            cont=str2num(Con1);
            
            
            set(txbx(1),'string',tempC);
            %Actualiza las variables del grafico
           
             tiempo=[tiempo tiempo(end)+dt];
             salida=[salida temp];
             control=[control cont];
             escalon=[escalon str2num(deg1)];
             set(lin(1),'xdata',tiempo,'ydata',salida);
             set(lin(2),'xdata',tiempo,'ydata',escalon);
             set(lin(3),'xdata',tiempo,'ydata',control);
             pause(dt); %% espera 1 seg para cada interación
             if tiempo(end)>=limx % actualizo grafica cuando llega a su limite en tiempo real
             limx=[0 limx(2)+40];
             set(axe(1),'xlim',limx) ;
             set(axe(2),'xlim',limx); 
             end
             
             if salida(end)>=limy % actualizo grafica cuando llega a su limite en tiempo real
             limy=[0 limy(2)+30];
             set(axe(1),'ylim',limy); 
             end
             
             k=k+1;
             if(k==nit)
                 parar=true;
             end
        end
        parar=false;
        

   % end
end

Perfecto controleros y controleras con esto llegamos al final de otro post del sitio web.

Recuerda que si deseas apoyar mi trabajo, puedes invitarme a un café y seguirme ayudando a mantener los servidores de este sitio web, es muy barato el café y contribuyes con el tiempo y esfuerzo invertidos en las clases elaboradas en el canal y pagina web: 👉Invitar a un Café a Sergio ☕️

Espero que esten muy bien y nos vemos en la próxima.

Salir de la versión móvil