X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 //_________________________________________________________________________________ // UNIVERSIDAD AUTONOMA DE MADRID. SEGAINVEX-Electronica // // Proyecto: Control para un motor de CC con realimentación mediante encoder digital // y salida PWM. // Micro: Philips 87LPC768 // // Nº Orden:1050460 // Realizado:Patricio Coronado Collado // Fichero: 1050460.c // // Fechas: // Inicio: 14 Septiembre 2005 // Final: 28 Noviembre 2005 // Versión: 1.0 //_________________________________________________________________________________ // DESCRIPCIÓN // // El sistema actua un motor cc de Maxon con reductora y encoder // Motor: RE36 70W de Maxon // Reductora:GP42C 230:1 de Maxon // Encoder:HEDS5540 // Utiliza un C.I. de potencia:LMD18200T // Y un lector de encoder HCTL2020 // // Utiliza un convertidor A/D para leer la consigna de ángulo, 90º a 120º, // otro A/D para la consigna de velocidad, 1 a 10 rpms. Un PWM para la salida // Una interrupción para leer los pasos del encoder y otra linea para la // dirección. // // El sistema mueve el eje de un portamuestras un ángulo de 90 a 120º // cambiando el sentido, y a una velocidad de 1 a 10 rpms // //__________________________________________________________________________________ // CODIGO #ifndef __1050460_main__ #define __1050460_main__ //................................................................................... // Ficheros include //................................................................................... #include <intrins.h> #include <math.h> #include <segainc.h> #include <REG768.H> #include "1050460.h" // Declaraciones, tipos, variables, ctes etc. //................................................................................... //--------------------------------- MAIN ----------------------------------------void main( void ) { // // INHABILITA EL MOVIMIENTO DEL MOTOR FrenoMotor = 1; // Hecha el freno del driver-motor con el bit de freno PosicionCero=FALSE; // El sistema al encender por defecto no está en la posición de inic io // // CONFIGURA LOS RECURSOS DEL MICROCONTROLADO inicializa_sistema(); // Hace 2 conversiónes para empezar while(ADCS || ADCI); adc_start(VELOCIDAD); while(ADCS || ADCI); adc_start(ANGULO); // Hace 2 conversiónes para empezar while(ADCS || ADCI); adc_start(VELOCIDAD); while(ADCS || ADCI); adc_start(ANGULO); Velocidad = VelocidadMADC;// Consigna de velocidad para buscar el cero // // SITUA EL MOTOR EN LA POSICIÓN DE REFERENCIA // Arranca el motor PWMCON0 |= 0x80; // Activa el PWM FrenoMotor = 0; // habilita el driver del motor // Sentido Page: 1 X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 PWMCON0 &= 0xFD;// Sentido del PWM CW Sentido = CW; // Regulador MarchaParo = PARO; // Comienza a barrer el ángulo de consigna desde parado en cero AceleraRegulador = SI; FrenadoRegulador = NO; // Para que el regulador no haga un frenado del sistema Tiempo = 0; // Variable del regulador par frenar suave TR1 = 1; // Arranca el timer para empezar a muestrear ET1=1; // if (PinCero) { while(PosicionCero==FALSE){/* Espera a estar en 0 grados (interrupción ex0) */ } } else { // Si ya está en cero... IE0=1; // Hace la int externa 0 (la de la posición de cero) } // ACTIVA LA INTERRUPCION DEL TOPE DE SEGURIDAD IE1 = 0; // Limpia el Flag de la interrupción externa 1 IT1 = 1; // Interrupción externa INT1 activa por flanco EX1 = 1; // Activa interr externa INT1 Rebasamiento de angulo // Una vez que está en la posición de referencia inicia el movimiento // //.............................. BUCLE PRINCIPAL ..................................... // Inicio while(1){ // feed the watchdog FEED_WD // Actualiza el WatchDog while(ADCS || ADCI); adc_start(VELOCIDAD); while(ADCS || ADCI); adc_start(ANGULO); //SI ESTÁ PARADO COMPRUEBA PIN MARCHA PARO PinMarchaParo = 1; // Si hay que arrancar desde parado actualiza variables de ángulo y velocidad if(MarchaParo == PARO && PinMarchaParo == 1) { // Consigna de velocidad por si se ha cambiado durante el paro Velocidad = VelocidadMADC; // Consigna de ángulo Coef1Recarga = (unsigned int)Coef1*AnguloADC; if(AnguloADC <= 122) { // Angulos menores de 102.45 grados NVueltasT0 = 1; RecargaT0=Coef2A-Coef1Recarga; } // del if else /* AnguloADCc > 106 */ { NVueltasT0 = 2; RecargaT0=Coef2B-Coef1Recarga; } // Recarga el timer 0 para que recorra el angulo correcto TL0 = L(RecargaT0); TH0 = H(RecargaT0); TF0 = 0; //Limpia el flag de interrupción del timer 0 y.. ET0 = 1; //Activa la interrupción del timer 0 para el rebote // Prepara y arranca el regulador TR1 = 1; // Arranca el regulador (Timer 1) ET1 = 1; // PinLed = LED_ON; MarchaParo = MARCHA; } }// del while(1) }// del main //----------------------------------------------------------------------------------// FUNCION PARA INICIALIZAR EL SISTEMA //----------------------------------------------------------------------------------void inicializa_sistema(void) { // ESTABLECE LA PRIORIDAD DE LAS INTERRUPCIONES pagina 24 del datasheet // Angulo cero : Externa0 // set isr priority level = 3 IP0 &= 0xFE; // XXXXXXX0 IP0H &= 0xFE; IP0 |= 0x01; IP0H |= 0x01; // Angulo tope : Externa1 // set isr priority level = 3 Page: 2 X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 IP0 &= 0xFB; // XXXXX0XX IP0H &= 0xFB; IP0 |= 0x04; IP0H |= 0x04; // Angulo final : Timer0 // set isr priority level 3 IP0 &= 0xFD; // XXXXXX0X IP0H &= 0xFD; IP0 |= 0x02; IP0H |= 0x02; // Regulador : Timer 1 // set isr priority level 2 IP0 &= 0xF7;// XXXXXX0X IP0H &= 0xF7; IP0H |= 0x08; // ADC // set isr priority level 0 IP1 &= 0xEF; // XXX0XXXX IP1H &= 0xEF; // Comparadores // set isr priority Level 3 for comparator 1 IP1 &= 0xDB;// XX0XX0XX IP1H &= 0xDB; IP1 |= 0x20; IP1H |= 0x20; // WatchDog (no necesita) //............................................................................... // INICIALIZA WATCHDOG // feed the watchdog FEED_WD // Actualiza el WatchDog // set up watchdog timer WDinterval WDinterval &= 0x07; WDCON = WDinterval; //............................................................................... // INICIALIZA COMPARADOR 1 // disable digital inputs on pins used PT0AD = 0x48; // set input pins to input mode P0M2 &= 0xF7; P0M1 |= 0x08; // Comparator outputs don't used // P0M2 |= 0x00; // P0M1 &= 0xFF; // configure comparator 1 // Positive input - P0.3 (CIN1B) // Negative input - Vref // Comparator output enabled // Comparator enabled CMP1 = 0x38; // configure comparator 2 // Comparator not enabled CMP2 = 0x00; //................................................................................... // INICIALIZA ADC RCCLK = 0; // 0 selecciona el clk de la CPU // disable digital input on channel pins PT0AD |= 0x30; //XX11-XXXX // disable digital output on channel pins (high impedance) P0M1 |= 0x30; //XX11-XXXX pines 4 y 5 del puerto 0 P0M2 &= 0xCF; //XX00-XXXX ENADC = 1; // Habilita la interrupción // //................................................................................... // INICIALIZA PWM CNSW0 = 0xFC; // Frecuencia PWM FPWM a 0X1FC = 508 CNSW1 = 0X01; // CPSW0 = CPSW1 = CPSW2 = CPSW3 = CPSW4 = PWMCON1 PWMCON0 Page: 3 0xFF; 0x00; 0x00; 0x00; 0x00; &= 0x00; |= 0x40; // PWM0 al 50% CPSW = 0XFF = 255 // Todos las salidas PWM0 totalmente a 1 // Estos son los bit 9 y 10 de los PWM todos a 1 // Frenado desactivado. // XFER=1 Transferir de registros "Shadows" a los activos X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 // //................................................................................... // CONFIGURA EL TIMER 0 COMO CONTADOR TR0 = 0; //Detiene el timer 0 TL0 = TL0_CUENTA; // Pone a cero el valor del contador TH0 = TH0_CUENTA; TMOD &= 0xf0; //Limpia los bits de configuración del timer 0 del TMOD TMOD |= 0x05; // Configura el timer 0 como contador con entrada por el pin T0 modo 0, gat e = 1 TR0 = 1; // Arranca el timer 0 // //................................................................................... // CONFIGURA EL TIMER 1 PARA PERIODO DE MUESTREO TR1 = 0; // stop timer 1 if it is already running init initial count value TL1 = TL1_2mS; // recarga para Ts = 2mS TH1 = TH1_2mS; TMOD &= 0x0f; // clear all timer 1 bits TMOD |= 0x10; // select timer mode 1 and options // //................................................................................... // CONFIGURA PUERTOS // P0.4 entrada A/D (configurado en función específica) // P0.5 entrada A/D (configurado en función específica) // P0.3 entrada comparador (configurado en función específica) // P0.2 entrada comparador (configurado en función específica) // P0.0 salida comparador (configurado en función específica) // P0.1 salida PWM (configurado en función específica) // P1.2 siempre salida open drain (escribir 1 para usar como entrada) UpDown = 1;// P1^2 = 1 para usar como interrupción // P1.0 salida rst 2020 P1M1 &= 0xFE; //XXXXXXX0 P1M2 &= 0xFE; //XXXXXXX0 // P1.4 interrupción 1 modo 00 standar 8051 quasibidireccional P1M1 &= 0xEF; //XXX0XXXX P1M2 &= 0xEF; //XXX0XXXX // P1.1 tope 1 modo standar 8051 quasibidireccional P1M1 &= 0xFD; //XXXXXX0X P1M2 &= 0xFD; //XXXXXX0X // P1.3 tope 2 int0 modo standar 8051 quasibidireccional P1M1 &= 0xF7; //XXXX0XXX P1M2 &= 0xF7; //XXXX0XXX // // Activa interrupciones EC1 = 0; // No enable comparator 1 interrupt EC2 = 0; // No enable comparator 2 interrupt IT0 = 1; // Interrupción externa INT0 activa por flanco EX0 = 1; // Activa interr externa INT0 (Angulo Cero) IT1 = 0; // Interrupción externa INT1 activa por flanco EX1 = 0; // Activa interr externa INT1 Rebasamiento de angulo EAD = 1; // Si enable adc interrupt EA = 1; // Si permitimos interrupciones activas CMP1 &= 0xfe; // clear comparator interrupt flags EC1 = 1; // Enable comparator 1 interrupt } // //----------------------------------------------------------------------------------// FUNCIONES PARA MANEJAR EL PWM0 //................................................................................... // Funcion para cargar el PWM0 // Controla la velocidad del motor. Si PWM_DC es 0 va a 76,6 rps de velocidad // Si PWM_DC es 255 va a 0 rps de velocidad // void carga_pwm0(unsigned int PWM_DC) { if (PWM_DC == 0) PWM_DC = 1; // Evita que el registro CPSW se ponga a 0 if (PWM_DC >= 255 ) PWM_DC = 255; // El rango es de 1 a 255 CPSW0 = L(PWM_DC); // Cargo la parte baja del nuevo valor CPSW4 |= H(PWM_DC); // Ahora translado sólo los dos bits útiles // XFER=1 Transferir de registros "shadows" a los activos PWMCON0 |= 0x40; } //................................................................................... // Funcion para hacer una conversión unsigned char adc_start(unsigned char canal) { Page: 4 X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 // check if conversion is already in progress if (ADCS || ADCI) return ADC_BUSY; // clear channel selection bits ADCON &= 0xFC; //XXXX-XX00 // select channel for conversion ADCON |= canal; //XXXX-XX00 // start conversion ADCS = 1; return ADC_STARTED; } //................................................................................... // Funcion para leer una conversión void lee_adc(void) interrupt 11 { switch (ADCON & 0x03 /* canal del convertidor */) { case VELOCIDAD: VelocidadMADC = DAC0; // Velfloat= 0.4*VelocidadADC + 0.4*VelocidadMADC_1 + 0.4*VelocidadMADC_2; // VelocidadMADC = (unsigned char)Velfloat; // VelocidadMADC_2 = VelocidadMADC_1; // VelocidadMADC_1 = VelocidadMADC; break; case ANGULO: AnguloADC = DAC0; break; default:break; } // clear interrupt flag to allow another conversion ADCI = 0; } //----------------------------------------------------------------------------------// REGULADOR // Función para dar servicio a la int timer 1. Periodo de muestreo Ts // Esta función calcula la velocidad del eje y la posición angulares // void timer1_muestreo(void) interrupt 3 using 1 //(banco de registros) { unsigned int ContadorPulsosTs,PulsosTs,CargaPWM; static unsigned int ContadorPulsosTs_1; float RpsConsigna,RpsReal,ek,Kek; static float ak_1,ak; // feed the watchdog FEED_WD // Actualiza el WatchDog // Actualiza variable para procesos de aceleración y frenado Tiempo++; // Recarga el timer TL1 = TL1_2mS ; TH1 = TH1_2mS ; // Lee los pulsos dados por el encoder L(ContadorPulsosTs)=TL0; H(ContadorPulsosTs)=TH0; // Calcula los pulsos producidos en un Ts PulsosTs=ContadorPulsosTs-ContadorPulsosTs_1; // Almacena la cuenta antigua ContadorPulsosTs_1=ContadorPulsosTs; // Como el encoder da 2000 pulsos*revolución // La Cuenta entre muestreos es pulsos en un Ts.=> Cuenta/Ts es pulsos por segundo pps // pps/2000 es revoluciones/segundo rps // Para Ts = 2mS, rps = PulsosTs*/(2000) = Cuenta/4 => rps // Luego para Ts = 2mS una cuenta es 1/4 rps del eje del motor // Calcula la velocidad en rpsMOTOR RpsReal=PulsosTs/4; // Calcula la velocidad de consigna // Para pasar de 0->255 a 3.833->38.33 rpsMOTOR (X 60/230)=> 1->10rpm REDUCTORA RpsConsigna=Velocidad*0.1353+3.8333; // // MODULA LA CONSIGNA PARA HACER FRENADO if (FrenadoRegulador==SI) { RpsConsigna=RpsConsigna*CoefVel[N_COEFICIENTES-Tiempo]; // Modula la consigna if(Tiempo > N_COEFICIENTES) { // Si ha pasado el tiempo de frenada Tiempo = 0; // Tiempo a cero ak_1 = 0.0; // Inicializa variables del regulador ContadorPulsosTs_1 = 0; if(Sentido == CW) { // Si está parando en la barrera óptica... PWMCON0 |= 0x02; // Cambia el sentido a CCW Page: 5 X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 Sentido = CCW; // Informa ala sistema que va a girar en sentido antihorario TL0 = L(RecargaT0); // Recarga el timer 0 para que recorra el angulo correcto TH0 = H(RecargaT0); TF0 = 0; //Limpia el flag de interrupción del timer 0 y.. ET0 = 1; //Activa la interrupción del timer 0 para el rebote // COMPRUEBA PIN MARCHA PARO PinMarchaParo = 1; if(PinMarchaParo == 0) { TR1 = 0; // Para el regulador (Timer 1) ET1 = 0; // PinLed = LED_OFF; MarchaParo = PARO; } } //if(Sentido == CW) else {/*Sentido == CW*/ // Si está parando en el Timer 0.... IE0=0; // Limpia el flag de la int ex 0 del paso por cero EX0 = 1; // Activa la interrupción del paso por cero PWMCON0 &= 0xFD;// Sentido del PWM CW Sentido = CW; // Informa al sistema que está girando en sentido horario } // else FrenadoRegulador = NO; AceleraRegulador = SI; }// if(Tiempo > 25) ha pasado el tiempo de frenada } // if (FrenadoRegulador == SI) // // MODULA LA CONSIGNA PARA HACER ACELERACION // Proceso de aceleración if(AceleraRegulador == TRUE ) { RpsConsigna=RpsConsigna*CoefVel[Tiempo]; // Calculo de la velocidad de consigna if(Tiempo > N_COEFICIENTES) { AceleraRegulador = FALSE; // Si ha pasado el tiempo de aceleración } } //....................................................... // Punto de suma y error ek=RpsConsigna-RpsReal; // Error por ganancia integral Kek=ek*10.0; // Integrador ak=TS*Kek+ak_1; // ak/ek=Ts/(z-1) // Antiwindup if(ak>24.0)ak=ak_1; if(ak<0.0)ak=ak_1; // Actualización de actuación anterior ak_1=ak; // ak está en voltios. Pasa a PWM de modo que: // 0V=>a 255 y 24V=>0. La pendiente es -10.625: // Pasa de voltios a formato PWM ak=ak*10.625; // Convierto a formato int que es lo que entiende el PWM CargaPWM=(int)ak; // Para el cambio de signo... // Necesito complementar CargaPWM=~CargaPWM; // Y trimar a 10 bits CargaPWM=CargaPWM & 0X00FF; // Cargo el PWM carga_pwm0(CargaPWM); // Proceso normal } //----------------------------------------------------------------------------------// Funcion que da sevicio al paso por cero void interrupcion_posicion_cero(void) interrupt 0 using 1 { unsigned int AnguloADCc,CorreccionAngulo; // Angulo ADC corregido con la velocidad // Este if es solo para poner el eje de la reductora en posición conocida if(PosicionCero == FALSE) { PosicionCero = TRUE; } // Calcula los valores de recarga del timer Velocidad = VelocidadMADC;// Consigna de velocidad para la siguiente vuelta // Programa el timer 0 para que avance 120 - 1.8 = 118.2 grados // más una corrección que va de 0 grados a máxima velocidad a 1.8 grados a mínima // de esta forma compensa el revasamiento de ángulo en el transitorio de frenado // que es de 15 periodos de muestreo Page: 6 X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 // CorreccionAngulo = VelocidadMADC/20+1; CorreccionAngulo = 32-VelocidadMADC/8; AnguloADCc = AnguloADC+CorreccionAngulo; // Angulo corregido con la velociad Coef1Recarga = (unsigned int)Coef1*AnguloADCc; if(AnguloADCc <= 122) { // Angulos menores de 102.45 grados NVueltasT0 = 1; RecargaT0=Coef2A-Coef1Recarga; } // del if else /* AnguloADCc > 106 */ { NVueltasT0 = 2; RecargaT0=Coef2B-Coef1Recarga; } // EX0 = 0; // Desactiva la interrupción del paso por cero Tiempo = 0; // Empieza a calcular el tiempo FrenadoRegulador = SI; // Cada vez que llegue a cero comienza a frenar } //----------------------------------------------------------------------------------// Función para dar servicio a la int contador 0. // Se produce una int. cuando el eje de la reductora se ha movido 1/7666 de grado void timer0_contador(void) interrupt 1 using 2 { if (NVueltasT0==0) { // Si ha llegado al final del angulo.... Tiempo = 0; // Empieza a calcular el tiempo FrenadoRegulador = SI; // Cada vez que llegue a cero comienza a frenar ET0 = 0; //desactiva la interrupción del timer 0 } else NVueltasT0--; } //----------------------------------------------------------------------------------// Funcion que da sevicio a la int ex 1 del tope de seguridad void interrupcion_tope_marcha_paro(void) interrupt 2 using 1 { unsigned int ContadorError; TR1 = 0; // Para el regulador (Timer 1) ET1 = 0; // Inhabilita la interrupción del Timer 1 TF1 = 0; // y limpia el flag carga_pwm0(255); // Parada del motor while (PinMarchaParo == MARCHA) { // while(ADCS || ADCI); adc_start(ANGULO); FEED_WD // Actualiza el WatchDog ContadorError ++; if (ContadorError == 50000) PinLed = !PinLed; // feed the watchdog } rst(); // Hace un reset al sistema } //----------------------------------------------------------------------------------// Funcion que da sevicio a la int del comparador 1 que se activa por sobrecorriente void interrupcion_sobrecorriente(void) interrupt 12 using 3 { // Si hay sobrecorriente es que se ha estrellado contra la pared de la campana unsigned int ContadorErrorCorriente; // PinOut = !PinOut; FrenoMotor = 1; // Parada del motor while (PinMarchaParo == MARCHA) { ContadorErrorCorriente ++; if (ContadorErrorCorriente == 10000) // feed the watchdog FEED_WD // Actualiza el WatchDog } rst(); // Hace un reset al sistema CMP1 &= 0xfe; // clear interrupt flag PinLed = !PinLed; } //----------------------------------------------------------------------------------// Funcion que da hace un reset del sistema (Función de Mariano Cuenca Alba) void rst() { void (code *Reset)(); Reset=0; Reset(); } Page: 7 X:\ProyecW\1050460 Brazo oscilante\C51\Primer micro grabado 28 Nov 2005\1050460_main.c 11/28 //________________________________ FIN _________________________________________________ #endif //#ifndef __1050460_main__ Page: 8