Quelle minuterie la fonction millis arduino utilise-t-elle. Interruption du temporisateur de débordement

/* ISR_Blink Même fruit, juste sous un angle différent Clignotement de la LED en utilisant le mécanisme d'interruption (timer/counter 2 overflow) */ volatile long mks100; volatile longue ms10 ; volatil int cntr ; tmilli long,tms10=0 ; char flip; void setup() ( mks100 = 0; // compteur de centaines de microsecondes, compteur déborde après environ 5 jours ms10 = 0; // compteur de dizaines de millisecondes, compteur déborde après environ 16 mois cntr = 0; flip = 0; / / LED standard clignotante. // Sur la plupart des cartes Arduino, il est connecté à la broche 13 : pinMode (13, OUTPUT); Serial.begin (9600); // Activez le mode minuterie / compteur dont nous avons besoin - normal TCCR2A = 0; // mode normal (selon la valeur par défaut 1 - PWM corrigé en phase ?) // Réglez le pré-échelonneur du minuteur/compteur sur 16 - // cela permettra au minuteur de "cocher" toutes les microsecondes // (en supposant que le cœur du microcontrôleur bat à / / 16.000.000 battements par seconde) TCCR2B = 2 ; // 010 - fclk/8 (par défaut 100 - fclk/64) //TCCR2B = 7 ; // 111 - fclk/1024 (par défaut 100 - fclk/64) TCNT2= 59 ; // 55 ; TIMSK2 |= (1<< TOIE2); // разрешаем прерывание таймера/счетчика 2 по переполнению } ISR(TIMER2_OVF_vect) { // прежде всего взводим счетчик TCNT2=59;//55; // прошли очередные 100 мксек - увеличиваем счетчик сотен микросекунд mks100++; // if(mks100%100==0) ms10++; cntr++; // прошли очередные 10 мсек? - увеличиваем счетчик десятков миллисекунд if(cntr>99) ( ms10++; cntr = 0; ) ) void loop() ( if (ms10>tms10) ( tmillis = millis(); tms10 = ms10; if (tms10%100==0) ( if(flip) digitalWrite(13 , HIGH); // allume la LED else digitalWrite(13, LOW); // éteint la LED flip = !flip; ) if (tms10%1000==0) ( // exécute toutes les 10 secondes Serial.print( tmillis, DEC); Serial.print(" millisecondes, "); Serial.println(tms10, DEC); ) ) )

  • Classé dans
  • Marqué avec

un commentaire

Lorsque j'ai voulu développer avec Arduino, j'ai rencontré plusieurs problèmes :
  • Sélection d'un modèle dans la liste des modèles disponibles
  • Essayer de comprendre ce dont j'ai besoin en plus de la plate-forme elle-même
  • Installation et configuration de l'environnement de développement
  • Recherche et analyse des cas de test
  • "Démontage" avec l'écran
  • "Démontage" avec le processeur

Pour résoudre ces problèmes, j'ai regardé et lu pas mal de sources différentes, et dans cet article je vais essayer de faire un tour d'horizon des solutions que j'ai trouvées et des méthodes pour les trouver.

Sélection de plateforme

Avant de commencer la programmation d'un morceau de fer, vous devez l'acheter au début. Et puis j'ai rencontré le premier problème : il s'est avéré qu'il y a pas mal de *duins différents. Il existe également une large gamme d'Arduino et à peu près le même large Freeduino et d'autres analogues. Il s'est avéré qu'il n'y a pas de grande différence sur ce qu'il faut exactement prendre. 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 n'apparaissent pratiquement que lorsque vous travaillez avec des registres de processeur, puis j'expliquerai plus en détail comment éviter les problèmes autant que possible.

J'ai choisi la plate-forme Arduino Leonardo comme la plus abordable et la plus disponible à l'époque dans la boutique Internet, dans laquelle 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 s'occupe à la fois de travailler avec le port USB et d'effectuer les tâches mêmes que nous accrocherons à 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 un peu surpris, mais m'a aussi fait plaisir, car, en tant que propriétaire d'un appareil Android moderne, je ne quitte pas du tout la maison sans ce câble.
Oui, presque tous les morceaux de fer compatibles * duino sont alimentés de plusieurs manières, y compris à partir du même câble par lequel ils sont programmés. De plus, presque toutes les cartes ont une LED 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 rien avoir entre les mains sauf un câble compatible.

Spectre de tâches

Je pense qu'avant d'entreprendre comme tel d'écrire quelque chose pour un bout de fer, il est intéressant de comprendre ce qu'on peut mettre en œuvre dessus. Presque tout peut être fait avec Arduino. Des systèmes d'automatisation, des idées pour une maison intelligente, des contrôleurs pour contrôler quelque chose d'utile, le "cerveau" des robots... Il y a juste beaucoup d'options. Et une gamme assez large de modules d'extension extrêmement faciles à connecter à la carte contrôleur aide beaucoup dans cette direction. Leur liste est assez longue et prometteuse, et ils sont recherchés sur Internet pour le mot bouclier. De tous ces appareils, j'ai trouvé l'écran LCD le plus utile avec un ensemble de boutons de base, sans lequel, à mon humble avis, il est totalement inintéressant de s'engager dans des projets de formation. L'écran a été tiré d'ici, il y a aussi une description de celui-ci, ainsi que des liens vers le site officiel du fabricant à partir de la page ci-dessus.

Formulation du problème

Lorsque j'ai un nouvel outil entre les mains, je me suis en quelque sorte habitué à me fixer immédiatement une tâche modérément complexe et absolument inutile, à la résoudre courageusement, puis à mettre le code source de côté et à entreprendre ensuite quelque chose de vraiment complexe et utile. Maintenant, j'avais sous la main un écran très similaire à un composant d'une mine d'un film hollywoodien avec tous les boutons nécessaires avec lesquels j'ai dû apprendre à travailler, et je voulais aussi vraiment apprendre à travailler avec des interruptions (sinon qu'est-ce que l'intérêt d'utiliser un contrôleur ?) donc la première chose qui m'est venue à l'esprit, il s'est avéré écrire une horloge. Et puisque la taille de l'écran le permettait, alors aussi avec la date.

