LOCODUINO

Forum de discussion
Dépôt GIT Locoduino
Flux RSS

mercredi 19 mai 2021

35 visiteurs en ce moment

L’assembleur (2)

Sa forte relation au matériel

. Par : Christian

DIFFICULTÉ :

Dans l’article précédent, nous avons vu ce qu’était l’assembleur et à quel point il était plus efficace que le code généré par l’IDE. Dans cet article, nous allons commencer à entrer dans le détail de tout ce qu’il faut avoir assimilé avant de se lancer dans cette aventure qu’est la programmation en assembleur. Attention : la lecture de cet article peut avoir un effet secondaire sur les neurones : surchauffe, blocage, insomnies !

Relation entre matériel et logiciel

Programmer en assembleur demande d’avoir une très bonne connaissance des possibilités matérielles du microcontrôleur cible (encore appelées ressources). Il est en effet primordial de bien connaître les ressources de votre microcontrôleur pour savoir ce que vous pourrez lui demander. On a d’ailleurs déjà un peu évoqué les ressources dans la série d’articles Les Timers (I). Par exemple, chaque timer possède son lot de registres de contrôle et utiliser les timers revient à lire ou écrire dans ces registres. Il faut donc connaître tout cela avant de pouvoir utiliser les timers.

Il faut bien comprendre qu’utiliser les ressources d’un microcontrôleur dans un programme écrit en assembleur demande beaucoup de travail. En effet, il devient difficile d’appeler les fonctions des bibliothèques Arduino de l’assembleur et utiliser ces bibliothèques ferait perdre l’intérêt de la compacité du code que nous avons évoqué dans le précédent article. D’abord, il faut étudier et comprendre comment utiliser ces ressources (et seule une lecture attentive de la datasheet du composant permet d’arriver à ce résultat), ensuite il faut mettre en pratique ces connaissances dans un programme qui soit fonctionnel. Un seul bit oublié dans un seul registre peut compromettre le résultat. Heureusement, les datasheets présentent souvent des exemples écrits en C et en assembleur, et on peut donc s’inspirer de ces derniers pour construire son programme.

Mais que faire si une ressource vous manque pour aller au bout de votre projet ? Et bien comme dans la vraie vie : soit vous trouvez la ressource qui vous manque (ce qui implique soit de changer de microcontrôleur cible, soit d’ajouter une carte externe), soit vous apprenez à vous en passer (modification du projet) !

Parmi les ressources à prendre en compte, on a en premier la quantité de mémoire disponible. Cette ressource n’est pas des moindres puisqu’elle limite la longueur de vos programmes donc aussi la complexité de votre projet ou bien elle limite le nombre de vos variables. Nous y reviendrons car il est très important de savoir comment cette mémoire est organisée.

Une deuxième ressource à prendre en compte est la faculté qu’aura le microcontrôleur à communiquer avec l’extérieur, donc le nombre de lignes d’entrée-sortie, comment elles sont organisées et ce qu’on peut en faire. Cela aussi nous en avons parlé dans la série d’articles sur les ATtiny : par exemple, pour faire un feu tricolore de carrefour ou une enseigne de commerçant, il faut au moins 6 sorties numériques et dans ce cas, un ATtiny24/44/84 est préférable à l’ATtiny25/45/85 [1].

Puisqu’on parle de communication avec l’extérieur, on peut aussi évoquer les différents canaux de communication (SPI, UART, I2C, CAN, etc.). Comme on vous l’a montré de nombreuses fois dans nos articles, il est toujours possible de rajouter une ressource avec une carte externe (cas du bus CAN non natif sur les AVR mais qui existe sur les ARM) ; toutefois, il est souvent moins cher de changer de microcontrôleur que de rajouter une carte externe. C’est donc à vous de peser le pour et le contre de chaque solution (trouver la ressource ou s’en passer).

Pour avoir une bonne vue d’ensemble des ressources d’un microcontrôleur, il suffit de consulter la première page de sa datasheet qui présente ces ressources de façon presque publicitaire. C’est en tout cas de cette façon que sont présentées les datasheet Microchip ou Atmel. Bien évidemment, la datasheet elle-même donne plus de précisions et il suffit de chercher à l’intérieur.

Microcontrôleurs AVR

