Compilation de code à exécuter à partir de RAM externe

13

J'envisage de concevoir un système de jeu minimaliste basé sur un PIC18F85J5. Une partie de ma conception est que les jeux peuvent être chargés à partir d'une carte SD sans reprogrammer la puce ou flasher la mémoire du programme. J'ai choisi cette puce car elle possède une interface de mémoire externe qui me permettra d'exécuter du code à partir d'une SRAM externe.

L'idée de base est que la mémoire du programme interne contiendra une interface pour parcourir la carte SD, et une fois que l'utilisateur aura sélectionné un programme, il copiera un fichier hexadécimal de la carte SD vers le RAM externe, puis exécutera l'exécution dans l'espace RAM externe. .

La mémoire de programme interne aura également diverses bibliothèques pour les graphiques, les entrées de contrôleur et d'autres utilitaires divers.

Je suis assez confiant, je sais comment faire fonctionner correctement les composants internes du firmware. Le problème est de créer des programmes à exécuter à partir de la RAM externe. Cela ne ressemble pas au ciblage d'une image régulière, et il doit être conscient des fonctions de bibliothèque disponibles dans la mémoire interne, mais ne pas les recompiler, seulement les lier à elles. Il doit également commencer à utiliser des adresses juste après les 32 Ko de flash interne, et non à zéro. Existe-t-il un bon moyen de compiler un programme en utilisant ces types de contraintes?

J'utilise l'IDE MPLab, mais je ne le connais pas très bien, ni comment faire ce type de personnalisation.

captncraig
la source
L'une des meilleures questions que j'ai vues ici depuis un moment ... J'ai hâte d'entendre les idées et les réponses.
Jon L
Certaines approches alternatives intéressantes sont discutées dans electronics.stackexchange.com/questions/5386/…
Toby Jaffey
Je l'ai vu, mais ils recommandent surtout d'écrire en flash, ce que j'aimerais éviter. Ma conception matérielle ressemblera à la figure 6 de la note d'application de la réponse acceptée.
captncraig

Réponses:

8

Vous avez deux problèmes distincts:

  1. Création du code pour la plage d'adresses RAM externe.

    C'est en fait très simple. Tout ce que vous avez à faire est de créer un fichier de liens qui contient uniquement les plages d'adresses que vous souhaitez que le code occupe. Notez que vous devez non seulement réserver une plage d'adresses de mémoire de programme particulière pour ces applications externes, mais également de l'espace RAM. Cet espace RAM, comme les adresses de mémoire du programme, doit être fixe et connu. Rendez simplement uniquement ces plages d'adresses fixes et connues utilisables dans le fichier de l'éditeur de liens. N'oubliez pas de les rendre NON disponibles dans le fichier de l'éditeur de code de base.

    Une fois que le code de base a chargé une nouvelle application dans la mémoire externe, il doit savoir comment l'exécuter. La chose la plus simple est probablement de lancer l'exécution au premier emplacement de RAM externe. Cela signifie que votre code aura besoin d'une section CODE à cette adresse de début absolue. Celui-ci contient un GOTO à la bonne étiquette dans le reste du code, qui seront tous déplaçables.

  2. Lier des applications aux routines de bibliothèque dans le code de base.

    Il n'y a pas de moyen simple et immédiat de le faire avec les outils Microchip existants, mais ce n'est pas si mal non plus.

    Un problème beaucoup plus important est de savoir comment gérer les modifications du code de base. La stratégie simpliste consiste à créer votre code de base, à exécuter un programme sur le fichier de carte résultant pour récolter les adresses globales, puis à lui faire écrire un fichier d'importation avec des instructions EQU pour tous les symboles définis globalement. Ce fichier d'importation serait alors inclus dans tout le code de l'application. Il n'y a rien à lier, car le code source de l'application contient essentiellement les références d'adresse fixes aux points d'entrée du code de base.

    C'est facile à faire et cela fonctionnera, mais pensez à ce qui se passe lorsque vous modifiez le code de base. Même une correction de bogue mineure pourrait entraîner le déplacement de toutes les adresses, puis tout le code d'application existant ne serait pas bon et devrait être reconstruit. Si vous ne prévoyez jamais de fournir des mises à jour du code de base sans mettre à jour toutes les applications, vous pouvez peut-être vous en tirer, mais je pense que c'est une mauvaise idée.

    Une meilleure façon est d'avoir une zone d'interface définie à une adresse connue fixe choisie dans le code de base. Il y aurait un GOTO pour chaque sous-programme que le code d'application peut appeler. Ces GOTO seraient placés à des adresses connues fixes, et les applications externes appelleraient uniquement à ces emplacements, qui passeraient ensuite à l'endroit où le sous-programme se terminait réellement dans cette version du code de base. Cela coûte 2 mots de mémoire de programme par sous-programme exporté et deux cycles supplémentaires au moment de l'exécution, mais je pense que cela en vaut la peine.

    Pour ce faire, vous devez automatiser le processus de génération des GOTO et du fichier d'exportation résultant que les applications externes importeront pour obtenir les adresses de sous-programme (en fait le redirecteur GOTO). Vous pourriez peut-être faire avec une utilisation intelligente des macros MPASM, mais si je faisais cela, j'utiliserais certainement mon préprocesseur car il peut écrire dans un fichier externe au moment du prétraitement. Vous pouvez écrire une macro de préprocesseur afin que chaque redirecteur puisse être défini par une seule ligne de code source. La macro fait tout le méchant sous le capot, qui est de générer le GOTO, la référence externe à la routine cible réelle, et d'ajouter la ligne appropriée au fichier d'exportation avec l'adresse constante connue de cette routine, le tout avec les noms appropriés. Peut-être que la macro crée simplement un tas de variables de préprocesseur avec des noms réguliers (un peu comme un tableau extensible au moment de l'exécution), puis le fichier d'exportation est écrit une fois après tous les appels de macro. L'une des nombreuses choses que mon préprocesseur peut faire que les macros MPASM ne peuvent pas faire est de manipuler des chaînes pour créer de nouveaux noms de symboles à partir d'autres noms.

    Mon préprocesseur et un tas d'autres éléments connexes sont disponibles gratuitement sur www.embedinc.com/pic/dload.htm .

