¿Cómo generar un segundo en un pic?

HOla gente que tal sucede que estoy implementando un contador con pic16f877, no sé cómo calcular las interrupciones para el montaje del micro con un cristal de 20Mhz: solo sé que para un cristal de 4MHz el número de rtccs es de 15, pero cuál es el procedimiento para calcular éstas RTCCS???

Agradezco su respuesta...

UN saludo...
 
No se bien que es el RTCCS, pero un método con el cuál si se generaban un seg. era mediante el empleo del Timer0.
El tiempo a ingresar se conoce mediante la siguiente fórmula:
Tiempo=4*T(oscialador)*(Cuenta*Escala+2)
TMR0=256-Cuenta
En tiempo estableces el tiempo que quieres que cuente el Timer, en Escala es la preescala que puedes seleccionar en el Option Reg.
Al ser un segundo tendrás que hacer un bucle ya que se desbordará antes el Timer.
Otra forma que puedes realizar,es mediante un bucle en la que primero miras cuantas instrucciones vas ha emplear y de hay sacas el tiempo que tarda en realizarlas el pic, antes del bucle creas unos registros en los que almacenas el tiempo necesario para realizar esas intrucciones, el llenado de estos registros entraran dentro del bucle, despues los vas descontando, primero uno, cuando llegue a cero quitas uno a otro registro y rellenas el anterior y vuelves a descontar hasta cero, vuelves a quitar otro al segundo, y asi sucesivamente, espero haberme explicado bien, si eso me dices.
Un saludo.
 
Mira sucede que estoy haciendo el programa en c la parte que debo modificar es la siguiente:

char const RTCCxS=15; // Número de RTCC's (interrupciones para obtener un segundo) para 1 segundo con 4 Mhz / 1:256.

El 15 hace referencia a que con un cristal de 4 MHz se deben hacer 15 interrupciones con un prescaler de 1:256, de ésta manera: 66.6 ms * 15= 999 ms (casi un seg).

Pero al hacer el montaje, cuando pongo un cristal de 20 MHz, porque obviamente está programado para 4 M; el reloj corre muy rapido, entonces necesito saber cuantas interrupciones con un prescaler de 1:256 se deben programar para que funcione perfecto con un cristal de 20 M y de ésa manera el contador incremente en tiempo real.

Hay una página dónde tratan de explicar eso pero no entiendo muy bien como ajustan las interrupciones:

http://picmania.garcia-cuervo.com/Conceptos.php#CristalyTime

Si llegas a entender algo de lo que hay en ese link, agradecería tu respuesta...

Un saludo gracias.....
 
no se absolutamente nada sobre pics, ni sobre nada... pero mi sentido comun dice que RTCC debe ser 75 (regla de tres simple)

4MHz________15
20MHz_______x

20MHz x 15 / 4MHz = 75

no se... supongo, pero no cuesta nada probar...
 
Excelente observacion la de Manonline... todos necesitamos mas sentido comun ;-)

Por otra parte, si no te molesta tomar una alternativa relativamente simple, y deseas una exactitud muy pero muy razonable, puedes usar el siempre confiable Timer2. El modulo Timer2 del PIC16F877A es extremadamente simple y permite generar bases de tiempo con una buena gama de valores de division de reloj posibles. Una solucion para generar 1s con el Timer2 seria:

Frecuencia de entrada al Timer2: Fosc/4 = 20MHz/4 = 5MHz

Seleccion de valores:
PR2 = 249 (hace sobreflujo cada 250 ciclos)
Valor de Prescaler: 16
Valor de Postscaler: 10

Luego, la frecuencia a la que Timer2 hace interrupciones seria:

F = 5MHz / (250 * 16 * 10) = 125Hz

Asi, para generar un segundo exacto, deberas esperar 125 interrupciones. No es nada dificil configurar el modulo Timer2 para ello, en serio. La unica parte dificil es manejar las interrupciones, que segun veo, ya tienes resuelto.

A lo mejor eso te sirva.
Saludos.
 
gracias por la respuesta...voy a probar el micro con las diferentes opciones que ustedes me dan....os comentaré una respuesta apenas obtenga los resultados sean buenos o malos....si alguien tiene otra opción bienvenida sea....