Dans cette série d’articles sur l’assembleur, nous nous limiterons à décrire les microcontrôleurs de la série AVR (qui signifierait Advanced Virtual RISC) [2] bien que nous n’ayons rien trouvé sur la signification de ces trois lettres sur le site d’Atmel-Microchip [3]. En effet, les microcontrôleurs AVR sont les plus employés pour les cartes Arduino et nous prendrons souvent l’ATmega328P en exemple (celui de la carte Uno). Certaines cartes, de marque Arduino ou autre, utilisent des microcontrôleurs de la série ARM (initialement Acorn RISC Machine) [4] qui diffèrent par leur architecture et leurs ressources. Bien évidemment, si vous persévérez dans la programmation en assembleur, vous comprendrez aisément comment utiliser une ou l’autre série et comment tenir compte de leurs différences dans votre programmation.

Les microcontrôleurs AVR sont des microcontrôleurs RISC (voir article L’assembleur (1)) à architecture Harvard. Cela signifie que l’espace mémoire réservé aux données (mémoire statique) est physiquement différent de l’espace mémoire réservé au code du programme (mémoire flash). En conséquence, on trouve deux bus reliant l’unité centrale (appelée MCU pour Microcontroller Unit) aux zones de mémoire : un bus de données et un bus d’instructions. L’intérêt d’une telle architecture est une augmentation de la vitesse d’exécution des programmes : on peut simultanément rechercher le code d’une instruction et les données qu’elle manipule. Un inconvénient de cette architecture est que les constantes sont copiées en RAM (qui n’est jamais très étendue) lors du boot ou bien qu’il faut faire des manipulations non standard pour y accéder. [5]

Les microcontrôleurs AVR sont réalisés en technologie CMOS haute vitesse (faible consommation, vitesse de travail importante et haute intégration) et ils disposent de 32 registres internes de 8 bits appelés registres généraux, 64 registres internes d’entrée-sortie (I/O) et 160 registres internes d’entrée-sortie étendue (ces 256 emplacements mémoires sont différents de la mémoire utilisée pour stocker des données).

Cette série AVR propose de petits microcontrôleurs aux possibilités limitées comme les ATtiny dont le nombre de broches est restreint ou bien de plus importants comme les ATmega qui peuvent avoir 100 broches comme l’ATmega2560 des cartes Mega. Cependant, pour ne pas restreindre les possibilités du microcontrôleur, les broches sont très souvent multiplexées, ce qui signifie qu’elles sont capables de remplir des rôles différents. Bien évidemment, une broche utilisée pour une fonction ne peut pas être utilisée pour autre chose (par exemple, si vous utilisez la capacité de communiquer en I2C, les deux broches utilisées pour véhiculer les signaux SCL et SDA de l’I2C ne peuvent pas servir à relever la valeur d’un capteur simple (non I2C)).

Une dernière chose est à considérer : la fréquence de travail du microcontrôleur qui peut aller jusqu’à 20 MIPS (Mega Instructions Per Second) pour les circuits les plus rapides. L’horloge qui équipe la carte Arduino Uno a une fréquence de 16 MHz et règle la vitesse à laquelle le microcontrôleur va exécuter les différentes instructions ; à 16 MHz, un cycle d’horloge dure 62,5 ns (nanoseconde) et les instructions n’ont besoin que d’un seul cycle d’horloge pour être exécutées (de très rares instructions en nécessitent plus). On peut donc calculer avec précision combien de temps durera l’exécution d’un programme ou d’un sous-programme, ce qui permet d’avoir des timings extrêmement bien calibrés comme nous le disions dans l’article L’assembleur (1). À ce sujet, il est important de comprendre que la fréquence du quartz relié à un microcontrôleur (réglant la fréquence de travail) n’est pas le seul élément pour déterminer la rapidité d’un microcontrôleur ; en effet, un AVR exécute les instructions en un cycle d’horloge alors qu’un PIC fait de même avec un cycle machine qui vaut quatre cycles d’horloge et dans ces conditions, un AVR avec un quartz de 20 MHz est deux fois plus rapide qu’un PIC avec un quartz de 40 MHz !

Après ce rapide survol des microcontrôleurs AVR, nous allons rentrer dans plus de détails et nous allons commencer par la mémoire puisque programmer revient à manipuler des données stockées en mémoire.

Organisation de la mémoire

