PIC16F84 – Control PID
Problema propuesto
Emular un Controlador Discreto PID con el PIC16F84.
Utilizar una gestión de tiempos con interrupción por temporizador. Se supone que la referencia es un valor interno, Referencia. Para simplificar se supone que el actuador y el sensor manejan la información en complemento a 2.
- Cada 0.01 debe leer el sensor, LeerPortB.
- Cada 0.1s debe:
- Calcular MedidaFiltrada como un promedio de las 8 últimas lecturas del sensor. Se puede realizar la media sumando dos elementos y un desplazamiento a la derecha y así sucesivamente hasta cerrar el árbol binario de medias, por eso he elegido 8 elementos.
- Calcular el Control.
- ErrorAnterior=Error
- Error= Referencia – MedidaFiltrada
- Integral= Integral + Error
- Derivativa= Error- ErrorAnterior
- Control= Kp*Error+Ki*Integral+Kd*Derivativa
- Guardar telemetrías en la EEPROM.
- Simular el sistema (en lazo abierto) inyectando estímulos en PortB que contengan ruido.
- El PIC16F84A no es el adecuado para implementar un PID, pues además de su ALU tan limitada, no dispone ni de conversores analógico-digital ADC para manejar sensores analógicos, ni de generadores de modulación de ancho de pulsos (PWM) para manejar actuadores. Un microntrolador muy básico que ya dispone de estos recursos es el PIC16F87.
Solución
Este programa ya supone 326 líneas de código, y en él se puede decir que se exprime la capacidad del microcontrolador al máximo. En resumen, programaremos para que haya una interrupción cada centésima de segundo (0,01s) en la que leeremos el sensor a través del puerto B e iremos guardando las lecturas realizadas a través de la subrutina LECTURAS. Debido a que solamente disponemos de un temporizador, utilizaremos un contador decreciente para saber cuándo han transcurrido 0,1 segundos (10 centésimas o 10 desbordamientos) y aprovecharemos para llamar a tres subrutinas. Primeramente filtraremos la medida realizando una media de las ocho últimas lecturas recogidas mediante la subrutina CALCFIL Como no disponemos de instrucción para dividir valores debemos improvisar con su equivalente en ensamblador, rotar a la derecha. Para ello sumaremos por parejas y rotaremos a la derecha guardando el resultado en la variable par mayor.
A continuación realizaremos el cálculo del control PID mediante la subrutina CALCONTROL En realidad no tratamos nada nuevo, pero supone unas cuantas líneas de código simplemente para realizar tres productos y tres sumas. Ya que es una simple simulación con limitaciones y en lazo abierto hay que tener en cuenta las siguientes consideraciones:
- Las constantes Ki, Kp, Kd se dejan con un valor de 1.
- No se corrige el Windup.
- No se tienen en cuenta ni el overshoot ni el undershoot.
- No se tiene en cuenta el error negativo.
Finalmente guardamos las telemetrías en la eeprom mediante la subrutina GUARDAR. Aunque es algo que tratamos por primera vez, no supone mayor complicación ya que el propio fabricante indica la manera correcta de leer y escribir de/en la eeprom.
Diagrama de flujo
Código
;------------------------------------------------------------------ __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _XT_OSC list p=16f84a include "p16f84a.inc" ;------------------------------------------------------------------ ;CBLOCK 0x0C ;Telemetria ;ENDC ORG 0x2100 ; Dirección 0 de la EEPROM DE 0x00 ; Telemetría a cero. Dato1 equ 0x0C Dato2 equ 0x0D DH equ 0x0E ; byte alto DL equ 0x0F ; byte bajo NumA equ 0x10 NumB equ 0x11 AxB equ 0x12 Conta equ 0x13 ; Contador RegW equ 0x14 ; Registro W RegS equ 0x15 ; Registro Status FlagL equ 0x16 ; Flag Lecturas n1 equ 0x17 ; 1ªLectura n2 equ 0x18 ; 2ªLectura n3 equ 0x19 ; 3ªLectura n4 equ 0x20 ; 4ªLectura n5 equ 0x21 ; 5ªLectura n6 equ 0x22 ; 6ªLectura n7 equ 0x23 ; 7ªLectura n8 equ 0x24 ; 8ªLectura Error equ 0x25 ; Error ErrorAnt equ 0x26 ; Error Anterior Integ equ 0x27 ; Integral Deriv equ 0x28 ; Derivada Control equ 0x29 ; Control Referen equ 0x30 ; Referencia MedFil equ 0x30 ; Medida filtrada kp equ 0x31 ; Kp ki equ 0x32 ; Ki kd equ 0x33 ; Kd ;------------------------------------------------------------------ org 0x00 goto INICIO org 0x04 goto INT_Timer INICIO bsf status,5 ; Banco 1 (RP0) movwf b'00000000' movwf TRISB ; Puerto B como salida movwf b'00011111' movwf TRISA ; Puerto A como entrada clrw ; Borro W movlw 0x07 ; Cargo W con 00000111 (PSAx,1,1,1) movwf option_reg ; Divisor = 256 bcf status,5 ; Banco 0 (RP0) movlw 0x88 ; Cargo W con 10101000 movwf intcon ; Habilitamos GIE y RBIE clrf portb ; Borro PORTB clrf porta ; Borro PORTA clrf Conta ; Borro Conta clrf Dato1 ; Borramos clrf Dato2 ; Borramos clrf NumA ; Borramos clrf NumB ; Borramos clrf AxB ; Borramos movlw 0xA ; 10cs = 0,1s movwf Conta movlw 0xD9 ; Cargo W con 0x00-0x27 movwf tmr0 ; Lo paso a TMR0 movlw 0x1 ; Cargo W con 1 movwf FlagL ; Inicializo FlagL PRINCIPAL ; Pasamos el cálculo al actuador (PORTA) movf Control,W ; W = Control movwf PORTA ; Lo pasamos a PORTA goto PRINCIPAL ;GESTIONO INTERRUPCIÓN------------------------------------------------- ;0,01S = 10MS = 1CS --> LEER PORTB ------------------------------------ ;0,1S = 100MS = 10CS -> CALC MED.F -> CALC CONTROL -> GUARDAR DATO ---- ;---------------------------------------------------------------------- INT_Timer bcf INTCON,GIE ; Deshabilitar interrupciones movwf RegW ; Guardamos W swapf STATUS,W ; Invertimos nibbles movwf RegS ; Guardamos estado call LECTURAS ; Leo puerto B decfsz Conta,F ; Conta -=1 goto SIGO ; No hemos acabado movlw 0xA ; 10cs = 0,1s movwf Conta ; Conta = 0xA call CALCFIL ; Calculo Medida filtrada call CALCONTROL ; Calculo Control movf Control,W ; W = Control call GUARDAR ; Guardo telemetrías SIGO movlw 0xD9 ; Cargo W con 0x00-0x27 movwf tmr0 ; Lo paso a TMR0 bcf INTCON,T0IF ; Limpiar bandera de interrupción swapf RegS,W ; Invertimos nibbles de RegS movwf STATUS ; Restauramos estado swapf RegW,fin ; Invertimos nibles swapf RegW,W ; Restauramos W bsf INTCON,GIE ; Habilitar interrupciones retfie ; Vuelvo de la interrupción ;------------------------------------------------------------------------- ;SUBRUTINA COGER LECTURAS------------------------------------------------- ;------------------------------------------------------------------------- LECTURAS movf 1,W ; subwf FlagL,W ; btfsc status,Z ; goto FLAGUNO ; 1ªLectura movf 2,W ; subwf FlagL,W ; btfsc status,Z ; goto FLAGDOS ; 2ªLectura movf 3,W ; subwf FlagL,W ; btfsc status,Z ; goto FLAGTRES ; 3ªLectura movf 4,W ; subwf FlagL,W ; btfsc status,Z ; goto FLAGCUATRO ; 4ªLectura movf 5,W ; subwf FlagL,W ; btfsc status,Z ; goto FLAGCINCO ; 5ªLectura movf 6,W ; subwf FlagL,W ; btfsc status,Z ; goto FLAGSEIS ; 6ªLectura movf 7,W ; subwf FlagL,W ; btfsc status,Z ; goto FLAGSIETE ; 7ªLectura goto FLAGOCHO ; 8ªLectura FLAGUNO movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n1 ; 1ª lectuta incf FlagL,1 ; FlagL +=1 goto FIN ; FLAGDOS movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n2 ; 2ª lectuta incf FlagL,1 ; FlagL +=1 goto FIN ; FLAGTRES movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n3 ; 3ª lectuta incf FlagL,1 ; FlagL +=1 goto FIN ; FLAGCUATRO movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n4 ; 4ª lectuta incf FlagL,1 ; FlagL +=1 goto FIN ; FLAGCINCO movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n5 ; 5ª lectuta incf FlagL,1 ; FlagL +=1 goto FIN ; FLAGSEIS movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n6 ; 6ª lectuta incf FlagL,1 ; FlagL +=1 goto FIN ; FLAGSIETE movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n7 ; 7ª lectuta incf FlagL,1 ; FlagL +=1 goto FIN ; FLAGOCHO movf portB,W ; sublw 0x00000000 ; Complemento a 2 movwf n8 ; 8ª lectuta movf 1,W ; Reinicio flag movwf FlagL ; FIN return ; fin subrutina ;------------------------------------------------------------------------- ;SUBRUTINA FILTRAR MEDIDA------------------------------------------------- ;------------------------------------------------------------------------- CALCFIL ;(n1+n2)/2 movf n1,W ; W = n1 addwf n2,1 ; n1+n2 y guardo en n2 rrf n2,1 ; roto a dcha y guardo en n2 ;(n3+n4)/2 movf n3,W ; W = n3 addwf n4,1 ; n3+n4 y guardo en n4 rrf n4,1 ; roto a dcha y guardo en n4 ;(n5+n6)/2 movf n5,W ; W = n5 addwf n6,1 ; n5+n6 y guardo en n6 rrf n6,1 ; roto a dcha y guardo en n6 ;(n7+n8)/2 movf n7,W ; W = n7 addwf n8,1 ; n7+n8 y guardo en n8 rrf n8,1 ; roto a dcha y guardo en n8 ;(n2+n4)/2 movf n2,W ; W = n2 addwf n4,1 ; n2+n4 y guardo en n4 rrf n4,1 ; roto a dcha y guardo en n4 ;(n6+n8)/2 movf n6,W ; W = n6 addwf n8,1 ; n6+n8 y guardo en n8 rrf n8,1 ; roto a dcha y guardo en n8 ;(n4+n8)/2 movf n4,W ; W = n4 addwf n8,1 ; n4+n8 y guardo en n8 rrf n8,0 ; roto a dcha y guardo en W movwf MedFil ; Guardo la Medida Filtrada return ; fin subrutina ;------------------------------------------------------------------------- ;SUBRUTINA CALCULAR CONTROL----------------------------------------------- ;ErrorAnterior=Error ;Error= Referencia - MedidaFiltrada ;Integral= Integral + Error ;Derivativa= Error- ErrorAnterior ;Control= Kp*Error+Ki*Integral+Kd*Derivativa ;------------------------------------------------------------------------- CALCONTROL ; Valores regulación PID movlw 0x1 ; w=1 movwf kp ; Kp=1 movlw 0x1 ; w=1 movwf ki ; Ki=1 movlw 0x1 ; w=1 movwf kd ; Kd=1 ; Error = Referencia - MedidaFiltrada movf Error,W ; W = Error movwf ErrorAnt ; ErrorAnt = Error movf MedFil,W ; W = MedFil subwf Referen,0 ; f - w → Referen - MedFil movwf Error ; Error = Referen - MedFil ; Integral = Integral + Error addwf Integ,1 ; Integ += Error ; Derivativa = Error - ErrorAnterior movf ErrorAnt,W ; W = ErrorAnt subwf Error,0 ; f - w → Error - ErrorAnt movwf Deriv ; Deriv = Error - ErrorAnt ; Control = Kp*Error+Ki*Integral+Kd*Derivativa movf kp,W ; W=Kp movwf Dato1 ; Dato1 = Kp movf Error,W ; W=Error movwf Dato2 ; Dato2 = Error call PRODUCTO ; Multiplico addwf Control,1 ; Control = Kp*Error movf ki,W ; W=Ki movwf Dato1 ; Dato1 = Ki movf Integ,W ; W=Integ movwf Dato2 ; Dato2 = Integ call PRODUCTO ; Multiplico addwf Control,1 ; Control = Kp*Error+Ki*Integral movf kd,W ; W=Kd movwf Dato1 ; Dato1 = Kd movf Deriv,W ; W=Deriv movwf Dato2 ; Dato2 = Deriv call PRODUCTO ; Multiplico addwf Control,1 ; Control = Kp*Err+Ki*Inte+Kd*Deriv return ; fin subrutina ;------------------------------------------------------------------------- ;SUBRUTINA GUARDAR TELEMETRÍAS-------------------------------------------- ;------------------------------------------------------------------------- GUARDAR CBLOCK GuardaINTCON ENDC bcf STATUS,5 ; Banco 0 movwf EEDATA ; El byte a escribir movf INTCON,W ; Valor anterior de INTCON movwf GuardaINTCON bsf STATUS,5 ; Banco 1 bcf INTCON,GIE ; Deshabilita interrupciones bsf EECON1,WREN ; Habilita escritura movlw 0x55 movwf EECON2 movlw 0xAA movwf EECON2 bsf EECON1,WR ; Inicia la escritura. TERMINA btfsc EECON1,WR ; ¿Fin de la escritura? goto TERMINA ; No bcf EECON1,WREN ; No escritura en EEPROM bcf EECON1,EEIF ; Limpia flag bcf STATUS,5 ; Banco 0 movf GuardaINTCON,W ; Restaura INTCON movwf INTCON return ; fin subrutina ;------------------------------------------------------------------------- ;SUBRUTINA MULTIPLICACIÓN------------------------------------------------- ;------------------------------------------------------------------------- PRODUCTO clrf DH clrf DL movf Dato1,W ; W = multiplicador btfsc status,Z ; Salta si Z=1 return ; Z=0 hemos terminado movf Dato2,W ; W = multiplicador btfsc status,Z ; Salta si Z=1 return ; Z=0 hemos terminado SIGUE movf DL,W ; W=DL addwf Dato1,W ; W += multiplicando movwf DL ; DL=W btfsc status,C ; Salta si C=0 incf DH,F ; decfsz Dato2,F ; multiplicador-1 goto SIGUE ; no hemos acabado movwf DL ; DL=W movwf AxB ; Producto=W ;movwf PORTB ; Puerto B=W clrf Dato2 return ; fin subrutina ;------------------------------------------------------------------ end ; fin ;------------------------------------------------------------------