souci avec attachInterrupt()

Bonjour.

Pour prendre en compte l'appui sur un bouton, j'ai voulu faire un anti-rebond en soft. La routine d'interruption "détache" l'interruption et met une variable à 1. C'est tout.
Dans la loop, quand la variable est à 1, il y a un délai puis l'interruption est "rattachée".

J'ai constaté que lors de ce rattachement, un nouvel appui fictif sur le bouton est compté en l'absence de tout appui réel.

Est-ce un bug connu ? peut-on l'éviter ?

merci d'avance

Salut,

c'est normal, et ça n'arrive peut-être pas toujours. c'est dû au rebond. Il y a un registre (une variable permanente dans le µc) nommé EIFR qui enregistre les évennements des interruptions, même s'il n'y a pas d'interruption déclarée. S'il enregistre une interruption après un detachInterrupt(), alors quand tu feras un attachInterrupt(), l'interruption ser exécutée puisque la variable indique qu'il y a eu un évennement.

Pour palier à celà, tu as deux solutions. Soit intervenir dans le fichier WInterrupts.c du core arduino et modifier la fonction attachInterrup(), mais il faut vérifier que ça puisse marcher avec TOUS les avr (ils n'ont pas tous la même définition de registres). L'autre solution plus simple est de rajouter une ligne dans ton code juste avant attachInterrupt() :

if (EIFR & (1 << Num_int)) EIFR |= 1 << Num_int;       // désactive le dernier enregistrement d'interruption si besoin
attachInterrupt(Num_int, ma_fonction, mon_mode);

en gros, si EIFR indique qu'il a enregistré une interruption, on efface l'enregistrement correspondant. (*) Ca peut parraître zarbi, mais dans les registres flags, on efface un bit en écrivant un 1 dedans... cherchez pas, c'est comme ça :wink:

Attention cependant, car ça ne marchera pas forcément pour leonardo, et peut-être même la MEGA, je me souviens avoir eu un souci avec les numéros d'int qui étaient inversés dans le core (bravo le team). C'est pour ça qu'il est plus efficace de rajouter cette ligne dans le core directement, mais en lisant le datasheet de chaque proc concerné en même temps (page 73 pour les ATMEGA48-328 par exemple) pour ne pas aller modifier n'importequoi..

(*) : d'ailleurs, je ne suis même pas sûr et tout dépend du compilateur, car si EIFR = b00000011 par exemple (il a enregistré les deux int 0 et 1) et que le compilateur fait

R24 = EIFR;   // donc R24 = 3
R24 |= 1;    // pour effacer l'int 0, R24 = 3
EIFR = R24;   // On écrit un 1 dans INT0 et INT1, donc on efface les deux au lieu de seulement INT0

il faut peut-être utiliser SBI

 if(SBIS(EIFR, Num_int)) SBI(EIFR, Num_int);  // mais je ne sais pas si ces instructions sont directement accessibles... sinon, le faire en ASM

et là, je m'en remets à ceux qui ont déjà essayé... :wink:

Merci,

Je regarderai ça demain. Aujourd'hui je manie un marteau-piqueur de location. C'est beaucoup plus lourd qu'une platine Arduino. J'étais juste venu lire le forum pour souffler un moment.

Ma carte est une mega 2560 mais à terme ce sera sans doute une PRO MINI. (j'en ai commandées)

Et utiliser une librairie toute faite ça te branche pas: Arduino Playground - HomePage

Tout dépend où tu mets tes priorités :

  1. comprendre ce qui se passe dans l'idée que cela pourra te servir plus tard.
    ou
  2. tout simplement faire un anti-rebond.

Le premier cas est très instructif, a titre personnel bien que j'ai lu la datasheet je n'avais fait attention au role du registre EIFR (Merci Super Cinci)..

Le lien indiqué par fdufnews est aussi intéressant pour la culture générale mais la méthode la plus simple pour faire un antirebond n'est pas logicielle mais matérielle. Le plus efficace c'est de placer un condensateur en parrallele sur le contact, 100 nF suffisent généralement .
Pour info suite à l'annonce de la TRE j'ai jeté un oeil sur les cartes BeagleBoard et j'ai pu voir que tous les contacts de leurs boutons poussoirs étaient équipés avec un condensateur en parrallele. Donc si des "bons" utilisent cette méthode c'est que c'est la bonne méthode.
C'est le plus efficace car l'effet néfaste du rebond est tué à la base, c'est ce qui donne le temps de réaction le faible et cela allège le programme.

68tjs:
C'est le plus efficace car l'effet néfaste du rebond est tué à la base, c'est ce qui donne le temps de réaction le faible et cela allège le programme.

exactement, car bien souvent, un "debounce" logiciel fait perdre du temps et dans le cas de plusieurs boutons on zappe l'appui sur un bouton de temps en temps.

donc mets une capa sur le contact, ce qui n'empêche pas que j'ai quand même répondu à ta question (souci attachInterrupt() ).

et ma réponse est vallable pour TOUTES les interruptions du processeur, sauf le RESET qui est tellement prioritaire qu'on ne se formalise pas dessus...

Super_Cinci:
L'autre solution plus simple est de rajouter une ligne dans ton code juste avant attachInterrupt() :

if (EIFR & (1 << Num_int)) EIFR |= 1 << Num_int;       // désactive le dernier enregistrement d'interruption si besoin

attachInterrupt(Num_int, ma_fonction, mon_mode);

En fait le test sur EIFR n'est pas réellement nécessaire, il ne fait qu'augmenter la taille du code et le nombre de cycles d'exécution sans apporter grand chose.
Je m'explique : EIFR est un registre du type « qui s'efface quand on y écrit » :

  • si un bit vaut 1 et qu'on y écrit 1 il passe à zéro
  • si un bit vaut 0 et qu'on y écrit 1 il ne se passe rien
  • si on écrit 0 dans un bit il ne se passe rien

Du coup il est plus « économique » de toujours écrire dans le registre sans chercher à tester si c'est nécessaire ou pas. Si Num_int est une constante la différence n'est pas énorme (2/3 cycles et 4 octets de prog, des pouillèmes quoi), en revanche si Num_int est une variable, la différence avec ou sans le « if » doit se monter à pas loin de 40 octets et au moins autant de cycles à mon avis.

Super_Cinci:
(*) : d'ailleurs, je ne suis même pas sûr et tout dépend du compilateur, car si EIFR = b00000011 par exemple (il a enregistré les deux int 0 et 1) et que le compilateur fait

R24 = EIFR;   // donc R24 = 3

R24 |= 1;    // pour effacer l'int 0, R24 = 3
EIFR = R24;  // On écrit un 1 dans INT0 et INT1, donc on efface les deux au lieu de seulement INT0



il faut peut-être utiliser SBI


if(SBIS(EIFR, Num_int)) SBI(EIFR, Num_int);  // mais je ne sais pas si ces instructions sont directement accessibles... sinon, le faire en ASM


et là, je m'en remets à ceux qui ont déjà essayé... ;)

Exact, et c'est pour cette raison que les registres tels que EIFR se manipulent en général avec « = » plutôt que « |= », ce qui évite à tous les coups le problème que tu décrit. À noter également que l'instruction SBI ne fonctionne que si un seul bit est à modifier, ce qui est probablement le cas dans ce cas particulier, mais pas dans le cas général, notamment si Num_int est une variable.

Donc, pour conclure, le code pour acquitter l'interruption devient donc « tout simplement » :

EIFR = (1 << Num_int);

haifger:

  • si on écrit 0 dans un bit il ne se passe rien

c'est justement ce comportement que je n'ai pas trouvé dans la dadachitte. si j'avais su ça, alors oui, je ne me serais pas posé de questions!!! merci à toi :wink:

Super_Cinci:

haifger:

  • si on écrit 0 dans un bit il ne se passe rien

c'est justement ce comportement que je n'ai pas trouvé dans la dadachitte. si j'avais su ça, alors oui, je ne me serais pas posé de questions!!! merci à toi :wink:

Si tu fait de l'ARM un jour garde cette astuce en tête c'est un piège classique :wink:
"écrire "1" pour effacer un bit (= écrire "0")" ça semble illogique mais en réalité ça évite de faire des tests, donc du code, et en interruption où il faut être rapide ça fait la différence.

Je dirais que c'est un énorme point positif chez ATMEL dans leurs docs (AVR, car les ARM, on dirait qu'il y a économie), pour chaque registre, ils recopie les phrases de fonctionnement, tout comme les registres 16 bits où il y a un ordre précis à respecter quand on lit les deux octets, c'est rappelé à chaque registre.

Mais quand on te dit simplement "peut être effacé en écrivant un 1", ça ne dit pas ce qu'il se passe si on écrit un 1 sur un 0, est-ce que le 0 reste 0 ou devient 1, déclenchant alors l'int correspondante... au niveau des timers, il y a les bits FOCnX qui permettent de forcer une intéruption, mais il n'est pas dit comment ça marche dans les autres...

me voilà donc avec une pensée de plus, j'ai dormi moins con cette nuit!

Pour les doc ARM c'est pas qu'il y a économie (loin de là même), c'est juste que la doc complète (le "reference manual") de +1200 pages est dispo séparément du datasheet constructeur (architecture commune).

skywodd:

Super_Cinci:

haifger:

  • si on écrit 0 dans un bit il ne se passe rien

c'est justement ce comportement que je n'ai pas trouvé dans la dadachitte. si j'avais su ça, alors oui, je ne me serais pas posé de questions!!! merci à toi :wink:

Si tu fait de l'ARM un jour garde cette astuce en tête c'est un piège classique :wink:
"écrire "1" pour effacer un bit (= écrire "0")" ça semble illogique mais en réalité ça évite de faire des tests, donc du code, et en interruption où il faut être rapide ça fait la différence.

Dans la même idée, je viens de découvrir (j'apprends tous les jours!) que l'on peut faire un toggle sur une pin de sortie simplement en écrivant un 1 dans le bit correspondant du registre d'entrée (PINx), et là, c'est très fort! (pour faire des pulse de validation par exemple). Je vais finir par croire que les AVR sont de vraies bêtes de course!

Super_Cinci:
Dans la même idée, je viens de découvrir (j'apprends tous les jours!) que l'on peut faire un toggle sur une pin de sortie simplement en écrivant un 1 dans le bit correspondant du registre d'entrée (PINx), et là, c'est très fort! (pour faire des pulse de validation par exemple). Je vais finir par croire que les AVR sont de vraies bêtes de course!

T'as de la doc là dessus j'en avais jamais entendu parler :astonished:

page 76 du datasheet 328P, Troisième paragraphe, seconde et troisième phrases, (ça fait un peu bible, mais en même temps... XD )

The Port Input Pins I/O location (i.e. PINx) is read only, while the Data Register (PORTx) and the Data Direction Register (DDRx) are read/write. However, writing a logical one to a bit in the PINx Register, will result in a toggle in the corresponding bit in the Data Register. In addition, the Pull-up Disable - PUD bit in MCUCR disables the pull-up function faor all pins in all ports when set.

la troisième phrase est bonne à garder dans un coin, si un jour les pull-ups ne marchent plus...

skywodd:

Super_Cinci:
Dans la même idée, je viens de découvrir (j'apprends tous les jours!) que l'on peut faire un toggle sur une pin de sortie simplement en écrivant un 1 dans le bit correspondant du registre d'entrée (PINx), et là, c'est très fort! (pour faire des pulse de validation par exemple). Je vais finir par croire que les AVR sont de vraies bêtes de course!

T'as de la doc là dessus j'en avais jamais entendu parler :astonished:

Haaaannnnn mais c'est LA chose à savoir lorsque l'on fait de la manipulation de ports sur avr :wink:

Super_Cinci:
page 76 du datasheet 328P, Troisième paragraphe, seconde et troisième phrases, (ça fait un peu bible, mais en même temps... XD )
(...)
la troisième phrase est bonne à garder dans un coin, si un jour les pull-ups ne marchent plus...

Moi c'est page 79, paragraphe 14.2.2 :grin:
Nan mais voila qu'ils changent les numéros de verset dans la bible, sont fou ces ATMELiens !

14.2.2 Toggling the Pin
Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn.
Note that the SBI instruction can be used to toggle one single bit in a port.

C'est encore plus subtile qu'un simple toggle, si DDRxn est en sortie ça change d'état la broche et si c'est en entrée ça active/désactive les pull-up ... humm c'est pas mal pour faire du 1-Wire ça !
Je prend note, j'avais pourtant lu ma bible plusieurs fois, faudra que je recommence :grin:

haifger:
Haaaannnnn mais c'est LA chose à savoir lorsque l'on fait de la manipulation de ports sur avr :wink:

Bof pas vraiment, je fait des XOR (instruction EOR en assembleur AVR, me demandez pas pourquoi E-OR et non X-OR, bref) pour manipuler les bits d'un port perso.
Ça prend 1 cycle cpu alors que SBI prend 2 cycles, donc CQFD mon ignorance était volontaire :grin:

skywodd:
Moi c'est page 79, paragraphe 14.2.2 :grin:
Nan mais voila qu'ils changent les numéros de verset dans la bible, sont fou ces ATMELiens !

14.2.2 Toggling the Pin

ouais, on a pas la même religion... j'ai aussi exactement le même texte, mais page 78, verset 13.2.2

Ma version est la Rev. 8271C - 08/10... ils auraient découvert d'autres trucs depuis sur le x8?

skywodd:
Bof pas vraiment, je fait des XOR (instruction EOR en assembleur AVR, me demandez pas pourquoi E-OR et non X-OR, bref) pour manipuler les bits d'un port perso.
Ça prend 1 cycle cpu alors que SBI prend 2 cycles, donc CQFD mon ignorance était volontaire :grin:

Euh, oui mais nan, tu ne peux pas faire un EOR tout seul, ou alors y'a un truc que je ne comprend pas. Tu dois forcément avoir un combo IN+EOR+OUT, soit 3 cycles et 6 octets, avec même très probablement un LDI quelque part avant qui ajoute encore 1 cycle.

À l'inverse, le « pin toggling », comme pour le cas de EIFR discuté plus haut se manipule avec « = » et pas « |= », ce qui fait que

PINB = _BV(PINB5)

est traduit par un LDI+OUT => 2 cycle et 4 octets : pwned ! (ou alors tu as une astuce que je ne connais pas et je veux bien que tu m'explique, ça m'intéresse).

Après, il faut bien avouer qu'on tombe là dans la femto-optimisation et que les cas où c'est absolument nécessaire ne sont pas si nombreux. Sans compter qu'on va probablement perdre l'interrogateur originel, si ce n'est déjà fait :wink:

plus fort encore : PINB = 0x20;

Super_Cinci:
plus fort encore : PINB = 0x20;

Euh j'ai pas compris :slight_smile: Plus fort que quoi ?