Tous les AVR sont munis de trois types de mémoire et seule la quantité diffère d’un modèle à l’autre. On trouve de la mémoire flash (analogue à la mémoire d’une clé USB) qui permet de stocker les instructions du programme et de les conserver même hors tension, de la mémoire vive réservée aux données (mémoire SRAM qui perd ses données lorsqu’elle n’est plus sous tension) et de la mémoire EEPROM qui conserve les données même hors tension. Voyons cela en détail car c’est important pour notre programmation.

Mémoire de programme
Rappelez-vous que cette mémoire est indépendante de toute autre mémoire, donc ses adresses lui sont propres. Mais les instructions sont codées sur 16 (parfois 32) bits, ce qui permet un codage plus efficace. L’espace mémoire de programme est donc organisée en 16K X 16 et commence donc à l’adresse 0x0000 jusqu’à une adresse qui dépend de la quantité disponible (par exemple 0x3FFF pour un ATmega328P qui dispose de 32 Kbytes).

Notre ATmega328P peut donc stocker 16384 instructions maximum (certaines instructions assez rares nécessitent plus que deux octets) et il faut se rappeler que le bootloader occupe également une petite partie de notre mémoire programme. C’est pourquoi la mémoire flash est organisée en deux sections, la section du programme de boot et la section du programme d’application, comme le montre la figure 1.

Pour exécuter un programme, le microcontrôleur va donc aller chercher les instructions les unes à la suite des autres, en tout cas dans un certain ordre déterminé par le programme (il peut y avoir des boucles ou des sous-programmes). Donc pour connaître l’instruction qu’il doit exécuter, le microcontrôleur a besoin d’avoir un compteur ordinal appelé PC (Program Counter) qui n’est rien d’autre qu’un registre dans lequel on met l’adresse de l’instruction qu’on traite et qu’on incrémente pour aller chercher l’instruction suivante.

Pour exécuter un sous-programme, le PC doit être chargé avec l’adresse de la première instruction du sous-programme mais auparavant, il faut sauvegarder l’adresse qui se trouvait dedans car à la fin du sous-programme, il faudra revenir au programme principal, exactement là où on l’a quitté. Retenez bien cela car on va en reparler dans le paragraphe suivant.

Figure 1
Figure 1
Organisation de la mémoire flash.

Mémoire de données
Comme son nom l’indique, la mémoire de données SRAM sert à stocker des données, c’est-à-dire des variables utilisées par votre programme (nombres, caractères, etc.). Par exemple, sur un ATmega328P, cette mémoire commence à l’adresse 0x0100, juste après les 256 registres internes (voir plus loin) dont les adresses vont de 00 à FF (toutes les cellules mémoires ne sont pas utilisées) [6]. Cette mémoire SRAM dont l’utilisateur peut disposer est donc en plus des registres comme on peut le voir sur la figure 2.

Figure 2
Figure 2
Organisation de la mémoire de données.

L’ATmega328P dispose de 2Kbytes de mémoire SRAM et vous vous dites sans doute que vous pouvez donc stocker 2048 octets de données. Et bien non, car le microcontrôleur a aussi besoin d’un peu de mémoire pour stocker certaines données comme le PC par exemple en cas d’appel de sous-programme. Et s’il y a des appels de sous-programme par d’autres sous-programmes, il faut stocker plusieurs fois la valeur du PC pour retrouver tout cela lorsqu’on sort des sous-programmes. Toutes ces données sont organisées en pile (on y reviendra) et il faut connaître en permanence la dernière adresse en mémoire vive utilisée pour stocker dans la pile, adresse qui est conservée dans un registre appelé SP (Stack Pointer).

Quelques mots au sujet de la pile qui permet de stocker des informations intéressantes. Comme on vient de le dire, la pile est une portion de la mémoire vive différemment gérée en fonction du microcontrôleur. Les petits ATtiny disposent d’une pile matérielle, contenant sa propre zone de mémoire vive, et son pointeur est géré automatiquement lors des appels ou retours de sous-programme. Cela entraîne un certain confort de programmation mais en contrepartie, cette pile est de taille définie limitant le nombre de valeurs à y stocker. Cette pile matérielle a une profondeur de trois mots ce qui limite à trois le nombre de sous-programmes imbriqués les uns dans les autres. Il faut bien évidemment en tenir compte.