Les premiers pas

J'ai donc finalement récupéré tous les composants achetés et je les ai assemblés. Le connecteur de l'écran connecté à la carte contrôleur en 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 peux dire, c'est que, me souvenant de ma jeunesse (ou plutôt du premier «projet» monté lors de mes études d'électronique radio au Pioneer Palace - un multivibrateur à deux LED), j'ai trouvé 2 LED et corrigé l'exemple donné dans l'article et a commencé à les flasher :).

"Deuxièmes étapes"

La prochaine question logique pour moi était "comment travailler avec l'écran LCD?". La page officielle de l'appareil m'a gentiment fourni des liens vers l'archive, qui contenait 2 bibliothèques avec d'excellents exemples. Elle n'a tout simplement pas dit quoi faire à ce sujet. Il s'est avéré que le contenu doit simplement être décompressé dans le dossier des bibliothèques de l'environnement de développement.

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

Le jeu 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 texte 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 à coup sûr. Naturellement, sans utiliser le mécanisme d'interruption, personne ne peut garantir que le temps est calculé avec une précision suffisante. Par conséquent, le calcul du temps doit définitivement leur être laissé. Tout le reste peut être déplacé dans le corps du programme principal. Et nous avons beaucoup de ce "reste" - tout le travail avec l'interface. Il serait possible de faire autrement, créer une pile d'événements, qui est également créée par le mécanisme de gestion des interruptions, et traitée à l'intérieur de l'application principale, cela permettrait, par exemple, de ne pas mettre à jour l'écran plus d'une fois toutes les demi-secondes (ou en appuyant sur un bouton), mais j'ai calculé ce superflu pour une tâche aussi simple, car à 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 liés à 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 brûlent pendant une demi-seconde, mais pas pendant une demi-seconde. Mais comme l'écran est redessiné tout le temps, il était nécessaire de déterminer d'une manière ou d'une autre dans quelle demi-seconde ils devaient être dessinés et dans quelle non. Le plus simple était de faire 120 secondes par minute et de tirer des deux-points toutes les secondes impaires.
vacillant
Avec un redessin constant de l'écran, le scintillement devient perceptible. Pour éviter cela, il est logique de ne pas effacer l'écran, mais de dessiner un nouveau texte par-dessus l'ancien. Si le texte lui-même ne change pas, il n'y aura pas de scintillement à l'écran. Ensuite, la fonction de rafraîchissement de l'heure ressemblera à ceci :
Clavier ACL ACL; 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 compté comme enfoncé à chaque fois que le programme est exécuté, de sorte que n'importe quel nombre de fois peut être traité en un seul clic. Vous devez forcer le programme à attendre les "pompes" séparément. Commençons le programme principal comme ceci :
intlb=0 ; // la variable stocke l'ancienne valeur du bouton void loop()() // programme principal int cb,rb; // définit 2 variables, pour le bouton effectivement pressé et pour celui que le programme considérera comme pressé cb=rb= lcd.button(); // au début, on peut supposer qu'il s'agit du même bouton si (rb!=KEYPAD_NONE) showval=1; // la variable indique que tant que le bouton est enfoncé, ce qui est configuré ne doit pas blink if (cb!=lb) lb= cb; // si l'état du bouton a changé, mémoriser le nouveau, else cb=KEYPAD_NONE; // sinon on dit au programme que tous les boutons ont été relâchés il y a longtemps .

Travailler avec une minuterie

