Interruption du timer exécutée dès que j'active celui-ci

Bonjour,

J'ai un montage intégrant 2 boutons sur lesquels une pression déclenche 2 traitements. Lorsque l'un des 2 boutons est enfoncé, aucun traitement ne doit pouvoir être (re)lancé avant 0.3s.

Les 2 boutons sont sur les ports INT0 et INT1 et déclenchent des interruptions externes. Pour les 2 boutons, le traitement commence par tester un boolean appelé AVAIBLE. Si AVAIBLE est false, on fait rien. Si il est true, on le passe à false, on active Timer1 et le traitement commence.

Timer1 est initialisé en CTC, en comparateur et pour duré d'environ 0.3s. Son code d'interruption met AVAIBLE à true et désactive timer1.

Sur le papier, je pense avoir réglé mon problème (y compris le debounce).

Dans la pratique, j'ai l'impression que le code d'interruption de timer1 s'exécute dès que j'active le timer, et le traitement est lancé 2 fois (en fait, on presse toujours les 2 boutons presque simultanément, c'est le but du montage de déterminé lequel à été pressé en 1er et de n’exécuter que le traitement de celui-ci)

J'ai essayé en rajoutant des sei()/cli() sans plus de résultat.

Quelqu'un saurait-il me dire si c'est normal que le code de l'interruption du timer s’exécute dès l'activation du timer ? Comment éviter ça ?

Voici le code :

void setup() {
  // activation TIMER1 
  TCCR1A = 0x00;  // set timer1 registers to 0
  TCCR1B = 0x00;     
  TIMSK1 = 0x00;
  OCR1A = 5000;// ~300ms //3906; // 1/4s
  TCCR1B |= (1 << WGM12); // CTC (Clear Timer On Compare)
  TCCR1B |= (1 << CS10); // 010 => prescaler 8
  TCCR1B |= (0 << CS11); // 011 => prescaler 64
  TCCR1B |= (1 << CS12); // 111 => prescaler 1024
  //TIMSK1 |= (1 << OCIE1A); // Output Compare A Match Interrupt Enable 

}

uint16_t test = 0;

ISR(TIMER1_COMPA_vect)
{
  TIMSK1 &= ~(1 << OCIE1A); // couper le timer
  TCNT1 = 0; // histoire d'etre sur !
  debug(test); // ca m'affiche la valeur de test sur un ecran, c'est comme ca que je vois que ce code est execute 2 fois... 1 fois au moment ou j'appuis sur le bouton... et une autre fois environ 1/3s plus tard
  test++;
  avaible = true;
  
}

ISR(INT0_vect){ // interception btn1
  cli();  
  if(avaible)
  {
    avaible = false;
    TIMSK1 |= (1 << OCIE1A); // demarrer timer1
    val_int0 = PIND & (1 << PIND2) ? 1 : 0;
    // mon traitement
  }
  sei();
}

ISR(INT1_vect){ // interception btn2 
  cli();
  if(avaible)
  {
    avaible = false;
    TIMSK1 |= (1 << OCIE1A);
    val_int1 = PIND & (1 << PIND3) ? 1 : 0;
    // mon traitement
  }
  sei();
}

En vous remerciant.

Bonjour,

L'interruption s’exécute lorsque le compteur atteint la valeur de comparaison, et non dès qu'on autorise l'interruption.
Le compteur continue à compter même si l'interruption de comparaison n'est pas autorisée. Si tu autorises cette interruption juste avant que le compteur atteigne la valeur de comparaison tu vas avoir une interruption presque immédiatement.

Note aussi que sei() et cli() agissent sur toutes les interruptions simultanément.
Les avr possèdent une gestion plus fine des interruptions ou chaque interruption est autorisée une par une.

Merci pour vos réponses.

En fait, j'ai exactement le même résultat si j'ajoute un TCNT1 = 0;

ISR(INT0_vect){ // interception btn1
  cli();  
  if(avaible)
  {
    avaible = false;
    TCNT1 = 0; 
    TIMSK1 |= (1 << OCIE1A); // demarrer timer1
    val_int0 = PIND & (1 << PIND2) ? 1 : 0;
    // mon traitement
  }
  sei();
}

De plus, le comportement est toujours exactement le même lors de l'appui sur les boutons (le traitement est toujours appelé 2 fois avec le même interval de temps entre les 2)...

l'explication ne me semble donc pas coller au problème que je décris.

J'ose me permettre un "UP", le problème étant toujours entier...

Bonjour,

Je ne vois pas bien pour quelle raison l'interruption CTC interviendrait deux fois. En revanche, je pense qu'il peut arriver ceci: les interruptions sur INT0 et INT1 sont probablement déclenchées lorsque le bouton est appuyé, mais également lorsqu'il est relâché. Comme 1/3 de seconde c'est potentiellement plutôt court comparé à un appui sur un bouton, est-ce qu'il ne faudrait pas tester l'état du bouton dans les fonctions d'interruption INT0 et INT1 pour ne les appliquer que lorsque le bouton est enfoncé, et pas lorsqu'il est relâché?

