Bonjour à tous. J'essais de comprendre le fonctionnement complet du timer1 pour un projet à venir, j'ai donc commencé par essayer de calculer un fréquence d'impulsion (d'un générateur de fonction) transmis sur l'entrée 8 de l'arduino UNO (pin dédié au flag ICR1)
J'ai donc programmé le code ci-dessous, qui marche bien jusqu'à 58Hz, mais au délà, la fonction millis() n'a plus l'air de suivre les interruptions... j'obtiens donc une fréquence incohérente au délà de 60Hz
J'aimerai donc savoir (sachant que je sais qu'il existe des programmes qui calculent déjà bien la fréquence) pourquoi au délà de 60Hz, avec mon codage actuel, je ne peux pas dépasser les 60Hz...
Merci d'avance pour vos précieuses aides
#include <Arduino.h>
byte i = 0;
unsigned long comptage = 0; // La variable compteur
float fHz=0.0, moyenne = 0.0;
bool tour = 0;
unsigned long echantillon [4] = {0};
void setup () {
cli(); // Désactive l'interruption globale
timer1Init();
sei(); // Active l'interruption globale
Serial.begin(9600);
}
void loop () {
// Mettre ici le programme. Exemple :
}
// Routine d'interruption
ISR(TIMER1_CAPT_vect) { // interruption programme par flag ICR1
// calcul de la fréquence
switch(tour){
case 0 :
echantillon[i] = micros();
i++;
if(i==4) tour = 1;
Serial.print("ech");
Serial.print(i-1);
Serial.print("=");
Serial.println(echantillon[i-1]);
break;
case 1 :
tour = 0;
moyenne = ((echantillon[3]-echantillon[2]) + (echantillon[1]-echantillon[0]) )/2.0;
i = 0;
fHz = 1/(moyenne/1000000.0);
Serial.println("");
Serial.print("moyenne=");
Serial.println(moyenne);
Serial.print(" - Hz=");
Serial.println(fHz);
Serial.println("");
break;
default : break;
}
}
void timer1Init() { //
/*
timer 1 desactivé de base,
entrée capture sun PIN8 du UNO
*/
// TCCR = registres de controle
TCCR1A = 0b00000000 ; //voir p.
TCCR1B = 0b11000101 ; //15.6KhZ voir p.173 : reglage du clock entre autres
//OCR2A = 0b00000000; pour utiliser le mode de comptage CTC
//OCR2B = 0b00000000; pour utiliser le mode de comptage CTC
TIMSK1 = 0b00100000; // Interruption locale autorisée par le flag OVF (lancement de la fc ISR)
//TIFR1 = 0b00000; //contient les flags TOV, et de compare match A et B
//TCCR0A = 0b00000000 ; p.138
//TCCR0A = 0b00000000 ; p.141
//TCCR1A = 0b00000000 ; p.170
//TCCR1A = 0b00000000 ; p.173
}
Salut.
Si tu configures Serial à 115200 baud, le résultat sera certainement meilleur, mais en général c'est une très mauvaise idée d'appeler des routines gourmandes comme Serial.print() dans le code d'une interruption.
1 -
tu fais trop de choses dans ton ISR : elle se doit d'être la plus courte (rapide) possible, par exemple dans ton cas n'effectuer que i++; et basta ! tout le reste doit être réalisé par la boucle principale.
2 -
pour des fréquences aussi basses, la méthode du comptage d'impulsions n'est pas adaptée : il vaut mieux mesurer la période, pour ensuite calculer la fréquence.
à la (re)lecture de ton code, je m'aperçois que j'ai zappé un truc ...
tu mesures bien la période pour calculer la fréquence (bonne méthode pour des fréquences basses) mais tu le fais avec micros(); alors que tu entres sur ICP1 et utilises l'ISR qui lui est dédiée, mais tu ne te sers pas du Timer1.
dans l'ISR tu devrais faire (pseudo-code):
temps <= nouvelle_capture_du_timer - ancienne_capture_du_timer ;
ancienne_capture_du_timer <= nouvelle_capture_du_timer ;
// avec nouvelle_capture_du_timer <= ICR1
et dans la boucle tu te sers de temps pour calculer la fréquence.
avec un timer en mode normal, si le prescaler est à 1 (soit 62,5ns de résolution) et si la différence entre la nouvelle capture et la précédente (temps) vaut par exemple 2345, soit 146,5625µs, alors la fréquence sera 6823,... Hz autrement dit f=16M/temps ;
si le PS est de 8, alors f=2M/temps, etc... (en augmentant la valeur du PS tu changes de gamme, en acceptant des fréquences plus basses encore).
si le PS à 1 ne te donne pas assez de précision (fréquence trop rapide) tu peux changer de méthode et passer au comptage d'impulsions (sur 1s, par ex).
J'ai lu le post entier, mais cela depasse visiblement mes connaissances ^^
J'ai compris qu'il y avait une histoire de résolution/précision la dedans, mais dans mon cas, je ne saurai comment expliqué pourquoi la résolution/précision expliquerait ce que j'obtiens.
J'espère que l'on pourra mexpliquer dans mon cas pour que je comprenne l'importance de la prise en compte de la résolution pour mes futures projets.
Super, merci ! Je vais tester cela et je reviens aux nouvelles
Ne vaudrait-il pas mieux que j'utilise directement ICR1 pour la soustraction, sachant qu'il conserve directement la valeur de TCNT1 au moment de l'ISR ? plutôt que d'utiliser le TCNT1 pour la soustraction ?
ancienne_capture est un entier 16 bits non signé, qui peut être déclaré volatile ;
temps est un autre entier 16 bits non signé, obligatoirement volatile car seulement lu dans la boucle, qui contient la différence entre la valeur actuelle du timer et la précédente.
explication partielle et rapide :
ICR1 ne changera qu'au prochain événement ;
au moment où le calcul est fait dans l'ISR il se peut que TCNT1 ait déjà évolué : il y a un certain délai entre la demande d'interruption et son exécution ; de plus il y a tout un boulot de sauvegarde de registres au début de l'ISR, avant même que ton code ne s'exécute, et ça prend du temps ;
déclarer une variable volatile force le compilateur à la créer, même s'il le juge inutile parce qu'elle n'est pas modifiée dans la boucle ;
(peut-être oublié des trucs ?)
conseil qui vaut ce qu'il vaut : pense à gérer les overflows du timer, s'il fait plus d'un tour la différence ne sera plus juste.
et on pourrait continuer longtemps ... je m'arrête là, je suis sec d'idées, mais tu auras certainement d'autres questions ...
Bonjour,
il faut en effet déclarer comme volatile les variables utilisées dans les ISR, si non ... ça marche pas
"à 500 000 bauds, je monte à 7000Hz maxi
idem à 9600 bauds"
ce qui semble dire que le problème n'est pas (pour l'instant) la vitesse de l'uart, mais la durée des calculs effectués dans la loop (avec des float ...)
" TCCR1A = 0b00000000 ;
TCCR1B = 0b11000101 ; //15.6KhZ voir p.173 : réglage du clock entre autres
TIMSK1 = 0b00100000; "
oui, mais encore ?
Non, cela force le compilateur à relire la valeur de la variable à chaque fois qu'elle est utilisée, en excluant toute optimisation, par exemple stockage dans un registre.
Il est donc recommandé de déclarer en volatile les variables partagées entre deux parties distinctes du logiciel (threads, ISR, etc.).
volatile ou non volatile alors ? @5_cylindres
Cela me permettrait de monter au délà des 7000Hz ?
Comment sait-on qu'une variable est partagées entrer 2 parties distinctes ? Le loop et l'ISR par exemple sont bien 2 parties distinctes si je ne me trompe pas ?
volatile ne te permettra pas d'aller plus vite, mais garantira qu'une variable partagée entre l'ISR et loop() soit relue à partir de son emplacement physique.
Si ce n'est que la durée des calculs avec des floats qui bride ma résolution, comment faire des calculs sans utiliser de float alors, qui semble être un type de variable inévitable pour moi ?