Fonctionnement flag ICR1

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
}


Quand je suis en dessous de 60Hz


Quand je suis au dessus de 60Hz

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.

bonsoir.

2 remarques :

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.

J'ai essayé, effectivement :
115200 baud -> 480 Hz max
500 000 baud -> 900 Hz max

Qu'est ce que ça signifie alors ? Que le traitement d'écriture Serial ralenti l'acquisition d'échantillons ?

Je vais essayer de rendre mon code plus light et voir ce que ça donne.

Je comprends ton point 2, mais à 1Hz, cela marche très très bien.

permets-moi d'en douter, mais tu fais comme tu veux

Bonjour, j'ai finalement rendu plus light mon code
resultats :slight_smile:
à 500 000 bauds, je monte à 7000Hz maxi
idem à 9600 bauds.

J'aimerais donc comprendre pourquoi je plafonne ?
Entre autres, est-ce la limite de ma façon de programmer ?
Qu'est ce qui limiterait cela ?

#include <Arduino.h>

byte i = 0;
bool traitement = true;
unsigned long comptage = 0; // La variable compteur
float fHz=0.0, moyenne = 0.0;
unsigned long echantillon [4] = {0};
 
void setup () {
  cli(); 
  timer1Init();
  sei(); 
  Serial.begin(9600);
}
 
 
void loop () {
 
 if(i == 4){
      moyenne = ((echantillon[3]-echantillon[2]) + (echantillon[1]-echantillon[0]) )/2.0;
      fHz = 1.0/(moyenne/1000000.0);
      Serial.print("puls=");
      Serial.println(comptage);
      Serial.print(" - Hz=");
      Serial.println(fHz);
      Serial.println("");

    traitement = true;
    i = 0;

  }

 if(Serial.available()){
    Serial.read() ;
    comptage = 0;}

 }


ISR(TIMER1_CAPT_vect) { 
// calcul de la fréquence
   
 if (traitement){
    
      echantillon[i] = micros();
      i++;
      
      if(i==4) traitement = false; 
    }
    
}

void timer1Init() { // 

  TCCR1A = 0b00000000 ; 
  TCCR1B = 0b11000101 ; //15.6KhZ voir p.173 : reglage du clock entre autres 

  TIMSK1 = 0b00100000; 
 
}

Sans preuve, on se doit de douter , je comprends :wink:

image

Hello, vers la fin de ce lien.
Tu verras une application détaillée

salut @tonynyny

à 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).

Bonjour dfgh

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 :slight_smile:

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 ?

si, bien sûr

dans mon pseudo-code :

  • nouvelle_capture c'est ICR1 ;
  • 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 ...

Je vois qu'il y a des personnes plus compétentes que moi. Bye, je mes ce poste en sourdine....

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 ?

Oui mais encore ? cad ?

Aucune autre solution alors pour optimiser mon code actuel sans forcement utiliser uniquement les registres comme évoqué au post#13

et dans quelle situation cela peut etre intéréssant de garantir cette relecture ?