Table des matières:

Tutoriel assembleur AVR 3 : 9 étapes
Tutoriel assembleur AVR 3 : 9 étapes

Vidéo: Tutoriel assembleur AVR 3 : 9 étapes

Vidéo: Tutoriel assembleur AVR 3 : 9 étapes
Vidéo: AVR Ассемблер. Урок 9. USART. AVR Assembler. Lesson 9. USART. 2024, Juillet
Anonim
Tutoriel assembleur AVR 3
Tutoriel assembleur AVR 3

Bienvenue dans le tutoriel numéro 3 !

Avant de commencer, je veux faire une remarque philosophique. N'ayez pas peur d'expérimenter avec les circuits et le code que nous construisons dans ces tutoriels. Changez les fils, ajoutez de nouveaux composants, retirez des composants, modifiez des lignes de code, ajoutez de nouvelles lignes, supprimez des lignes et voyez ce qui se passe ! Il est très difficile de casser quoi que ce soit et si vous le faites, qui s'en soucie ? Rien de ce que nous utilisons, y compris le microcontrôleur, n'est très coûteux et il est toujours instructif de voir comment les choses peuvent échouer. Non seulement vous découvrirez ce qu'il ne faut pas faire la prochaine fois mais, plus important encore, vous saurez pourquoi ne pas le faire. Si vous êtes comme moi, quand vous étiez enfant et que vous aviez un nouveau jouet, il n'a pas fallu longtemps avant de l'avoir en morceaux pour voir ce qui le faisait marcher, n'est-ce pas ? Parfois, le jouet finissait par être irrémédiablement endommagé, mais ce n'était pas grave. Permettre à un enfant d'explorer sa curiosité même au point de jouets cassés est ce qui fait de lui un scientifique ou un ingénieur au lieu d'un lave-vaisselle.

Aujourd'hui, nous allons câbler un circuit très simple, puis entrer un peu dans la théorie. Désolé pour cela, mais nous avons besoin des outils ! Je promets que nous allons compenser cela dans le tutoriel 4 où nous allons faire une construction de circuits plus sérieuse et le résultat sera plutôt cool. Cependant, la façon dont vous devez faire tous ces didacticiels est très lente et contemplative. Si vous vous contentez de parcourir, de construire le circuit, de copier et de coller le code, puis de l'exécuter, bien sûr, cela fonctionnera, mais vous n'apprendrez rien. Vous devez penser à chaque ligne. Pause. Expérience. Inventer. Si vous le faites de cette façon, à la fin du 5ème didacticiel, vous aurez fini de construire des trucs sympas et vous n'aurez plus besoin de tutorat. Sinon, vous regardez simplement plutôt que d'apprendre et de créer.

En tout cas, assez de philosophie, on se lance !

Dans ce tutoriel, vous aurez besoin de:

  1. votre planche de prototypage
  2. une LED
  3. fils de connexion
  4. une résistance autour de 220 à 330 ohms
  5. Le manuel du jeu d'instructions: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. La fiche technique: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. un oscillateur à cristal différent (facultatif)

Voici un lien vers la collection complète de tutoriels:

Étape 1: Construire le circuit

Construire le circuit
Construire le circuit

Le circuit de ce tutoriel est extrêmement simple. Nous allons essentiellement écrire le programme "blink", donc tout ce dont nous avons besoin est ce qui suit.

Branchez une LED à PD4, puis à une résistance de 330 ohms, puis à la terre. c'est à dire.

PD4 - LED - R(330) - MASSE

et c'est tout !

La théorie va être difficile à battre cependant…

Étape 2: Pourquoi avons-nous besoin des commentaires et du fichier M328Pdef.inc ?

Je pense que nous devrions commencer par montrer pourquoi le fichier d'inclusion et les commentaires sont utiles. Aucun d'entre eux n'est réellement nécessaire et vous pouvez écrire, assembler et télécharger du code de la même manière sans eux et il fonctionnera parfaitement (bien que sans le fichier d'inclusion, vous puissiez recevoir des plaintes de l'assembleur - mais pas d'erreurs)

Voici le code que nous allons écrire aujourd'hui, sauf que j'ai supprimé les commentaires et le fichier include:

.device ATmega328P

.org 0x0000 jmp a.org 0x0020 jmp ea: ldi r16, 0x05 out 0x25, r16 ldi r16, 0x01 sts 0x6e, r16 sei clr r16 out 0x26, r16 sbi 0x0a, 0x04 sbi 0x0b, 0x04 b: r16 cbi 0x0b, 0x04 rcall c rjmp bc: clr r17 d: cpi r17, 0x1e brne d ret e: inc r17 cpi r17, 0x3d brne PC+2 clr r17 reti

