Contacts

Minuteries Arduino mega 2560. AVR. Formation. Minuteries. Minuteries sur Arduino

Récemment, de plus en plus de débutants sont confrontés au problème de la maîtrise des minuteries/compteurs (ci-après dénommés T/C) au stade de l'étude des microcontrôleurs. Dans cet article je vais essayer de dissiper les craintes concernant ces modules et expliquer clairement comment et avec quoi ces mêmes T/S sont utilisés.

Nous prendrons comme base un livre très populaire parmi les développeurs d'appareils MK, rédigé par A.V. Evstifeev. En utilisant les liens à la fin de l'article, vous pouvez trouver le projet dans et le projet dans. Dans cet article, nous analyserons le fonctionnement du T/S T2 8 bits, qui fait partie de l'Atmega8 T/S MK.

Alors, qu’est-ce qu’un minuteur/compteur ? T/S est l'un des modules de l'AVR MK avec lequel vous pouvez mesurer certaines périodes de temps, organiser le PWM et bien d'autres tâches. Selon le modèle MK, le nombre de T/S peut être de 4 ou plus. Un exemple de ceci est les Atmega640x, 1280x/1281x, 2560x/2561x MK, qui contiennent 6 T/S à bord : deux 8 bits et quatre 16 bits. L'Atmega8 MK contient trois T/S : T0 et T2 avec 8 bits, T1 avec 16 bits.

Regardons de plus près le microcontrôleur T/C T2 Atmega8.

Ce timer peut fonctionner selon plusieurs modes : Normal, PWM correct de phase, CTC (réinitialisation sur coïncidence), PWM rapide. Vous pouvez en savoir plus sur chaque mode dans le livre.

Ce T/C se compose d'un registre de contrôle, d'un registre de comptage, d'un registre de comparaison et d'un registre d'état en mode asynchrone. Le schéma fonctionnel de T2 est présenté sur la figure 1.

Voyons comment ce module fonctionne en théorie. Pour que ce soit plus clair pour vous, nous ne considérerons pas tous les gadgets inutiles de la minuterie et considérerons son mode le plus courant - NORMAL. Déterminons par nous-mêmes que le MK est cadencé à partir d'un oscillateur RC interne avec une fréquence de 1 MHz et que la minuterie est configurée pour fonctionner en mode NORMAL.

Les impulsions d'horloge arrivent à l'entrée clk i\o et entrent dans le pré-échelonneur de minuterie. Le prescaler peut être configuré, selon vos besoins, pour transmettre directement les impulsions d'horloge ou pour diviser les impulsions entrantes, en n'en transmettant qu'une certaine partie. Les impulsions entrantes peuvent être divisées en /8, /64, /256, /1024. Étant donné que notre T\S peut fonctionner en mode asynchrone, lorsqu'il est allumé dans ce mode, le nombre de prescalers augmente considérablement, mais nous ne les considérerons pas pour l'instant. Depuis le pré-échelonneur, les impulsions d'horloge entrent dans l'unité de contrôle et de celle-ci entrent dans le registre de comptage. Le registre de comptage, à son tour, incrémente pour chaque impulsion entrante. Le registre de comptage T2 est de 8 bits, il ne peut donc compter que jusqu'à 255. Lorsque le registre de comptage déborde, il est remis à 0 et recommence à compter dans le même cycle. De plus, lorsque le registre du compteur déborde, l'indicateur TOV2 (indicateur d'interruption de débordement) du registre TIFR est activé.

Maintenant que nous avons abordé des mots tels que REGISTER, il est temps de les connaître. Pour commencer, nous n'aborderons que les registres avec lesquels nous travaillerons directement, afin de ne pas remplir le cerveau d'informations inutiles.

TCNT2 est un registre de comptage, nous avons déjà parlé de son fonctionnement.

TCCR2 - registre de contrôle de minuterie.

TIMSK - registre de masque d'interruption (dans Atmega8, ce registre est le seul pour tous les temporisateurs).

TIFR - registre des drapeaux d'interruption (dans Atmega8, ce registre est le seul pour tous les timers).

Et maintenant sur chacun en détail :

Registre de contrôle TCCR2. Vous pouvez voir le contenu de ce registre sur la figure 2.


Figure 2

Les bits 0 à 2 sont responsables de la synchronisation de la minuterie. La définition de certaines combinaisons de ces bits configure le pré-échelonneur pour une minuterie donnée. Si les trois bits sont effacés, la minuterie est désactivée.

Les bits 3.6 sont responsables du mode de fonctionnement du temporisateur.

