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();
}
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.
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.
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.
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"
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.
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 ?
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.
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)...
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