D'autre part, je pense que cette ligne dans l'initialisation du timer ne sert à rien:

TCCR1B |= (0 << CS11); // 011 => prescaler 64

Et pour finir, je ne suis pas sûr que cli() et sei() soient nécessaire dans les fonctions d'interruptions: si je me souviens bien les interruptions sont désactivées automatiquement lors de l'exécution d'une interruption. Par contre il me semble qu'il vaut mieux les mettre lors de l'initialisation.

En espérant avoir aidé...

L'interruption s'exécute lorsque le compteur atteint la valeur de comparaison, et non dès qu'on autorise l'interruption

Bonjour,

Kamil,
pour qu'une interruption s'exécute il faut que soient positionnés :

  • le flag interruption/évènement, qui se "set" automatiquement, et nécessite une action pour son "reset"
  • le bit correspondant autorisant l'interruption au niveau du périphérique
  • le bit d'autorisation globale GIE

par conséquent, si le flag est déjà positionné et pas encore effacé, (et cela risque fort d'être le cas avec l'over run d'un timer) l'interruption interviendra fatalement dès que les bits d'autorisation sont "set"

Bonjour,

Oui c'est bien ce que je dis:

L'interruption s'exécute lorsque le compteur atteint la valeur de comparaison

Je n'ai peut être pas été suffisamment clair: C'est sous entendu si l'interruption est autorisée et effectivement si l'interruption est autorisée après on va aoir une it immédiatement. De même si on est à 2 cycles horloge de la valeur de comparaison, on aura l'it dans 2 cycles d'horloge.

Je répondais simplement à la question qui liait le fait d'autoriser l'interruption et d'avoir une it immédiatement.

L'explication de troisiemetype est pertinente, il y a peut être des rebonds au relâchement des boutons.

TIMSK1 &= ~(1 << OCIE1A); // couper le timer

ceci ceci efface le flag de compare match mais ne coupe aucunement le timer, qui ne tardera pas à le repositionner, etc.

TCNT1 = 0; // histoire d'etre sur !

avoir judicieusement configuré le timer en CTC permet effectivement de se passe de cette instruction

TIMSK1 |= (1 << OCIE1A); // demarrer timer1

pour ceux qui ont suivi, à l'évidence ceci ne démarre pas le timer

Bonjour

Pour ma part, je suis un peu dubitatif sur le choix de coder une solution qui nécessite de manipuler directement les registres et interruptions. C'est quand même nettement plus complexe à maîtriser que des lignes de code de plus haut niveau d'encapsulation.

Certes, la manipulation directe des registres est toujours plus performante, mais est-ce que ce niveau de performance est vraiment requis pour couvrir le besoin ?

J'espère par exemple que tu n'es pas en train de te prendre la tête avec une interruption du timer, juste pour gérer un debounce de bouton poussoir.

Bonjour,

Merci pour ces nombreuses interventions.

Troisiemetype, il y a un débounce assuré par un petit condensateur. Par ailleurs, je peux confirmer qu'il ne s'agit pas d'un problème de délais entre le HIGH/LOW sur le bouton, le problème étant le même que je mette 300ms ou 2 secondes.
Les lignes inutiles sont souvent présentes pour ne pas avoir à les retaper quand je veux changer un bit lorsque j'ajuste le timing en fonction des tests. Elles disparaîtront de la version finale.
Les cli(), sei() et autre TCNT1 = 0 sont là pour "être sur" que le problème n'est pas là.

Pour le choix de la manipulation de registre plutôt que de fonctions haut niveau, c'est une préférence rapport à ce que je code d'habitude. Mes 1er projets arduino était un générateur de signal PPM et une incrustation vidéo qui était ULTRA CRITIQUE au niveau du timing, puis j'ai gardé cette habitude. Pour ce code, si c'est au final un genre de débounce.

Sinon, apres une longue soirée de galère, j'ai fini par trouver un truc qui marche (sur la tête)... J'ai trouvé ça lors d'un test complètement désespéré, et je sais pas pourquoi ça marche :

j'ai mis TCNT1 = OCR1A; dans le TIMER1_COMPA_vect, et j'ai mis TCNT1 = OCR1A/2; dans les interruptions... Voyant que ça marche, j'ai juste augmenté OCR1A et j'ai le résultat attendu... Mais vraiment, je comprend pas pourquoi...

Ok je comprends le contexte, mais ne peut m'empêcher de penser que c'est dommage de ne pas privilégier un coding davantage dans le standard proposé par arduino.
Un truc du style :

void loop()
{
  gererBouton1();
  gererBouton2();
}

void gererBouton1()
{
  static byte etat = HIGH;
  static unsigned long ref_millis = 0;

  if (digitalread(bouton1) != etat && millis() - ref_millis > 300) //changement d'état avec debounce à 300 ms
  {
    ref_millis = millis();              //prise d'une nouvelle référence temporelle pour la gestion du debounce
    etat = (etat == HIGH) ? LOW : HIGH; //inversion de la variable état
    if (etat == LOW) action1();         //action déclenchée sur appui bouton
  }
}

etc.

Ok pour le condensateur, en effet ça règle le problème des rebonds. Mais je crois que tel que le code est présenté, les interruptions INT0 et INT1 doivent quand même se déclencher au relâchement du bouton, non? Bon, ok, ce n'est pas la question. :slight_smile:

Comme l'a dit quelque avant, une fois la comparaison désactivée (OCIE1A = 0), le timer continue à tourner, même s'il ne déclenche plus rien. Il faudrait donc, ou bien l'arrêter (TCCR1B = 0) et le remettre en marche (rappeler la fonction d'initialisation?) à chaque ISR (TIMER1_COMPA_vect et INTx_vect repectivement), mais ça me semble plutôt fastidieux, ou bien le remettre à zéro (TCNT1 = 0) dans l'interruption INTx plutôt que dans l'interruption COMPA.

