Skip to content

Instantly share code, notes, and snippets.

@jerivas
Last active August 29, 2015 14:00
Show Gist options
  • Save jerivas/11360997 to your computer and use it in GitHub Desktop.
Save jerivas/11360997 to your computer and use it in GitHub Desktop.
Controlador PID para cocina mejorada
/* 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
}
}
}
/* 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