Autor Tema: Ejemplito 16F876A: El Termo-Servo o un servo controlado por temperatura.  (Leído 6696 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado RedPic

  • Administrador
  • DsPIC33
  • *******
  • Mensajes: 5538
    • Picmania by Redraven
Este ejemplito no es realmente un nuevo ejemplito, es mas bien un gazpacho de dos o tres de los anteriores. Me explico: estaba esta mañana pensando qué hacer y se me ocurrió buscar alguna aplicación de algo ya funcionando, entonces pensé en hacer algo con la temperatura y algun accionador en función de ella, algo así como un termostato. Pero tenía que tener niveles, no solo el ON/OFF típico de los termostatos, y entonces se encendió la lucecita y se me ocurrió posicionar un Servo en función de la lectura de temperatura.

Y este es el resultado. Una mezcla entre los Ejemplitos anteriores:

Recibiendo del RS232 sobre un Buffer y procesandolo posteriormente, Temperatura con un LM35a  y el Controlando un SERVO con el PIC desde nuestro PC.

He intentado explicar exhaustivamente el codigo para indicar exactamente lo que hago en cada línea de forma que tenga poco que explicaros aquí, sin embargo deseo haceros una descripción general de su funcionamiento, y hacer hincapié en algunas de las funciones o trucos implementados.

He partido originalmente del código de <a href="http://miarroba.com/foros/ver.php?foroid=46840&temaid=4742913" target="_blank">Controlando un SERVO con el PIC desde nuestro PC.[/url]

Como en este código obtengo una señal cada 20 milisegundos, para generar el periodo del pulso PWM para el servo, la primera modificación que introducí fue la de contar 50 de estas señales para obtener otra cuya periodicidad fuese de 1 segundo.

Este segundero era el que iba a utilizar para tomar las muestras de temperatura, no quería que el servo corriese locamente intentando seguir la temperatura cada pocos milisegundos.

Tras eso introducí la función de toma de temperaturas que me devolvía ésta ya convertida en grados centígrados, y con ella implementé un pequeño truco, realmente una simple regla de tres, para mover el Servo en función de lo leído pero solo en 7 posiciones prefijadas.

Como el Menú tiene definidas 7 posiciones prefijadas ajusté el máximo de temperatura leída a 35 grados centígrados y esto lo hago igual a 7, en el otro extremo 5 grados centígrados son igual a 1. Valores mayores de temperatura mantienen el resultado de 7 y menores a 1 matienen el 1. Y con este valor simulo que he recibido por la RS232 los caracteres del "1" al "7" que son los que realmente comandan al Servo.

Y como no iba a estar esperando a que la temperatura de mi habitación cambiase entre estos valores tan extremos habilité por PIN_B0 una resistencia de calentamiento pegada al LM35A, el sensor de temperatura. Con los comandos RS232 "a" y "d" encendía o apagaba la resistencia y así podia "forzar" la temperatura y por ende la respuesta del Servo.

Y por último le coloqué los comandos "z" y "p" para activar o desactivar el seguimiento de temperatura del Servo. Y con ello dí por finalizado este ejemplito.

Nota importante: Notad que todas las transmisiones vía RS232 y las lecturas de temperatura y su ajuste a los valores necesarios para calcular la posición del Servo se ejecutan siempre durante el semiperiodo bajo del pulso PWM, esto lleva a que la duración máxima de ejecución de estas funciones deben ser siempre menores a dicho tiempo. (Ver la nota correspondiente en el código).



// servo_pwm_232_plus_temperatura

// Ejemplo con un servo HITEC HS-300 CW
// Alimentación y pulsos a 5V
// y un LM35a Sensor de Temperatura
// ajustado para:
//   Pulso Minimo al Servo para  5º y
//   Pulso Maximo al Servo para 35º

// Cuadro de Tiempos :

//    Periodo 20 ms (Frecuencia 50 Hz)
//    Ancho Pulso minimo 0.75 ms
//    Ancho pulso medio  1.50 ms
//    Ancho pulso maximo 2.25 ms

//    TMR0 a 1:16 -> 1 RTCC cada 4.096 ms
//                -> 1 Tick cada 0.096 / 256 = 0.016 ms
//                -> 20 ms = (4 x RTCC completas) + (1 * RTCC - 30 ticks)

#include <16f876a.h>
#fuses   XT,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT
#use     delay(clock=4000000)
#use     standard_io(b)
#use     rs232(baud=19200, xmit=PIN_C6, rcv=PIN_C7) // Ajusto a 19200 baudios para tardar lo menos posible
                                                    // en transmitir por la RS232 lo que sea
                                                    // ver Nota en la función funciones_tras_duty_cicle()

#define PIN_SERVO1 PIN_B0

const int AJUSTE_FINO_DE_RTCC =  30;

const int ticks_PULSO_MINIMO  =  50;
const int ticks_PULSO_MINIMO2 =  63;
const int ticks_PULSO_MINIMO1 =  78;
const int ticks_PULSO_MEDIO   =  95;
const int ticks_PULSO_MAXIMO1 = 108;
const int ticks_PULSO_MAXIMO2 = 125;
const int ticks_PULSO_MAXIMO  = 140;

int1 flagRTCC        = 0;
int1 flagSEGUNDO     = 0;
int  contRTCC        = 0;
int  contSEGUNDO     = 0;
int1 flagSERVO1      = 0;
int  tSERVO1         = ticks_PULSO_MEDIO;
char Keypress        = 0;
int  adc_temperatura = 0;
int  kx              = 0;

int1 flagSEGUIDOR        = 0;
int1 flagXMITservo       = 0;
int1 flagXMITtemperatura = 0;
int1 flagXMITsegon       = 0;
int1 flagXMITsegoff      = 0;
int1 flagXMITmenu        = 0;

#int_rda
void rda_isr() {
   Keypress=0x00;           // limpio la variable de recepción
   if(kbhit()){             // si hay algo pendiente de recibir
      Keypress=getc();      // lo obtengo y lo guardo en  la variable de recepción
   }
}

#int_RTCC
RTCC_isr(){                             // RTCC esta ajustada a saltar cada 4.096 ms
   ++contRTCC;                          // cuento RTCCs para conseguir 20 ms (el periodo del PWM)
   if(contRTCC==4){                     // si es la cuarta RTCC que salta
      set_TIMER0(AJUSTE_FINO_DE_RTCC);  // pongo el valor del timer para que la quinta sea mas corta
   }
   if(contRTCC==5){                     // Si he llegado a la quinta
      flagRTCC=1;                       // pongo en alto el flag que indica que han pasado 20 ms
      contRTCC=0x00;                    // y vuelvo a comenzar
   }
}

void menu(void){

   // Presento por la RS232 el menú de opciones
   printf("TERMO-SERVO Commander" );
   printf("[ m ] Este menú" );
   printf("[ s ] Transmite valor Servo" );
   printf("[ + ] Incrementa pulso" );
   printf("[ - ] Decrementa pulso" );
   printf("[ t ] Lee y transmite Temperatura" );
   printf("[ a ] Activa calentador" );
   printf("[ d ] Desactiva calentador" );
   printf("[ z ] Activa seguidor de Temperatura" );
   printf("[ p ] Desactiva seguidor de Temperatura" );
   printf("[1] D.C. 0.75 ms *Minimo" );
   printf("[2] D.C. 1.00 ms" );
   printf("[3] D.C. 1.25 ms" );
   printf("[4] D.C. 1.50 ms *Medio" );
   printf("[5] D.C. 1.75 ms" );
   printf("[6] D.C. 2.00 ms" );
   printf("[7] D.C. 2.25 ms *Maximo" );
   printf("Servo al centro." );

}

int lee_temperatura(void){

   int  grados_temperatura =0x00;

   // Lectura del canal 1 -> AN1 LM35a
   set_adc_channel(1);                                          // selecciono el canal a convertir
   adc_temperatura=read_adc();                                  // realizo la conversion
   grados_temperatura = (int) ((adc_temperatura * 391) / 1000); // convierto a grados centigrados
   return(grados_temperatura);
}

void notifica(void){

   // Transmite Menu
   if(flagXMITmenu==1){
      flagXMITmenu =0;
      menu();
   }
   // Transmite Valor del Servo
   if(flagXMITservo ==1){
      flagXMITservo = 0;
      printf(" S=%u
",tSERVO1);
   }
   // Transmite Temperarura
   if(flagXMITtemperatura ==1){
      flagXMITtemperatura = 0;
      printf(" T=%uº
",lee_temperatura());
   }
   // Transmite Seguidor ON
   if(flagXMITsegon==1){
      flagXMITsegon =0;
      printf(" Seguidor de Temperatura ON
" );
   }
   // Transmite Seguidor OFF
   if(flagXMITsegoff==1){
      flagXMITsegoff =0;
      printf(" Seguidor de Temperatura OFF
" );
   }
}

void seguidor(void){
   int t, x;

   // SEGUIDOR de TEMPERATURA
   if(flagSEGUIDOR==1){          // Solo compruebo t si esta habilitado
     if(flagSEGUNDO==1){         // Solo compruebo t si ha pasado 1 segundo
        flagSEGUNDO=0;
        t = lee_temperatura();   // leo la temperatura real
        x = (int) (t * 7) / 35;  // Ajusto 35º a 7 y demas temperaturas proporcionalmente
        if(x>7){ x=7; }          // Si me sale mayor que 7 igualo a 7 (7ª posicion del servo)
        if(x<1){ x=1; }          // Si me sale menor que 1 igualo a 1 (1ª posicion del servo)
        if(x!=kx){               // Si ha cambiado la temperatura desde la ultima vez la transmito
         printf(" T=%uº %u
",t,x);
         kx=x;
         Keypress="0"+x;         // Simulo que pulso la tecla correspondiente al valor de x
        }
     }
   }
}

void funciones_tras_duty_cicle(void){ // ¡Importante! La duración maxima de esta funcion debe ser menor
   int1 flag;                         // al periodo del pulso PWM menos el maximo Duty Cicle previsto
                                      // o sea menor a 20 ms - 2.5 ms = 17.5 ms
                                      // porque de lo contrario se solaparían el nuevo pulso con
                                      // una nueva ejecucion de funciones_tras_duty_cicle()
   // si hay algo que notificar
   flag = flagXMITmenu || flagXMITservo || flagXMITtemperatura || flagXMITsegon || flagXMITsegoff;
   // lo notifica
   if(flag==1){
      notifica();
   }
   // y tras notificar ejecuta el seguidor
   seguidor();
}

void main() {

   int ValTIMER0;


   delay_ms(25);                               // Espero a que se estabilice antes de comenzar
   tSERVO1 = ticks_PULSO_MEDIO;                // Inicializo el Servo al centro
   setup_adc(ADC_CLOCK_INTERNAL);              // Le digo al converdor AD que el clock es el interno
   setup_adc_ports(RA0_RA1_ANALOG_RA3_REF);    // Que vamos a convertir AN0 y AN1 y que AN3 es el voltaje de referencia
   setup_counters(RTCC_INTERNAL,RTCC_DIV_16);  // Ajusto los contadores a preescaler 1:16 o 1 RTCC cada 4.096 ms
   output_low(PIN_B5);                         // Me aseguro de que el calentador esta apagado

   menu();

   enable_interrupts(int_rda);                 // Habilito la interrupción de rececpción rs232
   enable_interrupts(INT_RTCC);                // Habilito la interrupción deL TIMER0
   set_TIMER0(0);                              // Inicialico el TIMER0 a 0
   enable_interrupts(global);                  // Habilito todas las interrupciones habilitadas

   do {

      // DISPARO DEL PULSO PWM
      if(flagRTCC==1){              // Si han pasado 20 ms ...
         flagRTCC=0;                // indico que me he enterado
         output_high(PIN_SERVO1);   // y pongo en alto el pulso
         flagSERVO1=1;              // e indico que el pulso está en alto
                                    // Cuento 50 veces el pulso (de 20 ms) para un segundo
         ++contSEGUNDO;             // incremento el contador de veces que ocurren 20 ms
         if(contSEGUNDO==50){       // si he contado 50 veces
            flagSEGUNDO=1;          // indico que ha pasado un segundo completo
            contSEGUNDO=0;          // y vuelvo a comenzar
         }
      }

      // CONTROL DE ANCHO DEL PULSO PWM
      //( y otras funciones si proceden tras final del Duty Cicle )
      if(flagSERVO1==1){                 // Si el pulso del servo esta en alto
         valTIMER0 = get_TIMER0();       // obtengo el valor actual del Timer
         if(valTIMER0>tSERVO1){          // y si alcanza el valor predeterminado para cortar el pulso
            output_low(PIN_SERVO1);      // pues lo corto y me quedo tan tranquilo
            flagSERVO1=0;                // pero lo indico poniendo a bajo el correspondiente indicador
            funciones_tras_duty_cicle(); // y tras cortarlo ejecuto cosas mientras no hay Duty Cicle activo
         }                               // ¡IMPORTANTE! ver nota en la funcion funciones_tras_duty_cicle()
      }

      // CONTROL DESDE LA RS-232
      if(Keypress!=0x00 && flagSERVO1==0){
         switch(Keypress){
            // Periodos Prefijados
            case "1": tSERVO1=ticks_PULSO_MINIMO;
                      break;
            case "2": tSERVO1=ticks_PULSO_MINIMO2;
                      break;
            case "3": tSERVO1=ticks_PULSO_MINIMO1;
                      break;
            case "4": tSERVO1=ticks_PULSO_MEDIO;
                      break;
            case "5": tSERVO1=ticks_PULSO_MAXIMO1;
                      break;
            case "6": tSERVO1=ticks_PULSO_MAXIMO2;
                      break;
            case "7": tSERVO1=ticks_PULSO_MAXIMO;
                      break;
            // Ajuste manual
            case "+": if(++tSERVO1>ticks_PULSO_MAXIMO){
                        tSERVO1=ticks_PULSO_MAXIMO;
                      }
                      break;
            case "-": if(--tSERVO1<ticks_PULSO_MINIMO){
                        tSERVO1=ticks_PULSO_MINIMO;
                      }
                      break;
            // Recupera informacion
            case "m": flagXMITmenu = 1;
                      break;
            case "s": flagXMITservo = 1;
                      break;
            case "t": flagXMITtemperatura = 1;
                      break;
            // Actuadores
            case "a": output_high(PIN_B5);
                      break;
            case "d": output_low(PIN_B5);
                      break;
            case "z": flagSEGUIDOR  = 1;
                      flagXMITsegon = 1;
                      break;
            case "p": flagSEGUIDOR   = 0;
                      flagXMITsegoff = 1;
                      break;
         }
         Keypress=0;
      }
   } while (TRUE);
}


El resultado de una ejecución podeís verlo en la siguiente imagen, en la que muestro como iniciando de la temperatura ambiente de mi habitación activo primero el calentador y tras algunos minutos lo desactivo. La subida y bajada correspondiente de la temperatura la monitorizo, y como podeis ver, el número que acompaña a la temperatura simula la tecla enviada para posicionar al servo en cada una de las posiciones prefjadas en el menú:



Una aplicación práctica de esto puede ser, por ejemplo, el abrir mas o menos una ventana en función de la temperatura del interior, o aplicar distintas velocidades a un ventilador PWM ....

Esto esto todo por ahora, amigos Picmaníacos.


« Última modificación: 30 de Diciembre de 2006, 09:37:08 por RedPic »
Contra la estupidez los propios dioses luchan en vano. Schiller
Mi Güeb : Picmania

Desconectado RedPic

  • Administrador
  • DsPIC33
  • *******
  • Mensajes: 5538
    • Picmania by Redraven
Re: Ejemplito 16F876A: El Termo-Servo o un servo controlado por temperatura.
« Respuesta #1 en: 14 de Abril de 2006, 15:32:57 »
Actualizado en el índice.
Contra la estupidez los propios dioses luchan en vano. Schiller
Mi Güeb : Picmania

Desconectado LordLafebre

  • Moderador Global
  • DsPIC30
  • *****
  • Mensajes: 3529
    • Micros & micros
Re: Ejemplito 16F876A: El Termo-Servo o un servo controlado por temperatura.
« Respuesta #2 en: 14 de Abril de 2006, 15:42:18 »
Hola:

Diego, no creo que sea mi idea, pero veo gran parte de tu mensaje tachado... es asi? o lo hiciste sin querer?

Desconectado RedPic

  • Administrador
  • DsPIC33
  • *******
  • Mensajes: 5538
    • Picmania by Redraven
Re: Ejemplito 16F876A: El Termo-Servo o un servo controlado por temperatura.
« Respuesta #3 en: 14 de Abril de 2006, 15:56:22 »
Ea, amigo Lorlafebre ... todo solucionado.

Gracias por avisarme. Parece que la migración le ha hecho daño a algunos post (nada que no pueda arreglarse.

Contra la estupidez los propios dioses luchan en vano. Schiller
Mi Güeb : Picmania


 

anything