Étape 6: Pointeurs et Tables de choix
Quelqu'un d'entre vous qui ont une expérience à l’aide de C ou C++ déjà ont l’expérience des pointeurs. Nous utiliserons la même chose ici, dans le cadre des « tables de choix ».
Tables de recherche sont une autre façon de compactifying notre code pour le rendre plus court, plus élégant et plus facile à comprendre.
Tout d’abord permet d’écrire le code et puis nous allons expliquer ce qui se passe. Tout d’abord, en haut de notre programme, nous aurons une section intitulée "numéros:" suivie de certaines directives d’assembleur « .db ». Ces directives « define octets » et ce qu’ils font, c’est qu’ils placent ces octets dans l’ordre dans une certaine partie de « La mémoire de programme » définie par les numéros d’étiquettes. Afin que lorsque le code hexadécimal est chargé sur le microcontrôleur, un certain segment de la mémoire flash qui stocke toutes les instructions de programme contiendra ces octets un après l’autre dans l’ordre.
Puis nous pouvons obtenir réellement ces chiffres chaque fois que nous voulons qu’ils puisqu’ils seront toujours situés à certains emplacements de mémoire de programme spécifié. Rappelez-vous comment nous avons traité avec les interruptions ? Nous avons placé une instruction à exactement 0 x 0020 dans la mémoire programme. Nous savions que, advenant une interruption de débordement du timer le cpu serait vérifier cet emplacement exact et exécuter toute commande nous mis là. Eh bien, tables de recherche fonctionnent de façon très similaire.
Nous allons ré-écrire notre "dice:" étiqueté sous-routine, qui est celui qui raconte le microcontrôleur qui épingles pour mettre en marche pour obtenir quel numéro sur un dé, afin qu’au lieu d’un article long et laid du code, il peut utiliser une boucle et faire les choses plus simplement. Voici le nouveau code :
Vous voyez que c’est beaucoup plus courte. En fait, chaque fois que vous vous retrouvez répétant le même jeu d’instructions reprises à l’intérieur d’une sous-routine et la seule chose différente chaque fois est que certains nombre particulier est différent, c’est le moment idéal d’utiliser une table de choix. Dans les cas où vous utiliseriez une "boucle" for ou une routine « switch-case » en C ou en une autre langue, c’est le bon moment pour utiliser une table de correspondance en assembleur.
Tables de choix ont la réputation d’être compliqué, mais je ne pense pas que c’est mérité. Je vais essayer de l’expliquer de façon simple ici.
Commençons par la carte de mémoire d’atmega328p. Il existe trois différents types de mémoire stocker des choses. La « mémoire de programme » qui stocke notre programme, la « mémoire de données SRAM » qui contient tous les registres que nous utilisons comme ce généraliste travaillant les registres, les ports d’entrées-sorties et tous les registres que nous utilisons pour activer/désactiver bits et contrôle les façon dont les choses sont faites et enfin de la mémoire « EEPROM », ce qui nous introduira dans un prochain didacticiel (si j’ai dernier cette longue) et est utilisé pour stocker des informations qui ne disparaîtra pas, lorsque nous nous tournons l’appareil hors tension. Très utile si vous faites un jeu et que vous souhaitez stocker somebodies score jusqu'à la prochaine fois qu’ils jouent !
Nous savons que chaque octet d’un type donné de mémoire a une adresse. Par exemple, le premier octet du code nous exécutons est à 0 x 0000 et le gestionnaire d’interruption de débordement timer est à 0 x 0020, etc.. Vous remarquerez que puisque nous avons plus de 256 octets de mémoire dans notre espace de mémoire programme, nous ne pouvons simplement utiliser adresses 0 x 00 à 0xFF. En fait, nous avons 32k de mémoire flash dans l’espace mémoire de programme. Cela signifie que nous avons besoin des adresses de 0 x 0000 jusqu'à 0x7FFF.
Maintenant, supposons que nous voulons lire tout ce qui est à une adresse spécifique en mémoire ? Par exemple, lorsque le cpu obtient une interruption de débordement il va à 0 x 0020 et exécute l’instruction, que nous avons placés là. Que se passe-t-il si nous voulons placer des instructions ou données ou que ce soit à une adresse spécifique dans la mémoire programme et ensuite l’utiliser dans notre programme ? Nous pouvons, sauf que notre généraliste inscrit ne peut supporter 8 bits (1 octet) entre 0 x 00 et 0xFF, et comme nous l’avons vu, une adresse prend 2 octets à écrire vers le bas (entre 0 x 0000 et 0x7FFF). Y a donc pas assez de place dans un registre d’usage général (c'est-à-dire une variable comme r16) pour contenir une adresse mémoire programme. Nous ne pouvons dire « ldi r16, 0b0000000000000010 » par exemple, puisque R16 n’est pas assez grand. Donc si nous avons aucun moyen de stocker l’adresse complète comment pouvons nous y allons lors de l’émission ? Nous pouvons juste prendre le téléphone, appelez le cpu et dire « pouvez-vous aller et exécuter ce que nous avons stocké à 0x2a7f s’il vous plaît » vous devez avoir cette adresse dans un r16 ou quelque chose et puis « mov » il ou « out », il a partir de là.
Voici donc ce que les gens de ATmel ont fait pour résoudre ce dilemme. Ils ont double dessein quelques uns de nos registres à usage général. En particulier, si vous regardez le tableau 7-2 à la page 12 de la feuille de données, vous pouvez voir comment les registres d’usage général sont organisées. Les registres r26, r27, r28, r29, r30 et r31 peut également être combiné en paires appelées X, Y et Z. Alors que X est r26 et r27 ensemble, Y est r28 et r29 ensemble, et Z est r30 r31 ensemble. De cette façon, si nous prenons Z par exemple, la première moitié de celui-ci est r30 et la seconde moitié de celui-ci est r31. Donc si nous voulons conserver une adresse de mémoire de programme nous stockons seulement la moitié r30 et l’autre moitié de celui-ci dans la r31 et puis nous dire le cpu pour rechercher Z si nous voulons parler de tout cela ensemble. Ils ont mis en œuvre deux instructions que cela. Le premier est le spm, qui signifie « Store programme mémoire » et l’autre est l/min, qui est l’abréviation de « Mémoire de programme de charge ». Alors maintenant, si nous voulons obtenir ce que jamais les instructions ou les données sont stockées à l’adresse mémoire 0x24c8 par exemple, nous mettre cette adresse en r30 et r31 et puis, quand nous voulons obtenir les données, nous serions juste l/min à une variable par la pratique
qui ira adresse mémoire Z, prendre quelque données nous y mettre et le coller dans la r16. La chose cool à ce sujet, c’est que si nous y ajoutons 1 à l’aide de Z
puis Z sera désormais « point » l’adresse mémoire suivante après le premier. Alors que se nous tenir toute une liste de numéros en mémoire un après l’autre, que nous pouvons défile en Z s’incrémente.
Comment nous utiliser dans notre programme ?
Eh bien, puisque chaque nombre sur le dé est affiché par pour allumer et éteindre les différents ports comme PC2 et PB5 stockons-nous juste le nombre qui fait cela pour chaque nombre du dé. Par exemple si nous « out » 0b11010010 à PortC PC0 sera défini à 0, PB1 à 1, etc. et allumer les voyants correspondants de nous donner notre numéro sur le dé. Dans ce cas, le numéro 4.
Donc nous allons utiliser une « table de correspondance » appelée "numéros:" pour stocker toutes ces configurations différentes meurent et simplifier notre code.
Je pense que si vous lisez le code ci-dessus et chercher les diverses instructions du manuel d’instructions, vous pouvez facilement comprendre comment il fonctionne. La partie seulement bizarre est le premier bit où on Initialise le pointeur Z.
Ce qu’il fait est Initialise le pointeur Z pointe vers notre liste intitulée « numéros ». La raison pour les 2 fois à l’avant, c’est que nous voulons que l’adresse de « numéros » vers la gauche d’un espace (c’est à quel moment en deux fait nombres binaires). Cela laisse libre la mèche à droite (le bit le moins significatif) qui est ensuite utilisée pour décider quel octet de mémoire programme, nous nous référons au. C’est parce que la mémoire programme est large de 2 octets (16 bits). Ainsi, par exemple, dans notre table, nous avons tout d’abord deux nombres comme
0b01111111 .db, 0b11011110
Étant donné que l’espace mémoire programme est de 16 bits de large tous les deux de ces numéros seront réellement assis à la même adresse mémoire programme donc la façon dont nous saisir l’une ou l’autre est pourquoi nous avons besoin du « temps par 2 » ou vers la gauche des bits. Lorsque le « bit » de Z est 0 il pointera à la première de notre liste : 0b01111111, et lorsque le bit le moins significatif de Z est un 1 il pointera vers l’autre de notre liste : 0b11011110.
Comme vous pouvez le constater, l’ajout de 1 à Z modifie le moins significatif peu de 0 à 1, puis en ajoutant 1 à Z augmente à nouveau l’adresse de mémoire de programme et de la DSJ remonte à un zéro. Donc vous voyez qu’il fonctionne très bien pour la cueillette à toute notre liste de numéros mémorisés, un à la fois en incrémentant simplement Z.
Avis que quand nous passer l’adresse de « numéros » laissé en multipliant par 2 afin de libérer le bit le moins significatif à utiliser pour sélectionner le premier ou le deuxième octet stocké à cette adresse nous perdent le « bit le plus significatif » de l’adresse. Cela signifie que nous pouvons seulement stocker nos données de table de recherche dans les adresses où le bit le plus significatif n’est pas grave - c'est-à-dire toutes nos données nommées aura le bit le plus significatif même. Cela signifie que notre adresse est effectivement 15 bits de longs. 2 ^ 15 est 32768 différentes adresses disponibles pour nos données stockées. Nous allons regarder cela plus en détail dans le prochain tutoriel, alors ne vous inquiétez pas si c’est un peu confus à ce stade.
Maintenant vous savez comment utiliser les tables de recherche et les pointeurs de X, Y et Z pour simplifier votre écriture de code.
Nous allons maintenant donner le programme complet avec ces innovations incluses.