Mémoire EEPROM
Cette mémoire sert à stocker des données de façon permanente puisque les informations ne sont pas perdues lors de la mise hors tension. Avec l’IDE d’Arduino, nous avions une bibliothèque qui se chargeait des cycles de lecture ou d’écriture de cette mémoire. Là, il faut tout gérer par nous-mêmes. Il faut cependant se rappeler une chose : ce type de mémoire est prévu pour 100 000 cycles d’écriture ou effacement. On peut penser que c’est beaucoup mais si on programme une petite boucle pour aller écrire une donnée dans la même cellule mémoire, on arrive rapidement à ce nombre vu que les microcontrôleurs travaillent rapidement. Il faut donc être extrêmement précautionneux quand on utilise la mémoire EEPROM mais c’était déjà le cas avec l’IDE.

Registres internes
Les AVR disposent de 32 registres à usage général (ou si vous préférez des registres de travail) et ce qui est plutôt cool, c’est que ces registres s’utilisent de la même façon (enfin presque) et que tous ont accès à l’ALU (Arithmetic and Logic Unit) ou encore unité arithmétique et logique, là où se font la plupart des calculs (d’arithmétique, de logique ou de manipulation de bits). Ceci offre une grande souplesse de programmation. Le choix de projeter les registres à usage général en mémoire reliée à la fois au bus de données et ayant accès à l’ALU est une caractéristique rare pour des microcontrôleurs et peut-être même exclusive aux microcontrôleurs Atmel-Microchip.

La figure 3 représente l’architecture interne d’un AVR et on peut déjà y voir ce dont nous avons parlé (les différentes mémoires, le Program Counter, les 32 registres à usage général, la SRAM et l’EEPROM. 

Figure 3
Figure 3
Architecture interne du microcontrôleur.

Les registres de travail sont appelés de R0 à R31 et bien-sûr sont des registres de 8 bits à accès rapide. Les adresses de ces registres commencent à 0x00 (pour R0) à 0x0F (pour R15) puis 0x10 (pour R16) à 0x1F (pour R31). Les six derniers registres (R26 à R31) peuvent être utilisés comme trois registres de 16 bits pour adresser l’espace mémoire de données, ce qui permet des opérations d’adressage plus rapides ; ils sont alors appelés X-register, Y-register ou Z-register. Cependant, sur les ATtiny, seul le registre Z est présent et est composé des registres R30 et R31. La figure 4 montre les 32 registres de travail.

Figure 4
Figure 4
Les 32 registres de travail.

Ces 32 registres de travail s’utilisent de façon identique et sont interchangeables ; néanmoins, quelques instructions lorsqu’elles sont utilisées entre un registre et une constante ne peuvent être utilisées qu’avec la deuxième moitié des registres de travail, donc ceux compris entre R16 et R31.

Registres d’état
L’ALU peut réaliser des calculs arithmétiques entre deux registres ou bien un registre et une constante. Après chaque opération arithmétique, un registre spécial appelé Status Register (SREG) est mis à jour en fonction du résultat de l’opération qui peut être zéro, un débordement (overflow), une retenue (carry), etc. Lorsqu’on programme en assembleur, il est fréquent d’avoir à surveiller les différents bits de SREG (voir figure 5).

Figure 5
Figure 5
Le registre d’état.

Un deuxième registre d’état, appelé MCUSR (pour MCU Status Register) permet d’identifier la source d’un RESET.

Registres de contrôle
Tous les circuits de la famille AVR possèdent un registre de contrôle appelé MCUCR (pour MCU Control Register) permettant de définir certains comportements de l’unité centrale. L’organisation de ce registre dépendant du modèle de microcontrôleur, un recours à la datasheet est nécessaire pour comprendre l’organisation des bits du registre.

Il existe aussi bien d’autres registres de contrôle, par exemple pour gérer les interruptions ou bien pour utiliser les timers mais le mieux sera de les découvrir au fur et à mesure des besoins.

Conseils aux programmeurs en assembleur

Nous venons de faire un rapide survol des possibilités matérielles des microcontrôleurs et nous n’avons pas tout évoqué puisque nous avons surtout décrit les différentes zones de mémoire. Chaque ressource du MCU possède ses propres registres de contrôle et pour utiliser ces ressources, il faut bien comprendre l’utilisation de leurs registres de contrôle. Nous vous l’avions déjà un peu montré dans la série d’articles sur les timers où nous allions directement modifier ces registres pour obtenir le bon mode et la bonne vitesse de comptage. Seule une étude approfondie de la datasheet vous permettra de comprendre comment écrire vos programmes ; cela demande du temps et une certaine énergie. Mais c’est indispensable pour programmer en assembleur !

Pour voir si vous avez bien assimilé les notions décrites dans cette deuxième partie, voici une petite question : quelle est la taille du PC sur le MCU ATmega328P ? Bien évidemment, la réponse doit venir de vous, pas de la datasheet du composant…

Dans le prochain article, nous commencerons à voir le jeu d’instructions des microcontrôleurs AVR et nous ferons un premier programme. Cela vous permettra de mettre le pied à l’étrier car il faut bien commencer le chemin pour parcourir la route qui promet d’être longue. Mais plus vous parcourrez cette route, plus vous la trouverez passionnante.

[1L’ATtiny25/45/85 dispose de six entrées-sorties, mais l’une d’elle est utilisée pour le RESET du microcontrôleur ; en pratique, seules cinq E/S sont aisément accessibles.

[2d’après Christian Tavernier dans son livre Microcontrôleurs AVR : des ATtiny aux ATmega chez Dunod

[3Une autre interprétation de ces trois lettres serait qu’elles proviennent des prénoms des concepteurs de l’architecture, deux étudiants du Norvegian Institute of Technologie, Alf-Egil Bogen et Vegard Wollan, d’où le terme AVR pour Alf and Vegard’s RISC processor.

[4Processeurs ARM - Architecture et langage d’assemblage de Jacques Jorda chez Dunod

[5Pour plus de détails sur l’architecture Harvard, vous pouvez consulter https://fr.wikipedia.org/wiki/Architecture_de_type_Harvard

[6Le nombre de registres internes dépend du modèle de microcontrôleur, il est donc nécessaire de consulter les datasheets pour savoir où commence la SRAM.

17 Messages

Réagissez à « L’assembleur (2) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Programmation »

Comment gérer le temps dans un programme ?

La programmation, qu’est ce que c’est

Types, constantes et variables

Installation de l’IDE Arduino

Répéter des instructions : les boucles

Les interruptions (1)

Instructions conditionnelles : le if … else

Instructions conditionnelles : le switch … case

Comment concevoir rationnellement votre système

Comment gérer l’aléatoire ?

Calculer avec l’Arduino (1)

Calculer avec l’Arduino (2)

La compilation d’un projet Arduino

Les structures

Systèmes de numération

Les fonctions

Trois façons de déclarer des constantes

Transcription d’un programme simple en programmation objet

Ces tableaux qui peuvent nous simplifier le développement Arduino

Les chaînes de caractères

Trucs, astuces et choses à ne pas faire !

Processing pour nos trains

Arduino : toute première fois !

Démarrer en Processing (1)

TCOs en Processing (1)

TCOs en Processing (2)

Comment réussir son projet Arduino

Passage à niveau géré par Arduino (1)

Passage à niveau géré par Arduino (2)

Passage à niveau géré par Arduino (3)

Passage à niveau géré par Arduino (4)

Passage à niveau géré par Arduino (5)

Du sketch à l’exécutable

Programmer vos Arduino avec un fichier hexadécimal

Le monde des objets (1)

Le monde des objets (2)

Le monde des objets (3)

Le monde des objets (4)

Les pointeurs (1)

Les pointeurs (2)

Les Timers (I)

Les Timers (II)

Les Timers (III)

Les Timers (IV)

Les Timers (V)

Bien utiliser l’IDE d’Arduino (1)

Bien utiliser l’IDE d’Arduino (2)

Piloter son Arduino avec son navigateur web et Node.js (1)

Piloter son Arduino avec son navigateur web et Node.js (2)

Piloter son Arduino avec son navigateur web et Node.js (3)

Piloter son Arduino avec son navigateur web et Node.js (4)

Les derniers articles

L’assembleur (8)


Christian

L’assembleur (7)


Christian

L’assembleur (6)


Christian

L’assembleur (5)


Christian

L’assembleur (4)


Christian

L’assembleur (3)


Christian

L’assembleur (2)


Christian

L’assembleur (1)


Christian

Programmer vos Arduino avec un fichier hexadécimal


Christian

Du sketch à l’exécutable


Christian

Les articles les plus lus

Les interruptions (1)

Instructions conditionnelles : le if … else

Les Timers (I)

Comment gérer le temps dans un programme ?

Calculer avec l’Arduino (1)

Répéter des instructions : les boucles

Ces tableaux qui peuvent nous simplifier le développement Arduino

Les chaînes de caractères

Les Timers (II)

Instructions conditionnelles : le switch … case