assez simple non? Haha. Si vous avez assemblé et téléchargé ce fichier, la LED clignotera à une vitesse de 1 clignotement par seconde, le clignotement durant 1/2 seconde et la pause entre les clignotements durant 1/2 seconde.

Cependant, regarder ce code n'est guère éclairant. Si vous deviez écrire du code comme celui-ci et que vous vouliez le modifier ou le réutiliser à l'avenir, vous auriez du mal.

Mettons donc les commentaires et incluons le fichier afin que nous puissions en comprendre le sens.

Étape 3: Blink.asm

Voici le code dont nous allons discuter aujourd'hui:

;************************************

; écrit par: 1o_o7; Date:; version: 1.0; fichier enregistré sous: blink.asm; pour l'AVR: atmega328p; fréquence d'horloge: 16MHz (optionnel);************************************; Fonction du programme:---------------------; compte les secondes en faisant clignoter une LED;; PD4 - LED - R (330 ohms) - GND;;---------------------------------------.nolist.include "./m328Pdef.inc".list;===============; Déclarations:.def temp = r16.def débordements = r17.org 0x0000; emplacement mémoire (PC) du gestionnaire de réinitialisation rjmp Reset; jmp coûte 2 cycles cpu et rjmp n'en coûte que 1; donc à moins que vous ayez besoin de sauter plus de 8k octets; vous n'avez besoin que de rjmp. Certains microcontrôleurs donc uniquement; avoir rjmp et non jmp.org 0x0020; emplacement mémoire du gestionnaire de débordement Timer0 rjmp overflow_handler; aller ici si une interruption de débordement timer0 se produit;============ Reset: ldi temp, 0b00000101 out TCCR0B, temp; régler les bits du sélecteur d'horloge CS00, CS01, CS02 sur 101; cela met Timer Counter0, TCNT0 en mode FCPU/1024; donc ça marche à la CPU freq/1024 ldi temp, 0b00000001 sts TIMSK0, temp; définir le bit d'activation d'interruption de débordement du temporisateur (TOIE0); du registre de masque d'interruption temporisé (TIMSK0) sei; activer les interruptions globales -- équivalent à "sbi SREG, I" clr temp out TCNT0, temp; initialiser le Timer/Counter à 0 sbi DDRD, 4; définir PD4 pour sortir;======================; Corps principal du programme: clignotement: sbi PORTD, 4; allumer la LED sur le délai de rappel du PD4; le délai sera de 1/2 seconde cbi PORTD, 4; éteindre la LED sur PD4 rcall delay; le délai sera de 1/2 seconde rjmp clignoter; reboucle au départ différé: clr déborde; définir les débordements à 0 sec_count: débordements cpi, 30; comparer le nombre de débordements et 30 brne sec_count; branche pour revenir à sec_count s'il n'est pas égal à ret; si 30 débordements se sont produits, retournez à blink overflow_handler: inc déborde; ajouter 1 à la variable overflows cpi overflows, 61; comparer avec 61 brne PC+2; Program Counter + 2 (sauter la ligne suivante) si non égal clr déborde; si 61 débordements se sont produits remettre le compteur à zéro reti; retour d'interruption

Comme vous pouvez le voir, mes commentaires sont un peu plus brefs maintenant. Une fois que nous savons quelles sont les commandes du jeu d'instructions, nous n'avons plus besoin de l'expliquer dans les commentaires. Il suffit d'expliquer ce qui se passe du point de vue du programme.

Nous discuterons de ce que tout cela fait pièce par pièce, mais essayons d'abord d'avoir une perspective globale. Le corps principal du programme fonctionne comme suit.

Nous définissons d'abord le bit 4 de PORTD avec "sbi PORTD, 4", cela envoie un 1 à PD4 qui met la tension à 5V sur cette broche. Cela allumera la LED. Nous passons ensuite au sous-programme "delay" qui compte 1/2 seconde (nous expliquerons comment il procède plus tard). Nous revenons ensuite au clignotement et à l'effacement du bit 4 sur PORTD qui met PD4 à 0V et éteint donc la LED. Nous retardons ensuite encore 1/2 seconde, puis revenons au début du clignotement avec "rjmp blink".

Vous devriez exécuter ce code et voir qu'il fait ce qu'il devrait.