Les bits 4.5 sont nécessaires pour configurer le comportement de la sortie OSn (en d'autres termes, ils sont utilisés lors de la configuration du PWM)

Et le dernier bit de ce registre est le bit 7. Avec son aide, nous pouvons changer de force l'état de la broche OSn.

Registre de masque d'interruption - TIMSK. On le voit sur la photo n°3

A partir de ce registre nous ne nous intéressons qu'aux deux derniers bits, les bits 6 et 7. Avec ces bits nous activons les interruptions.

Le bit 6, s'il y est écrit, permet l'interruption due à l'événement "T\C T2 overflow"

Bit 7, si vous y écrivezNitsya, permet l'interruption sur l'événement "Coïncidence du registre de comptage avec le registre de comparaison"

Registre des indicateurs d'interruption TIFR. On le voit sur la photo n°4

Figure 4

Dans ce registre on s'intéresse également aux deux derniers bits : les bits 6 et 7.

Bit 6 - indicateur, activé par l'événement "Débordement T\C T2"
Bit 7 - drapeau, est installé par l'événement "Coïncidence du registre de comptage avec le registre de comparaison"

Ces bits sont réinitialisés automatiquement lorsque le gestionnaire d'interruption se termine, mais pour plus de sécurité, vous pouvez les réinitialiser vous-même en réinitialisant ces bits à "0".

Les bits restants des registres TIMSK et TIFR sont utilisés par T\C T0 et T1. Comme vous l'avez déjà remarqué, les bits de ces registres portent même les mêmes noms, à l'exception du numéro à la fin du nom, qui indique à quel timer ce bit s'applique.

Il reste à considérer deux tableaux simples, à savoir : un tableau qui décrit le contrôle du signal d'horloge (Fig. 6), et un tableau qui décrit comment configurer généralement une minuterie (Fig. 5).

J'ai écrit ci-dessus sur le contenu de ces tableaux, mais je vous les apporte pour plus de clarté.

Nous en avons maintenant terminé avec la théorie et il est temps de commencer la partie pratique. Je vais faire une réservation tout de suite.

LES HORLOGES QUE VOUS OBTENEZ EN ÉTUDIANT CET ARTICLE NE SONT PAS TRÈS PRÉCISES. CET ARTICLE EST ORIENTÉ SUR LES PRINCIPES GÉNÉRAUX DU TRAVAIL AVEC DES MINUTERIES.

Ouvrez Studio 6, créez un projet et sélectionnez Atmega8.

Au tout début, nous indiquons la fréquence d'horloge et connectons les bibliothèques dont nous avons besoin pour travailler

< avr/io.h >#inclure< avr/interrupt.h >

Dans la première ligne, nous indiquons la fréquence. Ceci est nécessaire pour que le compilateur nous comprenne mieux si nous voulons soudainement utiliser les fonctions _delay_().

La deuxième ligne de code comprend une bibliothèque avec une description générale des registres de notre MK. Il attribue également des noms lisibles à tous les registres.

La troisième ligne comprend une bibliothèque pour travailler avec des vecteurs d'interruption.

TIMSK |= (1< < TOIE2); TCCR2 |= (1< < CS22)|(1< < CS20); SREG |= (1< < 7);

Ceci termine la configuration de notre minuterie. Examinons de plus près les trois dernières lignes de code.

Dans la première ligne, nous avons activé les interruptions pour l'événement "Dépassement du timer/compteur T2".

Et dans la troisième ligne, nous avons globalement activé les interruptions. On pourrait aussi écrire ainsi :

Asm("sei");

Il ne reste plus qu'à ajouter le gestionnaire d'interruption et le code de notre horloge temps réel.

ISR (TIMER2_OVF_vect) ( takt++; if (takt>=4)(sek++; takt=0x00;) if (sek>=60) (min++; sek=0x00;) if (min>=60) (heure++; min=0x00 ;) si (heure>=24) (heure=0x00); )

Il n'y a rien de compliqué ou de nouveau pour vous dans le code contenu dans le gestionnaire d'interruption. Faisons attention uniquement à la variable takt et au nombre magique "4". D'où vient ce chiffre ? Regardons ce point de plus près.

Nous savons que notre MK fonctionne à partir d'un oscillateur interne d'une fréquence de 1 MHz, le timer est cadencé avec un prescaler de \1024, notre timer peut compter jusqu'à 255. Connaissant ces paramètres, nous pouvons calculer combien de débordements il fera en 1 seconde

1 000 000 \ 1024 \ 256 = 3,814697.....

Eh bien, puisque nous apprenons à travailler avec des minuteries et que nous ne nous sommes pas fixés pour objectif d'obtenir une horloge ultra précise, nous arrondissons notre résultat et obtenons « 4 ». Ceux. en 1 seconde, la minuterie débordera environ 4 fois.

Pourquoi avons-nous divisé par 256 si le minuteur ne compte que jusqu'à 255 ? Parce que "0" est aussi un nombre. Je pense que tout est clair ici.

N'oubliez pas que toutes les variables doivent être déclarées comme globales.

Voici la liste complète du programme que nous avons obtenu.

#définir F_CPU 1000000UL #inclure< avr/io.h >#inclure< avr/interrupt.h >takt de caractère non signé = 0 ; caractère non signé sek = 0 ; caractère non signé min=0 ; heure de caractère non signé = 0 ; ISR (TIMER2_OVF_vect) ( takt++; if (takt>=4)(sek++; takt=0x00;) if (sek>=60) (min++; sek=0x00;) if (min>=60) (heure++; min=0x00 ;) if (heure>=24) (heure=0x00); ) int main(void) ( TIMSK |= (1< < TOIE2); TCCR2 |= (1< < CS22)|(1< < CS20); SREG |= (1< < 7); while(1) { } }

Mais qu’en est-il de la transmission des informations à l’utilisateur ? Et puis celui qui aime ça. Vous pouvez utiliser des indicateurs à sept segments, des affichages graphiques ou générateurs de caractères, etc.

Dans l'archive, vous trouverez un projet avec des informations d'affichage de Nokia5110, un projet dans Proteus 7 et tous les fichiers et bibliothèques nécessaires au travail.

Veuillez noter que la bibliothèque LCD_5110 permettant de travailler avec l'écran a été écrite par un participant au forum et fournie avec son autorisation.

Compte tenu de tout ce qui a été dit, écrivons un programme qui commute la LED. Dans ce cas, il le fera en fonction de l'événement de dépassement de temps et de compteur. Minuterie 1(notre vecteur est désigné : TIM1_OVF). Étant donné que le compteur est de 16 bits, un événement de débordement se produira toutes les 65 536ème impulsions de la fréquence d'entrée. Si nous définissons le facteur de division de la fréquence d'horloge à l'entrée Minuterie 1égal à 64, alors à une fréquence de générateur de 4 MHz on obtient environ 1 Hz : 4 000 000/64/65 536 = 0,953674 Hz.

Ce n’est pas exactement ce dont nous avons besoin, et en plus, la fréquence n’est pas exactement d’un hertz. Pour que la LED commute exactement une fois toutes les demi-secondes (c'est-à-dire que sa période était égale à une seconde), le programme devra être légèrement compliqué en chargeant à chaque fois une certaine valeur dans les registres de comptage, qui est calculée simplement : si la période d'une impulsion d'horloge de la minuterie est égale à 16 µs (fréquence 4 000 000/64), alors pour obtenir 500 000 microsecondes, il faut compter 31 250 impulsions de ce type. Puisque le compteur additionne et qu'une interruption se produit lorsque le nombre 65 536 est atteint, vous devez d'abord y charger le nombre requis 65 536 – 31 250 = 34 286.

Ce n'est pas la seule méthode, mais la plus universelle, adaptée à tous les minuteurs. D'ailleurs, c'est exactement ainsi que le décompte du temps est implémenté dans Arduino(cm. chapitre 21). Une autre façon consiste à utiliser une interruption lorsqu'un certain nombre chargé dans le registre de comparaison est atteint. UN ou DANS. Nous verrons comment cela se fait plus loin dans ce chapitre. Pour effectuer le passage du rouge au vert, nous devrons faire comme précédemment, c'est-à-dire que pour chaque événement de débordement, retourner deux bits dans le registre PortD .

Le programme complet ressemblera alors à ceci :

Je ne commenterai pas en détail chaque affirmation, car cela prendrait trop de place. Après avoir exécuté toutes les commandes d'installation initiales, MK entre en boucle, mais la boucle sans fin sera interrompue par l'apparition d'une interruption - ici tout est similaire au système d'exploitation Windows, qui représente également une boucle sans fin d'attente d'événements. Comme vous l'apprendrez dans les chapitres suivants, Arduino une telle boucle est l'un des composants principaux de tout programme, précisément parce que les interruptions n'y sont presque jamais utilisées. Dans une boucle sans fin, vous pouvez mettre une commande familière ici dormir, sans paramètres de mode de consommation d'énergie supplémentaires, cela permettra d'économiser environ 30 % d'énergie. Mais vous ne pourrez pas économiser davantage, car vous devrez arrêter le cœur du processeur et le minuteur cessera de fonctionner.

Notes en marge

Au fait, comment arrêter un minuteur en cours si nécessaire ? C'est très simple : si vous réinitialisez le registre TCCR1B (celui dans lequel le facteur de division de fréquence d'horloge est défini), le temporisateur s'arrêtera. Pour le réexécuter avec un coefficient de 1/64, vous devez réécrire la valeur 0b00000011 dans ce registre.

Veuillez noter que l'opérateur réti(fin du traitement de l'interruption) se produit deux fois lors du traitement d'une interruption de minuterie - il s'agit d'une technique tout à fait normale lors du branchement d'un sous-programme. Vous pouvez bien sûr marquer la dernière déclaration rétiétiquette, puis le texte de la procédure deviendrait impossible à distinguer de la première option, mais ce serait plus correct.

Veuillez également prêter attention au formulaire d'inscription température ldi, (1<< TOIE1) . Puisque le bit désigné comme TOIE1 dans le registre TIMSK est le numéro 7, cette entrée est équivalente à l'entrée ldi temp,0b10000000 - vous pouvez l'écrire de cette façon, de cette façon et de plusieurs manières différentes. Par exemple, pour démarrer une minuterie avec un coefficient de 1/64, il est nécessaire, comme le montre le texte du programme, de définir les deux bits les moins significatifs du registre TCCR1B. Ici, nous les installons dans temp. directement, mais comme ces bits sont appelés CS11 et CS10, vous pouvez l'écrire comme ceci :

température ldi, (1<< CS11) I (1 << CS10)

ou même comme ça :

température ldi, (3<< CS10)

Cette méthode d'enregistrement est décrite en détail dans la description de l'assembleur AVR sur le site Atmel .

Détails

Il y a un point subtil dans ce programme lié au chargement des registres de comptage des minuteries. Lors de la lecture et de l'écriture de registres du temporisateur 1 de 16 bits, leur contenu peut changer dans l'intervalle entre la lecture ou l'écriture de « moitiés » individuelles de 8 bits (après tout, par exemple, dans ce cas, le temporisateur continue de compter pendant que l'interruption est en cours de traitement. ). Par conséquent, les temporisateurs AVR 16 bits disposent d'un mécanisme spécial pour lire et écrire de tels registres. Lors de l'écriture, la valeur de l'octet de poids fort est chargée en premier, qui est automatiquement placée dans un certain registre tampon (inaccessible au programmeur). Ensuite, lorsqu'une commande est reçue pour écrire l'octet de poids faible, les deux valeurs sont combinées et l'écriture est effectuée simultanément dans les deux « moitiés » du registre 16 bits. Au contraire, lors de la lecture, l'octet de poids faible doit être lu en premier, la valeur de l'octet de poids fort est automatiquement fixée en le plaçant dans le même registre tampon, et la prochaine fois que l'octet de poids fort est lu, sa valeur en est récupérée. Ainsi, lors de la lecture d’une valeur, les deux octets correspondent au même instant.

Lorsque j'ai décidé de développer pour Arduino, j'ai rencontré plusieurs problèmes :
  • Sélection d'un modèle dans la liste des modèles disponibles
  • J'essaie de comprendre ce dont j'aurai besoin en plus de la plateforme elle-même
  • Installation et configuration de l'environnement de développement
  • Recherche et analyse de cas de tests
  • "Showdown" avec l'écran
  • "Showdown" avec le processeur

Pour résoudre ces problèmes, j'ai consulté et lu de nombreuses sources différentes, et dans cet article, je vais essayer de passer en revue les solutions que j'ai trouvées et les méthodes pour les trouver.

Sélection de plateforme

Avant de commencer à programmer un élément matériel, vous devez d’abord l’acheter. Et puis je suis tombé sur le premier problème : il s’est avéré qu’il y avait pas mal de *duins différents. Il existe une large gamme d'Arduino et à peu près la même large gamme de Freeduino et d'autres analogues. Il s’est avéré qu’il n’y a pas de grande différence quant à ce qu’il faut prendre exactement. Autrement dit, certains de ces appareils sont un peu plus rapides, d'autres un peu plus lents, certains sont moins chers, d'autres sont plus chers, mais les principes de fonctionnement de base sont pratiquement les mêmes. Les différences apparaissent presque uniquement lorsque vous travaillez avec des registres de processeur, puis j'expliquerai plus en détail comment éviter les problèmes si possible.