Un saludo..
 
Hola que tal queria hacer una consulta y salir de unas dudas con respecto a hacer una interrupcion, por timer1 cada 1 segundo.
Estoy usando este programa para medir una frecuencia de 100 a 400 hz, en el cual cuento la cantidad de pulsos que me ingresan por el timer0 ,del pic 16f876a
(pin Ra4) ,
Código:
/

#int_timer1
void timer1_isr(void)
{
   contador=get_timer0();
   set_timer0(0);
   set_timer1(3036);
}
void main()
{
   set_tris_a(0b00010001); 
   output_a  (0b00000000);
  
   lcd_init();
   setup_timer_0(rtcc_ext_h_to_l | rtcc_div_1);
   setup_timer_1(t1_internal | t1_div_by_8);
   
   set_timer0(0);
   set_timer1(3036);
   enable_interrupts(int_timer1);
   enable_interrupts(global);
   
  //****************ADC**************************
   setup_adc_ports(AN0);
   setup_adc (ADC_CLOCK_INTERNAL);
 //********************************************************
   
  
   while(1)
    { 
    
       
    }
}
con el timer1 hago una interrupcion cada 0,5 seg ,el resultado de los pulsos que obtengo los multiplico por 2 y asi obtengo la frecuencia.
Todo esto por que no me doy cuenta como hacer una interrupcion cada 1 segundo.
El tiempo de 0,5seg lo obtuve con esta formula : T= 4/frec osc * prescaler*(65536-TMR1), donde frec osc =4Mhz y prescaler= 8
Esta formula y esta explicación esta en un monton de paginas de internet ,ahora bien con esta formula el tiempo maximo que puedo calcular es de 0,524 seg
Entonces la pregunta es como hago para hacer una interrupcion cada 1 segundo ?
Lo que se me ocurre es seguir usando los 0,5 seg y contar los pulsos cada 2 interupciones pero no me doy cuenta como hacerlo, si es esta la manera de hacerlo.
Si alguien me puede dar una explicacion o un ejemplo de como hacerlo le estaré muy agradecido.Saludos !!!
 
Hola ,estuve probando de adaptar el progrma de D@rkabytes al pic 16f876a que es el que estoy usando ,pero no logré hacer que cuando se produce la interrupcion me capture el valor del timer0,supongo que el problema esta en el oscilador externo, que tal vez no esta bien configurado para mi pic .Todo esto porsupuesto probado en simulacion.
Ahora encontré una rutina en esta pagina:http://www.puntoflotante.net/INTERRUPTC.htm Donde dice que hace una interrupcion cada 1 segundo y que es de alta presicion ,esta rutina es usada para hacer un reloj de tiempo real ,lo cual me venía a la perfeccion ya que mi idea es hacer un cuenta vueltas con cuenta horas inclusive.Pero tampoco logro hacer que cuando hace la interrupción me lea correctamente el valor del timer0, que es por donde cuento los pulsos que uso para hacer el cuentavueltas .Este es el programa que intento unificar
Código:
#include <16f876a.h>
#device adc=10
#use delay(clock=4000000)
#fuses HS,NOWDT,NOPROTECT,NOLVP,PUT,NODEBUG,BROWNOUT,NOCPD,NOWRT
#include <lcd.c> 
#define CRISTAL  4000000 
#define FRECUENCIA_TIMER1 CRISTAL/4
#use fast_io(B)
#use fast_io(c)
#use fast_io(a)

int16 contador=0,RPM;
float ajuste;
int32 Ticker;
int8 Segundos=0,Hora=0,Minuto=0;


void LCD_CEROS(void)
{
 char CEROS[16]="   00000:00:00";      //HORA INICIAL
 lcd_init();
 printf(lcd_putc,"  \f");
 lcd_putc("  CUENTA HORAS");
 lcd_gotoxy(4,2);
 printf(lcd_putc,"%s",CEROS);
} 

void DISPLAY_LCD(void)
{char wob[16];

  lcd_gotoxy(1,2);
 sprintf(wob,"   %05d:%02d:%02d",Hora,Minuto,Segundos);
 printf(lcd_putc,"%s", wob); 
}