Olin Lathrop
la source
J'aime vraiment l'idée d'une table de saut fixe. Je pourrais simplement commencer à partir de la fin de la mémoire flash et avoir des emplacements fixes pour chaque appel système. Je pourrais même probablement m'en tirer avec un fichier d'en-tête maintenu manuellement avec les adresses de tous les sous-programmes si je ne peux pas comprendre tout ce vaudou de préprocesseur que vous décrivez.
captncraig
5

Option 1: Langues interprétées

Cela ne répond pas directement à la question (qui est une excellente question, BTW, et j'espère apprendre d'une réponse qui la traite directement), mais c'est très courant lorsque vous faites des projets qui peuvent charger des programmes externes pour écrire les programmes externes dans un langage interprété. Si les ressources sont limitées (ce qu'elles seront sur ce processeur, avez-vous pensé à utiliser un PIC32 ou un petit processeur ARM pour cela?), Il est courant de restreindre la langue à un sous-ensemble de la spécification complète. Plus loin dans la chaîne se trouvent des langages spécifiques au domaine qui ne font que peu de choses.

Par exemple, le projet elua est un exemple de langage interprété à faibles ressources (64 Ko de RAM). Vous pouvez réduire cela à 32 Ko de RAM si vous supprimez certaines fonctionnalités (Remarque: cela ne fonctionnera pas sur votre processeur actuel, qui est une architecture 8 bits. L'utilisation de la RAM externe sera probablement trop lente pour les graphiques). Il fournit un langage rapide et flexible dans lequel les nouveaux utilisateurs pourraient facilement programmer des jeux si vous fournissez une API minimale. Il y a beaucoup de documentation disponible pour la langue en ligne. Il existe d'autres langues (comme Forth et Basic) que vous pourriez utiliser de la même manière, mais je pense que Lua est la meilleure option pour le moment.

Dans la même veine, vous pouvez créer votre propre langage spécifique au domaine. Vous devrez fournir une API plus complète et une documentation externe, mais si les jeux étaient tous similaires, cela ne serait pas trop difficile.

Dans tous les cas, le PIC18 n'est probablement pas le processeur que j'utiliserais pour quelque chose qui implique une programmation / un script et des graphiques personnalisés. Vous connaissez peut-être cette classe de processeurs, mais je dirais que ce serait le bon moment pour utiliser quelque chose avec un pilote d'affichage et plus de mémoire.

Option 2: reprogrammer le tout

Si, cependant, vous prévoyez déjà de programmer tous les jeux vous-même en C, ne vous embêtez pas à charger uniquement la logique du jeu à partir de la carte SD. Vous n'avez que 32 Ko de Flash à reprogrammer et pourriez facilement obtenir une carte microSD de 4 Go pour cela. (Remarque: les cartes plus grandes sont souvent SDHC, ce qui est plus difficile à interfacer). En supposant que vous utilisez le dernier octet de votre 32 Ko, cela laisse de la place sur la carte SD pour 131 072 copies de votre firmware avec la logique de jeu dont vous avez besoin.

Il existe de nombreuses annexes pour écrire des chargeurs de démarrage pour les PIC, comme AN851 . Vous devez concevoir votre chargeur de démarrage pour occuper une région spécifique de la mémoire (probablement le haut de la région de la mémoire, vous devez le spécifier dans l'éditeur de liens) et spécifier que les projets de micrologiciel complets n'atteignent pas cette région. L'annexe le précise plus en détail. Il suffit de remplacer "Section de démarrage du PIC18F452" par "Section de démarrage que je spécifie dans l'éditeur de liens" et tout cela aura du sens.

Ensuite, votre chargeur de démarrage doit simplement permettre à l'utilisateur de sélectionner un programme à exécuter à partir de la carte SD et de copier le tout. Une interface utilisateur pourrait être que l'utilisateur doit maintenir enfoncé un bouton-poussoir pour entrer dans le mode de sélection. Normalement, le chargeur de démarrage vérifie simplement l'état de ce bouton lors de la réinitialisation et, s'il n'est pas maintenu enfoncé, démarre le jeu. S'il est maintenu enfoncé, il devrait permettre à l'utilisateur de choisir un fichier sur la carte SD, de copier le programme et de continuer à démarrer dans le [nouveau] jeu.

Ceci est ma recommandation actuelle.

Option 3: magie profonde impliquant de ne stocker qu'une partie du fichier hexadécimal

Le problème avec votre mécanisme envisagé est que le processeur ne traite pas des API et des appels de fonction, il traite des nombres - adresses auxquelles le pointeur d'instruction peut sauter et s'attendre à ce qu'il y ait du code qui exécute un appel de fonction selon une spécification d'API. Si vous essayez de compiler juste une partie du programme, l'éditeur de liens ne saura pas quoi faire lorsque vous appelez check_button_status()ou toggle_led(). Vous savez peut-être que ces fonctions existent dans le fichier hexadécimal du processeur, mais il doit savoir précisément à quelle adresse elles résident.

L'éditeur de liens divise déjà votre code en plusieurs sections; vous pourriez théoriquement diviser cela en sections supplémentaires avec des incantations -sectionet certains #pragma. Je ne l'ai jamais fait et je ne sais pas comment. Jusqu'à ce que les deux méthodes ci-dessus me manquent (ou que quelqu'un poste une réponse impressionnante ici), je n'apprendrai probablement pas ce mécanisme et je ne peux donc pas vous l'enseigner.

Kevin Vermeer
la source
Mon objection au numéro 2 est que la mémoire flash a un nombre limité de cycles d'effacement pendant la durée de vie de la puce. Je ne veux pas utiliser un mcu plus costaud car je préfère le minimalisme 8 bits.
captncraig
@CMP - Il a au moins 10 000 cycles d'effacement. Si quelqu'un joue un jeu différent chaque jour, cela durera jusqu'en 2039. Le Flash survivra certainement au reste du circuit. Je ne pense pas que vous ayez besoin de vous inquiéter à ce sujet à moins que cela ne se passe dans une arcade pour être joué des dizaines de fois chaque jour.
Kevin Vermeer
1
Deuxièmement, le minimalisme 8 bits peut sembler cool, mais il craint pour la programmation. Il est beaucoup plus facile d'obtenir un microcontrôleur robuste et de le rendre rétro que d'être forcé de le rendre rétro parce que vous repoussez les limites de votre processeur.
Kevin Vermeer
1
Les deux points très justes. Même si vous optez pour un faible nombre de pièces, un pic32 n'est pas si différent en termes de coût ou de composants externes que celui-ci, et s'il a 512 Ko de flash interne, il gagne même.
captncraig
Il semble qu'une solution pratique serait d'utiliser un pic32 et d'écrire un chargeur de démarrage pour reflasher à partir de la carte SD. J'aurais du mal à réutiliser les fonctions dont le chargeur de démarrage et le code utilisateur auraient besoin, mais tant que je le garde dans le flash de démarrage 12k, il devrait donner au code utilisateur la puce entière, et ils peuvent inclure toutes les bibliothèques de leur choix à la source niveau.
captncraig