J'ai choisi la plateforme Arduino Leonardo comme la plus abordable et disponible à l'époque dans la boutique en ligne où j'ai tout commandé. Il diffère du reste de la gamme en ce qu'il n'a qu'un seul contrôleur installé à bord, qui est responsable à la fois du travail avec le port USB et de l'exécution des tâches mêmes que nous assignons à notre appareil. Cela a ses avantages et ses inconvénients, mais vous ne pourrez pas les rencontrer lors de l’étude initiale, alors oublions-les pour l’instant. Il s'est avéré qu'il se connecte à l'ordinateur via micro-USB, et non USB-B, comme la plupart des autres semblent l'être. Cela m'a quelque peu surpris, mais m'a aussi rendu heureux, car en tant que propriétaire d'un appareil Android moderne, je ne quitte jamais la maison sans ce câble.
Oui, presque tous les éléments matériels compatibles *Duino sont alimentés de plusieurs manières, notamment à partir du même câble par lequel ils sont programmés. De plus, presque toutes les cartes ont une LED située directement sur la carte contrôleur, ce qui vous permet de commencer à travailler avec l'appareil immédiatement après l'achat, même sans avoir rien d'autre entre les mains qu'un câble compatible.

Gamme de tâches

Je pense qu’avant de commencer à écrire quelque chose sur un matériel, il est intéressant de comprendre ce qui peut être implémenté dessus. Avec Arduino, vous pouvez implémenter presque tout. Systèmes d'automatisation, idées pour une « maison intelligente », contrôleurs pour contrôler quelque chose d'utile, « cerveaux » de robots... Les options sont nombreuses. Et une gamme assez large de modules d'extension, extrêmement simples à connecter à la carte contrôleur, aide beaucoup dans ce sens. Leur liste est assez longue et prometteuse, et ils sont recherchés sur Internet à l'aide du mot bouclier. De tous ces appareils, j'ai trouvé que le plus utile était un écran LCD avec un ensemble de boutons de base, sans lequel, à mon humble avis, réaliser tout type de projet de formation est totalement inintéressant. L’écran a été pris ici, il y a aussi une description de celui-ci, et aussi à partir de la page ci-dessus il y a des liens vers le site officiel du fabricant.

Formulation du problème

J'ai l'habitude, d'une manière ou d'une autre, que lorsque je mets la main sur un nouvel outil, je me fixe immédiatement une tâche moyennement complexe et absolument inutile, je la résous avec courage, puis je mets le code source de côté et je me lance ensuite dans quelque chose de vraiment complexe et utile. Maintenant, j'avais sous la main un écran qui ressemblait beaucoup à un composant d'une mine d'un film hollywoodien avec tous les boutons nécessaires, avec lequel je devais apprendre à travailler, et je voulais aussi vraiment maîtriser le travail avec des interruptions (sinon, qu'est-ce que c'est l'intérêt d'utiliser un contrôleur ?) Alors d'abord, ce qui m'est venu à l'esprit, c'était d'écrire une horloge. Et comme la taille de l’écran le permettait, il incluait également une date.

Premiers pas

Tous les composants achetés sont enfin arrivés et je les ai assemblés. Le connecteur de l'écran s'est connecté à la carte contrôleur comme un natif, la carte était connectée à l'ordinateur... Et puis cet article m'a beaucoup aidé. Je ne répéterai pas la même chose.

Texte masqué

