Timer interrupt pour métronome

Bonjour,

Pour un projet je dois faire un métronome en utilisant une fonction d'interruption sur timer. J'ai un potentiomètre qui me permet de choisir entre 0 et 200 BPM pour le métronome. On a jamais utilisé les timer et en recherchant j'ai compris que cela servait à faire des choses sans déranger dans les autres fonctions mais en pratique j'ai un peu de mal. Voici ce que j'ai essayé de faire.

int pot=A5;
int valeur;
int BPM;
int retour;

void setup() {
  Serial.begin(9600);

  cli(); //on désactive
  //valeur pour l'interrupt
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A = 15624;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS12) | (1 << CS10);  
  TIMSK1 |= (1 << OCIE1A);
  sei(); // on reactive
pinMode(10,OUTPUT);//test sur le buzzer.
}

void loop() {
Serial.println(BPM);
}

ISR(TIMER1_COMPA_vect){          
  retour=analogRead(pot);
  valeur=map(retour,0,1023,0,200);
  OCR1A=15624*(60/valeur);
}

Dans votre code qu’est qui va changer BPM ?

L’interruption devrait générer le tick simplement et la loop devrait ajuster le BPM en fonction de la lecture du potentiomètre

Lisez Les différentes parties de ce tuto sur les timers

Les Timers
Les Timers (I) - Les bases
Les Timers (II) - Les interruptions
Les Timers (III) - Comparaisons et interruptions
Les Timers (IV) - La génération des PWM
Les Timers (V) - L’unité de capture d’entrée

J-M-L:
Dans votre code qu’est qui va changer BPM ?

L’interruption devrait générer le tick simplement et la loop devrait ajuster le BPM en fonction de la lecture du potentiomètre

Lisez Les différentes parties de ce tuto sur les timers

Les Timers
Les Timers (I) - Les bases
Les Timers (II) - Les interruptions
Les Timers (III) - Comparaisons et interruptions
Les Timers (IV) - La génération des PWM
Les Timers (V) - L’unité de capture d’entrée

Normalement pour avoir le bpm je devrais calculer la fréquence puis la multiplier par 60 mais je vois pas comment faire pour décider quelle période je dois utiliser dans mon interrupt , on fait un calcul de millis dedans ?

Bonjour,

A mon avis, il serait beaucoup plus simple de ne pas utiliser une interruption timer, mais d'utiliser millis() dans la loop.

Oui ce serait plus simple avec millis mais si l’exercice c’est avec les timers...

Vous devez régler le timer pour qu’il déclenche au bon moment, cf Les Timers (III) - Comparaisons et interruptions

C’est ce que fait la loop en lisant le potentiomètre, régler la bonne comparaison

J-M-L:
Oui ce serait plus simple avec millis mais si l’exercice c’est avec les timers...

Vous devez régler le timer pour qu’il déclenche au bon moment, cf Les Timers (III) - Comparaisons et interruptions

C’est ce que fait la loop en lisant le potentiomètre, régler la bonne comparaison

Si j'ai bien compris ocr1a permet de savoir jusqu'à quel valeur compter , donc en gardant les mêmes valeurs que dans l'exemple mais en changeant ocr1a ça me donne ça

int pot=A5;
int valeur;
int BPM;
int retour;
int frequence;



void setup() {
  Serial.begin(9600);

  cli(); //on désactive
  //valeur pour l'interrupt
   noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0b00001100;
  TIMSK1 = 0b00000010;
  TCNT1 = 0;  
  OCR1A = valeur; 
  interrupts();
}

void loop() {
pinMode(10,OUTPUT);//test sur le buzzer.

  retour=analogRead(pot);
  valeur=map(retour,0,1023,0,3750000);


}

ISR(TIMER1_COMPA_vect){          



Serial.println(BPM);
 
}

Car j'ai diviser 60s par 16us puisqu'on cherche le bpm mais après je sais pas quoi faire

Pour moi, il y a trois choses:

  • la lecture du potentiomètre faite par
  retour=analogRead(pot);

Ca c'est bon. Cela doit se faire régulièrement, loop est un bon endroit

  • la sortie de l'info:
    BMP c'est quoi? des Blanches Par Minutes? Peu importe pour le programme... Il semble qu'il y a un buzzer. Il fonctionne? si non, c'est pas la peine d'aller plus loin. Rien ne pourra être testé. Y a-t-il un test qui permet de faire un seul bip?

  • entre les deux, une interruption, et on va jouer sur la valeur de OCR1A