void Inicializa_RTC(void) 
{ 
  Ticker = FRECUENCIA_TIMER1;                 // 1 Tick = 1 us. 
  setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 ); // initializa TIMER1 para interrupción 
                                              // cada 65536 us.
  enable_interrupts( INT_TIMER1 );            // habilita interrupción TIMER1 
} 


#int_TIMER1                                
void TIMER1_isr()                          //subrutina de interrupción.
{  
  Ticker -= 65536;                        // Decrementa ticker 
  if ( Ticker < 65536 )                   // Si ya pasó 1 segundo 
  {  
     contador=get_timer0();              // esto es lo que yo le agregé
     set_timer0(0);                      //   
                                       
     Ticker += FRECUENCIA_TIMER1;          //   Incrementa ticker
     Segundos++ ;                          // Incrementa seg
     
  } 
  
  if(Segundos == 60)
  {
  Minuto++;
  Segundos=0;
    if(Minuto == 60)
    {
    Hora++;
    Minuto=0;
    }
  }
  
  
} 
 
                                          //
void main()
{
   int8 segundo_previo; 
    set_tris_a(0b00010001);
    output_a  (0b00000000);
    Inicializa_RTC(); 
    
    setup_timer_0(rtcc_ext_h_to_l | rtcc_div_1);
    enable_interrupts(GLOBAL);
    LCD_CEROS();
    lcd_init();
  //****************ADC**************************
   setup_adc_ports(AN0);
   setup_adc (ADC_CLOCK_INTERNAL);
 //********************************************************
   
  
   while(1)
    { 
       if (Segundos != segundo_previo) 
    {
    segundo_previo=Segundos;
      
    } 
    if (input(pin_c4)==1 )
    {
    DISPLAY_LCD();
    lcd_gotoxy(1,1);
    lcd_putc("  CUENTA HORAS");
    }   
       set_adc_channel(0);
       delay_ms(10);
       ajuste=(float)read_adc()*0.029;      
       RPM= contador*ajuste;
       if (input(pin_c4)==0 )
       {
       lcd_gotoxy(1,1);
       printf(lcd_putc,"     RPM %lu         " ,RPM);
       
       lcd_gotoxy(1,2);
       printf(lcd_putc,"Contador  %lu     " ,contador); 
       }
    }
}

El cuenta horas por si solo ,funciona bien y el cuentavueltas que coloque en el post 7 tambien ,este ultimo lo tengo armado y probado.
Si alguien me puede orientar un poco en como unificar los dos, si es que es posible , le estaré agradecido.
Desde ya muchas gracias por su atencion .
Saludos!!!
 
Hola ,bueno me respondo a mi mismo ja ja , para poder repreguntar.
Ya consegui hacer funcionar el cuenta horas y el cuenta vueltas juntos ,despues de varias horas y dolor de cabeza, me quedó funcionando y probado ya que tengo todo montado en un banco de pruebas.
Les adjunto el codigo por si le sirve a alguien.
Lo que quiero hacer ahora es, poder almacenar las horas minutos y segundos en la eeprom para que cuando se apague el motor y vuelva a encender comience a contar ,el cuenta horas ,desde donde habia quedado .
La manera en que lo pensé es que cuando las vueltas del motor bajen de 600rpm el cuenta horas se detenga y me grabe los datos en la eeprom y cuando suba de este regimen, arranque a contar a partir de ese dato guardado.Esto lo hace mientras el pic este con alimentacion pero cuando le saco la alimentacion , el cuenta horas comienza de cero ,osea, o no me esta leyendo la eeprom o no me esta grabando.
La pregunta es ,como debería hacer esto ? por lo poco que entiendo yo, asi como esta el programa lo debería hacer, pero al parecer hay algo de lo que no me estoy dando cuenta
Bueno espero se entienda la explicacion y puedan darme una manito .Saludos!!!
Código:
#include <16f876a.h>
#device adc=10
#use delay(clock=4000000)
#fuses HS,NOWDT,NOPROTECT,NOLVP,PUT,NODEBUG,BROWNOUT,NOCPD,NOWRT
#include <lcd.c> 
#include <internal_eeprom.c>
#use fast_io(B)
#use fast_io(c)
#use fast_io(a)