Et voila! C'est tout ce que ce code fait physiquement. La mécanique interne de ce que fait le microcontrôleur est un peu plus impliquée et c'est pourquoi nous faisons ce tutoriel. Discutons donc de chaque section à tour de rôle.

Étape 4: Directives de l'assembleur.org

Nous savons déjà ce que font les directives d'assembleur.nolist,.list,.include et.def de nos précédents tutoriels, alors examinons d'abord les 4 lignes de code qui suivent:

.org 0x0000

jmp Réinitialiser.org 0x0020 jmp overflow_handler

L'instruction.org indique à l'assembleur où dans la "Mémoire du programme" placer l'instruction suivante. Au fur et à mesure de l'exécution de votre programme, le "Compteur de programme" (en abrégé PC) contient l'adresse de la ligne en cours d'exécution. Donc, dans ce cas, lorsque le PC est à 0x0000, il verra la commande "jmp Reset" résidant dans cet emplacement mémoire. La raison pour laquelle nous voulons mettre jmp Reset à cet emplacement est que lorsque le programme démarre ou que la puce est réinitialisée, le PC commence à exécuter le code à cet endroit. Donc, comme nous pouvons le voir, nous venons de lui dire de "sauter" immédiatement à la section intitulée "Réinitialiser". Pourquoi avons-nous fait ça? Cela signifie que les deux dernières lignes ci-dessus sont simplement ignorées ! Pourquoi?

Eh bien, c'est là que les choses deviennent intéressantes. Vous allez maintenant devoir ouvrir une visionneuse pdf avec la fiche technique complète ATmega328p que j'ai indiquée sur la première page de ce tutoriel (c'est pourquoi c'est l'élément 4 dans la section "vous aurez besoin"). Si votre écran est trop petit ou si vous avez déjà beaucoup trop de fenêtres ouvertes (comme c'est le cas pour moi), vous pouvez faire ce que je fais et le mettre sur une liseuse ou sur votre téléphone Android. Vous l'utiliserez tout le temps si vous prévoyez d'écrire du code assembleur. Ce qui est cool, c'est que tous les microcontrôleurs sont organisés de manière très similaire et donc une fois que vous vous êtes habitué à lire les fiches techniques et à les coder, vous trouverez presque trivial de faire la même chose pour un microcontrôleur différent. Nous apprenons donc à utiliser tous les microcontrôleurs dans un sens et pas seulement l'atmega328p.

Bon, allez à la page 18 de la fiche technique et jetez un œil à la figure 8-2.

C'est ainsi que la mémoire de programme dans le microcontrôleur est configurée. Vous pouvez voir qu'il commence par l'adresse 0x0000 et est séparé en deux sections; une section flash d'application et une section flash de démarrage. Si vous vous référez brièvement au tableau 27-14 de la page 277, vous verrez que la section flash de l'application occupe les emplacements de 0x0000 à 0x37FF et que la section flash de démarrage occupe les emplacements restants de 0x3800 à 0x3FFF.

Exercice 1: Combien y a-t-il d'emplacements dans la mémoire du programme ? C'est à dire. convertissez 3FFF en décimal et ajoutez 1 puisque nous commençons à compter à 0. Étant donné que chaque emplacement mémoire a une largeur de 16 bits (ou 2 octets), quel est le nombre total d'octets de mémoire ? Convertissez maintenant ceci en kilo-octets, en vous rappelant qu'il y a 2^10 = 1024 octets dans un kilo-octet. La section flash de démarrage passe de 0x3800 à 0x37FF, combien de kilo-octets cela fait-il ? Combien de kilo-octets de mémoire nous reste-t-il à utiliser pour stocker notre programme ? En d'autres termes, quelle peut être la taille de notre programme ? Enfin, combien de lignes de code pouvons-nous avoir ?

Bon, maintenant que nous savons tout sur l'organisation de la mémoire flash du programme, continuons notre discussion sur les instructions.org. Nous voyons que le premier emplacement mémoire 0x0000 contient notre instruction pour sauter à notre section que nous avons nommée Reset. Maintenant, nous voyons ce que fait l'instruction ".org 0x0020". Il dit que nous voulons que l'instruction de la ligne suivante soit placée à l'emplacement mémoire 0x0020. L'instruction que nous y avons placée est un saut vers une section de notre code que nous avons étiquetée "overflow_handler"… maintenant pourquoi diable exigerions-nous que ce saut soit placé à l'emplacement mémoire 0x0020 ? Pour le savoir, nous nous tournons vers la page 65 de la fiche technique et examinons le tableau 12-6.