Il y a une apparente impossibilité dans le cahier des charges, si on utilise des interruptions, on ne peut pas en avoir moins que une toutes les 4 s environ (horloge de 64µs et compteur à 65536). Ce qui fait 15 BMP. Si l'énoncé est "entre 15 et 200 BMP", c'est facile. Si l"énoncé maintient "entre 0 et 200 BMP cela se complique salement, car il faudrait que dans la fonction d'interruption compter pour faire bip une fois toutes les N appels.

La place des diverses fonctions:

L'interruption devrait générer le tick simplement et la loop devrait ajuster le BPM en fonction de la lecture du potentiomètre

J'aurais dit doit. Mais c'est déjà dit.

Dans le programme:

valeur=map(retour,0,1023,0,3750000);

"valeur" n'est jamais utilisé, cette ligne ne sert à rien, d'ailleurs il y a une grosse erreur, erreur doit être uint32_t car on ne mettra pas 3750000 dans un int. Si cette ligne ne sert à rien, on peut l'enlever

retour=analogRead(pot);

Comme retour n'est plus utilisé, on peut aussi retirer cette ligne

pinMode(10,OUTPUT);//test sur le buzzer.

La broche 10 n'est pas pilotée par le programme, cette ligne peut être donc retirée.

la structure du code pourrait être comme cela

const byte potPin = A5;
const byte buzzerPin = 10;
const unsigned long minBPM = 0;
const unsigned long maxBPM = 200;
unsigned long BPM = 0;

// Routine d'interruption timer 1
ISR(TIMER1_COMPA_vect) {
  /* A FAIRE */
}

void reglerTimer() {
  /* A FAIRE, BPM contient la bonne valeur */
}

void gestionBPM() {
  unsigned long nouveauBPM = map(analogRead(potPin), 0, 1023, minBPM, maxBPM);
  if (nouveauBPM != BPM) {
    // on a bougé le potentiomètre, il faut changer le réglage du timer
    BPM = nouveauBPM;
    reglerTimer();
  }
}

void setup() {
  pinMode(buzzerPin, OUTPUT);
}

void loop() {
  gestionBPM();
}

J-M-L:
la structure du code pourrait être comme cela

const byte potPin = A5;

const byte buzzerPin = 10;
const unsigned long minBPM = 0;
const unsigned long maxBPM = 200;
unsigned long BPM = 0;

// Routine d'interruption timer 1
ISR(TIMER1_COMPA_vect) {
 /* A FAIRE */
}

void reglerTimer() {
 /* A FAIRE, BPM contient la bonne valeur */
}

void gestionBPM() {
 unsigned long nouveauBPM = map(analogRead(potPin), 0, 1023, minBPM, maxBPM);
 if (nouveauBPM != BPM) {
   // on a bougé le potentiomètre, il faut changer le réglage du timer
   BPM = nouveauBPM;
   reglerTimer();
 }
}

void setup() {
 pinMode(buzzerPin, OUTPUT);
}

void loop() {
 gestionBPM();
}

D'accord merci , je vais essayer de faire ce que je peux et je reviens vers vous si j'ai un problème

Est ce que pour la fonction reglerTimer ces valeurs vous paraissent correct ?

void reglerTimer() {

OCR1A=15625*(60.0/bpm);
TCCR1A = 0;     
TCCR1B = 0;    
OCR1B=31;     //2ms
TCNT1=0;
TCCR1B |= (1 << CS10);

}

commencez par expliquer en Français comment vous voulez configurer le timer

J-M-L:
commencez par expliquer en Français comment vous voulez configurer le timer

Selon moi , il faudrait que l'0CR1A qui est la valeur plafond du comptage de l'horloge interne du timer1 change en fonction du BPM , plus le BPM est grand et donc plus l'0CR1A sera petit et donc la période plus petite et inversement si le BPM est plus petit , l'0CR1A sera plus grand et donc la période plus grande

Ok oui

depuis le BPM il faut trouver la période, c'est à dire le temps ∆t entre 2 pulsations --> (60 / BPM)

Pourquoi proposez vous OCR1A=15625*(60.0/bpm);
(et savez vous expliquer les autres registres ?)

J-M-L:
Ok oui

depuis le BPM il faut trouver la période, c'est à dire le temps ∆t entre 2 pulsations --> (60 / BPM)

Pourquoi proposez vous

OCR1A=15625*(60.0/bpm);

(et savez vous expliquer les autres registres ?)