int16 contador=0,RPM;
float ajuste;
int8 Segundos,Minuto, Hora;

void grabar(void)
{
  
   write_eeprom(0x10, Hora);
   delay_ms(150);
   write_eeprom(0x50, Minuto);
   delay_ms(150);
   write_eeprom(0x100, Segundos);
   delay_ms(150);
   

}
void leer_memoria(void)
{

      Hora=read_eeprom(0x10); 
      delay_ms(150);                  
        
      Minuto=read_eeprom(0x50);
      delay_ms(150);
      
      Segundos=read_eeprom(0x100);
      delay_ms(150);
      
  lcd_gotoxy(1,2);
  printf(lcd_putc,"   %05d:%02d:%02d ",Hora,Minuto,Segundos);      


} 

void DISPLAY_LCD(void)
{
//leer_memoria();
   char wob[16];   
   lcd_gotoxy(1,2);
   sprintf(wob,"   %05d:%02d:%02d ",Hora,Minuto,Segundos);
   printf(lcd_putc,"%s", wob); 
}

#int_timer1
void timer1_isr(void)
{
   contador=get_timer0();
   set_timer0(0);
   set_timer1(3036);
   
 if (rpm>600)
  { 
   
    Segundos++;
  if(Segundos == 120)
  {
  Minuto++;
  Segundos=0;
    if(Minuto == 60)
    {
    Hora++;
    Minuto=0;
    }
  }
} 
}
void main()
{
   set_tris_a(0b00010001); 
   output_a  (0b00000000);
    
   int8 segundo_previo;
   
   lcd_init();
   setup_timer_0(rtcc_ext_h_to_l | rtcc_div_2);
   setup_timer_1(t1_internal | t1_div_by_8);
   
   set_timer0(0);
   set_timer1(3036);
   enable_interrupts(int_timer1);
   enable_interrupts(global);
   grabar();
  leer_memoria();

     //****************ADC**************************
   setup_adc_ports(AN0);
   setup_adc (ADC_CLOCK_INTERNAL);
 //********************************************************
   
  
   while(1)
    { 
       if (Segundos != segundo_previo) 
         {
           segundo_previo=Segundos;
         } 
       if(rpm<600)
       {grabar();
       leer_memoria();
       }
      else
      {
      
      DISPLAY_LCD();
      }
   
       set_adc_channel(0);
       delay_ms(10);
       ajuste=(float)read_adc()*0.029;      
       RPM= contador*ajuste;
       lcd_gotoxy(1,1);
       printf(lcd_putc,"     RPM %lu         " ,RPM);
        
       }
    }

PD :las horas deberia guardarlas con un formato int16 ,esto tampoco lo tengo resuelto.
 
Última edición:
Para la detección de corte eléctrico, puedes usar un canal análogo.
Y para guardar datos de 16 bits en la EEPROM interna, puedes usar la librería "internal_eeprom.c" (Viene incluida con el compilador)
 
Gracias D@rkbytes con lo de leer el adc para detectar el corte me di cuenta por que no me graba en la eeprom , a la noche les subo las conclusiones
Saludos!!!
 
Última edición:
Hola, compañeros del foro.
Les cuento que ya pude resolver el tema de grabar en la eeprom el valor del cuenta horas, para luego poder apartir de ese valor, seguir incrementando el cuenta horas.

Lo que me pasa es que siempre estoy guardando el dato en la misma dirección de memoria y no sé si puedo dañar la memoria.

Se me había ocurrido que cada vez que grabe, aumente una dirección y cuando se complete que vuelva a cero, pero el tema es que no sé cómo hacer para que me lea la última dirección de memoria que se grabó.
Ésto para no sobrecargar siempre la misma direccion.
¿Alguien me puede orientar en cómo debería hacerlo?

También le implementé como dijo D@kbytes, que me lea un canal adc para detectar el corte de energia.

Estuve viendo en el mismo foro, que se trata el tema de colocar un capacitor para darle tiempo al PIC a que guarde los datos antes que se quede totalmente sin alimentación, así que creo que voy a implementar el circuito.