Le tableau 12-6 est un tableau de "Vecteurs de réinitialisation et d'interruption" et il montre exactement où le PC ira lorsqu'il recevra une "interruption". Par exemple, si vous regardez le vecteur numéro 1. La "source" de l'interruption est "RESET" qui est définie comme "Broche externe, réinitialisation à la mise sous tension, réinitialisation de Brown-out et réinitialisation du système de surveillance", ce qui signifie, le cas échéant ces choses arrivent à notre microcontrôleur, le PC commencera à exécuter notre programme à l'emplacement de mémoire du programme 0x0000. Et notre directive.org alors ? Eh bien, nous avons placé une commande à l'emplacement mémoire 0x0020 et si vous regardez dans le tableau, vous verrez que si un débordement Timer/Counter0 se produit (provenant de TIMER0 OVF), il exécutera tout ce qui se trouve à l'emplacement 0x0020. Donc, chaque fois que cela se produit, le PC sautera à l'endroit que nous avons nommé "overflow_handler". Cool non ? Vous verrez dans une minute pourquoi nous avons fait cela, mais terminons d'abord cette étape du didacticiel par un aparté.

Si nous voulons rendre notre code plus net et ordonné, nous devrions vraiment remplacer les 4 lignes dont nous discutons actuellement par les suivantes (voir page 66):

.org 0x0000

rjmp Réinitialiser; PC = 0x0000 reti; PC = 0x0002 reti; PC = 0x0004 reti; PC = 0x0006 reti; PC = 0x0008 reti; PC = 0x000A … reti; PC = 0x001E jmp overflow_handler: PC = 0x0020 reti: PC = 0x0022 … reti; PC = 0x0030 reti; PC = 0x0032

