Quand dans gcc il y a des adresses 16 bits, et soudain la mémoire est de 256k

... ou comment se tirer une balle dans le pied sur un Arduino




À l'école d'informatique d'été, nous utilisons un ordinateur ancien fabriqué par nous-mêmes pour enseigner le développement de jeux.

Maintenant, il dispose d'une carte Arduino Mega avec un processeur ATmega2560, dans lequel jusqu'à 256 kilo-octets de mémoire flash. On a supposé que cela durerait très longtemps, car les jeux sont simples (l'écran ne fait que 64x64 pixels). En réalité, nous avons rencontré des problèmes lorsque le micrologiciel a atteint environ 128 kilo-octets.

Dans la mémoire du programme, malgré son nom, en plus du code exécutable des jeux, toutes sortes de données inchangées telles que les sprites et les tables de niveaux sont stockées. Ces données ne sont pas tellement.

Mais lorsque nous avons connecté la puce audio YM2149F à notre ordinateur et téléchargé quelques dizaines de morceaux dans la même mémoire de programme, les problèmes ont commencé.


Le préfixe s'est écrasé en essayant de jouer une mélodie, ou a attiré des ordures dans le menu du jeu. Il n'était pas clair du tout comment déboguer cela, car le processeur ne s'occupe pas seulement de la logique du jeu, mais affiche également l'image et le son. En conséquence, il s'est avéré que le compilateur gcc-avr utilise des variables de deux octets pour stocker des pointeurs. Mais adresser 256 kilo-octets avec seulement deux octets est impossible! Comment sort-il?

Pointeurs de code


Tout d'abord, les instructions d'appel de fonction et les sauts peuvent utiliser des adresses à trois octets. Par conséquent, il suffit que l'éditeur de liens remplace l'adresse complète dans une telle instruction et cela fonctionnera. Si l'adresse de la fonction est passée par un pointeur, alors un tel nombre ne passera pas - car nous avons un pointeur à deux octets.

Dans cette situation, gcc insère un «tremplin» dans les 64 ko inférieurs - l'instruction jmp, qui passe à la fonction souhaitée. L'adresse de ce tremplin agira alors comme l'adresse de la fonction qui doit être stockée dans la variable - après tout, elle est placée sur deux octets. Et une fois appelé, il y aura une transition si nécessaire.

Pointeurs de données


Mais nous stockons dans la mémoire du programme non seulement le code exécutable. Donc, les sauts n'aideront pas ici - nous déréférençons les pointeurs, mais nous n'y allons pas.

La bibliothèque AVR a même des fonctions / macros comme pgm_read_byte_far (addr) pour déréférencer le pointeur complet (elles reçoivent des valeurs de quatre octets). Mais gcc ne sait pas comment obtenir ces pointeurs en utilisant le langage C.

Heureusement, il existe une macro pgm_get_far_address (var) pour obtenir l'adresse complète d'une variable. Cela se fait à l'aide de l'assembleur intégré (le cas lorsque l'assembleur est plus intelligent que le compilateur).

Il reste à réécrire tout le code qui utilise les données dans la ROM. Autrement dit, un lecteur de musique, le rendu des sprites, la sortie de texte, ... Pas une expérience très agréable. Oui, et le code deviendra plus inhibiteur, et pour les graphiques, il est très critique. Par conséquent,

Nous distribuons des données sur ROM


Linker essaie très fort de mettre des données pour la mémoire du programme dans le 64k inférieur. Cela ne fonctionne pas s'il y a trop de données. Mais les plus grandes données dont nous disposons sont les fichiers musicaux. Donc, si vous les supprimez uniquement, tout le reste rentrera dans la mémoire inférieure et vous n'aurez pas à refaire la partie principale du code.

Pour ce faire, nous exploiterons les fonctionnalités du script de l'éditeur de liens. L'une des dernières sections que l'éditeur de liens place dans la ROM s'appelle .fini7. Enregistrez tous les tableaux de musique dans cette section:

#define MUSICMEM __attribute__((section(".fini7"))) const uint8_t tetris2[] MUSICMEM = { ... }; 

Maintenant, avr-nm nous dit que tout est en ordre - les données avec les sprites et les niveaux se sont révélées être au bas de la ROM, et la musique au sommet.

 00002f9c t _ZL10level_menu 00002e0f t _ZL10rope_lines 000006de t _ZL10ShipSprite 00023a09 t tetris2 00024714 T the_last_v8 

Il reste à refaire le lecteur pour utiliser des pointeurs à quatre octets et au lieu d'utiliser un pointeur vers un tableau avec le code de la mélodie, utilisez la fonction pour obtenir son adresse. Des fonctions sont nécessaires car nous avons une application de lecture où vous pouvez écouter tous les morceaux de votre choix. Il stocke désormais des pointeurs vers des fonctions de ce type:

 00006992 <_Z12tetris2_addrv>: 6992: 61 ef ldi r22, 0xF1 ; 241 6994: 7a e3 ldi r23, 0x3A ; 58 6996: 82 e0 ldi r24, 0x02 ; 2 6998: 99 27 eor r25, r25 699a: 08 95 ret 

La fin du monde est retardée jusqu'à ce que les sprites atteignent le dernier 64k. C'est peu probable, car il y a encore plus de code que les sprites, ce qui signifie que la mémoire se terminera probablement en général.

Bonus


Cet été, nous avons écrit un jeu dans le style de Sokoban. Certains niveaux se sont révélés assez difficiles. Essayez, par exemple, de passer celui-ci:



Les références


  1. Page du projet Github
  2. Affichage Arduino et LED
  3. Arduino et la pierre musicale du philosophe
  4. Quelques matchs de l'an dernier

Source: https://habr.com/ru/post/fr421601/


All Articles