PIC16f887 - Dérive du Timer1
Répondre à la discussion
Affichage des résultats 1 à 19 sur 19

PIC16f887 - Dérive du Timer1



  1. #1
    KorkXx

    PIC16f887 - Dérive du Timer1


    ------

    Bonjour à tous !

    Je suis nouveau sur ce forum et débutant en électronique, alors merci d'avance pour votre indulgence !

    Pour me lancer dans la programmation des microcontrôleurs, je me suis procuré un PIC16f887 et un MPLAB ICD2. Ce choix n'est le fruit ni d'un endoctrinement ni d'une réflexion, c'est de la récup' ! Je code en C et j'utilise le compilo HI-TECH Lite.

    Bref, comme je suis batteur dans mes heures perdues, j'ai choisi comme premier projet de réaliser un métronome. Classique, me direz-vous, et vous aurez raison ! Voici le petit programme de développement que je me suis fixé :
    1/ Dans un premier temps, je veux pouvoir régler "en dur" une pulsation et afficher cette pulsation sur une diode
    2/ Ajouter un réglage de la pulsation avec une résistance ajustable et un affichage de la pulsation sur 3x7 segments par exemple
    3/ Je veux ajouter une fonction 'tapping' avec un bouton poussoir qui permet de taper une pulsation et d'en déterminer la valeur (fonction inverse en quelque sorte)
    4/ réaliser une communication avec mon PC et un petit logiciel pour contrôler tout ça en usb (j'ai récupéré une petit carte DLP - USB232M)

    Voilà le programme ! Évidemment je coince sur le 1 !

    Voici mon problème : j'ai choisi de faire le comptage des pulsations avec le Timer1 (16bit). J'ai gardé la valeur par défaut de Fosc à 4 MHz, le Timer1 utilise Fosc/4, et j'ai gardé le prescaler à 1. Au début de mon programme, je calcule combien de fois il va falloir remplir mes deux registres TMR1H et TMR1L, et quelle sera le point de départ du dernier remplissage, pour allumer ma diode au tempo choisi. Par exemple pour une pulsation X en bpm : (60bpm=1bps=1Hz),

    Code:
    	Timer1NbCycles =(int)(4.0e6/4.0/1.0*60.0/(float)X/65536.0);
    	Timer1LastCycleStart=65536-((int)(4.0e6/4.0*60.0/(float)X)%(65536));
    Ainsi on fait Timer1NbCycles cycles complets plus un cycle avec comme point de départ Timer1LastCycleStart. A la fin de ces Timer1NbCycles+1 cycles, j'utilise le Timer0 pour générer un allumage de la diode qui ressemble un peu à une impulsion.

    Tout cela semblait très bien fonctionner jusqu'à ce que je prenne successivement ma montre, mon téléphone et l'horloge parlante pour me rendre compte que lorsque je commande 60 bpm (i.e. 1 pulsation = 1s) mon métronome bat en réalité à 55 bpm, ce qui n'est évidemment pas du tout acceptable pour mon application.

    Je vous mets le code complet :

    Code:
    #include <htc.h>
    
    unsigned short cnt = 0 ;
    
    unsigned int targetBeat = 0;
    
    unsigned int Timer1LastCycleStart  = 0b0000000000000000;
    unsigned short Timer1NbCycles = 0 ;
    
    //GESTION DES INTERRUPTIONS
    void interrupt interruptions(void) {
    
    	// Interruption du Timer1
    	if (TMR1IF) {
    	
    		if (cnt==Timer1NbCycles){
    			TMR1H   = Timer1LastCycleStart>>8 ;			// On met Timer1 à la valeur initiale calculée
    			TMR1L   = Timer1LastCycleStart-TMR1H<<8 ;
    		}
    
    		if (cnt==Timer1NbCycles+1){
    			RD2 	= 1 ;
    			cnt 	= 0 ;
    			TMR0    = 0 ;
    		}
    		cnt++;	
    		TMR1IF = 0 ;	// Réinitialisation du flag d'interruption du timer1
    	}
    
    	// Interruption du Timer0
    	if (T0IF){
    		if(RD2){	
    			RD2 	= 0 ;	// Exctinction de la diode
    		}		
    		T0IF    = 0 ;	// Réinitialisation du flag d'interruption du timer0
    	}	
    }
    
    
    
    
    //PROCEDURE D'INITALISATION
    void init(void) {
    
    	// Paramètres de l'oscillateur interne
    	IRCF2   = 1 ; 	// Sélection de la fréquence en sortie du post-scaler de l'oscillateur interne
    	IRCF1   = 1 ;
    	IRCF0   = 0 ;
    	SCS		= 1 ;	// Sélection de l'oscillateur interne comme horloge
    	TUN4	= 0 ;	// Fine-tuning de la fréquence de l'oscillo interne
    	TUN3	= 0 ;	    //(on garde la valeur par défaut)
    	TUN2	= 0 ;
    	TUN1	= 0 ;
    	TUN0	= 0 ;
    
    	// Activation des interruptions
    	GIE     = 1 ;	// Activation des interruptions générales (registre INTCON)
    	PEIE    = 1 ;	// Activation des interruptions de périphériques externes (registre INTCON)
    	
    	// Diode en sortie sur RD2
    	TRISD2  = 0 ;
    	RD2     = 0 ;
    	
    	// Initialisation du timer0;
    	T0CS    = 0 ;   // Oscillateur externe (1) / Interne Fosc/4 (0)
    	PSA     = 0 ;   // Assignation du prescaler à Timer0 plutôt qu'au Watchdog
    	PS2     = 1 ;	// Prescaler à 16 (011)
    	PS1     = 0 ;
    	PS0     = 1 ; 
    	T0IE    = 1 ;   // Activation des interruptions sur Timer0
    	TMR0    = 0 ;	// RAZ du Timer0
    	T0IF    = 0 ;	// Flag d'interruption du Timer0
    
    	// Initialisation du timer1;
    	TMR1CS  = 0 ; 	// Oscillateur externe (1) / Interne Fosc/4 (0)
    	T1OSCEN = 0 ;	// Oscillateur 32768 Hz activé (1) / désactivé (0)
    	T1CKPS1 = 0 ;	// Prescaler à 1 (00) / 2 (01) / 4 (10) / 8 (11)
    	T1CKPS0 = 0 ;
    	T1SYNC  = 0 ;	// Synchronisation activée (0) / désactivée (1)
    	TMR1IE  = 1 ;   // Activation des interruptions sur Timer1
    	TMR1ON  = 0 ;	// Timer1 éteint
    	TMR1H   = 0 ;	// RAZ du Timer1
    	TMR1L   = 0 ;
    	TMR1IF  = 0 ;	// Flag d'interruption du Timer1
    
    }
    
    
    
    void Timer1Param(int beat) {
    	Timer1NbCycles =(int)(4.0e6/4.0*60.0/(float)beat/65536.0);
    	Timer1LastCycleStart=65536-((int)(4.0e6/4.0*60.0/(float)beat)%(65536));
    }
    
    void Timer1Start(void) {
    	TMR1H   = 0b00000000 ;
    	TMR1L   = 0b00000000 ;
    	TMR1ON  = 1 ;
    	TMR1IF  = 0 ;	// Flag d'interruption du Timer
    }
    
    
    
    // PROGRAMME PRINCIPAL
    void main() {
    	
    	// Initialisation
    	init();
    
    	targetBeat = 60;
    	Timer1Param(targetBeat);
    	
    
    	RD2     = 1 ;
    	Timer1Start();
    
    	// Boucle principale	
    	while(1) {
    
    	}
    }
    Est-il possible que cette erreur soit liée à la pulsation réelle de Fosc qui ne serait pas exactement à 4MHz ? Y a-t-il quelque chose d'incorrect dans mon code ?

    Merci de votre lecture et de votre intérêt !
    KorkXx.

    -----

  2. #2
    jonathan82

    Re : PIC16f887 - Dérive du Timer1

    Haaa le temps et les µC...
    Bon, 5 bpm, c'est beaucoup tout de même...

    Un décalage se produit par du code, mais tu as eu le bon réflex d'utiliser des interruptions.
    Il y a aussi l'imprécision du quartz qui est aussi liée à la température, mais cela ne devrait pas justifier de dérive aussi importante.
    Je ne connais pas par cœur les registres des PIC et de leur Timer, mais je pense que le problème doit se situer là. Dans le chargement et le décompte des registres.
    Cela dit, tu fais tout de même de gros calculs avec un petit µC 8 bits...
    Dernière modification par jonathan82 ; 05/08/2014 à 11h01.

  3. #3
    KorkXx

    Re : PIC16f887 - Dérive du Timer1

    Merci pour ta réponse Jonathan !

    D'après la datasheet du µC, la fréquence de l'oscillateur interne est calibrée à +-1%. C'est loin des ~10% de dérive observés. Je pense donc comme toi que l'imprécision du quartz n'est pas la source de mon problème.

    J'ai passé pas mal de temps dans la datasheet pour régler les registres du Timer1. Est-ce qu'un habitué de ce timer pourrait confirmer ma configuration ?

    Jonathan32, le dernier point que tu soulèves me fait tiquer : dans les calculs que je fais pour déterminer le nombre de cycles et le point de départ du dernier cycle, je suppose une précision d'au moins 16bits de toutes les opérations, et d'ailleurs la variable Timer1LastCycleStart est une variable 16bit. Est-ce que c'est le compilo qui se charge de convertir ma variable en plusieurs variables 8bits et de traiter comme il faut les opérations dessus, ou est-ce que dans ce cas là le calcul fait n'importe quoi ?

    KorkXx

  4. #4
    jonathan82

    Re : PIC16f887 - Dérive du Timer1

    Je ne sais pas trop ce que le compilo fait et c'est la popote de l'éditeur...
    Mais quand je voit les grosses variable de différents type, je me dis qu'une erreur de conversion peut vite se glisser et de suite, cela peut fausser un calcul. (De int vers float, pas grave, mais l'inverse...)

  5. A voir en vidéo sur Futura
  6. #5
    KorkXx

    Re : PIC16f887 - Dérive du Timer1

    J'ai mis en dur les valeurs pour 60bpm pour m'affranchir de ce problème de calcul et voir si ça fonctionne :

    Pour avoir 1s avec une fréquence d'incrémentation de 1MHz (Fosc = 4MHz => Fosc/4 = 1MHz et prescaler à 1) il faut 1e6 incrémentations soit, avec un compteur 16bits, 15 remplissages complets plus un comptage jusqu'à 16960 (15*2^16+16960 =1M) (i.e. un comptage depuis 2^16-16960=48576 jusqu'à 2^16).

    Code:
    void Timer1Param(int beat) {
    	Timer1NbCycles 			= 15;
    	Timer1LastCycleStartH 	= 0xBD ;
    	Timer1LastCycleStartL 	= 0xC0 ;
    }
    J'ai remanié un peu la façon d'incrémenter mon compteur, notamment pour gérer le cas où Timer1NbCycles=0 (ce qui n'arrivera jamais pour un métronome, ou alors le groupe qui l'utilise en a vraiment plein le nez !). Ci-dessous la nouvelle version du code.

    Code:
    #include <htc.h>
    
    unsigned short cnt = 0 ;
    
    unsigned int targetBeat = 0;
    
    unsigned int test1 = 0;
    unsigned int test2 = 0;
    
    unsigned int Timer1LastCycleStartH  = 0b00000000;
    unsigned int Timer1LastCycleStartL  = 0b00000000;
    unsigned short Timer1NbCycles = 0 ;
    
    //GESTION DES INTERRUPTIONS
    void interrupt interruptions(void) {
    
    	// Interruption du Timer1
    	if (TMR1IF) {
    		cnt++;	
    		if (cnt==Timer1NbCycles){
    			TMR1L   = Timer1LastCycleStartL + TMR1L + 3  ; // On met Timer1 à la valeur initiale calculée
    			TMR1H   = Timer1LastCycleStartH + TMR1H ;
    		}
    
    		if (cnt==Timer1NbCycles+1){
    			if (Timer1NbCycles==0){
    				TMR1H   = Timer1LastCycleStartH + TMR1H ; // On met Timer1 à la valeur initiale calculée
    				TMR1L   = Timer1LastCycleStartL + TMR1L ;
    			}
    			RD2 	= 1 ;
    			cnt 	= 0 ;
    			TMR0    = 0 ;
    		}
    		TMR1IF = 0 ;	// Réinitialisation du flag d'interruption du timer1
    	}
    
    	// Interruption du Timer0
    	if (T0IF){
    		if(RD2){	
    			RD2 	= 0 ;	// Exctinction de la diode
    		}		
    		T0IF    = 0 ;	// Réinitialisation du flag d'interruption du timer0
    	}	
    }
    
    
    
    
    //PROCEDURE D'INITALISATION
    void init(void) {
    
    	// Paramètres de l'oscillateur interne
    	IRCF2   = 1 ; 	// Sélection de la fréquence en sortie du post-scaler de l'oscillateur interne
    	IRCF1   = 1 ;
    	IRCF0   = 0 ;
    	SCS		= 1 ;	// Sélection de l'oscillateur interne comme horloge
    	TUN4	= 0 ;	// Fine-tuning de la fréquence de l'oscillo interne
    	TUN3	= 0 ;	    //(on garde la valeur par défaut)
    	TUN2	= 0 ;
    	TUN1	= 0 ;
    	TUN0	= 0 ;
    
    	// Activation des interruptions
    	GIE     = 1 ;	// Activation des interruptions générales (registre INTCON)
    	PEIE    = 1 ;	// Activation des interruptions de périphériques externes (registre INTCON)
    	
    	// Diode en sortie sur RD2
    	TRISD2  = 0 ;
    	RD2     = 0 ;
    	
    	// Initialisation du timer0;
    	T0CS    = 0 ;   // Oscillateur externe (1) / Interne Fosc/4 (0)
    	PSA     = 0 ;   // Assignation du prescaler à Timer0 plutôt qu'au Watchdog
    	PS2     = 1 ;	// Prescaler à 16 (011)
    	PS1     = 0 ;
    	PS0     = 1 ; 
    	T0IE    = 1 ;   // Activation des interruptions sur Timer0
    	TMR0    = 0 ;	// RAZ du Timer0
    	T0IF    = 0 ;	// Flag d'interruption du Timer0
    
    	// Initialisation du timer1;
    	TMR1CS  = 0 ; 	// Oscillateur externe (1) / Interne Fosc/4 (0)
    	T1OSCEN = 0 ;	// Oscillateur 32768 Hz activé (1) / désactivé (0)
    	T1CKPS1 = 0 ;	// Prescaler à 1 (00) / 2 (01) / 4 (10) / 8 (11)
    	T1CKPS0 = 0 ;
    	T1SYNC  = 0 ;	// Synchronisation activée (0) / désactivée (1)
    	TMR1IE  = 1 ;   // Activation des interruptions sur Timer1
    	TMR1ON  = 0 ;	// Timer1 éteint
    	TMR1H   = 0 ;	// RAZ du Timer1
    	TMR1L   = 0 ;
    	TMR1IF  = 0 ;	// Flag d'interruption du Timer1
    
    }
    
    
    
    void Timer1Param(int beat) {
    	Timer1NbCycles 			= 15;
    	Timer1LastCycleStartH 	= 0xBD ;
    	Timer1LastCycleStartL 	= 0xC0 ;
    }
    
    void Timer1Start(int initH, int initL) {
    	TMR1H   = initH ;
    	TMR1L   = initL ;
    	TMR1ON  = 1 ;
    	TMR1IF  = 0 ;	// Flag d'interruption du Timer
    }
    
    
    
    // PROGRAMME PRINCIPAL
    void main() {
    	
    	// Initialisation
    	init();
    
    	targetBeat = 60;
    	
    	Timer1Param(targetBeat);
    
    	RD2     = 1 ;
    
    	if(Timer1NbCycles == 0){Timer1Start(Timer1LastCycleStartH,Timer1LastCycleStartL);}
    	else {Timer1Start(0,0);}
    
    	// Boucle principale	
    	while(1) {
    
    	}
    }
    Les plus pointilleux (perfectionnistes?) ne manqueront pas de noter le +3 sur TMR1L dans la fonction d'interruption, qui me permet de prendre en compte les instructions du cnt++ et du if.

    J'ai fait une simulation avec le debugger MPLAB SIM, l'allumage de la diode se fait pile tous les 1e6 instructions ... mais j'ai toujours mon problème de tempo à 55bpm ! Est-ce qu'il peut y avoir des dérives entre le simulateur et la vraie vie ? Sinon, je crois qu'il va falloir me replonger dans la datasheet pour comprendre pourquoi 1e6 instructions ne fait pas 1s ...

    KorkXx.

  7. #6
    RISC

    Re : PIC16f887 - Dérive du Timer1

    Salut,
    Citation Envoyé par KorkXx Voir le message
    D'après la datasheet du µC, la fréquence de l'oscillateur interne est calibrée à +-1%. C'est loin des ~10% de dérive observés. Je pense donc comme toi que l'imprécision du quartz n'est pas la source de mon problème.
    Tu n'a pas bien lu la documentation... +/- 1% c'est à 25C uniquement. Dès que la température change la dérive augmente.
    L'horloge interne n'est pas un quartz. Un quartz c'est toujours à l'extérieur et c'est très précis généralement +/-10 ppm.

    Le timer1 n'est pas autoreload, c'est à dire qu'il faut le recharger dans l'interruption et donc il y aura toujours un léger jitter sauf dans un cas particulier : si la valeur que tu recharges est multiple de 0x100, il suffit de ne recharger que la partie haute. LA partie basse n'ayant pas été changée le comptage est synchrone.
    C'est le cas quand on veut faire une horloge temps-réel à partir d'un quartz 32768Hz sur le timer1. Pour avoir une interruption toutes les secondes, on charge 0x8000.
    Lors qu'il y a une interruption, il suffit de ne recharger que la partie haute TMR1H = 0x80 sans toucher à TMR1L.

    D'autre part, attention aux interruptions, si tu veux éviter un cas critique ou une des 2 interruptions est désactivée, il faut absolument faire les tests suivants dans l'interruption :
    Code:
    	if (TMR1IF && TMR1IE) {
    ...
    	if (TMR0IF && TMR0IE)
    a+

  8. #7
    KorkXx

    Re : PIC16f887 - Dérive du Timer1

    Salut RISC, et merci pour ta réponse.

    Merci pour les infos sur l'oscillo interne, je pensais que c'était un quartz. Ceci dit je suis en Bretagne et il fait 22°C dans la maison (!) donc a priori la dérive liée à la température devrait être loin des 10%. Je passerais sur un quartz externe si je veux plus de précision, mais si déjà j'étais près des 1% je serais content .

    Comme je ne cherche pas à avoir des interruptions régulières, je n'ai pas besoin de recharger le timer1 à chaque fois, il repart de 0 et ça me convient, sauf sur le dernier cycle où je force la valeur. Dans la simulation ça fonctionne très bien en tout cas.

    J'ai ajouté "&& TMR1IE" et "&& T0IE" comme conseillé !

    KorkXx

  9. #8
    paulfjujo

    Re : PIC16f887 - Dérive du Timer1

    bonsoir,

    le probleme est plutot dans le code..calcul de la partie restante de temps à ecouler via le timer1

    à Confirmer toutefois si j'ai bien compris la logique..

    Timer1 initialisé à 0x000 avec prescaler =1 genere au depart une IT toute les 65,535mS
    donc pour un beat à 60BPM => 1Hz 1000mS
    on peut avoir Entier(1000/65,535) soit 15 tours avant de le completer
    pour le reste soit 16.935mS soit une nouvelle init Timer1 autour de 48600
    pour ecouler le reste

    il vaut mieux charger TMR1L AVANT TMR1H
    car c'est le chargement de TMR1H qui met à jour TMR1L dans la foulée.
    Se mefier des operandes avec tailles differentes

    Code:
    unsigned int cnt = 0 ;
    unsigned int targetBeat = 0;
    unsigned int Timer1LastCycleStart  = 0;
    unsigned int Timer1NbCycles = 0 ;
    ......
    
    
    	// Interruption du Timer1
    	if (TMR1IF) {
    	if (cnt==Timer1NbCycles){
    		
    	TMR1L   = (unsigned char)(Timer1LastCycleStart & 0x00FF) ;
    	TMR1H   = (unsigned char) (Timer1LastCycleStart >>8) ;	// On met Timer1 à la valeur initiale calculée
    	}
    concernant les calculs, et vu que le quartz est bien defini pour 4MHz ,on peut simplifier
    Code:
    void Timer1Param(int beat) {
         // Timer1NbCycles =(int)(4.0e6/4.0*60.0/(float)beat/65536.0);
         // Timer1LastCycleStart=65536-((int)(4.0e6/4.0*60.0/(float)beat)%(65536));
         //  915.527 correspond à  60 000 000 / 65536
         // 1 000 000 correspond à Fosc Quartz/4
    	Timer1NbCycles=(int) (915.527 / (float)beat);
    	Timer1LastCycleStart=65536-(unsigned int) (1000000.0-(float) Timer1NbCycles * 65536.0) ;
    }
    ce qui me donne dans la fentre Watch Data
    Code:
    Name				 Value	Address
    Timer1NbCycles		15		0x0025
    Timer1LastCycleStart	48576	0x0023
    targetBeat			60		0x0021
    nota: je ne l'ai pas testé avec un 16F mais un 18F
    et just avec le debugger..de MikroC

  10. #9
    KorkXx

    Re : PIC16f887 - Dérive du Timer1

    Salut Paulfjujo, merci de tes conseils.

    Mon compilateur n'arrive pas à compiler les expressions que tu m'as proposé, mais j'ai pu en trouver qui me permettent d'obtenir les bonnes valeurs pour TMR1L et TMR1H (vérifié au débogage). Au final je fais tous les calculs dans la fonction Timer1Param, y compris la subdivision des bits de poids faible et des bits de poids fort, pour éviter d'avoir à le faire dans l'interruption.

    Malheureusement, ceci ne règle pas mon problème de décalage du tempo, j'ai toujours un tempo autour de 55BPM !

    KorkXx

  11. #10
    RISC

    Re : PIC16f887 - Dérive du Timer1

    Salut,
    Concernant l'ordre d'écriture du TIMER1, l'accès à TMR1L provoque soit la lecture / soit l'écriture simultanée de TMR1H et TMR1L.
    En mode écriture d'abord TMR1H puis TMR1L. En mode lecture c'est l'inverse, la lecture de TMR1L latche aussi TMR1H, tu lis donc TMR1L puis TMR1H.

    L'erreur de fréquence est due à 2 facteurs :
    1/ l'imprécision de l'oscillateur interne (+/-2% dans la gamme 0 à 85C) à condition que tu n'aies pas décalibré la valeur d'origine du OSCTUNE...

    2/ Le TIMER1 n'est pas AUTORELOAD, il y a donc toujours un delai pour le reprogrammer. Plus le PIC tourne vite plus ce délai sera faible car les instructions s'exécuteront plus rapidement.
    Ce délai est lui-même composé de 2 délais :
    a/ le délai de prise en compte de l'interruption et de retour de l'interruption (latence hardware) : 3 à 3,75 Tcy en asynchrone à l'entrée et je crois 3 à la sortie de l'interruption donc n total de 6,75 Tcy max
    b/ la latence logicielle ( sauvegarde du contexte + instructions jusqu'au rechargement )

    On peut compenser les 2.
    Pour 1/ le plus simple est d'utiliser un fréquencemètre si tu en as un pour connaitre avec précision la fréquence de l'oscillateur interne dans tes conditions de fonctionnement. Tu peux éventuellement retoucher OSCTUNE en suivant les indications données dans une note d'application
    Pour 2/ tu peux utiliser le simulateur, mais attention il ne tient pas compte de la latence hardware...

    Le TIMER2 est autoreload et se recharge de façon hardware pour éviter toute désynchronisation

    a+
    Dernière modification par RISC ; 06/08/2014 à 11h02.

  12. #11
    paulfjujo

    Re : PIC16f887 - Dérive du Timer1

    Salut RISC,


    ce que j'ai ecrit au post #8 ne serait donc pas faux ?
    il vaut mieux charger TMR1L AVANT TMR1H
    Microchip propose en plus de RAZer TMR1L avant de le modifier !

    Sur certain PIC18F , il y a aussi le bit RD16
    qui permettrait de s'affranchir de ces probleme de priorité d'ecriture/lecture entre TMR1L et TMR1H.

    recupéré sur un cours C18..
    Lorsque le bit RD16 est positionné à 1, l'accès 16bits est activé : une lecture de TMR1L génère la
    sauvegarde immédiate de TMR1H : il est donc inutile de vérifier s'il n'y a pas eu de changement de TMR1H
    entre la lecture du poids faible et celle du poids fort. Cette sécurité est aussi active en écriture
    peux-tu confirmer ?

    J'ai deja utilisé des PIC18F avec horloge interne ,mais jamais avec un decalage aussi grand..
    (et horloge à 8 ou 16MHz. sans toucher à OSCTUNE)
    je n'ai malheureusement pas de PIC16F887A pour le tester/verifier

  13. #12
    RISC

    Re : PIC16f887 - Dérive du Timer1

    Salut,

    Le terme "charger" n'est pas assez clair...
    Je répète mon post précédent : l'ordre d'accès à TMR1H et TMR1L dépend exclusivement de l'opération : lecture ou écriture.
    En mode lecture du TIMER1, d'abord TMR1L puis TMR1H
    En mode écriture du TIMER1, d'abord TMR1H puis TMR1L
    Dans tous les cas, c'est toujours l'accès à TMR1L qui provoque soit la lecture soit l'écriture des 16 bits simultanément.
    Ce mécanisme est bien sûr nécessaire car les PIC 8 bits ont un bus de données de 8 bits et pour garantir une opération simultanée sur 16 bits, il faut passer par un registre "intermédiaire" sur TMR1H.

    NB : les conditions ci-dessus s'appliquent aux PICs capables d'avoir une opération en mode 16 bits sur les TIMERs. Si ce n'est pas le cas il faut avoir recours à plusieurs lectures pour garantir la lecture de la bonne valeur (voir la note d'application ou l'exemple de code qui montre comment faire).

    Concernant le décalage du timer, il peut arriver, surtout quand le programmateur est une réalisation trouvée sur le web, que lors de la programmation du PIC, le registre OSCTUNE ne soit pas préservé...cela a pour effet désastreux d'effacer la valeur réglée en usine pour chaque PIC de façon à ajuster +/-1% la fréquence.
    Il y a bien sûr plein d'autres raisons que j'ai listées dans mon post précédent

    a+
    Dernière modification par RISC ; 07/08/2014 à 14h07.

  14. #13
    KorkXx

    Re : PIC16f887 - Dérive du Timer1

    Salut,

    J'ai remanié un peu ma façon de compter: je commence par faire le cycle incomplet avant de faire les cycles complets. Ça a simplifié la fonction d'interruption et le main() mais n'a pas réglé le problème.

    Lorsque je réinitialise TMR1H et TMR1L pour les cycles incomplets, j'écris TMR1H puis TMR1L, comme conseillé par RISC. Ça n'a pas non plus réglé mon problème, mais ça vaut le coup de s'habituer aux bonnes façons de faire !

    Pour rebondir sur l’idée de RISC d'une potentielle décalibration de la fréquence interne de mon PIC, j'ai imposé différentes pulsations dans le code et mesuré la pulsation effectivement obtenue. ça m'a donné la courbe suivante :

    Sans titre.gif

    On voit que la pulsation obtenue est systématiquement environ 8% plus basse que la pulsation désirée. Dans la mesure où le simulateur me donne les bonnes valeurs de nombre d'instructions entre deux allumages de ma diode, il y a de fortes chances que cette dérive soit liée à la fréquence interne de l'oscillateur non ?

    Du coup, j'ai ajouté une variable fmod correspondant à la fréquence réelle estimée de mon oscillateur (dans mes conditions environnementales en tout cas !), que j'utilise pour les calculs de nombre de cycles. Le code résultant de tout ça est le suivant :

    Code:
    #include <htc.h>
    
    unsigned int cnt = 0 ;
    unsigned int targetBeat = 0;
    unsigned int Timer1FirstCycleStartH  = 0 ;
    unsigned int Timer1FirstCycleStartL  = 0 ;
    unsigned int Timer1NbCycles = 0 ;
    float fmod = 3.672e6 ; 
    
    //GESTION DES INTERRUPTIONS
    void interrupt interruptions(void) {
    
    	// Interruption du Timer1
    	if (TMR1IF && TMR1IE) {
    		cnt++;	
    
    		if (cnt==Timer1NbCycles+1){
    			TMR1H   = Timer1FirstCycleStartH + TMR1H ; 		// On met Timer1 à la valeur initiale calculée
    			TMR1L   = Timer1FirstCycleStartL + TMR1L + 3 ;
    			cnt 	= 0 ;
    			RD2 	= 1 ;
    			TMR0    = 0 ;
    		}
    		TMR1IF = 0 ;	// Réinitialisation du flag d'interruption du timer1
    	}
    
    	// Interruption du Timer0
    	if (T0IF && T0IE){
    		if(RD2){	
    			RD2 	= 0 ;	// Exctinction de la diode
    		}		
    		T0IF    = 0 ;	// Réinitialisation du flag d'interruption du timer0
    	}	
    }
    
    
    
    
    //PROCEDURE D'INITALISATION
    void init(void) {
    
    	// Paramètres de l'oscillateur interne
    	IRCF2   = 1 ; 	// Sélection de la fréquence en sortie du post-scaler de l'oscillateur interne
    	IRCF1   = 1 ;
    	IRCF0   = 0 ;
    	SCS		= 1 ;	// Sélection de l'oscillateur interne comme horloge
    	TUN4	= 0 ;	// Fine-tuning de la fréquence de l'oscillo interne
    	TUN3	= 0 ;	    //(on garde la valeur par défaut)
    	TUN2	= 0 ;
    	TUN1	= 0 ;
    	TUN0	= 0 ;
    
    	// Activation des interruptions
    	GIE     = 1 ;	// Activation des interruptions générales (registre INTCON)
    	PEIE    = 1 ;	// Activation des interruptions de périphériques externes (registre INTCON)
    	
    	// Diode en sortie sur RD2
    	TRISD2  = 0 ;
    	RD2     = 0 ;
    	
    	// Initialisation du timer0;
    	T0CS    = 0 ;   // Oscillateur externe (1) / Interne Fosc/4 (0)
    	PSA     = 0 ;   // Assignation du prescaler à Timer0 plutôt qu'au Watchdog
    	PS2     = 1 ;	// Prescaler à 16 (011)
    	PS1     = 0 ;
    	PS0     = 1 ; 
    	T0IE    = 1 ;   // Activation des interruptions sur Timer0
    	TMR0    = 0 ;	// RAZ du Timer0
    	T0IF    = 0 ;	// Flag d'interruption du Timer0
    
    	// Initialisation du timer1;
    	TMR1CS  = 0 ; 	// Oscillateur externe (1) / Interne Fosc/4 (0)
    	T1OSCEN = 0 ;	// Oscillateur 32768 Hz activé (1) / désactivé (0)
    	T1CKPS1 = 0 ;	// Prescaler à 1 (00) / 2 (01) / 4 (10) / 8 (11)
    	T1CKPS0 = 0 ;
    	T1SYNC  = 0 ;	// Synchronisation activée (0) / désactivée (1)
    	TMR1IE  = 1 ;   // Activation des interruptions sur Timer1
    	TMR1ON  = 0 ;	// Timer1 éteint
    	TMR1H   = 0 ;	// RAZ du Timer1
    	TMR1L   = 0 ;
    	TMR1IF  = 0 ;	// Flag d'interruption du Timer1
    
    }
    
    
    
    void Timer1Param(int beat) {
    	//Timer1NbCycles			 = (int) (915.527 / (float)beat);
    	Timer1NbCycles			 = (int) (fmod/4.0*60.0/(float)beat/65536.0);
    	Timer1FirstCycleStartH   = (65536-((int)(fmod/4.0*60.0/(float)beat)%(65536))) >>8 ;
    	Timer1FirstCycleStartL   = (65536-((int)(fmod/4.0*60.0/(float)beat)%(65536))) & 0x00FF ;
    }
    
    void Timer1Start(int initH, int initL) {
    	TMR1H   = initH ;
    	TMR1L   = initL ;
    	cnt 	= 0 ;
    	TMR1ON  = 1 ;
    	TMR1IF  = 0 ;	// Flag d'interruption du Timer
    }
    
    
    
    // PROGRAMME PRINCIPAL
    void main() {
    	
    	// Initialisation
    	init();
    
    	targetBeat = 60;
    	
    	Timer1Param(targetBeat);
    
    	RD2     = 1 ;
    
    	Timer1Start(Timer1FirstCycleStartH,Timer1FirstCycleStartL);
    
    	// Boucle principale	
    	while(1) {
    
    	}
    }
    Voilà, sauf avis contraire de votre part, je vais passer à l'étape 2: réglage de la pulsation avec une résistance ajustable et affichage de la pulsation sur des afficheurs 7 seg. En tout cas, merci de votre aide, je vous tient au courant de la suite !

    KorkXx
    Images attachées Images attachées  
    Dernière modification par KorkXx ; 08/08/2014 à 11h00.

  15. #14
    paulfjujo

    Re : PIC16f887 - Dérive du Timer1

    bonjour,



    As-tu fait les tests avec OSCTUNE
    en modifiant dans l'init
    les extremes
    Code:
    OSCTUNE=Ox1F; //mini frequence
    ou
    Code:
    OSCTUNE=0x0F, // maxi frequence
    Dernière modification par paulfjujo ; 08/08/2014 à 12h23.

  16. #15
    RISC

    Re : PIC16f887 - Dérive du Timer1

    Salut,

    L'erreur que tu vois est uniquement due à ton programme.
    Il faut recharger le timer1 à chaque fois que tu as une interruption sans quoi tu repart pour 65536 impulsions
    Si tu mets 65536 - 16667 dans le TIMER1, tu auras des interruptions au rythme de 60Hz avec une très légère erreur ( 16666,66 ) qui est obligatoirement acceptable si tu utilises un oscillateur interne dont l'erreur du à la stabilité est supérieure.
    NB : ta température ambiante variant très peu (je suppose que tu travailles dans un environnement tempéré) il est impossible d'avoir plus de 1 ou 2% de variation (c'est la datasheet qui le garantit de 0 à 85C).

    a+

  17. #16
    paulfjujo

    Re : PIC16f887 - Dérive du Timer1

    bonsoir


    tu auras des interruptions au rythme de 60Hz
    il recherche 60 BPM .. par minute

  18. #17
    KorkXx

    Re : PIC16f887 - Dérive du Timer1

    Salut !

    Paulfjujo: j'ai essayé en réglant OSCTUNE, ça a le même effet que de mettre une variable de correction, mais avec la variable j'ai pu faire une calibration un peu plus facilement.

    RISC: je crois qu'il y a mésentente sur la stratégie de comptage: je ne cherche pas ici à obtenir des interruptions régulières sur timer1, c'est pourquoi je ne le recharge pas à chaque fois. Je calcule un nombre d'instructions à faire et je le répartis en n fois un comptage complet sur le registre et un comptage incomplet. Ça me permet d'éviter d'avoir une erreur d'arrondi (minime, certes !) à chaque interruption.

    J'ai bien avancé sur le réglage de la pulsation avec une résistance ajustable et le réglage du CAN. Dès que j'ai fait l'affichage je vous poste le code !

    KorkXx

  19. #18
    r4ph

    Re : PIC16f887 - Dérive du Timer1

    Hmm... Précision avec une résistance variable? Va falloir bien régler ça... Pour une solution élégante et qui ne nécessite pas d'aller écrire dans un registre de timer: http://www.romanblack.com/one_sec.htm

    A+

  20. #19
    RISC

    Re : PIC16f887 - Dérive du Timer1

    Salut r4ph
    Citation Envoyé par r4ph Voir le message
    Pour une solution élégante et qui ne nécessite pas d'aller écrire dans un registre de timer: http://www.romanblack.com/one_sec.htm
    Merci pour le lien très intéressant sur les tempos précises. ;=)
    C'est vraiment un petit bijou !

    a+

Discussions similaires

  1. PIC16F887 , interruption RB0 ????
    Par hassanmottaki dans le forum Électronique
    Réponses: 14
    Dernier message: 23/05/2014, 23h06
  2. 16 bits PIC16F887!!
    Par yazeero dans le forum Électronique
    Réponses: 8
    Dernier message: 11/01/2013, 21h08
  3. PIC16f887, RTC et microC
    Par kevin8z dans le forum Électronique
    Réponses: 4
    Dernier message: 27/04/2012, 11h09
  4. LCD couleur pic16F887
    Par belly147 dans le forum Électronique
    Réponses: 2
    Dernier message: 28/08/2011, 00h25
  5. PIC16f887, decodage RC5 philips
    Par mistral83 dans le forum Électronique
    Réponses: 16
    Dernier message: 21/04/2011, 20h29
Découvrez nos comparatifs produits sur l'informatique et les technologies.