La idea de este circuito, es dejar el PIC siempre alimentado, sólo apagar el LCD y el resto de las cosas que tengan consumo.
Lo de guardar en la memoria, sería para el caso de cuando le cambian la batería al motor y que no se pierda el dato del cuenta horas.

Me gustaría saber que les parece la idea.

Éste es el código que sólo está probado en simulación y el lunes lo voy a probar en el PIC.

Saludos!
PHP:
#include <16f876a.h>
#device adc=10
#use delay(clock=4000000)
#fuses HS,NOWDT,NOPROTECT,NOLVP,PUT,NODEBUG,BROWNOUT,NOCPD,NOWRT
#include <lcd.c> 
#include <internal_eeprom.c>
#use fast_io(B)
#use fast_io(c)
#use fast_io(a)

int16 contador=0,RPM,corte, Hora;
float ajuste;
int8 Segundos,Minuto;



#int_timer1
void timer1_isr(void)
{  
   contador=get_timer0();
   set_timer0(0);
   set_timer1(3036);
   if (rpm>600)
    Segundos++; 
}
void main()
{
   set_tris_a(0b00010011); 
   output_a  (0b00000000);
    set_tris_c(0b00011000); 
   output_c  (0b00000000);
   
   
   lcd_init();
   setup_timer_0(rtcc_ext_h_to_l | rtcc_div_1);
   setup_timer_1(t1_internal | t1_div_by_8);
   
   set_timer0(0);
   set_timer1(3036);
   enable_interrupts(int_timer1);
   enable_interrupts(global);

 //****************ADC**************************
   setup_adc_ports(AN0_AN1_AN3);
   setup_adc (ADC_CLOCK_INTERNAL);
   
 //*************Lectura de eeprom **************
   Hora=read_int16_eeprom(0x10); 
   delay_ms(150); 
   Minuto=read_eeprom(0x50);
   delay_ms(150);
      
  
   while(1)
    { 
         set_adc_channel(0);
         delay_ms(10);
         ajuste=(float)read_adc()*0.029;      
         RPM= contador*ajuste;
       
         set_adc_channel(1);
         delay_ms(10);
         corte=read_adc();
       
         lcd_gotoxy(1,2);
         printf(lcd_putc,"   %05lu:%02u:%02u ",Hora,Minuto,Segundos);  
       
         lcd_gotoxy(1,1);
         printf(lcd_putc,"RPM %lu" ,RPM);
         
         lcd_gotoxy(9,1);
         printf(lcd_putc,"cont %lu " ,contador);

      if(Segundos == 5) //El 5 es solo para  acelerar la simulacion aca iría 120 ya que estoy                           
      {                        // cada 0,5 seg
        
          Minuto++;
          Segundos=0; 
     
         
      if(Minuto == 60)
       {
          Hora++;
          Minuto=0;
       }
      }
       
      if (corte<820)
       {
          write_int16_eeprom(0x10, Hora);
          delay_ms(150);
          write_eeprom(0x50, Minuto);
          delay_ms(150);
       }
             
      if (input(pin_c3)==1) // Reset de cuenta horas
        {
          Minuto=00;
          write_eeprom(0x50, Minuto);
          delay_ms(150);
          Hora=00000;
          write_int16_eeprom(0x10, Hora);
          delay_ms(150);
       }
    }
}
 
Última edición por un moderador:
Lo que me pasa es que siempre estoy guardando el dato en la misma dirección de memoria y no sé si puedo dañar la memoria.

Se me había ocurrido que cada vez que grabe, aumente una dirección y cuando se complete que vuelva a cero, pero el tema es que no sé cómo hacer para que me lea la última dirección de memoria que se grabó.
Ésto para no sobrecargar siempre la misma dirección.
¿Alguien me puede orientar en cómo debería hacerlo?
La memoria EEPROM interna la puedes escribir más de 1,000,000 de veces y tiene una retención de más de 40 años.
Así que por ese lado no deberías preocuparte tanto.
Pero tampoco es recomendable que los datos sean escritos constantemente en la memoria.
Debes buscar en evento específico para realizar esa tarea.

Y por lógica, si quieres incrementar la dirección para ir guardando los datos en una locación diferente, también necesitarás usar una locación de la EEPROM para almacenar esa información.
Por lo tanto, también estarás usando una dirección de memoria constantemente.

Ahora que si no quieres dañar la memoria interna por tantas escrituras, también puedes usar una externa, que es reemplazable.
 
Hay varias formas de guardar las horas en una posición distinta cada vez:

Como las horas siempre se incrementan, siempre será mayor a la ultima guardada, así que haces una búsqueda comparando y grabas inmediatamente superior a la hora que más se acerca a la ultima a grabar.

Otro seria grabar un carácter de control después de la hora, por ejemplo un asterisco, grabas la hora y seguidamente el *, la siguiente vez buscas el asterisco y grabas la hora en él y seguidamente grabas otro asterisco y así en ese bucle.

Por supuesto esta operación se lleva un tiempo, por eso no se usan eeprom de mucha capacidad, por suerte la lectura de la eeprom es mucho más rápida que la escritura.
 
Última edición:
Muchas gracias por sus respuestas ,creo que me preocupé demasiado por guardar en la memoria pero pienso que si dejo el pic siempre alimentado, no será necesario escribir tantas veces la memoria solo cuando se efectue algún cambio de batería, pero esto puede ocurrir tal vez una vez al año ,asi que por lo que dicen uds no creo que tenga problema en grabar un par de veces en la misma posicion, de todos modos les agradezco por las ideas que tal vez me puedan servir para otra ocasion.
Ahora me surge la pregunta : se puede dejar al pic siempre alimentado ? Con la llave de contacto solo cortaría la alimentacion al lcd pero dejaría al pic alimentado directo de bateria (con su respectiva proteccion).Estaría bien así?
Gracias por colaborar .
Saludos !!!
 
Tengo proyectos funcionado 24 horas diarias desde hace años, otros 10 o 12 horas al días y otros por unas pocas horas nada más. Por supuesto que se pueden tener alimentados 24 horas al día 365 días al año.
 
Hola que tal!!! vuelvo con el tema de grabar en la eeprom, por que estuve modificando el programa para finalmente grabar 4 datos en la memoria en caso que se quede sin alimentacion el pic ,pero esta vez quería hacerlo por interrupcion externa ,basicamente detectar un flanco de bajada por Rb0 y mediante la interrupcion grabar en la memoria, pero cuando compilo el programa me tira errores de delay esta es la funcion que hice:
Código:
#int_ext
void corte()
       {
          write_int16_eeprom(100, Hora);
          delay_ms(15);
          write_int16_eeprom(110, HoraParcial);
          delay_ms(15);          
          write_eeprom(120, Minuto);
          delay_ms(15);
          write_eeprom(130, MinutoParcial);
          delay_ms(15);
y estos los errores del compilador :
interrupts disabled call to prevent re-entrancy {@delay_ms1}
interrupts disabled call to prevent re-entrancy { write_int16_eeprom}
Como debería hacer la funcion para que cuando entre la interrupcion me grabe estos datos en la memoria lo mas rapido posible es decir antes que se quede sin alimentacion el pic
Tambien probé hacerlo asi :
Código:
#int_ext
void corte()
 {
    a=1;         
 }

Código:
 while(1)
    { 
 
         if (a==1)
       {
          write_int16_eeprom(100, Hora);
          delay_ms(15);
          write_int16_eeprom(110, HoraParcial);
          delay_ms(15);          
          write_eeprom(120, Minuto);
          delay_ms(15);
          write_eeprom(130, MinutoParcial);
          delay_ms(15);
          a=0;          
       }
pero logicamente aparte de esto tengo mucho mas codigo ,en el cual estaria perdiendo demasiado tiempo dependiendo de en que punto estaba el programa cuando entró la interrupcion y hasta que vuelva a llegar a aquí, tal vez no me alcance el tiempo para grabar antes que se termine la alimentacion .
Y un ultima pregunta, de que manera desactivo todos los perifericos cuando entre esta interrupcion ,me refiero si con una instruccion dentro de la interrupcion o como debería hacerlo ? ya que desactivando los perifericos estaría ahorrando energia y ganando tiempo para grabar.
Desde ya les agradezco cualquier ayudita .Saludos!!!!
 
Atrás
Arriba