Alors pour cette ligne , j'ai proposé 15625 car cela correspond à 1hz et ensuite multiplié par 60 pour une minute et par le bpm comme je l'ai expliqué plus haut.
Ensuite le OCR1B c'est le "bottom" c'est l'inverse de l'0CR1A donc c'est les s mais je pense après réflexion qu'il faudrait enlever cette valeur.
Ensuite les TCR1B et TCR1A sont les registres de contrôle du timer1,la ligne TCCR1B |= (1 << CS10); sert à décaler le bits du TCCR1B si j'ai biens compris
Le TCNT1 le timer counter du timer1

#define bpin 5
#define Toggle PORTD ^=(1<<Toggle)

const byte potPin = A5;
const byte buzzerPin = 10;
const unsigned long minBPM = 0;
const unsigned long maxBPM = 200;
unsigned long BPM = 120;

// Routine d'interruption timer 1
ISR(TIMER1_COMPA_vect) {
  if(varcompteur++>125)
  {
    varcompteur=0;
    //Active le buzzer ?
  }
}

void reglerTimer() {

OCR1A=15625*(60.0/bpm);
TCCR1A = 0;     
TCCR1B = 0;    
OCR1B=31;     //2ms
TCNT1=0;
TCCR1B |= (1 << CS10);


  
}

void gestionBPM() {
 // unsigned long nouveauBPM = map(analogRead(potPin), 0, 1023, minBPM, maxBPM);
//  if (nouveauBPM != BPM) {
    // on a bougé le potentiomètre, il faut changer le réglage du timer
  //  BPM = nouveauBPM;
    reglerTimer();
  }
//}

void setup() {
  pinMode(buzzerPin, OUTPUT);
  
  }

void loop() {
  gestionBPM();
}

Entre temps j'ai fait ça , mais je sais pas quoi mettre pour dire qu'on active ou non le buzzer

est-ce que vous comprenez le code que vous postez ?

votre code ne doit pas compiler car varcompteur n'est pas déclarée...
pourquoi comptez vous jusqu'à 125 dans l'interruption ?

c'est vous qui avez écrit #define Toggle PORTD ^=(1<<Toggle)
qu'est-ce que vous aviez en tête pour ce Toggle ?

pourquoi avoir commenté une partie du code?

--> il faut que vous soyez clair dans votre tête sur chacune des lignes du code et de la configuration des timers

tant que vous avancez au petit bonheur, ça ne va pas marcher...

vous pouvez mettre le buzzer à plus tard, pour le moment connectez une LED avec une résistance de limitation de courant sur la pin du buzzer et inversez son état à chaque interruption. vous verrez déjà si la période est correcte

Pour le var compteur c'est bizarre qu'il soit pas dans le code parce que je l'avais déclaré donc bon.
Sinon ce que j'avais en tête c'est que dans l'interrupt , après avoir fini de compter on dit au buzzer qu'il peut envoyer le signal et ensuite on repasse le compteur à 0 , je m'étais simplement inspiré du lien que vous aviez envoyé sur les timer avec le code de la partie 3 , ensuite pour la valeur de 125 c'est juste une valeur de référence pour tester comme dans le code.

il faut déclarer toutes les variables utilisées

125 ça veut dire que c'est uniquement à la 125ème interruption que vous allez faire quelque chose. ce 125 vient d'un calcul lié à la configuration du timer

par exemple si vous aviez configuré le timer pour qu'il fasse une interruption toutes les 8ms alors 125*8=1000 et donc quand le compteur atteint 125 c'est qu'on a eu une seconde de passée.

===> il faut bien comprendre comment régler le timer, ce 125 n'est pas là par hasard

J-M-L:
il faut déclarer toutes les variables utilisées

125 ça veut dire que c'est uniquement à la 125ème interruption que vous allez faire quelque chose. ce 125 vient d'un calcul lié à la configuration du timer

par exemple si vous aviez configuré le timer pour qu'il fasse une interruption toutes les 8ms alors 125*8=1000 et donc quand le compteur atteint 125 c'est qu'on a eu une seconde de passée.

===> il faut bien comprendre comment régler le timer, ce 125 n'est pas là par hasard

Oui mais du coup c'est surtout par rapport à l'0CR1A non ? Plus il sera grand plus le compteur se chargera lentement non? C'est pour éviter que ça bip trop rapidement

oui si vous êtes en mode "comparaison" alors ce que l'on a mis dans OCRxx sert de borne de déclenchement

Le principe c'est bien de compter et de déclencher l'interruption, mais la pratique c'est comment régler le prescaler, le Output Compare (et les autres registres du timer) pour avoir quelque chose qui correspond au besoin