En fait, pour que tout le travail avec la minuterie se compose de deux éléments importants :
  • Initialisation du mécanisme d'interruption à partir de la minuterie dans un mode pratique pour nous
  • 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 souhaitées. Quels registres et quelles valeurs exactement à définir, vous devez regarder dans ... la fiche technique du processeur :(. Honnêtement, 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 de la série, les numéros de bits peuvent différer.Et j'ai personnellement découvert qu'une tentative de réglage des bits conformément à la fiche technique du processeur voisin entraînait des résultats désastreux ... Mais néanmoins, tout n'est pas aussi triste que cela puisse paraître, car car il y a aussi des noms pour ces bits, ils sont plus ou moins communs pour différents processeurs... Donc, nous n'utiliserons pas de valeurs numériques, seulement des noms.

Pour commencer, rappelez-vous qu'il existe plusieurs minuteries dans les microcontrôleurs AVR. Zéro est utilisé pour calculer les valeurs de delay() et des choses comme ça, donc nous ne l'utiliserons pas. En conséquence, nous utilisons le premier. Par conséquent, plus loin dans la désignation des registres, on glissera souvent, pour mettre en place, disons, la deuxième minuterie, il faut y mettre un diable.

Toute initialisation de temporisateur doit se produire dans la procédure setup(). Il consiste à placer des valeurs dans 4 registres, TCCR1A, TCCR1B, TIMSK1, OCR1A. Les 2 premiers d'entre eux sont appelés "registres de contrôle du compteur 1 A et B". Le troisième est le "registre de masque d'interruption du temporisateur/compteur 1" et le dernier est le "registre de comparaison du compteur 1 A".

Il est d'usage d'utiliser les commandes suivantes pour régler les bits (il est clair qu'il existe de nombreuses options, mais ce sont les plus couramment utilisées) :
MORDRE |= (1<< POSITION)
c'est-à-dire que nous poussons un "1" sur le bit POSITION de droite à gauche et dessinons un "ou" logique entre les octets cible et reçu. Lorsque le contrôleur est allumé, les valeurs de tous ces registres contiennent 0, nous oublions 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 régler la minuterie, mais nous devons obtenir les éléments suivants à partir de la minuterie :

  • Pour que la minuterie passe en mode CTC (c'est-à-dire en mode de comptage avec réinitialisation après un match, "Clear Timer on Compare match"), à en juger par la fiche technique, cela est réalisé en définissant les bits WGM12 : 0 = 2 , ce qui signifie en soi mettre les bits de la seconde à zéro à la valeur "2", c'est-à-dire "010", commande TCCR1B |= (1<< WGM12) ;
  • Puisque 16 MHz (c'est-à-dire une telle fréquence pour un résonateur à quartz sur ma carte) c'est beaucoup, choisissez le diviseur maximum possible, 1024 (c'est-à-dire que seul chaque 1024e cycle atteindra notre compteur), CS12 : 0 = 5
  • Faites en sorte que l'interruption arrive lorsqu'elle correspond au registre A, pour ce compteur TIMSK1 |= (1<< OCIE1A)
  • Précisez quand il atteint quelle valeur appeler le traitement d'interruption, cette valeur est placée dans le même registre A du compteur 1 (son nom complet est OCR1A), l'interruption, par coïncidence avec laquelle nous avons inclus le paragraphe précédent.

Comment calculer combien de temps nous avons besoin pour effectuer des calculs? - C'est facile, 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 rapport de division était de 1. Puisqu'il est de 1024, 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. Nous avons donc fait une erreur auparavant et nous devons prendre un facteur de division plus petit. Et le suivant, nous avons 256, ce qui donne 62 500 ticks par seconde, ou 31 250 en une demi-seconde. Nous avons un compteur 16 bits, il peut donc compter jusqu'à 65536. En d'autres termes, il nous suffit d'une demi-seconde et d'une seconde. Nous grimpons dans la fiche technique, puis dans la source et la fixons à CS12:0=4 , et après cela OCR1A = 31249 ; (si je comprends bien, un cycle va soit se réinitialiser, 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 gestion des interruptions a quelque peu changé, elle ressemble maintenant à 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 se compose maintenant du mot réservé ISR et d'une indication de l'interruption spécifique que cette fonction gère entre parenthèses. Et à l'intérieur de cette fonction, comme vous pouvez le voir, il n'y a rien de fantastique. Même le RETI obligatoire, comme vous pouvez le voir, 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 sur la carte. Pratique et cool :) second++; if ((second %2) && lastshowval) ( // ceci et les 7 lignes suivantes sont juste pour lastshowval = 0; // pour pouvoir obtenir cet effet amusant, comme sur une horloge matérielle, showval = 0; // quand en mode configuration, disons minutes, la valeur du paramètre réglable clignote ) if (!(second %2) && !lastshowval)( // uniquement lorsque les boutons sont relâchés, tandis que les boutons sont enfoncés, il s'allume juste. lastshowval = 1; showval = 1; ) if ( second>=120) ( // encore mes 120 secondes dans une minute. Eh bien, qui s'en soucie maintenant ? seconde-=120 ; minute++ ; if (minute>=60)( minute- =60; heure++; si (heure>=24) ( heure-=24; jour++; si (jourlarge(jour,mois,année) // renvoie vrai si la valeur du jour // est supérieure au maximum possible pour ce mois de cette année.) ( jour=1; mois++; si (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 le travail avec les interruptions de minuterie en russe.

Leçon 10

Minuteries-compteurs. Interruptions

Aujourd'hui, nous allons découvrir ce qui est minuteries-compteurs dans les microcontrôleurs et à quoi ils servent, ainsi que ce qui est interrompt et pourquoi ils sont nécessaires.

Minuteries-compteurs- ce sont des dispositifs ou des modules dans le microcontrôleur qui, comme son nom l'indique, considèrent constamment quelque chose. Ils comptent soit jusqu'à une certaine valeur, soit jusqu'à une telle valeur car ce sont des bits. Ils comptent constamment à la même vitesse, avec la vitesse de la fréquence d'horloge du microcontrôleur, corrigée des diviseurs de fréquence, que nous configurerons dans certains registres.

Et ces temporisateurs comptent constamment si on les initialise.

Minuteries en MK Atmega8 Trois.

Deux d'entre eux sont huit bits les temporisateurs, c'est-à-dire ceux qui ne peuvent compter que jusqu'à 255. Cette valeur ne nous suffira pas. Même si nous appliquons le diviseur de fréquence maximum, alors nous ne compterons pas une seconde, nous ne pourrons même pas compter une demi-seconde. Et notre tâche est exactement celle-ci, compter jusqu'à 1 seconde afin de contrôler l'augmentation du nombre d'indicateurs LED. Bien sûr, vous pouvez également appliquer une augmentation de la variable à une certaine valeur, mais je voudrais un compte entièrement matériel.

Mais il y a une autre minuterie - c'est un véritable 16 bits minuteur. Il n'a pas seulement 16 bits, mais il y a encore certains charmes que d'autres minuteries n'ont pas. Nous verrons ces options plus tard.

C'est ce temporisateur 16 bits que nous allons étudier et utiliser aujourd'hui. De plus, après avoir pris connaissance de cette minuterie, il ne vous en coûtera rien d'étudier par vous-même le travail des deux autres, car ils sont beaucoup plus simples. Néanmoins, nous envisagerons également des temporisateurs 8 bits à l'avenir, car un seul temporisateur ne nous suffira pas pour réaliser des tâches plus complexes.

Maintenant brièvement sur les interruptions.

Interruptions (Interruptions) sont des mécanismes qui cassent le code en fonction de certaines conditions ou d'une certaine situation, ce qui dictera certains des périphériques, modules et bus qui se trouvent dans le microcontrôleur.

Il existe 19 types d'interruptions dans notre contrôleur Atmega8. Les voici tous dans le tableau de la documentation technique du contrôleur

Quel type de conditions peut être? Dans notre cas, par exemple, la minuterie a compté jusqu'à une certaine valeur, ou, par exemple, un octet et d'autres conditions sont arrivés à un bus.

Pour le moment, nous traiterons l'interruption, qui se trouve dans le tableau placé 7 positions plus haut - MINUTERIE1 COMPA, appelée à l'adresse 0x006.

Regardons maintenant notre temporisateur 16 bits, ou MINUTERIE1.

Voici sa structure

On y voit un registre TCNTn, dans lequel le nombre change constamment, c'est-à-dire qu'il augmente constamment. En fait, c'est le compteur. C'est-à-dire que ce registre stocke le nombre jusqu'auquel le temporisateur a compté.

Et dans les registres OCRNA et OCRnB(les lettres n sont le numéro de la minuterie, dans notre cas ce sera 1) sont les registres dans lesquels on entre le numéro avec lequel le chilo sera comparé dans le registre TCNTn.

Par exemple, nous avons entré un certain nombre dans le registre OCRnA, et dès que ce nombre correspond à la valeur dans le registre de comptage, une interruption se produit et nous pouvons la traiter. Les temporisateurs avec interruptions sont très similaires au délai habituel dans le code, seulement lorsque nous sommes dans un délai, alors nous ne pouvons exécuter aucun code à ce moment-là (enfin, encore une fois, au sens figuré "nous", en fait ALU). Et lorsque la minuterie compte, alors tout le code de notre programme est exécuté silencieusement à ce moment-là. Nous gagnons donc énormément en ne laissant pas d'énormes ressources de contrôleur inactives pendant une seconde ou même une demi-seconde. À ce stade, nous pouvons traiter les pressions sur les boutons, que nous pouvons également traiter dans une minuterie et plus encore.

Il existe également un registre TCCR. Ce registre est le registre de contrôle. Là, certains bits sont configurés qui sont responsables de la configuration du temporisateur.

La minuterie dispose également de plusieurs modes, que nous découvrirons également un peu plus tard.

Il se compose de deux moitiés, puisque nous avons un contrôleur 8 bits et qu'il ne peut pas avoir de registres 16 bits. Par conséquent, dans une moitié du registre (et physiquement dans un registre), la partie supérieure du registre est stockée, et dans l'autre, la partie inférieure. Vous pouvez également l'appeler une paire de registres composée de deux registres distincts TCCR1A et TCCR1B. Le chiffre 1 signifie que le registre appartient au temporisateur 1.

Ce registre TCCR est chargé de régler le diviseur pour que le temporisateur ne compte pas si vite, il est également responsable (ou plutôt, ses certains bits) de régler un certain mode.

Les bits WGM sont responsables de la définition du mode.

On voit ici beaucoup de variétés de régimes.

Normal- c'est le mode normal, la minuterie compte jusqu'à la fin.

PWM- c'est PWM seules différentes variétés, c'est-à-dire que la minuterie peut jouer un rôle modulateur de largeur d'impulsion. Nous nous familiariserons avec cette technologie dans les leçons ultérieures.

CTC- c'est une réinitialisation par coïncidence, juste ce dont nous aurons besoin. C'est ici que les registres TCNT et OCR sont comparés. Il existe deux modes de ce type, nous avons besoin du premier, le second fonctionne avec un registre différent.

Nous n'étudierons pas tous les types de modes dans cette leçon. Lorsque nous aurons besoin de ces modes, nous le découvrirons.

Eh bien, ne nous tourmentons pas avec la documentation et essayons enfin de mettre quelque chose dans certains registres.

Le code, comme toujours, a été créé à partir d'un projet précédent. Pour le proteus, le code a également été copié et renommé à partir de la dernière leçon, et le chemin vers le nouveau firmware a également été indiqué dans les propriétés du contrôleur. Nous nommerons les projets Test07.

Essayons de compiler le code comme toujours et de l'exécuter dans Proteus. Si tout fonctionne bien, nous commençons à ajouter un nouveau code.

Ajoutons une autre fonction, puisque nous avons appris à ajouter des fonctions dans la dernière leçon. Le code de la fonction sera placé après la fonction segchar et avant la fonction main. Après, en raison du fait que nous appellerons la fonction segchar à l'intérieur de notre nouvelle fonction.

De plus, nous allons créer non pas une fonction, mais deux. Dans une fonction, nous placerons tout le code d'initialisation de notre minuterie, et l'autre fonction sera le gestionnaire d'interruption de la minuterie, et ces fonctions sont spécifiques et vous n'avez pas besoin de les appeler. En cas de besoin, ils s'appelleront eux-mêmes, selon certaines conditions qui ont été convenues ci-dessus.

Nous appellerons donc la première fonction timer_ini

//———————————————

videtimer_ini( vide)

{

}

//———————————————

Aussi, donnez nos fonctions, ainsi que des blocs complets avec la déclaration des variables globales, avec des prototypes de fonctions, nous nous séparerons les uns des autres avec des tirets tels que le compilateur ne traitera pas en raison de la présence de deux barres obliques devant et prendra eux pour commentaires. A travers ces grandes lignes, nous verrons où une fonction se termine et une autre commence.

Cette fonction, comme nous pouvons le voir, n'a pas d'arguments - pas d'entrée, pas de retour. Appelons immédiatement cette fonction dans la fonction main()

non signécarbonisermaiscount=0,butstate=0 ;

timer_ini();

Nous allons maintenant commencer à remplir lentement cette fonction avec du code.

Commençons par un registre de contrôle de minuterie, tel que TCCR1B. En utilisant notre opération "OU" préférée, nous en mettrons un dans un certain bit du registre

videtimer_ini( vide)

TCCR1B|= (1<< WGM12);

D'après le commentaire, nous voyons que nous travaillons avec des bits de mode, et nous ne définirons que le bit WGM12 à partir d'eux, le reste sera laissé à zéro. Sur cette base, nous avons configuré le mode suivant :

La minuterie a également un tel registre - TIMSK. Ce registre est responsable des masques d'interruption - masque d'interruption. Ce registre est disponible pour tous les temporisateurs, pas seulement pour le premier, il est commun. Dans ce registre, nous positionnerons le bit OCIE1A, ce qui activera le type d'interruption dont nous avons besoin MINUTERIE1 COMPA

TCCR1B|= (1<< WGM12); // définit le mode CTC (réinitialisé par coïncidence)

TIMSK|= (1<< OCIE1A);

Jouons maintenant avec les registres de comparaison eux-mêmes OCR1A(H et L). Pour ce faire, vous devez faire quelques calculs. S'inscrire OCR1AH stocke la partie haute du nombre pour comparaison, et le registre OCR1AL- plus jeune.

Mais avant de compter, écrivons le code pour l'instant avec n'importe quelles valeurs de ce registre puis corrigeons-le, puisque nous allons alors initialiser le diviseur et il participera également au calcul du temps de comptage requis. Sans diviseur, la minuterie comptera trop vite.

TIMSK|= (1<< OCIE1A); // définit le bit d'activation d'interruption du 1er compteur par coïncidence avec OCR1A (H et L)

OCR1AH= 0b10000000 ;

OCR1AL= 0b00000000 ;

TCCR1B|= ( ); // définit le diviseur.

Nous ne fixons pas encore de diviseur, puisque nous ne l'avons pas encore calculé. Allons-y.

Alors que nous avons dans le registre OCR1A est le nombre 0b1000000000000000, qui correspond au nombre décimal 32768.

Le microcontrôleur travaille pour nous, comme convenu, à une fréquence de 8 000 000 Hz.

Divisez 8 000 000 par 32 768 pour obtenir environ 244,14. C'est avec une telle fréquence en hertz que notre temporisateur fonctionnera si nous n'appliquons pas de diviseur. C'est-à-dire que nos chiffres changeront 244 fois par seconde, donc nous ne les verrons même pas. Par conséquent, vous devrez appliquer un diviseur de fréquence de minuterie. Choisissons un diviseur par 256. Cela nous conviendra parfaitement, puis nous ajusterons exactement jusqu'à 1 Hz avec un nombre de comparaison.

Voici les diviseurs pour 1 minuterie

J'ai mis en surbrillance le diviseur dont nous avons besoin dans le tableau. Nous voyons qu'il nous suffit de régler le bit CS12.

Puisque nous avons un diviseur de fréquence de 256, nous diviserons 8000000 par ce diviseur, il en résultera 31250, c'est ce que nous devons entrer dans le nombre en TCNT. Jusqu'à un tel nombre, notre minuterie comptera jusqu'à 1 seconde. Le nombre 31250 est 0b0111101000010010 en binaire. Mettons ce nombre dans la paire de registres et appliquons également le diviseur

OCR1AH= 0b 01111010 ; //écrit un nombre dans le registre pour comparaison

OCR1AL= 0b 00010010 ;

TCCR1B|= (1<< CS12 ); // définit le diviseur.

C'est tout avec cette fonctionnalité.

Maintenant, la fonction suivante est le gestionnaire d'interruption du minuteur par coïncidence. Elle écrit comme ça

ISR( TIMER1_COMPA_vect)

{

}

Et le corps de cette fonction sera exécuté par lui-même lors de l'occurrence d'une coïncidence de nombres.

Nous avons besoin d'une variable. Déclarons-le globalement, en début de fichier

#comprendre

//———————————————

non signécarboniserje;

//———————————————

En conséquence, nous supprimerons la même variable du code dans la fonction main ()

entierprincipale( vide)

non signécarboniserje;

Nous commentons également tout le code dans une boucle infinie. Son rôle sera désormais joué par un chronométreur, et, je pense, il s'en sortira pas pire, et même mieux, sans interférer avec "personne".

tandis que(1)

{

// pour(i=0;i<10;i++)

// {

// tandis que (butstate==0)

// {

// si (!(PINB&0b00000001))

// {

// si(mais compte< 5)

// {

// butcount++;

// }

// autre

// {

// je=0 ;

// butstate=1;

// }

// }

// autre

// {

// si(butcount > 0)

// {

// butcount-;

// }

// autre

// {

// butstate=1;

// }

// }

// }

// segmentchar(i);

// _delay_ms(500);

//butstate=0;

// }

Maintenant, en fait, le corps de la fonction de gestionnaire. Ici, nous appellerons la fonction segchar. Ensuite, nous augmenterons de 1 variable je. Et pour qu'il ne dépasse pas un seul chiffre, nous allons le réinitialiser sous cette condition

ISR( TIMER1_COMPA_vect)

si( je>9) je=0;

segchar( je);

je++;

Maintenant, corrigeons un peu le code au début de la fonction main(). Port , qui est responsable de l'état des segments, nous marquerons avec des, de sorte que lorsqu'il est allumé, l'indicateur ne s'allume pas, car il est avec une anode commune. Ensuite, nous mettons le nombre 0 dans la variable globale i ici, juste pour des raisons d'ordre. En général, en règle générale, au départ dans des variables non initialisées, et donc toujours des zéros. Mais nous l'initialisons toujours. Et, plus important encore, pour que l'interruption du temporisateur fonctionne, il ne suffit pas de l'inclure dans l'initialisation du temporisateur. Aussi, en général, pour que toutes les interruptions fonctionnent, les interruptions globales doivent être activées. Il existe une fonction spéciale pour cela. sei() - Définir une interruption.

Maintenant, le code sera comme ça

DDRB= 0x00 ;

PORTD= 0b 11111111 ;

PORTB= 0b00000001 ;

je=0;

sei();

tandis que(1)

De plus, nous devons également inclure le fichier de bibliothèque d'interruptions au début du fichier

#comprendre

#comprendre

#comprendre

De plus, nous n'aurons pas encore besoin de variables pour le bouton, puisque nous n'allons pas travailler avec le bouton aujourd'hui. Commentons-les

entierprincipale( vide)

//caractère non signé butcount=0, butstate=0 ;

timer_ini();

Collectons notre code et vérifions d'abord ses performances dans Proteus. Si tout fonctionne bien, nous vérifierons également le schéma en direct

Tout fonctionne pour nous. Excellent!

Voici un tel chronomètre. Mais comme nous n'avons même pas de résonateur à quartz, ce chronomètre ne peut pas être qualifié de précis.

Cependant, aujourd'hui, nous avons beaucoup appris. Nous avons appris les interruptions, appris à les gérer, appris à travailler avec des minuteries, configuré plusieurs nouveaux registres de microcontrôleur, avant cela, nous ne travaillions qu'avec des registres de port. De plus, à cause de tout cela, nous avons considérablement déchargé l'unité arithmétique-logique de notre microcontrôleur.

Regarder la leçon vidéo

Vues des publications : 17 258

Plusieurs interruptions peuvent être nécessaires lors de la mise en œuvre du projet, mais si chacune d'elles a la priorité maximale, alors en fait aucune des fonctions ne l'aura. Pour la même raison, il n'est pas recommandé d'utiliser plus d'une douzaine d'interruptions.

Les gestionnaires ne doivent être appliqués qu'aux processus qui ont la sensibilité maximale aux intervalles de temps. N'oubliez pas que tant que le programme est dans le gestionnaire d'interruptions, toutes les autres interruptions sont désactivées. Un grand nombre d'interruptions entraîne une détérioration de leur réponse.

Au moment où une interruption est active et que les autres sont désactivées, il y a deux nuances importantes que le concepteur de circuit doit prendre en compte. Tout d'abord, le temps d'interruption doit être le plus court possible.

Cela garantira que toutes les autres interruptions planifiées ne seront pas manquées. Deuxièmement, lors du traitement d'une interruption, le code du programme ne doit pas nécessiter d'activité d'autres interruptions. Si cela n'est pas empêché, le programme se bloquera simplement.

N'utilisez pas de traitement long boucle() , il est préférable de développer du code pour un gestionnaire d'interruptions avec la variable définie sur volatile. Il indiquera au programme qu'aucun autre traitement n'est nécessaire.

Si l'appel de fonction Mettre à jour() est nécessaire, vous devrez d'abord vérifier la variable d'état. Cela déterminera si un traitement supplémentaire est nécessaire.

Avant de commencer à configurer la minuterie, vous devez vérifier le code. Les minuteries Anduino doivent être attribuées à des ressources limitées, car il n'y en a que trois et elles sont utilisées pour exécuter diverses fonctions. Si vous êtes confus avec l'utilisation des minuteries, un certain nombre d'opérations peuvent simplement cesser de fonctionner.

Sur quelles fonctions fonctionne une minuterie particulière ?

Pour le microcontrôleur Arduino Uno, chacune des trois minuteries a ses propres opérations.

Donc Minuterie0 est responsable de PWM sur les cinquième et sixième broches, fonctions millis() , micros() , retard() .

Une autre minuterie - minuterie1, utilisé avec PWM sur les neuvième et dixième broches, avec bibliothèques WaveHC et servo.

Minuterie2 fonctionne avec PWM sur les broches 11 et 13, ainsi qu'avec Ton.

Le concepteur du circuit doit veiller à l'utilisation sûre des données partagées. Après tout, une interruption arrête toutes les opérations du processeur pendant une milliseconde et l'échange de données entre boucle() et les gestionnaires d'interruption doivent être permanents. Une situation peut survenir lorsque le compilateur, afin d'atteindre ses performances maximales, commence à optimiser le code.

Le résultat de ce processus sera de sauvegarder une copie des principales variables de code dans le registre, ce qui garantira la vitesse maximale d'accès à celles-ci.

L'inconvénient de ce processus peut être que les valeurs réelles sont remplacées par des copies enregistrées, ce qui peut entraîner une perte de fonctionnalité.

Pour éviter cela, vous devez utiliser une variable volatil , ce qui aidera à éviter les optimisations inutiles. Lorsque vous utilisez de grandes baies qui nécessitent des cycles pour les mises à jour, vous devez désactiver les interruptions au moment de ces mises à jour.

En fait, la minuterie du microcontrôleur est un compteur numérique, uniquement "fantaisie". Un signal d'horloge est appliqué à l'entrée du compteur, sur la base duquel le compteur augmente sa valeur. Lorsque des événements se produisent - un débordement de compteur ou une coïncidence de sa valeur avec une donnée - une demande d'interruption est générée.

Voyons comment utiliser la minuterie T0 en mode Normal. Dans ce mode, le temporisateur compte à partir d'une certaine valeur initiale du registre de comptage jusqu'à la valeur maximale possible (jusqu'à 255 ou 0xFF). Lorsque le temporisateur T0 compte jusqu'au maximum, puis au cycle suivant du temporisateur, le registre de comptage TCNT0 déborde - il est remis à zéro et le drapeau TOV0 est mis à 1. Si les interruptions sont activées globalement dans le programme (drapeau I du registre SREG) et l'interruption du temporisateur T0 sur débordement (drapeau TOIE0 du registre TIMSK), alors le microcontrôleur appellera le gestionnaire approprié. Si la valeur du registre de comptage correspond au registre de comparaison OCR0, alors le drapeau OCF0 sera défini et, si l'interruption d'événement de correspondance est activée, son gestionnaire sera lancé.

Temporisateur T0 en mode Normal

Considérons une tâche pratique - nous devons interroger le bouton toutes les 20 ms. Fréquence du microcontrôleur 8 MHz, microcontrôleur ATmega16.

La première chose à faire est de décider du choix du facteur de prédivision du temporisateur et de calculer la valeur initiale du registre de comptage TCNT0.

Le temporisateur T0 peut être cadencé à partir du signal d'horloge interne du microcontrôleur ou d'un signal externe, qui est envoyé à la broche T0. Lorsqu'il fonctionne à partir d'un signal d'horloge interne, l'utilisateur peut sélectionner les facteurs de division de fréquence de ce signal. Le temporisateur T0 a cinq facteurs de prédivision possibles - 1, 8, 64, 256, 1024.

Pour résoudre le problème, je raisonne comme suit. Si un cycle de la minuterie T0 avait une période de 1 ms, cela me conviendrait. 20 cycles donnent 20 ms. Quel est le rapport du prédiviseur du temporisateur pour se rapprocher d'une période d'horloge de 1 ms ? Tu peux compter.

Fréquence d'horloge du microcontrôleur Fcpu = 8000000 Hz
Période d'horloge du microcontrôleur Tcpu = 1/Fcpu
La période d'horloge du temporisateur T0 est Tt0 = (1/Fcpu)/k = k/Fcpu

Avec k = 1024, la période d'horloge du temporisateur T0 sera égale à Tt0 = 1024/8000000 = 0,128 ms

C'est la période d'horloge maximale que nous pouvons obtenir dans nos conditions (Fcpu = 8 MHz). Avec des coefficients plus petits, la période sera encore plus courte.

Eh bien, disons qu'un cycle de minuterie est de 0,128 ms, le registre de comptage aura-t-il une capacité suffisante pour compter cet intervalle de temps et combien de cycles cela prendra-t-il ? Nous divisons l'intervalle de temps requis (20 ms) par la durée d'un cycle de minuterie et obtenons la réponse.

n = t/Tto = 20 ms/ 0,128 ms = 156,25

Arrondi à un tout, nous obtenons 156 cycles. Celle-ci est inférieure à 255 (la valeur maximale du registre de comptage), donc la capacité du registre de comptage TCNT0 est suffisante.

La valeur initiale du registre de comptage TCNT0 est calculée comme la différence entre le nombre maximal de cycles de temporisateur T0 et celui requis, c'est-à-dire 256 - 156 = 100. (256 est le nombre maximal d'intervalles de temps que tout temporisateur 8 bits peut compter.)

Je pense qu'il est maintenant clair comment calculer la valeur initiale de TCNT0 pour le mode Normal:

Calculer la période d'un cycle de temporisateur Tt0 = k/Fcpu,
- calculer le nombre de cycles requis pour un intervalle donné n = t/Tto,
- calculer la valeur initiale du registre de comptage TCNT0 = 256 - n.

Vous pouvez automatiser cette procédure à l'aide de macros. Par exemple, comme ceci :

#définir F_CPU 8000000UL
#define TIME_MS(temps, k) (256L - ((temps)*(F_CPU))/(1000L*(k)))

Mais avec une telle macro, il faut être vigilant, pour certaines valeurs de temps et de k, des erreurs peuvent survenir.

Passons maintenant au code. Pour utiliser le temporisateur T0 (et tout autre également), vous devez le configurer (l'initialiser) et décrire le gestionnaire d'interruption (s'il est utilisé).

L'initialisation du temporisateur comprend les étapes suivantes :

arrêter le chronomètre,
- réglage du mode Normal sur TCCR0 sans démarrage,
- fixer la valeur initiale de TCNT0,
- réinitialiser les drapeaux dans le registre TIFR,
- activer l'interruption de débordement dans TIMSK,
- réglage du prescaler sur TCCR0, c'est-à-dire le début de la minuterie

Cette séquence est sujette à variation.

Pour notre tâche, le code d'initialisation ressemblera à ceci :


/*valeur du registre de comptage*/
#définir T_POLL 100

TCCR0 = 0 ;
TCCR0 = (0<TCNT0=T_POLL ;
TIFR = (1<TIMSK |= (1<TCCR0 |= (1<

La deuxième ligne d'initialisation est, en fait, inutile, elle est ajoutée par souci de clarté. Pour voir clairement quel mode de minuterie est réglé.

Les drapeaux d'interruption dans le registre TIFR sont effacés en écrivant un 1 sur le bit approprié. Cette opération doit être effectuée précisément en réécrivant le registre, et non en utilisant un OU au niveau du bit. Et c'est pourquoi.

Disons que le registre TIFR a deux drapeaux d'interruption, TOV1 et TOV0, définis. TOV0 nous devons réinitialiser. Lors du réglage du bit requis avec OUla chose suivante se produit.


//TIFR est 0b00000101
// ensemble de drapeaux TOV1 et TOV0
//le code est exécuté TIFR |= (1<
//TIFR est copié dans R16
EN R16, 0x38

//dans R16 le bit TOV0 est défini
// même s'il est déjà installé
ORI R16, 0x02

// R16 égal à 0b00000101 est écrit dans le registre TIFR
SORTIE 0x38, R16

En conséquence, les deux drapeaux sont réinitialisés et nous voulions en réinitialiser un.

Nous continuons.

La syntaxe pour décrire les gestionnaires d'interruptions est légèrement différente pour différents compilateurs. Pour IAR, le gestionnaire d'interruption du temporisateur T0 pour l'événement de débordement ressemblera à ceci :



{
TCNT0=T_POLL ;

/*le sondage du bouton devrait être ici*/

TIMER0_OVF_vect est l'adresse du vecteur d'interruption d'événement de débordement. Il est extrait des fichiers d'en-tête du microcontrôleur. Dans ce cas, je l'ai extrait du fichier iom16.h.

La première ligne du gestionnaire (TCNT0 = T_POLL;) réécrit le registre de comptage, puis fixe sa valeur initiale. Si cela n'est pas fait, le temporisateur continuera à compter à partir de 0. La réécriture du registre de comptage doit être effectuée au début du gestionnaire d'interruption.

Tout le code de notre tâche ressemblera à ceci. (Le code est pour IAR. Pour les autres compilateurs, vous devez modifier les fichiers d'en-tête et le gestionnaire d'interruption.)

#comprendre
#comprendre
#comprendre

#définir T_POLL 100

int principal (vide)
{
/*initialisation de la minuterie*/

TCCR0 = 0 ;
TCCR0 = (0<TCNT0=T_POLL ;
TIFR |= (1<TIMSK |= (1<TCCR0 |= (1<

/*initialisation du reste des périphériques*/
DDRB |= (1<

enable_interrupt();
tandis que(1);

/* gestionnaire d'interruptions T0
événement de débordement*/
#pragma vecteur = TIMER0_OVF_vect
__interrupt void TimerT0Ovf(void)
{
/*réécrire le registre de comptage*/
TCNT0=T_POLL ;

/*interroger le bouton*/

/*inverser PB0 pour le débogage*/
PORTB ^= (1<

Contrôle de sortie OC0

En mode Normal, le temporisateur T0 peut changer l'état de la broche OC0 lorsque le registre de comptage et le registre de comparaison correspondent. Et même sans interruption. Les options de contrôle sont définies par les bits COM01 et COM00 du registre TCCR0.

Voici un exemple de programme qui génère une onde carrée sur la broche OC0.

#comprendre
#comprendre

int principal (vide)
{
/*initialise le temporisateur T0*/

TCCR0 = 0 ;
TCCR0 = (0<TCNT0 = 0 ;
ROC0 = 0 ;
TIMSK = 0 ;
TCCR0 |= (1<

/*initialise OC0*/
DDRB |= (1<

Tandis que(1);
renvoie 0 ;
}

La sortie OS0 changera d'état à l'opposé à la valeur zéro du registre de comptage.

Quelques points sur l'utilisation de la minuterie

Le gestionnaire d'interruption de minuterie (et tout autre périphérique) doit être aussi court que possible.

Si la valeur calculée pour le registre de comptage (ou registre de comparaison) est arrondie, l'intervalle de temps sera compté par le temporisateur avec une erreur.

Et le dernier. Il peut arriver que le traitement de l'interruption du temporisateur soit retardé (par exemple, à cause de la faute d'un autre gestionnaire) et que le registre TCNT0 ait déjà compté plusieurs cycles. Si vous écrasez simplement la valeur de TCNT0, la prochaine interruption sera appelée plus tard que nécessaire. Il s'avère que les interruptions précédentes (retardées) et nouvelles ne résisteront pas à l'intervalle requis.

Cette situation peut être lissée en écrasant le registre de comptage comme ceci :

TCNT0 = TCNT0 + startValue ;

L'ajout de la valeur courante du registre de comptage à celle initialisée tiendra compte de ces cycles supplémentaires.Certes, il y en a un MAIS ! Pour les grandes valeurs de startValue, l'opération d'addition peut provoquer un débordement du registre du compteur.

Par exemple, startValue = 250, et la minuterie a réussi à compter jusqu'à 10. Ensuite, l'opération d'addition conduira au résultat suivant :

10 + 250 = 260

Nous prenons 8 bits sur 260, nous en obtenons 4. 4 seront écrits dans TCNT0.

Partager