La seule chose que je dirai, c'est qu'en me souvenant de ma jeunesse (ou plutôt du premier "projet", assemblé alors que j'étudiais la radioélectronique au Palais des Pionniers - un multivibrateur à deux LED), j'ai trouvé 2 LED et corrigé l'exemple donné dans l'article et j'ai commencé à les faire clignoter :).

"Deuxièmes étapes"

La prochaine question logique pour moi était « comment travailler avec un écran LCD ? » La page officielle de l'appareil m'a gentiment fourni des liens vers les archives, qui contenaient 2 bibliothèques avec de merveilleux exemples. Elle n’a tout simplement pas dit quoi faire à propos de tout cela. Il s'est avéré que le contenu doit simplement être décompressé dans le dossier bibliothèques de l'environnement de développement.

Après cela, vous pouvez ouvrir l'exemple GuessTheNumber.pde et le télécharger sur le tableau de la même manière que l'exemple avec la LED clignotante. Cependant, personnellement, après avoir flashé le firmware, mon écran est resté uniformément brillant et sans une seule lettre. Après une courte recherche du problème, il s'est avéré qu'il fallait simplement serrer le seul potentiomètre de la carte écran avec un tournevis afin de régler la valeur de contraste normale.

L'ensemble de commandes utilisé dans l'exemple est, en principe, suffisant pour un travail simple avec l'écran, mais si vous voulez quelque chose de plus, vous pouvez ouvrir le code source des bibliothèques LCDKeypad et LiquidCrystal et voir ce qu'il y a d'autre.

Architecture du programme

La tâche principale d'une montre est de compter le temps. Et ils doivent le faire précisément. Naturellement, sans utiliser le mécanisme d'interruption, personne ne peut garantir que le temps est calculé avec suffisamment de précision. Par conséquent, le calcul du temps devrait définitivement leur être laissé. Tout le reste peut être placé dans le corps du programme principal. Et nous avons beaucoup de ce « repos » - tout le travail avec l'interface. Il serait possible de procéder différemment, de créer une pile d'événements, créés notamment par le mécanisme de gestion des interruptions, et traités à l'intérieur de l'application principale, cela permettrait, par exemple, de mettre à jour l'écran pas plus d'une fois toutes les demi-secondes ( ou sur simple pression d'un bouton), mais j'ai calculé cela superflu pour une tâche aussi simple, puisqu'à part redessiner l'écran, le processeur n'a toujours rien à faire. Par conséquent, tout le temps libre, le programme relit l'état des boutons et redessine l'écran.
Problèmes avec cette approche
Changements d'écran périodiques
Je voulais vraiment faire clignoter des deux-points entre les heures, les minutes et les secondes, pour que, comme dans une montre classique, ils s'allument pendant une demi-seconde, mais pas le sol. Mais comme l'écran est redessiné tout le temps, il fallait en quelque sorte déterminer dans quelle moitié de seconde les dessiner et dans quelle moitié de seconde pas. Le moyen le plus simple était de faire 120 secondes par minute et de dessiner deux points toutes les secondes impaires.
Clignotant
Lorsque l'écran est constamment redessiné, un scintillement devient perceptible. Pour éviter que cela ne se produise, il est logique de ne pas effacer l'écran, mais de dessiner un nouveau texte sur l'ancien. Si le texte lui-même ne change pas, il n'y aura pas de scintillement sur l'écran. Ensuite, la fonction de redessinage du temps ressemblera à ceci :
Clavier LCD LCD ; void showTime())( lcd.home(); if (heure<10) lcd.print("0"); // Случай разной длины приходится обрабатывать руками lcd.print(hour,DEC); // английские буквы и цифры ОНО пишет само, русские буквы нужно определять программисту if (second %2) lcd.print(" "); else lcd.print(":"); // вот они где используются, мои 120 секунд в минуте if (minute<10) lcd.print("0"); lcd.print(minute,DEC); if (second %2) lcd.print(" "); else lcd.print(":"); if (second<20) lcd.print("0"); lcd.print(second / 2,DEC); lcd.print(" "); lcd.setCursor(0,1); // переходим в координаты x=0, y=1 то есть в начало второй строки lcd.print(" "); lcd.print(day,DEC); lcd.print(months); // месяцы мне было приятнее нумеровать от 1 до 12, а массив с названиями от 0 до 11 lcd.print(year,DEC); }
Travailler avec des boutons
La situation est similaire avec les boutons. Le bouton enfoncé est répertorié comme étant enfoncé lors de chaque exécution du programme, de sorte qu'une pression peut être traitée un nombre illimité de fois. Nous devons forcer le programme à attendre les « pompes » séparément. Commençons le programme principal comme ceci :
int lb=0 ; // la variable stocke l'ancienne valeur du bouton void loop())( // programme principal int cb,rb; // définit 2 variables, pour le bouton réellement enfoncé et pour celui que le programme considérera enfoncé cb=rb =lcd.button(); // au début nous pouvons supposer qu'il s'agit du même bouton if (rb!=KEYPAD_NONE) showval=1; // la variable indique que pendant que le bouton est enfoncé, ce qui est en cours de configuration ne doit pas clignoter if (cb!=lb) lb= cb; // si l'état du bouton a changé, mémoriser le nouveau, sinon cb=KEYPAD_NONE; // sinon on dit au programme que tous les boutons sont relâchés depuis longtemps .

Travailler avec la minuterie

En fait, tout travail avec une minuterie se compose de deux éléments importants :
  • Initialisation du mécanisme d'interruption de la minuterie dans un mode qui nous convient
  • En fait, la gestion des interruptions
Initialisation de la minuterie
Afin de commencer à recevoir les interruptions dont nous avons besoin, nous devons configurer le processeur pour qu'il commence à les générer. Pour ce faire, nous devons définir les registres dont nous avons besoin sur les valeurs requises. Quels registres et quelles valeurs doivent être définis, vous devez consulter... la fiche technique du processeur :(. Pour être honnête, j'espérais vraiment que ces informations pourraient être trouvées dans la documentation de l'Arduino lui-même, mais non, ce serait trop simple. De plus, pour différents processeurs, les numéros de bits de série peuvent différer. Et j'ai moi-même constaté qu'une tentative de réglage des bits conformément à la fiche technique sur un processeur voisin a conduit à des résultats désastreux. Mais néanmoins, tout n'est pas aussi triste qu'il y paraît, car ces bits ont aussi des noms, ils sont plus ou moins courants pour différents processeurs. Par conséquent, nous n'utiliserons pas de valeurs numériques, seulement des noms.

Pour commencer, rappelons que les microcontrôleurs AVR disposent de plusieurs timers. Null est utilisé pour calculer les valeurs delay() et des choses comme ça, nous ne l'utiliserons donc pas. En conséquence, nous utilisons le premier. Par conséquent, plus tard dans la désignation des registres, il y aura souvent un 1 ; pour configurer, par exemple, une deuxième minuterie, vous devez y mettre un 2.

Toutes les initialisations de minuterie doivent avoir lieu dans la routine setup(). Elle consiste à placer des valeurs dans 4 registres, TCCR1A, TCCR1B, TIMSK1, OCR1A. Les 2 premiers d'entre eux sont appelés « Registres de contrôle Timer-Counter 1 A et B ». Le troisième est le « Registre de masque d'interruption du minuteur/compteur 1 » et le dernier est le « Registre de comparaison du compteur 1 A ».

Les commandes de réglage des bits sont généralement utilisées comme suit (bien sûr, il existe de nombreuses options, mais celles-ci sont les plus souvent utilisées) :
MORSURE |= (1<< POSITION)
c'est-à-dire que nous poussons « 1 » sur le bit POSITION de droite à gauche et effectuons un « ou » logique entre la cible et les octets reçus. Lorsque le contrôleur est allumé, les valeurs de tous ces registres contiennent 0, on oublie donc simplement les zéros. Donc après avoir exécuté le code suivant

A=0 ; UNE |= (1<< 3)

La valeur de A deviendra 8.

Il existe de nombreuses options pour configurer la minuterie, mais nous devons obtenir les éléments suivants à partir de la minuterie :

  • Pour que le temporisateur passe au mode de fonctionnement CTC (c'est-à-dire au mode de comptage avec réinitialisation après un match, « Clear Timer on Compare match »), à en juger par la fiche technique, cela est obtenu en réglant les bits WGM12:0 = 2, ce qui en soi signifie mettre les bits de la seconde à zéro à la valeur "2", c'est-à-dire "010", commande TCCR1B |= (1<< WGM12) ;
  • Puisque 16 MHz (à savoir, c'est la fréquence du résonateur à quartz de ma carte), c'est beaucoup, choisissez le diviseur maximum possible, 1024 (c'est-à-dire que seul chaque 1024ème cycle d'horloge atteindra notre compteur), CS12 : 0 = 5
  • Assurez-vous que l'interruption se produit lorsque le registre A correspond, pour ce compteur TIMSK1 |= (1<< OCIE1A)
  • Spécifiez quand une valeur spécifique est atteinte pour déclencher le traitement de l'interruption; cette valeur est placée dans le même registre A du compteur 1 (son nom complet est OCR1A), l'interruption coïncidant avec laquelle nous avons activé dans le paragraphe précédent.

Comment calculer le temps nécessaire pour effectuer les calculs ? - C'est simple, si la fréquence d'horloge du résonateur à quartz est de 16 MHz, alors lorsque le compteur atteint la valeur de 16000, une seconde s'écoulerait si le coefficient de division était de 1. Puisqu'il est de 1024, alors on obtient 16000000/1024 = 15625 par seconde. Et tout irait bien, mais nous devons obtenir des valeurs toutes les demi-secondes, et 15625 n'est pas divisible par 2. Cela signifie que nous avons déjà commis une erreur et que nous devrons prendre un facteur de division plus petit. Et le prochain plus petit que nous avons est 256, ce qui donne 62 500 ticks par seconde ou 31 250 en une demi-seconde. Notre compteur est de 16 bits, il peut donc compter jusqu'à 65536. En d'autres termes, cela nous suffit à la fois pour une demi-seconde et pour une seconde. Nous allons dans la fiche technique, puis dans le code source et le corrigeons à CS12:0=4, et après cela OCR1A = 31249 ; (si je comprends bien, un cycle est consacré soit à la réinitialisation, soit ailleurs, il existe donc des astuces pour en réinitialiser un autre à partir du numéro reçu).

Gestion des interruptions
La syntaxe de la fonction de service d'interruption a légèrement changé ; elle ressemble désormais à l'exemple ci-dessous. Ne soyez donc pas surpris si vous voyez quelque part une description légèrement différente du nom de la fonction.

En fait, il s'agit désormais du mot réservé ISR et d'une indication de l'interruption spécifique que cette fonction traite entre parenthèses. Mais comme vous pouvez le constater, cette fonction n’a rien de fantastique à l’intérieur. Même le RETI obligatoire, comme vous pouvez le constater, est automatiquement inséré par le compilateur pour nous.

ISR(TIMER1_COMPA_vect) ( digitalWrite(LEDPIN, !digitalRead(LEDPIN)); // LEDPIN=13. Cette ligne fait clignoter la LED de la carte. Pratique et cool :) seconde++; if ((second %2) && lastshowval) ( // cette ligne et les 7 suivantes ne sont nécessaires que pour lastshowval = 0; // pour que vous puissiez obtenir cet effet amusant, comme sur une horloge matérielle, showval = 0; // en mode configuration, disons minutes, la valeur du paramètre configuré clignote) if (!(seconde %2) ​​&& !lastshowval)( // uniquement lorsque les boutons sont relâchés, et pendant que les boutons sont enfoncés, il s'allume simplement. lastshowval = 1; showval = 1; ) if ( seconde>=120) ( // encore mes 120 secondes en une minute. Eh bien, qui a la tâche facile maintenant ? seconde-=120; minute++; if (minute>=60)( minute-=60; hour++; if (hour>=24) ( hour-=24; day++; if (daylarge(day,month,year) // renvoie vrai si la valeur du jour // est supérieure au maximum possible pour ce mois de cette année.) ( jour=1; mois++; if (mois>12) ( mois = 1; année++; ) ) ) ) ) )

J'espère que cet article sera utile à quelqu'un, car il existe de nombreuses instructions détaillées sur l'utilisation des interruptions de minuterie en russe.

Nous avons compris le compteur d'itérations de la boucle principale et découvert qu'il est totalement inadapté à des lectures précises du temps - la vitesse d'obturation flotte et il est difficile de la compter. Ce qu'il faut faire?

Évidemment, nous avons besoin d’une sorte de compteur externe qui fonctionnerait quel que soit le fonctionnement du processeur, et le processeur pourrait à tout moment voir ce qui s’y passait. Ou pour que le compteur génère des événements de débordement ou de sous-débordement, levez le drapeau ou générez une interruption. Et le pour cent le sentira et le traitera.

Et il existe un tel compteur, pas même un seul - ce sont des minuteries périphériques. Il peut y en avoir plusieurs dans un AVR, et même avec des profondeurs de bits différentes. ATmega16 en a trois, ATmega128 en a quatre. Et dans les nouveaux MK de la série AVR il y en a peut-être encore plus, je ne l'ai pas reconnu.

De plus, une minuterie peut être plus qu'un simple compteur stupide ; une minuterie est l'un des périphériques les plus sophistiqués (en termes de fonctions alternatives).

Que peuvent faire les minuteries ?

  • Tiquez à différentes vitesses, en comptant le temps
  • Compter les impulsions entrantes de l'extérieur (mode compteur)
  • Tick ​​​​du quartz externe à 32768Hz
  • Générer plusieurs types de signaux PWM
  • Émettre des interruptions (une demi-douzaine d'événements différents) et définir des indicateurs

Différentes minuteries ont des fonctionnalités différentes et des profondeurs de bits différentes. Voir la fiche technique pour plus de détails.

Source du tick du minuteur
Le minuteur/compteur (ci-après je l'appellerai T/C) compte soit les impulsions d'horloge du générateur d'horloge intégré, soit de l'entrée de comptage.

Regardez attentivement le brochage des pattes de l'ATmega16, y voyez-vous les pattes T1 et T0 ?

Il s'agit donc des entrées de comptage Timer 0 et Timer 1. Avec les réglages appropriés, le T/S comptera soit le front montant (front de 0-1), soit le front descendant (front de 1-0) du impulsions arrivant à ces entrées.

L'essentiel est que la fréquence des impulsions entrantes ne dépasse pas la fréquence d'horloge du processeur, sinon il n'aura pas le temps de traiter les impulsions.

De plus, T/C2 est capable de fonctionner en mode asynchrone. Autrement dit, le T/S ne compte pas les impulsions d'horloge du processeur, ni les impulsions entrantes dans les jambes, mais les impulsions de son propre oscillateur, alimenté par un quartz séparé. Pour ce faire, le T/C2 dispose d'entrées TOSC1 et TOSC2, sur lesquelles vous pouvez fixer un résonateur à quartz.

Pourquoi est-ce même nécessaire ? Oui, organisez au moins une horloge en temps réel. J'y ai accroché une horloge à quartz à 32768 Hz et j'ai compté le temps - 128 débordements se produiront par seconde (puisque T/C2 est à huit bits). Un débordement équivaut donc à 1/128 de seconde. De plus, le temporisateur ne s'arrête pas pendant le traitement de l'interruption de débordement ; il continue également à compter. L'horloge est donc un jeu d'enfant !

Pré-diviseur
Si le temporisateur compte les impulsions provenant d'un générateur d'horloge ou de son générateur interne, elles peuvent toujours être transmises via un préscaler.

Autrement dit, avant même d'entrer dans le registre de comptage, la fréquence d'impulsion sera divisée. Vous pouvez diviser par 8, 32, 64, 128, 256, 1024. Donc, si vous placez un quartz d'horloge sur T/C2 et que vous le passez dans un pré-échelonneur à 128, alors votre minuterie fonctionnera à une vitesse d'un tick par seconde.

Confortable! Il est également pratique d'utiliser un prescaler lorsque vous avez juste besoin d'obtenir un intervalle important, et que la seule source de ticks est le générateur d'horloge du processeur à 8 MHz, vous en aurez marre de compter ces mégahertz, mais si vous le passez par le prescaler , à 10h24, alors tout est beaucoup plus heureux.

Mais il y a une particularité ici, le fait est que si nous lançons le T/S avec une sorte de pré-échelonneur brutal, par exemple à 1024, alors le premier tick du registre de comptage n'interviendra pas nécessairement après 1024 impulsions.

Cela dépend de l'état dans lequel se trouve le préscaler. Et si, au moment où nous l'avons allumé, il avait déjà compté jusqu'à près de 1024 ? Cela signifie qu'il y aura une coche immédiatement. Le pré-échelonneur fonctionne tout le temps, que la minuterie soit activée ou non.

Par conséquent, les préscalers peuvent et doivent être réinitialisés. Vous devez également prendre en compte le fait que le pré-échelonneur est le même pour tous les compteurs, donc lors de sa réinitialisation, vous devez prendre en compte le fait qu'un autre minuteur perdra du temps jusqu'au prochain tick, et il peut se tromper exactement Par ici.

Par exemple, le premier timer fonctionne sur la broche 1:64 et le second sur la broche 1:1024 du prescaler. Le second a presque atteint 1024 dans le pré-échelonneur et maintenant il devrait y avoir un tick de minuterie, mais vous êtes ensuite allé réinitialiser le pré-échelonneur pour démarrer le premier temporisateur exactement à partir de zéro. Que va-t-il se passer ? C'est vrai, le deuxième diviseur sera immédiatement réinitialisé à 0 (le pré-échelonneur est le même, il a un registre) et le deuxième temporisateur devra attendre encore 1024 cycles d'horloge pour obtenir l'impulsion souhaitée !

Et si vous réinitialisez le pré-échelonneur dans la boucle, au profit du premier minuteur, plus d'une fois tous les 1024 cycles d'horloge, alors le deuxième minuteur ne fonctionnera jamais et vous vous cognerez la tête sur la table en essayant de comprendre pourquoi votre deuxième minuterie ne fonctionne pas, bien que ce soit nécessaire.

Pour réinitialiser les prescalers, écrivez simplement le bit PSR10 dans le registre SFIOR. Le bit PSR10 sera réinitialisé automatiquement au prochain cycle d'horloge.

Registre des comptes
Le résultat complet du tourment décrit ci-dessus est accumulé dans le registre de comptage TCNTx, où x est le numéro de la minuterie. il peut être de huit ou seize bits, auquel cas il se compose de deux registres TCNTxH et TCNTxL - respectivement les octets de poids fort et de poids faible.

Et il y a un piège ici, si vous devez mettre un nombre dans un registre de huit bits, alors il n'y a aucun problème OUT TCNT0, Rx et pas de clous, alors avec des registres à deux octets, vous devrez jouer.

Et le fait est que le minuteur compte indépendamment du processeur, donc on peut mettre un octet en premier, il commencera à compter, puis le second, et le recalcul commencera en tenant compte du deuxième octet.

Avez-vous l'impression que je m'entends bien ? Ici! Le timer est un appareil précis, ses registres de comptage doivent donc être chargés en même temps ! Mais comment? Et les ingénieurs d'Atmel ont résolu le problème simplement :
Le registre haut (TCNTxH) est écrit en premier dans le registre TEMP. Ce registre est purement officiel et ne nous est en aucun cas accessible.

Ce que nous obtenons : nous écrivons l'octet de poids fort dans le registre TEMP (pour nous, c'est un sacré TCNTxH), puis nous écrivons l'octet de poids faible. A ce moment, la valeur que nous avons enregistrée précédemment est entrée dans le TCNTxH réel. Autrement dit, deux octets, haut et bas, sont écrits simultanément ! Vous ne pouvez pas modifier la commande ! La seule façon

Cela ressemble à ceci :

CLI ; Nous interdisons les interruptions, à coup sûr ! SORTIE TCNT1H,R16 ; L'octet de poids fort a été écrit en premier dans TEMP OUT TCNT1L,R17 ; Et maintenant, je me suis inscrit aux cours seniors et juniors ! SEI ; Activer les interruptions

Pourquoi désactiver les interruptions ? Oui, pour qu'après avoir écrit le premier octet, le programme ne se précipite pas accidentellement sans interruption, puis quelqu'un viole notre minuterie. Alors dans ses registres ce ne sera pas ce que nous avons envoyé ici (ou dans l'interruption), mais qu'est-ce que c'est. Alors essayez d'attraper un tel bug plus tard ! Mais cela peut survenir au moment le plus inopportun, mais vous ne l’attraperez pas, car une interruption est presque une variable aléatoire. De tels moments doivent donc être abordés immédiatement.

Tout se lit de la même manière, mais dans l'ordre inverse. Tout d'abord, l'octet de poids faible (tandis que celui de poids fort est placé dans TEMP), puis celui de poids fort. Cela garantit que nous comptons exactement l'octet qui se trouvait actuellement dans le registre de comptage, et non celui qui était en cours d'exécution pendant que nous le retirions du registre de comptage.

Registres de contrôle
Je ne décrirai pas toutes les fonctions des minuteries, sinon cela se révélera être un traité écrasant. Il vaut mieux parler du principal - celui du comptage, et toutes sortes de générateurs PWM et autres seront dans un autre article. Alors soyez patient, ou mâchez la fiche technique, c’est aussi utile.

Le registre principal est donc TCCRx
Pour T/C0 et T/C2, il s'agit respectivement de TCCR0 et TCCR2, et pour T/C1, il s'agit de TCCR1B.

Pour l’instant, nous ne nous intéressons qu’aux trois premiers bits de ce registre :
CSx2.. CSx0, remplacez x par le numéro du minuteur.
Ils sont responsables du réglage du prescaler et de la source d’horloge.

Les différents timers sont légèrement différents, je décrirai donc les bits CS02..CS00 uniquement pour le timer 0.

  • 000 - minuterie arrêtée
  • 001 — le préscaler est égal à 1, c'est-à-dire désactivé. la minuterie compte les impulsions d'horloge
  • 010 - le préscaler est de 8, la fréquence d'horloge est divisée par 8
  • 011 - le préscaler est de 64, la fréquence d'horloge est divisée par 64
  • 100 - le préscaler est de 256, la fréquence d'horloge est divisée par 256
  • 101 - le préscaler est 1024, la fréquence d'horloge est divisée par 1024
  • 110 - les impulsions d'horloge proviennent de la broche T0 lors de la transition de 1 à 0
  • 111 - les impulsions d'horloge proviennent de la broche T0 lors de la transition de 0 à 1

Interruptions
Chaque événement matériel comporte une interruption et la minuterie ne fait pas exception. Dès qu'un débordement ou un autre événement curieux se produit, une interruption apparaît immédiatement.

Les registres TIMSK et TIFR sont responsables des interruptions des minuteries. Et les AVR plus cool, comme ATMega128, ont également ETIFR et ETIMSK - une sorte de continuation, puisqu'il y aura plus de minuteries.

TIMSK est un registre de masques. Autrement dit, les bits qu'il contient activent localement les interruptions. Si le bit est activé, l'interruption spécifique est activée. Si le bit est nul, alors cette interruption est recouverte d'un bassin. Par défaut, tous les bits sont nuls.

Pour le moment, nous ne nous intéressons qu’aux interruptions de débordement. Les bits en sont responsables

  • TOIE0 - autorisation d'interrompre en cas de débordement du timer 0
  • TOIE1 - autorisation d'interrompre en cas de débordement du timer 1
  • TOIE2 - autorisation d'interrompre en cas de débordement du timer 2

Nous parlerons d'autres fonctionnalités et des interruptions de minuterie plus tard, lorsque nous examinerons PWM.

Le registre TIFR est directement un registre de drapeaux. Lorsqu'une interruption est déclenchée, un drapeau apparaît indiquant que nous avons une interruption. Cet indicateur est réinitialisé par le matériel lorsque le programme quitte le vecteur. Si les interruptions sont désactivées, l'indicateur restera là jusqu'à ce que les interruptions soient activées et que le programme passe à l'interruption.

Pour éviter que cela ne se produise, le drapeau peut être réinitialisé manuellement. Pour cela, vous devez écrire 1 dans le registre TIFR !

Maintenant, baisons
Eh bien, repensons le programme pour qu'il fonctionne avec une minuterie. Introduisons une minuterie de programme. L’orgue de Barbarie le restera, laissez-le tourner. Et nous ajouterons une deuxième variable, également quatre octets :

ORG 010$ RETI ; (TIMER1 OVF) Débordement du minuteur/compteur1 .ORG $012 RJMP Timer0_OV ; (TIMER0 OVF) Débordement minuterie/compteur0 .ORG $014 RETI ; (SPI, STC) Transfert série terminé

Ajoutons un gestionnaire d'interruption pour le dépassement du minuteur 0 à la section Interruption. Puisque notre macro de ticking fonctionne activement avec les indicateurs de registre et de corruption, nous devons d'abord enregistrer tout cela sur la pile :

Au fait, créons une autre macro qui place le registre du drapeau SREG sur la pile et une seconde qui le récupère à partir de là.

1 2 3 4 5 6 7 8 9 10 11 12 .MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

MACRO PUSHF PUSH R16 IN R16,SREG PUSH R16 .ENDM .MACRO POPF POP R16 OUT SREG,R16 POP R16 .ENDM

Comme effet secondaire, il retient également le R16, n'oubliez pas que :)

1 2 3 4 5 6 7 8 9 10 11 12 13 Timer0_OV : PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Timer0_OV : PUSHF PUSH R17 PUSH R18 PUSH R19 INCM TCNT POP R19 POP R18 POP R17 POPF RETI

Maintenant, initialisez la minuterie. Ajoutez-le à la section Internal Hardware Init.

; Initialisation du matériel interne ======================================= SETB DDRD,4,R16 ; DDRD.4 = 1 JEU DDRD,5,R16 ; DDRD.5 = 1 JEU DDRD,7,R16 ; DDRD.7 = 1 JEU PORTD,6,R16 ; Sortie PD6 vers entrée pull-up CLRB DDRD,6,R16 ; Lire le bouton SETB TIMSK,TOIE0,R16 ; Activer l'interruption du temporisateur OUTI TCCR0,1<

Il ne reste plus qu'à réécrire notre bloc de comparaison et recalculer le nombre. Maintenant, tout est simple, on coche une barre. Sans aucun problème avec différentes longueurs de code. Pour une seconde à 8 MHz, 8 millions de ticks doivent être effectués. En hexadécimaux, c'est 7A 12 00, en tenant compte du fait que l'octet de poids faible est TCNT0, puis 7A 12 est laissé pour notre compteur ainsi que les deux octets les plus élevés 00 00, ils n'ont pas besoin d'être vérifiés. Il n’est pas nécessaire de masquer ; nous réinitialiserons le chronomètre plus tard de toute façon.

Il n'y a qu'un seul problème : l'octet de poids faible, celui du timer. Il coche chaque case et il sera presque impossible de vérifier la conformité. Parce que le moindre écart et la condition de comparaison apparaîtront dans NoMatch, mais le deviner pour que la vérification de sa valeur coïncide avec cette étape particulière... Il est plus facile de retirer une aiguille d'une botte de foin du premier coup au hasard.

La précision dans ce cas est donc limitée - vous devez avoir le temps de vérifier la valeur avant qu'elle ne quitte la plage. Dans ce cas, la plage sera, pour simplifier, 255 - la valeur de l'octet de poids faible, celui du temporisateur.

Ensuite, notre deuxième est doté d'une précision de 8 000 000 plus ou moins 256 cycles. L'erreur n'est pas grande, seulement 0,003 %.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ; Principal ================================================== ================ ======== Principal : SBIS PIND,6 ; Si le bouton est enfoncé - transition RJMP BT_Push SETB PORTD,5 ; Allumons LED2 CLRB PORTD,4 ; Éteignez la LED1 Suivant : LDS R16,TCNT ; Charger les nombres dans les registres LDS R17,TCNT+1 CPI R16.0x12 ; Comparons octet par octet. Premier octet BRCS NoMatch ; Si c’est moins, cela signifie qu’il n’a pas touché. IPC R17.0x7A ; Deuxième octet BRCS NoMatch ; Si c’est moins, cela signifie qu’il n’a pas touché. ; Si cela correspond, alors nous effectuons l'action Match : INVB PORTD,7,R16,R17 ; LED3 inversée ; Il faut maintenant réinitialiser le compteur, sinon lors de la même itération de la boucle principale ; nous y reviendrons plus d'une fois - le minuteur n'aura pas le temps d'atteindre 255 valeurs ; de sorte que le nombre dans les deux premiers octets du compteur change et que la condition soit déclenchée. ; Bien sûr, vous pouvez contourner cela avec un indicateur supplémentaire, mais c'est plus simple de réinitialiser le compteur :) CLR R16 ; Nous n’avons besoin d’aucun CLI ; Accès à une variable multi-octets ; simultanément de l'interruption et de l'arrière-plan ; Un accès atomique est nécessaire. Désactiver les interruptions OUTU TCNT0,R16 ; Zéro au registre du compteur de minuterie STS TCNT, R16 ; Zéro dans le premier octet du compteur en RAM STS TCNT+1,R16 ; Zéro dans le deuxième octet du compteur en RAM STS TCNT+2,R16 ; Zéro dans le troisième octet du compteur en RAM STS TCNT+3,R16 ; Zéro dans le premier octet du compteur en RAM SEI ; Réactivons les interruptions. ; Si cela ne correspond pas, nous ne le faisons pas :) NoMatch: NOP INCM CCNT ; Le compteur de cycles tourne ; Même s'il n'est pas utilisé. BT_Push principal JMP : SETB PORTD,4 ; Allumons LED1 CLRB PORTD,5; Éteignez LED2 RJMP Suivant ; Fin principale ================================================= ================ =====

; Principal ================================================== ================ ======== Principal : SBIS PIND,6 ; Si le bouton est enfoncé - transition RJMP BT_Push SETB PORTD,5 ; Allumons LED2 CLRB PORTD,4 ; Éteignez la LED1 Suivant : LDS R16,TCNT ; Charger les nombres dans les registres LDS R17,TCNT+1 CPI R16.0x12 ; Comparons octet par octet. Premier octet BRCS NoMatch ; Si c’est moins, cela signifie qu’il n’a pas touché. IPC R17.0x7A ; Deuxième octet BRCS NoMatch ; Si c’est moins, cela signifie qu’il n’a pas touché. ; Si cela correspond, alors nous effectuons l'action Match : INVB PORTD,7,R16,R17 ; LED3 inversée ; Il faut maintenant réinitialiser le compteur, sinon lors de la même itération de la boucle principale ; nous y reviendrons plus d'une fois - le minuteur n'aura pas le temps d'atteindre 255 valeurs ; de sorte que le nombre dans les deux premiers octets du compteur change et que la condition soit déclenchée. ; Bien sûr, vous pouvez contourner cela avec un indicateur supplémentaire, mais c'est plus simple de réinitialiser le compteur :) CLR R16 ; Nous n’avons besoin d’aucun CLI ; Accès à une variable multi-octets ; simultanément de l'interruption et de l'arrière-plan ; Un accès atomique est nécessaire. Désactiver les interruptions OUTU TCNT0,R16 ; Zéro au registre du compteur de minuterie STS TCNT, R16 ; Zéro dans le premier octet du compteur en RAM STS TCNT+1,R16 ; Zéro dans le deuxième octet du compteur en RAM STS TCNT+2,R16 ; Zéro dans le troisième octet du compteur en RAM STS TCNT+3,R16 ; Zéro dans le premier octet du compteur en RAM SEI ; Réactivons les interruptions. ; Si cela ne correspond pas, nous ne le faisons pas :) NoMatch: NOP INCM CCNT ; Le compteur de cycles tourne ; Même s'il n'est pas utilisé. BT_Push principal JMP : SETB PORTD,4 ; Allumons LED1 CLRB PORTD,5; Éteignez LED2 RJMP Suivant ; Fin principale ================================================= ================ =====

Voilà à quoi ça ressemble en action

Et si nous devons faire clignoter une deuxième diode avec une période différente, nous pouvons alors mettre en toute sécurité une autre variable dans le programme, et dans le gestionnaire d'interruption de la minuterie, nous pouvons incrémenter deux variables à la fois. Les vérifier un par un dans la boucle du programme principal.

Vous pouvez optimiser un peu plus le processus de vérification. Fais le plus rapidement.

Il vous suffit de faire augmenter le compte, mais de le diminuer. Ceux. Nous chargeons un nombre dans une variable et commençons à le décrémenter dans l'interruption. Et là, dans le gestionnaire, nous vérifions zéro. Si zéro, alors définissez un indicateur en mémoire. Et notre programme d'arrière-plan capte ce drapeau et lance l'action, réinitialisant simultanément la vitesse d'obturation.

Mais que se passe-t-il si vous avez besoin d’être plus précis ? Eh bien, il n'y a qu'une seule option : utiliser le traitement des événements directement dans le gestionnaire d'interruption et ajuster la valeur dans TCNT:TCNT0 à chaque fois afin que l'interruption se produise exactement au bon moment.

Les interruptions permettent aux microcontrôleurs de répondre aux événements sans avoir à vérifier constamment des conditions pour déterminer quand des changements importants se sont produits. En plus de la possibilité de connecter des sources d'interruption à certaines broches, vous pouvez également utiliser des interruptions générées par une minuterie.

Interruptions matérielles

Pour démontrer l'utilisation des interruptions, revenons aux entrées numériques. Souvent, pour déterminer le moment d'un événement d'entrée (par exemple, appuyer sur un bouton), le code suivant est utilisé :

si (digitalRead(inputPin) == FAIBLE)

// Effectue quelques actions

Ce code vérifie constamment le niveau de tension à l'entrée inputPin, et lorsque digitalRead renvoie LOW, il fait quelque chose, indiqué par le commentaire // Do Something. C'est une solution tout à fait réalisable, mais que se passe-t-il s'il y a beaucoup d'autres opérations à effectuer dans la fonction de boucle ? Toutes ces opérations prennent du temps, il est donc possible de manquer un appui court sur un bouton pendant que le processeur est occupé avec autre chose. En fait, il est presque impossible de rater le fait que le bouton a été enfoncé, car selon les normes du microcontrôleur, il reste enfoncé très longtemps.

Mais qu’en est-il des impulsions courtes du capteur, qui peuvent durer des millionièmes de seconde ? Pour recevoir de tels événements, vous devez utiliser des interruptions, définissant les fonctions qui seront appelées sur ces événements, quelle que soit ce que fait le microcontrôleur. De telles interruptions sont appelées interruptions matérielles(interruptions matérielles).

Dans l'Arduino Uno, seules deux broches sont associées aux interruptions matérielles, c'est pourquoi elles sont utilisées avec parcimonie. Leonardo possède cinq de ces broches, les cartes plus grandes comme la Mega2560 en ont beaucoup plus et les broches de Due sont toutes capables d'interruption.

Ce qui suit explique le fonctionnement des interruptions matérielles. Pour essayer l'exemple présenté, vous aurez besoin d'une planche à pain supplémentaire, d'un bouton, d'une résistance de 1 000 ohms et de quelques câbles de démarrage.

En figue. La figure 3.1 montre le circuit assemblé. À travers la résistance, la tension HAUTE est appliquée à la broche D2 jusqu'à ce que le bouton soit enfoncé, auquel cas la broche D2 sera mise à la terre et le niveau de tension chutera à BAS.

Téléchargez le croquis suivant sur votre carte Arduino :

// croquis 03_01_interrupts

int ledPin = 13 ;

pinMode(ledPin, SORTIE);

void stuffHapenned()

digitalWrite(ledPin, ÉLEVÉ);

Riz. 3.1. Schéma de circuit pour les tests d'interruption

En plus de configurer la broche LED pour qu'elle fonctionne comme une sortie numérique, la fonction de configuration utilise une autre ligne pour associer la fonction à une interruption. Désormais, cette fonction sera appelée automatiquement en réponse à chaque interruption. Regardons cette ligne de plus près, car les arguments de la fonction appelée ici semblent un peu inhabituels :

attachInterrupt(0, stuffHapenned, FALLING);

Le premier argument - 0 - est le numéro d'interruption. Il serait plus clair si le numéro d'interruption coïncidait avec le numéro de broche, mais ce n'est pas le cas. Dans Arduino Uno, l'interruption 0 est associée à la broche D2 et l'interruption 1 est associée à la broche D3. La situation est rendue encore plus confuse par le fait que dans d'autres modèles Arduino, ces interruptions sont associées à différentes broches, et de plus, dans Arduino Due, vous devez spécifier le numéro de broche. Sur la carte Arduino Due, toutes les broches sont associées à des interruptions.

Je reviendrai sur ce problème plus tard, mais pour l'instant passons au deuxième argument. Cet argument, stuffHappened, représente le nom de la fonction qui doit être appelée pour gérer l'interruption. Cette fonction est définie plus loin dans le croquis. De telles fonctions sont appelées interrompre les routines(Interrupt Service Routine, ISR), il existe des exigences particulières. Ils ne peuvent pas avoir de paramètres et ne doivent rien renvoyer. Cela a du sens : même s'ils sont appelés à différents endroits dans l'esquisse, il n'y a pas une seule ligne de code qui appelle directement l'ISR, il n'y a donc aucun moyen de leur transmettre des paramètres ou d'obtenir une valeur de retour.

Le dernier paramètre de la fonction, attachInterrupt, est une constante, dans ce cas FALLING. Cela signifie que la routine d'interruption ne sera appelée que lorsque la tension sur la broche D2 passe du niveau HAUT au niveau BAS (c'est-à-dire en cas de chute), ce qui se produit lorsque le bouton est enfoncé.

Notez qu'il n'y a pas de code dans la fonction de boucle. En général, cette fonction peut contenir du code qui s'exécute jusqu'à ce qu'une interruption se produise. La routine d'interruption elle-même allume simplement la LED L.

Lorsque vous expérimentez, après avoir réinitialisé l'Arduino, la LED L devrait s'éteindre. Et après avoir appuyé sur le bouton, il s'allumera immédiatement et restera allumé jusqu'à la prochaine réinitialisation.

Après avoir expérimenté, essayez de changer le dernier argument de l'appel attachInterrupt en RISING et téléchargez le croquis modifié. Après le redémarrage de l'Arduino, la LED doit rester éteinte, car la tension sur la broche, bien qu'au niveau ÉLEVÉ, est restée à ce niveau depuis le redémarrage. Jusqu'à ce moment, la tension au niveau du contact ne descendait pas jusqu'au niveau BAS puis montait (montait) jusqu'au niveau HAUT.

Une fois que vous appuyez et maintenez le bouton, la LED doit rester éteinte jusqu'à ce que vous la relâchiez. Le relâchement du bouton provoquera une interruption associée à la broche D2 car pendant que le bouton était maintenu enfoncé, le niveau de tension sur la broche était FAIBLE et lorsqu'il était relâché, il montait à ÉLEVÉ.

Si lors du test, il s'avère que ce qui vous arrive ne correspond pas à la description donnée précédemment, cela est probablement dû à l'effet du rebond des contacts dans le bouton. Cet effet est dû au fait que le bouton n'assure pas une transition claire entre les états « on » et « off » ; au contraire, au moment de la pression, il y a une transition répétée entre ces états jusqu'à ce que l'état « on » soit atteint. fixé. Essayez d'appuyer sur le bouton plus vigoureusement, cela devrait aider à obtenir une transition claire entre les états sans effet de rebond.

Une autre façon d'essayer ce croquis consiste à maintenir le bouton enfoncé tout en appuyant et en relâchant le bouton Réinitialiser sur la carte Arduino. Ensuite, lorsque le croquis démarre, relâchez le bouton de la planche à pain et la LED L s'allumera.

Broches activées par interruption

Revenons maintenant au problème de la dénomination des interruptions. Dans le tableau 3.1 répertorie les modèles les plus courants de cartes Arduino et montre la correspondance des numéros d'interruptions et de contacts qu'elles contiennent.

Tableau 3.1. Broches activées par interruption dans différents modèles Arduino

Modèle Numéro d'interruption Remarques
0 1 2 3 4 5
Uno D2 D3 - - - -
Léonard D3 D2 D0 D1 J7 - En effet, par rapport à Uno, les deux premières interruptions sont affectées à des broches différentes
Méga2560 D2 D3 J21 J20 J19 J18
Exigible - - - - - - Au lieu des numéros d'interruption, la fonction attachInterrupt doit recevoir des numéros de broche

Changer les broches des deux premières interruptions dans Uno et Leonardo crée un piège dans lequel il est facile de tomber. Dans le modèle Due, au lieu des numéros d'interruption, la fonction attachInterrupt doit recevoir des numéros de broches, ce qui semble plus logique.

Modes d'interruption

Les modes d'interruption RISING (front montant) et FALLING (front descendant) utilisés dans l'exemple précédent sont les plus souvent utilisés en pratique. Il existe cependant plusieurs autres modes. Ces modes sont répertoriés et décrits dans le tableau. 3.2.

Tableau 3.2. Modes d'interruption

Mode Action Description
FAIBLE L'interruption est générée au niveau de tension FAIBLE Dans ce mode, la routine d'interruption sera appelée en continu tant que la broche restera basse.
EN HAUSSE Une interruption est générée lorsque la tension chute positivement, de FAIBLE à HAUTE. -
CHUTE Une interruption est générée sur une chute de tension négative, de HIGH à LOW. -
HAUT L'interruption est générée au niveau de tension ÉLEVÉ Ce mode n'est pris en charge que dans le modèle Arduino Due et, comme le mode LOW, est rarement utilisé en pratique.

Activer l'impédance interne

Le circuit de l'exemple précédent utilisait une résistance de rappel externe. Cependant, dans la pratique, les signaux provoquant des interruptions sont souvent pilotés par les sorties numériques des capteurs, auquel cas il n'est pas nécessaire d'utiliser une résistance de rappel.

Mais si le rôle du capteur est joué par un bouton connecté exactement de la même manière que la maquette de la Fig. 3.1, il est possible de se débarrasser de la résistance en activant la résistance interne « pull-up » d'une valeur nominale d'environ 40 kOhm. Pour ce faire, vous devez définir explicitement le mode INPUT_PULLUP pour la broche liée à l'interruption, comme indiqué dans la ligne en gras :

pinMode(ledPin, SORTIE);

pinMode(2, INPUT_PULLUP);

attachInterrupt(0, stuffHapenned, FALLING);

Routines d'interruption

Parfois, il peut sembler que la possibilité de gérer les interruptions pendant l'exécution d'une fonction de boucle offre un moyen simple de gérer des événements tels que les frappes au clavier. Mais en réalité, il existe des restrictions très strictes sur ce que les routines d’interruption peuvent et ne peuvent pas faire.

Les routines d'interruption doivent être aussi courtes et rapides que possible. Si une autre interruption se produit pendant l'exécution de la routine d'interruption, la routine ne sera pas interrompue et le signal résultant sera simplement ignoré. Cela signifie, par exemple, que si des interruptions sont utilisées pour mesurer la fréquence, vous risquez d'obtenir une valeur incorrecte.

De plus, pendant l'exécution de la routine d'interruption, le code de la fonction de boucle est inactif.

Les interruptions sont automatiquement désactivées pendant le traitement. Cette solution évite la confusion entre les sous-programmes qui s'interrompent les uns les autres, mais entraîne des effets secondaires indésirables. La fonction de retard utilise des minuteries et des interruptions, elle ne fonctionnera donc pas dans les routines d'interruption. Il en va de même pour la fonction millis. Essayer d'utiliser des millis pour obtenir le nombre de millisecondes écoulées depuis la dernière réinitialisation de la carte pour effectuer un délai de cette manière ne réussira pas, car cela renverra la même valeur jusqu'à la fin de la routine d'interruption. Cependant, vous pouvez utiliser la fonction delayMicroseconds, qui n'utilise pas d'interruptions.

Les interruptions sont également utilisées dans les communications série, n'essayez donc pas d'utiliser Serial.print ou les fonctions de lecture du port série. Cependant, vous pouvez essayer, et parfois ils fonctionneront même, mais n'attendez pas une grande fiabilité d'une telle connexion.

Variables opérationnelles

Étant donné que la routine d'interruption ne peut avoir aucun paramètre et ne peut rien renvoyer, un moyen de transmettre des informations entre elle et le reste du programme est nécessaire. Cela se fait généralement à l'aide de variables globales, comme le montre l'exemple suivant :

// croquis 03_02_interrupt_flash

int ledPin = 13 ;

booléen volatile flashFast = false ;

pinMode(ledPin, SORTIE);

attachInterrupt(0, stuffHapenned, FALLING);

période int = 1000 ;

si période (flashFast) = 100 ;

digitalWrite(ledPin, ÉLEVÉ);

digitalWrite(ledPin, FAIBLE);

void stuffHapenned()

flashFast = ! flashFast ;

Dans cette esquisse, la fonction de boucle utilise la variable globale flashFast pour déterminer la période de retard. La routine de traitement modifie la valeur de cette variable entre vrai et faux.

Notez que la déclaration de variable flashFast inclut le mot volatile. Vous pouvez réussir à développer une esquisse sans le spécificateur volatile, mais c'est absolument nécessaire car sans le spécificateur volatile, le compilateur C peut générer du code machine qui met en cache la valeur de la variable dans un registre pour améliorer les performances. Si, comme dans ce cas, le code de mise en cache est interrompu, il se peut qu'il ne remarque pas le changement dans la valeur de la variable.

En conclusion sur les routines d'interruption

Lorsque vous écrivez des routines d'interruption, n'oubliez pas les règles suivantes.

Les sous-programmes doivent agir rapidement.

Pour transférer des données entre la routine d'interruption et le reste du programme, les variables déclarées avec le spécificateur volatile doivent être utilisées.

N'utilisez pas de délai, mais vous pouvez utiliser delayMicroseconds.

Ne vous attendez pas à des communications très fiables sur les ports série.

Ne vous attendez pas à ce que la valeur renvoyée par la fonction millis change.

Activer et désactiver les interruptions

Par défaut, les interruptions sont activées dans les esquisses et, comme mentionné précédemment, sont automatiquement désactivées pendant l'exécution de la routine d'interruption. Cependant, il est possible de désactiver et d'activer explicitement les interruptions dans le code du programme en appelant les fonctions noInterrupts et interrompus. Ces fonctions n'ont pas de paramètres, la première désactive les interruptions et la seconde les active.

Un contrôle explicite peut être nécessaire pour garantir qu'un morceau de code, tel qu'un morceau qui génère une séquence de données ou génère une séquence d'impulsions et est chronométré avec précision à l'aide de la fonction delayMicroseconds, ne peut pas être interrompu.

Interruptions de minuterie

L'appel des routines de gestion des interruptions peut être organisé non seulement par des événements externes, mais également par des événements internes de changement d'heure. Cette fonctionnalité est particulièrement utile lorsque vous devez effectuer certaines opérations à certains intervalles.

La bibliothèque TimerOne facilite la configuration des interruptions de minuterie. Il peut être trouvé et téléchargé sur http://playground.arduino.cc/Code/Timer1.

L'exemple suivant montre comment utiliser TimerOne pour générer un train d'impulsions carrées de 1 kHz. Si vous disposez d'un oscilloscope ou d'un multimètre capable de mesurer la fréquence, connectez-le à la broche 12 pour voir le signal (Fig. 3.2).

Riz. 3.2. Séquence d'impulsions rectangulaires générée à l'aide d'une minuterie

// croquis_03_03_1kHz

#inclure

int sortiePin = 12 ;

sortie int volatile = FAIBLE ;

pinMode(12, SORTIE);

Timer1.initialize(500);

Timer1.attachInterrupt(toggleOutput);

annuler toggleOutput()

digitalWrite (outputPin, sortie);

sortie = ! sortir;

La même chose pourrait être implémentée en utilisant un délai, mais l'utilisation d'interruptions de minuterie vous permet d'organiser l'exécution de toute autre opération à l'intérieur de la boucle. De plus, l'utilisation de la fonction de retard n'atteindra pas une grande précision, car le temps nécessaire pour modifier le niveau de tension sur le contact ne sera pas pris en compte dans la valeur du retard.

NOTE

Toutes les restrictions concernant les routines d'interruption externes évoquées précédemment s'appliquent également aux routines d'interruption temporisées.

À l'aide de la méthode présentée, vous pouvez définir n'importe quel intervalle entre les interruptions compris entre 1 et 8 388 480 μs, soit jusqu'à environ 8,4 s. La valeur de l'intervalle est transmise à la fonction d'initialisation en microsecondes.

La bibliothèque TimerOne permet également d'utiliser un timer pour générer des signaux de modulation de largeur d'impulsion (PWM) sur les broches 9 et 10 de la carte. Cela peut sembler excessif car analogWrite fait la même chose, mais l'utilisation d'interruptions permet un contrôle plus précis du signal PWM. En particulier, grâce à cette approche, il est possible d'organiser la mesure de la longueur d'une impulsion positive dans la plage 0...1023 au lieu de 0...255 dans la fonction analogWrite. De plus, lorsque vous utilisez analogWrite, le taux de répétition des impulsions dans le signal PWM est de 500 Hz, et en utilisant TimerOne, vous pouvez augmenter ou diminuer cette fréquence.

Pour générer un signal PWM à l'aide de la bibliothèque TimerOne, utilisez la fonction Timer1.pwm, comme indiqué dans l'exemple suivant :

// croquis_03_04_pwm

#inclure

pinMode(9, SORTIE);

pinMode(10, SORTIE);

Timer1.initialize(1000);

Minuterie1.pwm(9, 512);

Minuterie1.pwm(10, 255);

Ici, la période de répétition des impulsions est choisie pour être de 1 000 µs, c'est-à-dire que la fréquence du signal PWM est de 1 kHz. En figue. 3.3 montre la forme d'onde sur les broches 10 ( en haut) et 9 ( au fond).

Riz. 3.3. Signal de largeur d'impulsion de 1 kHz généré à l'aide de TimerOne

Juste pour le plaisir, voyons dans quelle mesure la fréquence du signal PWM peut être augmentée. Si la durée de la période est réduite à 10, la fréquence du signal PWM devrait augmenter jusqu'à 100 kHz. Les formes d'onde obtenues avec ces paramètres sont présentées sur la Fig. 3.4.

Malgré la présence de distorsions transitoires importantes, ce qui est tout à fait attendu, la durée des impulsions positives reste encore assez proche de 25 et 50 %, respectivement.

Riz. 3.4. Signal de largeur d'impulsion de 100 kHz généré à l'aide de TimerOne

Enfin

Les interruptions, qui semblent parfois être la solution idéale pour des projets difficiles, peuvent rendre le débogage du code difficile et ne constituent pas toujours le meilleur moyen de résoudre des problèmes difficiles. Examinez attentivement les solutions possibles avant de vous y engager. Au chapitre 14, nous examinerons une autre astuce pour surmonter la difficulté liée à l'incapacité de l'Arduino à gérer plus d'une tâche à la fois.

Nous reviendrons sur les interruptions au chapitre 5, où nous verrons comment elles peuvent être utilisées pour réduire la consommation électrique de la carte Arduino en la mettant périodiquement en mode d'économie d'énergie, et au chapitre 13, où nous utiliserons interruptions pour augmenter la précision du traitement du signal numérique.

Dans le chapitre suivant, nous explorerons les techniques permettant de maximiser les performances de l'Arduino.



Avez-vous aimé l'article? Partagez-le