De même, je ne sais pas à quoi correspond la fonction debug(), mais je rappelle à toute fin utile qu'il est fortement déconseillé d'utiliser Serial dans une fonction d'interruption.

Bricoleau, je pense que la manière de coder dépend de chacun. Il peut en effet arriver (comme ici, peut-être, encore que l'on ne connaisse pas le reste du projet) que l'utilisation des fonctions du micro-controlleur soient sur-dimensionnées, mais certaines des fonctions et librairies d'arduino sont bloquantes, simplement lourdes, voir s'excluent mutuellement. Du coup, quand sur un projet ou deux on a pris le plis de coder d'une manière, il est dur de revenir à plus simple. D'autant qu'il ne faut pas forcément un projet lourd pour que les problèmes arrivent, parfois il est nécessaire de faire des optimisations même si l'on n'utilise que deux broches...

INT0 et INT1 doivent quand même se déclencher au relâchement du bouton

Oui, en vérité, les boutons en questions sont dans un encodeur rotatif, j'ai donc besoin de détecter les 2 changements d'état. Je souhaitais éviter de préciser ce détail pour ne pas que la conversation dérive sur les encodeurs rotatifs ou sur le fait que ce code ne gère pas les encodeurs rotatifs, ce qui est un autre problème sujet.

Pour le Debug(), c'est pas du serial, mais ça m'affiche la valeur de la variable test sur un écran (piloté par ISP, je sais, c'est pas bien non plus). Comme son nom l'indique, le but de cette fonction est simplement de comprendre le comportement de mon programme, et en l'occurrence, c'est ce qui me permet de diagnostiquer que l'interruption du compteur est appelée 2 fois. C'est également quelque chose qui restera pas là sur la version finie, bien entendu.

Du reste, la présence de TCNT1 = 0 et même TCNT1 = 1 dans les INTx à bien été testée sans plus de résultat (ce qui rend la solution que j'ai trouvé encore plus incompréhensible)...

En tout cas, merci de chercher à m'aider.

troisiemetype:
Ok pour le condensateur, en effet ça règle le problème des rebonds

Ca reste a voir. Bien souvent un condensateur fait plus de mal que de bien (surtout si on le met directement aux bornes du switch)

Ca reste a voir. Bien souvent un condensateur fait plus de mal que de bien (surtout si on le met directement aux bornes du switch)

Grrrrrrrrrrrrr

troisiemetype:
Bricoleau, je pense que la manière de coder dépend de chacun. Il peut en effet arriver (comme ici, peut-être, encore que l'on ne connaisse pas le reste du projet) que l'utilisation des fonctions du micro-controlleur soient sur-dimensionnées, mais certaines des fonctions et librairies d'arduino sont bloquantes, simplement lourdes, voir s'excluent mutuellement. Du coup, quand sur un projet ou deux on a pris le plis de coder d'une manière, il est dur de revenir à plus simple. D'autant qu'il ne faut pas forcément un projet lourd pour que les problèmes arrivent, parfois il est nécessaire de faire des optimisations même si l'on n'utilise que deux broches...

Oui bien sûr, je suis d'accord, jusqu'à un certain point...
Là on parle quand même seulement de gérer les appuis sur deux boutons poussoirs avec un debounce logiciel.
Ca peut aussi être codé en assembleur :smiling_imp:

bricoleau:
Ca peut aussi être codé en assembleur :smiling_imp:

C'est juste. :smiley: