Last active
August 29, 2015 14:00
-
-
Save jerivas/11360997 to your computer and use it in GitHub Desktop.
Controlador PID para cocina mejorada
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Controlador PID para sistema de cocina mejorada | |
* Por Eduardo Rivas, Ángel Moreno, David Escobar, Josué Hernández, Henry Orellana | |
* Para la Universidad Don Bosco, Ciclo I - 2014 | |
* A partir de los valores del sensor de oxígeno se ajusta la posición de la | |
* válvula de la chimenea a través de un servo motor. | |
* El programa hace uso de las capacidades de DSP del dsPIC30F para procesar las | |
* señales. Las constantes Kp, Ki, Kd fueron obtenidas con Matlab. | |
* Probado en el dsPIC30F4011, pero debería funcionar con toda la familia 30F */ | |
/* Archivos de cabecera | |
* Todos los headers son genericos para hacer el proyecto más portable a otros | |
* dispositivos. El cambio de dispositivo debe hacerse en la configuración | |
* de proyecto, no en el código fuente. */ | |
#include <libpic30.h> | |
#include <p30Fxxxx.h> | |
#include <dsp.h> | |
// Configuración general | |
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc = 16x7.5MHz, Fcy = Fosc/4 = 30MHz | |
_FWDT(WDT_OFF); // Watchdog timer apagado | |
_FBORPOR(MCLR_DIS); // Pin de reset deshabilitado | |
// Pin donde se encuentra el LED y su registro TRIS relacionado | |
#define LED _LATB1 | |
#define TRISLED _TRISB1 | |
#define PDCMAX 1425 // Valor PWM para 90º en el servo (máximo) | |
#define PDCMIN 500 // Valor PWM para 0º en el servo (mínimo) | |
fractional temp_in = 0; // Variable para la temperatura de la termocupla | |
float ctrl_out = 0; // Salida del controlador | |
char contador = 0; // Contador de interrupciones del Timer 1 | |
// Declaración de una instancia PID llamada cocinaPID | |
tPID cocinaPID; | |
/* La estructura cocinaPID contiene punteros a los coeficientes del controlador | |
* en la memoria X y punteros al arreglo histórico (valores anteriores) en la | |
* memoria Y. Las siguientes directivas crean los arreglos en memoria X y Y. */ | |
fractional coeficientesABC[3] __attribute__ ((section (".xbss, bss, xmemory"))); | |
fractional historicoControl[3] __attribute__ ((section (".ybss, bss, ymemory"))); | |
/* Los coeficientes A, B y C se derivan a partir de Kp, Ki y Kd. | |
* Los coeficientes K deben definirse más abajo en formato Q15. */ | |
fractional coeficientesK[] = {0,0,0}; | |
// Prototipos de funciones | |
void config_pines(); | |
void leer_adc(); | |
unsigned int map(float x, unsigned int in_min, unsigned int in_max, unsigned int out_min, unsigned int out_max); | |
void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void); | |
int main(void) { | |
// Inicializar la estructura PID | |
// Puntero a los coeficientes A, B, C | |
cocinaPID.abcCoefficients = &coeficientesABC[0]; | |
// Puntero al arreglo histórico (valores previos) | |
cocinaPID.controlHistory = &historicoControl[0]; | |
// Limpiar el arreglo histórico y la salida del controlador | |
PIDInit(&cocinaPID); | |
cocinaPID.controlReference = 0; // Valor de consigna | |
coeficientesK[0] = Q15(0.025); // Valor de Kp | |
coeficientesK[1] = Q15(0.051); // Valor de Ki | |
coeficientesK[2] = Q15(0); // Valor de Kd | |
// Encontrar los coeficientes A, B, C a partir de los coeficientes K | |
PIDCoeffCalc(&coeficientesK[0], &cocinaPID); | |
// Configurar pines de I/O | |
config_pines(); | |
// Iniciar con el LED indicador apagado | |
LED = 0; | |
/* Programa principal: | |
* No se hace nada porque todo está manejado por interrupción de Timer 1. */ | |
while(1); | |
return 0; | |
} | |
// Función que configura los pines de I/O | |
void config_pines() { | |
// Configuración de pines digitales | |
ADPCFG = 0xF; // Todas las entradas analógicas deshabilitadas | |
// (serán rehabilitadas individualmente) | |
TRISLED = 0; // Pin del LED como salida | |
// Configuración de ADC | |
_TRISB0 = 1; // RB0 como entrada (canal 0) | |
_PCFG0 = 0; // Habilitar entrada analógica en RB0 | |
_TRISB3 = 1; // RB3 como entrada (canal 1) | |
_PCFG3 = 0; // Habilitar entrada analógica en RB3 | |
_ASAM = 0; // Requerir "set" manual de SAMP para iniciar muestreo | |
_SSRC = 0b111; // Iniciar conversión después del tiempo definido por SAMC | |
_SAMC = 31; // Tiempo de muestreo de 31 Tad (máximo) | |
_VCFG = 0; // Referencia de voltaje (Vref) en AVDD y AVSS | |
_ADCS = 5; // Tad = (ADCS+1)*Tcy/2 = 0.1us para ADCS = 5 | |
_FORM = 3; // Formato del ADC como Fractional signado | |
_CHPS = 1; // Activar canal 0 y 1 del ADC | |
_SIMSAM = 1; // Muestreo simultáneo de los canales | |
_CH0SA = 0; // Entrada positiva de canal 0 en RB0 | |
_CH0NA = 0; // Entrada negativa de canal 0 en Vref- | |
_CH123SA = 1; // Entrada positiva de canal 1 en RB3 | |
_CH123NA = 0; // Entrada negativa de canal 1 en Vref- | |
_ADON = 1; // Encender ADC | |
// PWM en modo de carrera libre | |
// Período del PWM = Tcy * preescala * PTPER = 0.33ns * 64 * 9470 = 20ms | |
PWMCON1 = 0x00FF; // Habilitar todos los pares PWM en modo complementario | |
PTCON = 0; // Limpiar el registro de base de tiempo del PWM | |
_PTCKPS = 3; // prescala = 1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64) | |
PTPER = 9470; // 20ms de período PWM (Valor de 15 bits) | |
PDC1 = 0; // 0% de ciclo de trabajo en PWM 1 (máximo es 65536) | |
PDC2 = 0; // 0% de ciclo de trabajo en PWM 2 (máximo es 65536) | |
PDC3 = 0; // 0% de ciclo de trabajo en PWM 3 (máximo es 65536) | |
PTMR = 0; // Limpiar el timer de 15 bits del PWM | |
_PTEN = 1; // Habilitar la base de tiempo PWM | |
// Configuración de interrupción por Timer1 | |
PR1 = 0x1FFF; // Período de Timer1 = 8191 | |
TMR1 = 0; // Limpiar el Timer 1 | |
_T1IE = 1; // Habilitar interrupción por Timer1 | |
_TCKPS = 3; // Preescala de 256 | |
_TON = 1; // Encender Timer 1 | |
// Frecuencia del Timer1 = Fcy/PR1/Preescala = 30MHz/8191/256 = 14.3 Hz | |
// Período del Timer1 = 0.070 segundos. | |
} | |
/* Esta función toma una única muestra en todos los canales habilitados del ADC | |
* Los resultados aparecen en el buffer de cada canal (ADCBUF0, 1, 2, 3). */ | |
void leer_adc() { | |
_SAMP = 1; // Iniciar muestreo | |
while (!_DONE); // Esperar que el ADC convierta automáticamente | |
// En este punto, los buffers de cada canal tendran los valores analógicos | |
} | |
// Función de mapeo basada en la de Arduino | |
// Permite acomodar un rango de valores a otro | |
unsigned int map(float x, unsigned int in_min, unsigned int in_max, unsigned int out_min, unsigned int out_max) { | |
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; | |
} | |
/* Rutina de interrupción del Timer1 | |
* Verifica que el sensor de O2 haya alcanzado una temperatura ideal a través | |
* de la termocupla. Si es así, aplica la acción controladora */ | |
void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void) { | |
_T1IF = 0; // Limpiar la bandera de interrupción del Timer 1 | |
leer_adc(); // Se leen los dos canales del ADC (0 y 1) | |
temp_in = ADCBUF1; // Obtener la temperatura del canal 1 | |
// Si la temperatura está por debajo del umbral de trabajo del sensor... | |
if (temp_in < Q15(0)) { | |
// Se abre por completo la válvula | |
PDC1 = PDCMAX; | |
// También se apaga el LED indicador para indicar inactividad | |
LED = 0; | |
/* Pero si la temperatura es adecuada, verificar que haya pasado el tiempo | |
* adecuado para aplicar la acción controladora. */ | |
} else { | |
/* La acción controladora se aplica en un período el doble del | |
configurado en el Timer 1 = 2*0.070 = 0.140 segundos. | |
La frecuencia de muestreo es 7.15 Hz. */ | |
if (contador == 1) { | |
contador = 0; // Limpiar contador | |
LED = 0; // Apagar el LED brevemente en cada muestra | |
// Leer el valor capturado por el canal 0 y alimentarlo al PID | |
cocinaPID.measuredOutput = ADCBUF0; | |
PID(&cocinaPID); // Aplicar la acción controladora | |
// Ajustar la salida a valores comprensibles para el PWM | |
ctrl_out = Fract2Float(cocinaPID.controlOutput) + 1; | |
PDC1 = map(ctrl_out, 0, 2, PDCMIN, PDCMAX); | |
// Mientras no se alcance el conteo deseado, solo se incrementa el contador | |
} else { | |
contador++; | |
LED = 1; // Encender el LED inidicador para indicar funcionamiento | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Es necesario incluir tres cosas en este script. | |
1. La ubicación de la carpeta lib del compilador (XC16 o C30) | |
2. La ubicación de la carpeta gld del compildaro (XC16 o C30) | |
3. El archivo .gld para el dispositivo a usar (p30FXXX.gld) | |
NOTA: Debe recordarse que las rutas son distintas para Windows y Linux | |
Además, Windows usa plecas invertidas para separar rutas (\), | |
pero Linux usa plecas "regulares" (/). | |
*/ | |
/* Ruta a la carpeta lib de XC16 (Linux) */ | |
SEARCH_DIR(/opt/microchip/xc16/v1.21/lib) | |
/* Ruta a la carpeta gld de XC16 (Linux) */ | |
SEARCH_DIR(/opt/microchip/xc16/v1.21/support/dsPIC30F/gld) | |
/* Ruta a la carpeta lib (Windows) */ | |
/* SEARCH_DIR(..\..\..\..\lib) */ | |
/* Ruta a la carpeta gld (Windows) */ | |
/* SEARCH_DIR(..\..\..\..\support\gld)*/ | |
INPUT(-ldsp) | |
INPUT(-lpic30) | |
INPUT(-lm) | |
INPUT(-lc) | |
/* Archivo .gld, debe encontrarse en una de las rutas anteriores */ | |
INCLUDE p30F4011.gld |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment