Avr 062024
 

Ceci est un pense-bête résumant ce que j’ai compris de l’architecture des AVR et plus particulièrement du Atmega2560

Bus de données différent du bus d’instruction.

Le bus de données fait 8 bits.

Le bus d’adresses données fait 16 bits.

Le bus d’instruction fait 16 bits (accédés dans la flash en 1 seul cycle)

Le bus d’adresse Flash fait 16 bits et peut aller jusque 22bits.

On a 5 espaces d’adressage

1) les Registres CPU de 0 à 31 (soit 5 bits)

Ce sont les registres de travail. On ne les adresse jamais quand on programmes en C.
Et même au niveau assembleur, on les les accède jamais jamais vraiment par adresses comme ça serait la cas pour de la RAM « normale » Les adresses de registres sont en fait intégrées dans les opcodes d’instruction (1 instruction fait 16bit mais les 5 derniers bits indiquent le registre cible pour les instructions à un seul paramètre (exemple : incrémenter) ou alors ce sont les 10 derniers bits pour les instructions à 2 paramètres, le resultat étant généralement stocké dans le second.
exemple : Addition. add_A_B est un unique opcode 16bit qui réalise registreA + registreB et stocke le résultat dans registreB.

2) Les registres d’ I/O de 0x00 à 0x3F (soit 6bits)

Pilotent le hardware d’IO. Ces registres ont des instructions assembleur spécifiques dont les 6 derniers bits indiquent l’adresse du registre cible. Ces instructions spécifiques permettent de réaliser des IO de façon très performantes. Un point particulier est la possibilité de modifier directement un bit particulier en une seule instruction assembleur comme « cbi PORTB, 3 » qui est fonctionellement identique à une opération logique complexe du genre PORTB &= ~{1<<3); (à noter que cette dernière opération en C est théoriquement optimisée, mais ça peut dépendre des options de compilation)

3) Les registres de fonction spécifiques.

S’accèdent grossomodo comme la ram mais ces registres ont des fonctions spéciales comme par exemple configurer un périphérique intégré du genre UART/SPI/ADC… (Les instruction spéciales des IO interdites ou ont des équivalents avec un autre opcode)

Ce registres ont toujours des adresses comprises entre 0x40 et 0x1FF

Les adresses de cette plage ne correspondant à aucun périphérique ne sont (potentiellement) pas mappés et ne doivent pas être utilisées.

4) la RAM. de 0x200 à potentiellement 0xFFFF en fonction de la quantité équipée.

Le Mapping de mémoires ou périphériques externes est possible pour certains MCU équipé d’un bus d’extension (8Bits Data multiplexeés avec 8 bits adresses basses + 8 bits d’adresses hautes avec zones masquables)

5) La flash.

Vue de façon différente selon si on parle d’instructions ou de data.
Généralement adressés sur 16 bit sur les petits MCU mais sur potentiellement jusque 22bits ce qui pose beaucoup de problèmes. (Attention l’adressage 22bits et prévu dans le jeu d’instruction mais pas universelement supporté)

Pour le programme, les instruction JMP et CALL prennent en paramètre un second mot de 16bit qui vient s’ajouter aux 6 derniers bits du mot d’instruction pour spécifier des adresses jusqu’à 22bits.
Le cas 16bit est juste un cas particulier ou ces 6bits valent 0. (et souvent les MCU dotés de moins de 64k de flash ne sauront pas gérer ces opcodes d’instructions là que l’on qualifie d’étendues. Il n’y a pas d’interception des opcodes invalides et cela mènera donc à un résultat indéterminé (roll-over des adresses de flash ou blocage CPU, ou autre).
Le linker va normalement résoudre les symboles statiques automatiquement, et donc il importe peu que leur adresse fasse plus ou moins que 16 bits. C’est automatique. Mais il y a un cas particulier pour les pointeurs de fonction. En effet, selon la taille de flash connue par GCC (via option de compilation) les pointeurs de fonction auront une adresse 22 bits (stocké sur 32) ou bien ils peuvent être stockées sur seulement 16bits. (ce qui interdit de dépasser 64k de programme ou en tout cas d’avoir des pointeurs de fonction sur du code placé en dehors. Le plus simple (mais un peu moins performant) est de toujours employer le stockage sur 22bits si le matériel le supporte. De ce fait, les pointeurs de fonction sont toujours valides mais prennent un peu plus de place.

Les data constantes en flash « PROGMEM » ne sont pas directement adressables et sont lues avec des fonctions spéciales.
Par contre du point de vu du langage C, PROGMEM est un attribut de stockage mais pas un type différent. et de ce fait, elles reçoivent donc des pointeurs de même taille que pour le stockage en ram (c’est à dire 16bit). Et ça, c’est ennuyeux, car Si les données en flash sont plus loin que les 64k, alors le pointeur 16bit « déborde » et il faut employer une astuce spéciale du linker pour résoudre l’adresse du symbole en un « far-pointer » et ensuite le lire la valeur pointée avec des fonctions spéciales pour ce type de pointeur.
exemple :

#include <avr/pgmspace.h>

const uint8_t myData[] PROGMEM = { /* vos données ici, peut-être très grandes */ };

void accessData() {
    // Obtenir l'adresse "far" de myData
    uint_farptr_t far_ptr = pgm_get_far_address(myData);

    // Lire un byte depuis cette adresse "far"
    uint8_t data = pgm_read_byte_far(far_ptr);
   
}

On peut remarquer que dans ce code, pgm_get_far_address() prend en paramètre un pointeur 16 bit pour en déduire un pointeur 32bits. Cela n’est possible que qu’a l’édition de lien où pgm_get_far_address() récupèrera la vraie adresse physique du symbole. Il a d’ailleurs besoin du vrai nom du symbole pour cela.

En pratique, le compilateur place les chaines de données préférentiellement en début de flash.
On a donc peu de chance d’avoir besoin de recourir à ce type d’adressage sur les cas courants.
Une difficulté est cependant de savoir si la constante est far ou pas. Bien sur on peut utiliser du far systématiquement au prix d’un léger cout de performances mais c’est un peu dommage.

Sorry, the comment form is closed at this time.