Léger retard après le retour d’une interruption

J’ai écrit un petit programme qui utilise un bouton sur une carte STM32 Discovery pour agir en tant que compteur en mode binary / décimal / hexadécimal (l’écran parcourt les 3 options et, une fois enfoncé, compte jusqu’à 16 à chaque pression avant de revenir à l’état initial. parcourir les options).

Je rencontre un petit “bug” (lu, pas vraiment) qui m’a un peu confus. Si je compte en décimal / hexadécimal, il revient à parcourir les options immédiatement, mais si j’ai compté en binary, il faut environ 1 seconde avant de le faire (délai notable).

int main(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); lcd_init(); button_init(); while (1) { while (!counting) { standard_output(); } } } void standard_output(void) { state = 0; lcd_command(0x01); delay_microsec(2000); lcd_putssortingng("Binary"); for (i=0; i<40; i++) delay_microsec(50000); // keep display for 2 secs if (counting) return; // if we have pressed the button, want to exit this loop state = 1; lcd_command(0x01); delay_microsec(2000); lcd_putstring("Decimal"); for (i=0; i<40; i++) delay_microsec(50000); // keep display for 2 secs if (counting) return; // if we have pressed the button, want to exit this loop state = 2; lcd_command(0x01); delay_microsec(2000); lcd_putstring("Hexadecimal"); for (i=0; i<40; i++) delay_microsec(50000); // keep display for 2 secs if (counting) return; // if we have pressed the button, want to exit this loop } void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { if (!stillBouncing) { // a button press is only registered if stillBouncing == 0 if (!counting) { // if we weren't already counting, a valid button press means we are now counting = 1; count = 0; // starting count from 0 } else { count++; } if (count = 16 } } stillBouncing = 10; // every time a button press is registered, we set this to 10 while (stillBouncing > 0) { // and check that it hasn't been pressed for 10 consecutive 1000microsec intervals if (!delay_millisec_or_user_pushed(1000)) { stillBouncing--; } } } EXTI_ClearITPendingBit(EXTI_Line0); } void format_int(unsigned int n) { if (state == 0) { // if we selected binary for (i=0;i> i) & 1; // generate array of bit values for the 4 least significant bits } i = 4; while (i>0) { i--; lcd_putint(num[i]); // put ints from array to lcd in reverse order to display correctly } } else if (state == 1) { // if we selected decimal lcd_putint(n); // lcd_putint is enough for decimal } else { // if we selected hex snprintf(hex, 4, "%x", n); // format ssortingng such that integer is represented as hex in ssortingng lcd_putssortingng(hex); // put ssortingng to lcd } } int delay_millisec_or_user_pushed(unsigned int n) { delay_microsec(n); if (!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)) { return 0; } return 1; } 

Je n’ai vraiment aucune idée de la raison pour laquelle il fait cela et je joue avec, mais je suis toujours incapable de comprendre. C’est bon en l’état, mais j’aimerais savoir pourquoi il le fait.

lcd_putint prend probablement beaucoup de temps pour actualiser l’affichage. Il convertit probablement chaque nombre en chaîne, puis le place à l’écran. format_int() En cas binary, il boucle 4 fois, puis 4 fois plus que les cas Hex et Dec.

Si vous modifiez le code comme ci-dessous, je suppose que ce sera plus rapide:

 char bin[5]; sprintf(bin, "%d%d%d%d", ((n&0x08)>>3), ((n&0x04)>>2), ((n&0x02)>>1), (n&0x01)); lcd_putssortingng(bin); 

Je sais qu’il y a beaucoup de solutions pour convertir un nombre en chaîne binary, mais le point clé est d’utiliser lcd_putssortingng qui est sûrement plus rapide que d’appeler 4 fois lcd_putint

Tout d’abord, vous devez vous assurer que la broche d’entrée connectée au bouton possède des résistances de traction, soit sur le circuit imprimé, soit activées de manière interne dans le port d’E / S du microcontrôleur. Si vous ne l’avez pas déjà fait, l’entrée ne sera pas définie lorsque le bouton sera inactif et vous obtiendrez des interruptions parasites. Les résistances doivent tirer vers l’état inactif.

Comme indiqué dans les commentaires, vous ne devriez jamais avoir de retard dans les routines de service d’interruption. Les ISR doivent être aussi petits et rapides que possible.

Il est important de noter que si une broche déclenchant une interruption est connectée à un bouton, cela signifie que vous obtiendrez des interruptions pour chaque rebond ou autre bruit EMI apparaissant sur la broche. Ces fausses interruptions parasites bloqueront le programme principal et la performance globale en temps réel en souffrira. C’est une erreur classique du débutant et elle est présente dans votre programme.

Vous pouvez utiliser une broche qui déclenche une interruption pour les boutons, mais vous devez alors savoir ce que vous faites. Vous devez désactiver l’interruption elle-même depuis l’intérieur de l’ISR dès que vous obtenez le premier déclencheur de bord, de la manière suivante:

  • Assurez-vous que l’interruption du bouton est réglée pour déclencher à la fois le front montant et le bord descendant.
  • Dès réception de l’interruption, désactivez l’interruption depuis l’intérieur de l’ISR. Depuis l’ISR, démarrez l’un des temporisateurs matériels sur puce et faites-le se déclencher par une interruption du temporisateur au bout de x millisecondes.

    Pseudo-code pour un tel ISR pour un MCU générique et fictif, avec des noms de registre fictifs:

     void button_isr (void) { BUTTON_ISR_ENABLE = CLEAR; // some hw register that disables the ISR BUTTON_ISR_FLAG = CLEAR; // some hw register that clears the interrupt flag TIMER_COUNTER = CURRENT_TIME + DEBOUNCE_TIME; // some hw timer register TIMER_ISR_ENABLE = SET; // some hw timer register } 
  • Les temps de rebond typiques sont compris entre 5 et 20 ms. Vous pouvez mesurer le rebond sur le commutateur particulier avec un oscilloscope.

  • Lorsque le temps imparti est écoulé, son ISR est déclenché et vous relisez l’entrée. S’ils se lisent égaux (tous les deux élevés), définissez un indicateur “bouton enfoncé”. Sinon, vous avez eu du bruit sur la ligne, ce qui devrait être ignoré. Désactivez la timer, mais activez à nouveau la touche I / O du bouton.

    Pseudo-code pour le temporisateur ISR, pour un MCU générique et fictif:

     static bool button_pressed = false; void timer_isr (void) { TIMER_ISR_FLAG = CLEAR; TIMER_ISR_ENABLE = CLEAR; if(BUTTON_PIN == ACTIVE) // whatever you have here, active high/active low { button_pressed = true; } else { button_pressed = false; } BUTTON_ISR_ENABLE = SET; } 

Dans le code réel, les noms de registre seront quelque chose de plus crypté, et la façon de définir / effacer les indicateurs variera d’une MCU à la MCU. Parfois, vous effacez en écrivant 1, parfois par 0.

Le code ci-dessus devrait bien fonctionner pour les applications standard. Pour les applications nécessitant des temps réels plus ssortingcts, une timer / tâche fonctionnant en continu, interroge le (s) bouton (s) à des intervalles de temps égaux. Exigez que les deux lectures suivantes donnent la même valeur enfoncée / non enfoncée, afin de l’accepter comme une modification de l’état du bouton.

Des algorithmes plus avancés impliquent des filtres médians qui effectuent plusieurs lectures. Un filtre médian à 3 lectures est assez facile à mettre en œuvre et suffit même pour de nombreuses applications critiques pour la sécurité.