Ainsi, si une interruption donnée se produit, elle ne fera que "reti", ce qui signifie "retour d'interruption" et rien d'autre ne se produira. Mais si nous n'activons jamais ces diverses interruptions, alors elles ne seront pas utilisées et nous pourrons mettre du code de programme à ces endroits. Dans notre programme "blink.asm" actuel, nous n'allons activer que l'interruption de débordement timer0 (et bien sûr l'interruption de réinitialisation qui est toujours activée) et nous ne nous embêterons donc pas avec les autres.

Comment pouvons-nous "activer" l'interruption de débordement timer0 alors? … c'est le sujet de notre prochaine étape dans ce tutoriel.

Étape 5: Minuterie/Compteur 0

Minuterie/Compteur 0
Minuterie/Compteur 0

Jetez un oeil à l'image ci-dessus. C'est le processus de prise de décision du « PC » lorsqu'une influence extérieure « interrompt » le déroulement de notre programme. La première chose qu'il fait lorsqu'il reçoit un signal de l'extérieur indiquant qu'une interruption s'est produite est de vérifier si nous avons défini le bit « activation de l'interruption » pour ce type d'interruption. Si ce n'est pas le cas, il continue simplement à exécuter notre prochaine ligne de code. Si nous avons défini ce bit d'activation d'interruption particulier (de sorte qu'il y ait un 1 dans cet emplacement de bit au lieu d'un 0), il vérifiera alors si nous avons activé les "interruptions globales", sinon il ira à nouveau à la ligne suivante de code et continuer. Si nous avons également activé les interruptions globales, il ira à l'emplacement de la mémoire de programme de ce type d'interruption (comme indiqué dans le Tableau 12-6) et exécutera la commande que nous y avons placée. Voyons donc comment nous avons implémenté tout cela dans notre code.

La section Réinitialiser étiquetée de notre code commence par les deux lignes suivantes:

Réinitialiser:

ldi temp, 0b00000101 sur TCCR0B, temp

Comme nous le savons déjà, cela charge dans temp (c'est-à-dire R16) le nombre qui suit immédiatement, qui est 0b00000101. Ensuite, il écrit ce numéro dans le registre appelé TCCR0B à l'aide de la commande "out". Quel est ce registre ? Eh bien, passons à la page 614 de la fiche technique. Celui-ci se trouve au milieu d'un tableau résumant tous les registres. A l'adresse 0x25 vous trouverez TCCR0B. (Maintenant, vous savez d'où vient la ligne "out 0x25, r16" dans ma version non commentée du code). Nous voyons par le segment de code ci-dessus que nous avons défini le 0ème bit et le 2ème bit et effacé tout le reste. En regardant le tableau, vous pouvez voir que cela signifie que nous avons défini CS00 et CS02. Passons maintenant au chapitre de la fiche technique intitulé "Minuteur/Compteur 8 bits avec PWM". En particulier, allez à la page 107 de ce chapitre. Vous verrez la même description du registre "Timer/Counter Control Register B" (TCCR0B) que nous venons de voir dans le tableau récapitulatif des registres (donc nous aurions pu venir directement ici, mais je voulais que vous voyiez comment utiliser les tableaux récapitulatifs pour référence future). La fiche technique continue de donner une description de chacun des bits de ce registre et de ce qu'ils font. Nous allons sauter tout cela pour l'instant et tourner la page vers le tableau 15-9. Ce tableau montre la "Description du bit de sélection d'horloge". Maintenant, regardez dans cette table jusqu'à ce que vous trouviez la ligne qui correspond aux bits que nous venons de définir dans ce registre. La ligne dit "clk/1024 (depuis le prescaler)". Cela signifie que nous voulons que Timer/Counter0 (TCNT0) avance à une vitesse qui correspond à la fréquence du processeur divisée par 1024. Étant donné que notre microcontrôleur est alimenté par un oscillateur à cristal de 16 MHz, cela signifie que la fréquence à laquelle notre processeur exécute les instructions est 16 millions d'instructions par seconde. Ainsi, la fréquence à laquelle notre compteur TCNT0 tiquera est alors de 16 millions/1024 = 15625 fois par seconde (essayez-le avec différents bits de sélection d'horloge et voyez ce qui se passe - vous vous souvenez de notre philosophie ?). Gardons le nombre 15625 en tête pour plus tard et passons aux deux lignes de code suivantes:

température ldi, 0b00000001

sts TIMSK0, temp

Cela définit le 0ème bit d'un registre appelé TIMSK0 et efface tout le reste. Si vous jetez un œil à la page 109 de la fiche technique, vous verrez que TIMSK0 signifie "Timer/Counter Interrupt Mask Register 0" et notre code a défini le 0ème bit nommé TOIE0 qui signifie "Timer/Counter0 Overflow Interrupt Enable" … Là! Maintenant, vous voyez de quoi il s'agit. Nous avons maintenant le "bit d'activation d'interruption défini" comme nous le voulions à partir de la première décision de notre image en haut. Alors maintenant, tout ce que nous avons à faire est d'activer les "interruptions globales" et notre programme sera capable de répondre à ce type d'interruptions. Nous allons activer les interruptions globales sous peu, mais avant cela, vous avez peut-être été dérouté par quelque chose. Pourquoi diable ai-je utilisé la commande "sts" pour copier dans le registre TIMSK0 au lieu de l'habituel "out" ?

Chaque fois que vous me voyez, utilisez une instruction que vous n'avez jamais vue auparavant, la première chose à faire est de vous rendre à la page 616 de la fiche technique. Il s'agit du "Résumé du jeu d'instructions". Trouvez maintenant l'instruction "STS" qui est celle que j'ai utilisée. Il dit qu'il prend un nombre dans un registre R (nous avons utilisé R16) et l'emplacement k "Stocker directement dans la SRAM" (dans notre cas donné par TIMSK0). Alors pourquoi avons-nous dû utiliser "sts" qui prend 2 cycles d'horloge (voir la dernière colonne du tableau) pour stocker dans TIMSK0 et nous n'avions besoin que de "out", qui ne prend qu'un seul cycle d'horloge, pour stocker dans TCCR0B auparavant ? Pour répondre à cette question il faut revenir à notre tableau récapitulatif des registres page 614. Vous voyez que le registre TCCR0B est à l'adresse 0x25 mais aussi à (0x45) non ? Cela signifie qu'il s'agit d'un registre dans la SRAM, mais c'est aussi un certain type de registre appelé "port" (ou registre d'e/s). Si vous regardez le tableau récapitulatif des instructions à côté de la commande "out", vous verrez qu'il prend des valeurs dans les "registres de travail" comme R16 et les envoie à un PORT. Nous pouvons donc utiliser "out" lors de l'écriture sur TCCR0B et nous épargner un cycle d'horloge. Mais recherchez maintenant TIMSK0 dans la table des registres. Vous voyez qu'il a l'adresse 0x6e. Ceci est en dehors de la plage de ports (qui ne sont que les premiers emplacements 0x3F de SRAM) et vous devez donc vous rabattre sur la commande sts et prendre deux cycles d'horloge CPU pour le faire. Veuillez lire la note 4 à la fin du tableau récapitulatif des instructions à la page 615 dès maintenant. Notez également que tous nos ports d'entrée et de sortie, comme PORTD sont situés au bas du tableau. Par exemple, PD4 est le bit 4 à l'adresse 0x0b (vous voyez maintenant d'où viennent tous les trucs 0x0b dans mon code non commenté !) arrive? Rappelez-vous notre philosophie! casse le! ne me croyez pas sur parole.

D'accord, avant de continuer, passez à la page 19 de la fiche technique pendant une minute. Vous voyez une image de la mémoire de données (SRAM). Les 32 premiers registres de la SRAM (de 0x0000 à 0x001F) sont les "registres de travail à usage général" R0 à R31 que nous utilisons tout le temps comme variables dans notre code. Les 64 registres suivants sont les ports d'E/S jusqu'à 0x005f (c'est-à-dire ceux dont nous parlions qui ont ces adresses non crochetées à côté d'eux dans la table des registres que nous pouvons utiliser la commande "out" au lieu de "sts") Enfin la section suivante de SRAM contient tous les autres registres de la table récapitulative jusqu'à l'adresse 0x00FF, et enfin le reste est de la SRAM interne. Maintenant rapidement, passons à la page 12 pendant une seconde. Vous y voyez un tableau des "registres de travail à usage général" que nous utilisons toujours comme variables. Vous voyez la ligne épaisse entre les nombres R0 à R15 puis R16 à R31 ? Cette ligne est la raison pour laquelle nous utilisons toujours R16 comme le plus petit et j'y reviendrai un peu plus dans le prochain tutoriel où nous aurons également besoin des trois registres d'adresses indirectes 16 bits, X, Y et Z. Je ne le ferai pas entrer dans cela tout de suite cependant puisque nous n'en avons pas besoin maintenant et nous nous enlisons assez ici.

Retournez une page à la page 11 de la fiche technique. Vous verrez un schéma du registre SREG en haut à droite ? Vous voyez que le bit 7 de ce registre est appelé "I". Maintenant, descendez la page et lisez la description du Bit 7…. Yay! Il s'agit du bit Global Interrupt Enable. C'est ce que nous devons définir pour passer par la deuxième décision de notre diagramme ci-dessus et autoriser les interruptions de débordement de minuterie/compteur dans notre programme. Ainsi, la ligne suivante de notre programme devrait lire:

sbi SREG, je

qui positionne le bit appelé "I" dans le registre SREG. Cependant, au lieu de cela, nous avons utilisé l'instruction

sei

au lieu. Ce bit est défini si souvent dans les programmes qu'ils ont simplement créé un moyen plus simple de le faire.

D'accord! Maintenant, nous avons les interruptions de débordement prêtes à être utilisées afin que notre "jmp overflow_handler" soit exécuté chaque fois qu'il se produit.

Avant de continuer, jetez un coup d'œil au registre SREG (Status Register) car il est très important. Lisez ce que chacun des drapeaux représente. En particulier, la plupart des instructions que nous utilisons définiront et vérifieront ces indicateurs tout le temps. Par exemple, plus tard, nous utiliserons la commande "CPI" qui signifie "comparer immédiatement". Jetez un œil au tableau récapitulatif des instructions pour cette instruction et notez le nombre d'indicateurs qu'il définit dans la colonne « drapeaux ». Ce sont tous des drapeaux dans SREG et notre code les définira et les vérifiera en permanence. Vous verrez des exemples sous peu. Enfin, le dernier morceau de cette section de code est:

température clr

sur TCNT0, temp sbi DDRD, 4

La dernière ligne ici est assez évidente. Il définit simplement le 4ème bit du registre de direction des données pour PortD, provoquant la sortie de PD4.

Le premier met la variable temp à zéro, puis la copie dans le registre TCNT0. TCNT0 est notre Timer/Counter0. Cela le met à zéro. Dès que le PC exécute cette ligne, le timer0 démarre à zéro et compte 15625 fois par seconde. Le problème est le suivant: TCNT0 est un registre "8 bits", n'est-ce pas ? Alors, quel est le plus grand nombre qu'un registre de 8 bits peut contenir ? Eh bien, c'est 0b11111111. C'est le nombre 0xFF. Ce qui fait 255. Alors vous voyez ce qui se passe ? La minuterie avance en augmentant 15625 fois par seconde et chaque fois qu'elle atteint 255, elle "déborde" et revient à 0. En même temps qu'il revient à zéro, il envoie un signal Timer Overflow Interrupt. Le PC comprend cela et vous savez ce qu'il fait maintenant, n'est-ce pas ? Ouais. Il va à l'emplacement de mémoire de programme 0x0020 et exécute l'instruction qu'il y trouve.

Super! Si vous êtes toujours avec moi, alors vous êtes un super-héros infatigable ! Continuons…

Étape 6: Gestionnaire de débordement

Supposons donc que le registre timer/counter0 vient de déborder. Nous savons maintenant que le programme reçoit un signal d'interruption et exécute 0x0020 qui indique au compteur de programme, au PC de passer à l'étiquette "overflow_handler", voici le code que nous avons écrit après cette étiquette:

overflow_handler:

inc déborde cpi déborde, 61 brne PC+2 clr déborde reti

La première chose qu'il fait est d'incrémenter la variable "débordements" (qui est notre nom pour le registre de travail à usage général R17), puis il "compare" le contenu des débordements avec le nombre 61. La façon dont l'instruction cpi fonctionne est qu'elle soustrait simplement les deux nombres et si le résultat est zéro, il met le drapeau Z dans le registre SREG (je vous ai dit que nous verrions ce registre tout le temps). Si les deux nombres sont égaux, le drapeau Z sera un 1, si les deux nombres ne sont pas égaux, ce sera un 0.

La ligne suivante dit "brne PC+2" ce qui signifie "branche si différent". Essentiellement, il vérifie le drapeau Z dans SREG et s'il n'est PAS un (c'est-à-dire que les deux nombres ne sont pas égaux, s'ils étaient égaux, le drapeau zéro serait défini) le PC passe à PC+2, ce qui signifie qu'il saute le suivant ligne et va directement à "reti" qui revient de l'interruption à n'importe quel endroit où il se trouvait dans le code lorsque l'interruption est arrivée. Si l'instruction brne trouvait un 1 dans le bit d'indicateur zéro, elle ne se brancherait pas et à la place, elle continuerait simplement à la ligne suivante, ce qui entraînerait un débordement de clr en le réinitialisant à 0.

Quel est le résultat net de tout cela ?

Eh bien, nous voyons qu'à chaque fois qu'il y a un débordement de timer, ce gestionnaire augmente la valeur des "débordements" de un. Ainsi, la variable "débordements" compte le nombre de débordements au fur et à mesure qu'ils se produisent. Chaque fois que le nombre atteint 61, nous le remettons à zéro.

Maintenant, pourquoi diable ferions-nous cela ?

Voyons. Rappelez-vous que la vitesse d'horloge de notre processeur est de 16 MHz et que nous l'avons "pré-échelonnée" à l'aide de TCCR0B afin que la minuterie ne compte qu'à un taux de 15625 coups par seconde, n'est-ce pas ? Et chaque fois que la minuterie atteint un compte de 255, elle déborde. Cela signifie donc qu'il déborde 15625/256 = 61,04 fois par seconde. Nous suivons le nombre de débordements avec notre variable "overflows" et nous comparons ce nombre à 61. Nous voyons donc que les "overflows" seront égaux à 61 une fois par seconde ! Ainsi, notre gestionnaire réinitialisera les "débordements" à zéro une fois par seconde. Donc, si nous devions simplement surveiller la variable "débordements" et prendre note de chaque fois qu'elle se remet à zéro, nous compterions seconde par seconde en temps réel (notez que dans le prochain tutoriel, nous montrerons comment obtenir un délai en millisecondes de la même manière que la routine "délai" d'Arduino fonctionne).

Nous avons maintenant "traité" les interruptions de débordement de la minuterie. Assurez-vous de comprendre comment cela fonctionne, puis passez à l'étape suivante où nous utilisons ce fait.

Étape 7: Retarder

Maintenant que nous avons vu que notre routine "overflow_handler" du gestionnaire d'interruption de débordement du temporisateur mettra la variable "overflows" à zéro une fois par seconde, nous pouvons utiliser ce fait pour concevoir un sous-programme "delay".

Jetez un œil au code suivant sous notre délai: label

retard:

clr déborde sec_count: cpi déborde, 30 brne sec_count ret

Nous allons appeler ce sous-programme chaque fois que nous aurons besoin d'un délai dans notre programme. La façon dont cela fonctionne est qu'il met d'abord la variable "débordements" à zéro. Ensuite, il entre dans une zone intitulée "sec_count" et compare les débordements avec 30, s'ils ne sont pas égaux, il revient à l'étiquette sec_count et compare encore, et encore, etc. jusqu'à ce qu'ils soient finalement égaux (rappelez-vous que tout le temps cela va sur notre gestionnaire d'interruption de minuterie continue d'incrémenter les débordements variables et il change donc à chaque fois que nous faisons le tour ici. retard de 1/2 seconde

Exercice 2: Remplacez la routine overflow_handler par:

overflow_handler:

inc déborde reti

et exécutez le programme. Y a-t-il quelque chose de différent ? Pourquoi ou pourquoi pas?

Étape 8: Clignez des yeux

Regardons enfin la routine de clignotement:

cligner:

sbi PORTD, 4 rcall delay cbi PORTD, 4 rcall delay rjmp clignoter

Nous activons d'abord PD4, puis nous rappelons notre sous-programme de délai. Nous utilisons rcall pour que lorsque le PC arrive à une instruction "ret", il revienne à la ligne suivant rcall. Ensuite, la routine de retard retarde de 30 comptes dans la variable de débordement comme nous l'avons vu et cela fait presque exactement 1/2 seconde, puis nous éteignons PD4, retardons encore 1/2 seconde, puis revenons au début.

Le résultat net est une LED clignotante !

Je pense que vous conviendrez maintenant que "blink" n'est probablement pas le meilleur programme "hello world" en langage assembleur.

Exercice 3: Modifiez les différents paramètres du programme pour que la LED clignote à des rythmes différents comme une seconde ou 4 fois par seconde, etc. Exercice 4: Modifiez-le pour que la LED s'allume et s'éteigne pendant des durées différentes. Par exemple, allumé pendant 1/4 de seconde, puis éteint pendant 2 secondes ou quelque chose comme ça. Exercice 5: Changez les bits de sélection de l'horloge TCCR0B à 100, puis continuez à remonter le tableau. À quel moment devient-il indiscernable de notre programme "hello.asm" du didacticiel 1 ? Exercice 6 (facultatif): Si vous avez un oscillateur à quartz différent, comme un 4 MHz ou un 13,5 MHz ou autre, changez votre oscillateur 16 MHz sur votre planche à pain pour le nouveau et voyez comment cela affecte le taux de clignotement de la LED. Vous devriez maintenant être en mesure de faire le calcul précis et de prédire exactement comment cela affectera le taux.

Étape 9: Conclusion

Pour ceux d'entre vous qui sont allés jusqu'ici, félicitations !

Je me rends compte qu'il est assez difficile de travailler dur lorsque vous faites plus de lecture et de recherche que de câblage et d'expérimentation, mais j'espère que vous avez appris les choses importantes suivantes:

  1. Comment fonctionne la mémoire de programme
  2. Comment fonctionne la SRAM
  3. Comment rechercher des registres
  4. Comment rechercher des instructions et savoir ce qu'elles font
  5. Comment implémenter les interruptions
  6. Comment le CP exécute le code, comment fonctionne le SREG et que se passe-t-il pendant les interruptions
  7. Comment faire des boucles et des sauts et rebondir dans le code
  8. Comme il est important de lire la fiche technique !
  9. Comment une fois que vous saurez comment faire tout cela pour le microcontrôleur Atmega328p, ce sera une promenade de gâteau relative pour apprendre tous les nouveaux contrôleurs qui vous intéressent.
  10. Comment changer le temps CPU en temps réel et l'utiliser dans les routines de retard.

Maintenant que nous avons beaucoup de théorie, nous sommes capables d'écrire un meilleur code et de contrôler des choses plus compliquées. Donc, le prochain tutoriel, nous allons faire exactement cela. Nous allons construire un circuit plus compliqué, plus intéressant et le contrôler de manière amusante.

Exercice 7: « Casser » le code de différentes manières et voir ce qui se passe ! Curiosité scientifique bébé ! Quelqu'un d'autre peut faire la vaisselle correctement ? Exercice 8: Assemblez le code en utilisant l'option "-l" pour générer un fichier liste. C'est à dire. "avra -l blink.lst blink.asm" et jetez un œil au fichier de liste. Crédit supplémentaire: Le code non commenté que j'ai donné au début et le code commenté dont nous discuterons plus tard diffèrent ! Il y a une ligne de code qui est différente. Peux-tu le trouver? Pourquoi cette différence n'a-t-elle pas d'importance ?

J'espère que tu t'es amusé! A la prochaine fois…

Conseillé: