TODOPIC
FORO TÉCNICO => Foro Técnico => Mensaje iniciado por: Picuino en 03 de Enero de 2012, 13:22:29
-
Hola a todos, aprovecho para desearles feliz año.
Estoy programando un frecuencímetro con un PIC18F2550 y me he encontrado con que el oscilador de cuarzo tiene una exactitud bastante mala.
Comparando el oscilador del pic con un reloj digital, el error es de 2.4 segundos en 3000 segundos (error de 750 ppm) y este error me parece exagerado.
El programa para contar segundos se basa en el timer0:
unsigned int sys_clk, sys_second;
/*******************************************************************
INTERRUPT SERVICE ROUTINE
*******************************************************************/
void isr_main(void);
#pragma code low_vector=0x18
void isr_low(void) {
_asm GOTO isr_main _endasm
}
#pragma interrupt isr_main
void isr_main(void) {
if (INTCONbits.TMR0IF==1) { // TIMER0 overflow interrupt
TMR0L -= 125;
INTCONbits.TMR0IF = 0;
sys_clk++;
if (sys_clk == 375) { // 1 SECOND EVERY 375*125*128 = 6000000 CYCLES
sys_clk = 0;
sys_second++;
}
}
}
/*******************************************************************
MAIN PROGRAM
*******************************************************************/
void main(void) {
unsigned int old_second;
//
// CONFIGURE INTERRUPTS
//
INTCON = 0; // All interrupts disabled
INTCONbits.TMR0IE = 1; // Enable TMR0 interrupt
INTCONbits.PEIE = 1; // Global Interrupt Enable. Enable all unmasked interrupts
INTCONbits.GIE = 1; // Global Interrupt Enable. Enable all unmasked interrupts
//
// CONFIGURE TIMER 0
//
T0CON = (char)
(0<<7) // TMR0ON: Timer0 On/Off Control
+ (1<<6) // T08BIT: 1 = Timer0 is configured as an 8-bit timer/counter
+ (0<<5) // T0CS: 1 = Transition on T0CKI pin
+ (0<<4) // T0SE: 1 = Increment on high-to-low transition on T0CKI pin
+ (0<<3) // PSA: 1 = Timer0 prescaler is NOT assigned.
+ (0b110); // T0PS: Prescale value 2^(T0PS+1)
// 111 = 1:256 Prescale value
// 110 = 1:128 Prescale value
// 101 = 1:64 Prescale value
// 100 = 1:32 Prescale value
// 011 = 1:16 Prescale value
// 010 = 1:8 Prescale value
// 001 = 1:4 Prescale value
// 000 = 1:2 Prescale value
TMR0H = 0;
TMR0L = -1;
T0CONbits.TMR0ON = 1;
//
// MAIN ROUTINE
//
rs232_init();
sys_clk = 0;
sys_second = 0;
while(1) {
if (sys_second != old_second) {
fprintf(_H_USART, "Second = %d\r\n", sys_second);
old_second = sys_second;
}
};
}
Si el error fuese mayor, pensaría que es un error del programa; pero creo que es un problema del oscilador.
¿Es normal este error o debo pensar que hay un problema en el cristal?
Saludos.
-
El datasheet del uc dice al respecto que no es recomendable usar el oscilador interno si se ha de trabajar con tiempos crìticos, incluso recomendia un cristal externo para el uso del USART. Pero incluso en estos casos, se le puede dar solución por medio el firmware.
-
Estoy utilizando un cristal de cuarzo de 20MHz.
Esta frecuencia la paso a traves del PLL del PIC18F2550 y el divisor posterior para conseguir una frecuencia de trabajo de 24Mhz
¿Da problemas de precisión utilizar el PLL?
Saludos!
-
Aya, mmmm.... tecnicamente no deberìa darte ese tipos de problemas; debe haber un desfase... si, pero este no debe de ser tanto, y generalmente es cada 1 ò 10 horas, dependiendo de tu firmware. Parece que hay un bug ahi en tu firmware, como veo que estas programando en C, derepente una instrucciòn està haciendo una latencia demasiado larga. Como estas trabajando con el interrup hab, asegurate de actualizar de la forma apropiada el TMR0... saludos.
-
Eso había pensado, que podía ser un error de software.
¿Alguien conoce un programa ya probado para 'afinar' o medir la frecuencia del oscilador de cuarzo?
Tengo un polímetro con frecuencímetro, pero si le conecto al cristal deja de oscilar.
Saludos!
-
el TMR0 pierde 2 ciclos cada escritura, luego algunos ciclos mas para el if(INTCON....
Lo normal es que los cristales tengan unos 100ppm de error como mucho y el error del PLL de tu pic es de 0.25%, o bien tu cristal es malo (alguno de mala fabricacion china) o algo hay de software que no te cuadra.
-
Gracias,
Mirando el manual he visto que el prescaler se pone a cero cada vez que se escribe en el registro TMR0L. Esto hace perder bastantes pulsos (el prescaler está en 1/128) y el contador se retrasa.
El problema es que no se como solucionarlo.
Si dejo correr libre al TMR0 para que divida por 256, salen números decimales a la hora de contar segundos:
Fosc = 24000000Hz = 6000000 pulsos/segundo
F timer0 = 6000000/256 = 23437.5 ciclos/segundo
Saludos!
-
yo diria que no se pone a 0 eh? Me suena haber usado el timer0 en otras ocasiones para bajas frecuencias sin ese problema.
Bueno, el tema esta en que puedes usar otro timer, como estas usando variables para calcular los segundos te seria tan facil como un timer de 16bits
le cargas el valor de 28036 al timer. Y ahora cada 50ms tendras una interrupcion, 50ms*20=1segundo, para que te quede exacto deberias calcular el tiempo que tarda en volver a cargar el registro, asi le restas un par de Tcy y tendras un tiempo bastante exacto.
-
He conseguido un adelanto de 2.1 segundos en 8 horas y media (70ppm) :-/
El programa funciona así:
Micro funcionando a 20Mhz (directamente del cuarzo)
Prescaler al mínimo (1/2)
No pongo a cero el Timer0 (como comentas) así que tengo 9765.625 interrupciones por segundo
Cada 9766 interrupciones atraso el contador: TMR0L += 96; // 96 = 256 * 0.375
De esta forma, el programa puede perder uno o dos pulsos de un total de 5 millones cada segundo (error de 0.4 ppm)
(Utilizo el timer0 en modo 8 bit porque quiero que este mismo programa sirva también para otro micro más pequeño con menos timers)
Muchas gracias y saludos!
-
Prescaler al mínimo (1/2)
Creo que puedes usarlo 1/1 sino tienes habilitado el WDT, le asignas el preescaler al WDT y ya esta.
(Utilizo el timer0 en modo 8 bit porque quiero que este mismo programa sirva también para otro micro más pequeño con menos timers)
El TMR1 es el mas preciso, toda la serie 16f lo lleva excepto el 16f84 (no se si hay alguna excepcion muy especial) y la serie 12f los modernos creo que lo traen todos. La 10f si que no lo se.
-
Tienes razón, se puede desabilitar el prescaler y es más preciso.
El timer1 lo quiero utilizar para contar pulsos de entrada (la temporización con el timer0 servirá después para hacer un frecuencímetro)
El timer0 quiero utilizarle en modo 8bit para que el programa valga para un 16F628 o un 16F88.
Por ahora programo un reloj para poder compararlo con otro reloj digital y medir el error del cuarzo. Luego compenso el error por software.
Al final he conseguido un error muy bueno (menos de 10ppm):
/****************************************************************************
SECOND COUNTER
****************************************************************************/
#include <p18cxxx.h>
#include <stdio.h>
#include <string.h>
/****************************************************************************
GLOBAL VARIABLES AND DEFINITIONS
****************************************************************************/
#define FOSC 20000000
#define BAUD 57600
#define FOSC_ERROR_PPM 70
#define FOSC_REAL (FOSC + FOSC_ERROR_PPM*(FOSC/1000000))
#define TMR0_COUNT (FOSC_REAL/(4*256)+1) // Number of carrys per second
#define TMR0_OFFSET (TMR0_COUNT*256-(FOSC_REAL/4)) // TMR0L preset every second
unsigned int sys_clk, seconds, old_seconds;
/****************************************************************************
INTERRUPT SERVICE ROUTINE
****************************************************************************/
// Rutinas de Interrupcion.-
#pragma interrupt isr_main
void isr_main(void) {
if (INTCONbits.TMR0IF==1) { // if TIMER0 overflow interrupt
INTCONbits.TMR0IF = 0; // Clear Timer0 interrupt flag
sys_clk--;
if (sys_clk == 0) {
TMR0L += TMR0_OFFSET + 3;
sys_clk = TMR0_COUNT;
seconds++;
}
}
}
#pragma code high_vector=0x08
void isr_high(void) {
_asm GOTO isr_main _endasm
}
/****************************************************************************
MAIN PROGRAM
****************************************************************************/
#pragma code
/*
MAIN ROUTINE
*/
void main(void) {
// Configure Timer0
sys_clk = 1;
seconds = 0;
T0CON = 0b11001000; // Timer0 ON, 8bit count, No prescaler
INTCON = 0b11100000; // Global Interrupt, Peripheral Interrupt, TMR0 interrupt
// Configure UART
BAUDCONbits.BRG16 = 0; // BRG16: 16-Bit Baud Rate Register Enable bit
SPBRGH = 0;
SPBRG = (FOSC/(16*BAUD))-1; // Configure UART speed
TXSTA = 0b00100110;
RCSTA = 0b10010000;
TRISCbits.TRISC6 = 0; // Enable TX output
PIE1bits.TXIE = 0; // Disable RS232 interrupts
// Main loop
old_seconds = -1;
while(1) {
if (seconds != old_seconds) {
old_seconds = seconds;
fprintf(_H_USART, "Seconds=%u\r\n", seconds);
}
if (seconds == 60000)
seconds = 0;
}
}
Saludos!
-
Para realmente poder medir el error del cristal deberías trabajar en assembler. De esa forma sabrás exáctamente cuantas instrucciones realiza el PIC en cada bucle, y de esa manera tener un valor exacto del tiempo de oscilación del cristal. Todos los programas compiladores agregan instrucciones entre bucle y bucle, y generalmente esas instrucciones no tenidas en cuenta estan incrementando el error.
Salud.- 8)
-
El disassembly listing puede ayudar en estos casos.
-
El disassembly listing puede ayudar en estos casos.
Efectivamente, esa sería una buena forma de contar las instrucciones si utilizas un compilador.
-
TIMER0, mala elección para el frecuencímetro.
Mi sugerencia es utilizar un módulo CCP+TIMER1 y TIMER1 con oscilador externo y de precisión para el mismo. Ya con eso te evitarás una muy buena parte de los problemas.
El módulo CCP en modo captura te permitirá analizar señales de baja frecuencia y también las de alta solamente con configurar el valor de comparación para activar la interrupción cuando la captura coincida con el valor de comparación deseado. Esto es algo relativamente de lograr con un PIC si se utilizan los periféricos apropiados.
Reconozco que todo mundo tira al pobre TIMER0 este tipo de aplicaciones cuando lo correcto es utilizar el hardware que viene con el PIC para ello.
Espero que esta sugerencia te ayude a resolver tu problema.
-
TIMER0, mala elección para el frecuencímetro.
Hombre el TMR0 da problemas cuando hay que contar tiempos grandes y usar preescaler altos, pero si son tiempos que se pueden contar con preescaler 1/1 considero que es bastante eficiente.
Hacer la rutina directamente en assembler puede ser una buena solucion.
Al final he conseguido un error muy bueno (menos de 10ppm):
Aunque Picuino dice que ya tiene lo que quiere.
-
He conseguido que el timer0 cuente correctamente sumandole 3 cada vez que modifico TMR0L (aunque el manual dice que se pierden sólo 2 ciclos.)
La rutina final para contar tiempo es tan precisa como lo sea el cristal de cuarzo
Esta rutina de interrupción cuenta centésimas de segundo de forma exacta:
#define FOSC 20000000
#define TMR0_COUNT (FOSC/((unsigned long)4*250*100))
static unsigned char sys_clk, cent_second;
void isr_main(void) {
if (INTCONbits.TMR0IF==1) { // if TIMER0 overflow interrupt
INTCONbits.TMR0IF = 0; // Clear Timer0 interrupt flag
TMR0L += 6+3;
sys_clk--;
if (sys_clk==0) {
sys_clk = TMR0_COUNT;
cent_second++;
}
}
}
Para ajustar la frecuencia del cristal de cuarzo de la placa PIC he añadido una capacidad extra al cuarzo. El procedimiento completo es un poco complicado y utiliza un reloj casio (10ppm o 1 segundo/dia) y un generador de señal digital/contador (http://www.elecfreaks.com/store/dds-signal-generatorsg100x-p-90.html).
Al final no he necesitado código máquina y funciona muy bien.
Saludos!
-
Se me olvidó poner en el ejemplo anteriór la configuración inicial del timer0:
INTCON = 0b11100000; // Enable interrupts
T0CON = 0b01001000;
sys_clk = TMR0_COUNT;
TMR0L = 6;
cent_second = 0;
TIMER0, mala elección para el frecuencímetro.
Mi sugerencia es utilizar un módulo CCP+TIMER1 y TIMER1 con oscilador externo y de precisión para el mismo. Ya con eso te evitarás una muy buena parte de los problemas.
El módulo CCP en modo captura te permitirá analizar señales de baja frecuencia y también las de alta solamente con configurar el valor de comparación para activar la interrupción cuando la captura coincida con el valor de comparación deseado. Esto es algo relativamente de lograr con un PIC si se utilizan los periféricos apropiados.
Reconozco que todo mundo tira al pobre TIMER0 este tipo de aplicaciones cuando lo correcto es utilizar el hardware que viene con el PIC para ello.
Espero que esta sugerencia te ayude a resolver tu problema.
Gracias por tu sugerencia. De hecho, ahora estoy intentando realizar el frecuencímetro con el TIMER2 (CCP) como contador de tiempo.
Lo que si he descubierto por ahora es que el TIMER1 cuenta pulsos fenomenal (es capaz de contar hasta 80Mhz sin prescaler en un 18F2550)
Saludos!
-
Lo que si he descubierto por ahora es que el TIMER1 cuenta pulsos fenomenal
Es porque este timer con los módulos CPP han sido diseñados para eso mismo. Es muy común intentar utilizar el TIMER0 para contar y comparar porque si miras al TIMER0 tiene un esquema muy simple y pocos bits que tocar para configurarlo, pero no es la mejor recomendación. En cambio los TIMERS acoplados a módulos de captura y comparación son mucho más eficaces y eficientes.
Mi recomendación un poco insistente y a veces hasta molesta para algunos es que si quieren contar y comparar o medir frecuencia o período es que utilicen esta combinación. Es más complejo de entender el hw, pero vale la pena cuando una vez que cambias unos pocos bits se hace la magia. En este foro hemos tenido gente que se ha pasado meses tratando de hacer eso con TIMER0 y siempre salta algún resultado inesperado cuando agregan un fragmento de software en otro lado y se afecta su rutina de conteo/medición.
Veo que ya vas por el buen camino entonces y que pronto estarás haciendo maravillas.
Un saludo
Reinier