dimanche 14 avril 2013

Introduction aux PIC32MX, partie 3, la mémoire

mémoire virtuelle

Les PIC32 ont une organisation mémoire différente des autres PIC. Du point de vue du core il n'y a qu'un espace d'adressage linéaire de 4Go. Cette espace englobe la mémoire flash, la RAM, le régistres de configureations, les périphériques. En fait tout ce qui est accessible par un programme. Cet espace mémoire n'existe pas réellement c'est une mémoire virtuelle. Pour faire le lien entre mémoire virtuelle et la mémoire flash, RAM, les SFRs, les registres de configuration de démarrage et le reste il y a un circuit qu'on appelle un gestionnaire de mémoire. Dans le cas des PIC32 ce gestionnaire est un table de transcription fixe. Cette table convertie l'adresse virtuelle en addresse physique.

La mémoire virtuelle elle-même est divisée en segments. Voici la représentation qu'en fait Microchip dans son manuel de spécification, section 3.

Seul les segments 0 et 1 sont utilisés sur les PIC32. Ces 2 segments pointent vers la même mémoire physique, la seule différence est que le segment 1 n'utilise pas la mémoire cache alors que le 0 peut l'utiliser.

2 modes de fonctionnement

Vous-vous demandez peut-être si c'est important de connaître ces détails, dans les parties 1 et 2 de cette introduction on s'est très bien passé de cette information. Et si vous rester au niveau de base vous n'avez en effet pas besoin de connaître ces détails. Au démarrage le MCU est en mode noyau et y reste à moins que le programme d'initialisation le configure pour qu'il passe en mode utilisateur.

Mais qu'est-ce au juste que ces modes de fonctionnement. Comme tous les processeurs avancés les PIC32 ont 2 niveaux de fonctionnement, le mode noyau et le mode utilisateur. Ces modes différents existent pour proteger le système. En mode noyau (kernel), le programme a accès à toutes les instructions et à tout l'espace mémoire. En mode utilisateur (user), le programme n'est pas autorisé à accéder aux périphériques et à la mémoire réservé au noyau. De plus certaines instructions sont interditent d'exécution. Si un programme utilisateur essaie d'exécuter une instruction interdite ou d'accéder un périphérique une exception est déclenchée, le MCU repasse en mode noyau et la routine qui gère ce type d'exception est exécutée. Ce qui ce passe à ce moment dépend entièrement du logiciel noyau. le MCU peut-être réinitialisé, entré dans une boucle infinie, afficher un message d'erreur, etc. D'une façon ou d'une autre le progamme utilisateur est interrompu. Ce genre de protection est utilisé sur tous les systèmes d'exploitations modernes. Par exemple lorsqu'on parle du noyau Linux, on parle justement de la partie du système d'exploitation qui est protégée contre les accès utilisateurs et qui contrôle le système à la base. Seul le noyau a accès aux ressources du système. Mais alors comment les programmes utilisateurs font pour utilisés les périphériques? A travers à appel au noyau qui porte le nom de SYSCALL. Mais cette question dépasse le cadre de cette chronique.

Les PIC32 possèdent donc tous les mécanismes nécessaires pour faire tourner un système d'exploitation protégé. D'ailleurs certains font tourner un système BSD unix sur des platines de développement duinomite.

exécution en mode utilisateur

Puisqu'il y a 2 modes de fonctionnement il faut que la mémoire aussi bien flash que RAM soit partionnée entre noyau et utilisateur. Comme je le mentionnais plus haut, au démarrage le MCU est en mode noyau et toute la mémoire lui est réservée. Supposons qu'à ce moment on fait passé le MCU en mode utilisateur c'est le plantage assuré car aucune mémoire n'a été réservé pour l'utilisateur. Donc avant de passer en mode utilisateur il faut partionner la mémoire flash et la mémoire RAM pour en attribuer une partie au mode utilisateur. Il y a plusieurs registres qui permette cette configuration.

  • BMXCON, gère les paramètres d'exceptions, et de mémoire cache. On peut l'ignorer pour le moment.
  • BMXPUPBA, addresse de début de la partition mémoire flash réservée aux programmes utilisateur.
  • BMXDUDBA, adresse de début de la partition mémoire RAM réservée aux données utilisateurs.
  • BMXDKPBA, adresse de début de la partition mémoire RAM réservée aux programmes noyau exécutant en RAM.
  • BMXDUPBA, adresse de début de la partition mémoire RAM réservée au programmes utlisateurs exécutants en RAM.
  • BMXPFMSZ, dimension de la mémoire flash en Ko.
  • BMXDRMSZ, dimension de la mémoire RAM en Ko.
  • BMXBOOTSZ, dimension de la mémoire flash du secteur boot.
Le contenu des registres BMXxxxBA sont des valeurs relative au début du segment. Les partitions commencent sur des multiples de 2 Ko. Donc pour le PIC32MX110F016B qui n'a que 4Ko de RAM on ne peut que créer 2 partitions RAM et les partitions noyau et utilisateurs ne peuvent être superposées.

programme démo

Ce programme démo montre comment exécuter une sous-routine en mémoire RAM. Le MCU demeure en mode noyau mais la mémoire RAM est partitionnée en 2 partie de 2Ko chacune. La partie du bas qui commence à l'adresse virtuelle 0xA0000000 est réservée au données et la partie du haut à l'adresse virtuelle 0xA0000800 est réservée pour l'exécution de routines en mémoire RAM.

J'ai simplement repris le programme de la partie 2 de cette introcution au PIC32MX et apporté quelques modifications. La première chose à faire est de partitionner la RAM au début du programme comme indiqué ici. Il est important lorsqu'on cré la partition BMXDKPBA (partition exécution en mémoire RAM pour le noyau) de définir BMXDUDBA et BMXDUPBA à une valeur supérieur à BMXDKPBA sinon le programme va planté. Les partitions utilisateurs sont au dessus des partitions noyau. Inclure dans votre programme source:

#include <sys/attribs.h>
Pour qu'une fonction soient copiée dans la mémoire RAM au démarrage du MCU il faut lui donner un attribu __longramfunc__ comme ici.
On peut aussi donné un attribu __ramfunc__ à une fonction en RAM mais celle-ci ne pourra-être appellée que par une autre fonction qui est en RAM et non par une qui est en mémoire FLASH. Dans ce démo comme la function square() est appellée par la prcédure main() qui est en mémoire flash elle doit avoir l'attribu __longramfunc__.

linker script

Malheureusement ceci ne suffit pas. Pour que ça fonctionne il faut modifier les scripts du lieur ld.exe, le programme qui est appellé par l'IDE pour créer l'exécutable final. Les scripts utilisés par défaut réserve toute la mémoire RAM pour les données. Il faut donc en créer deux autres pour informer le lieur que la mémoire RAM est maintenant partionnée et que les functions qui sont dans les sections *.ramfunc* doivent-être préparée pour copie dans la RAM.

Copiez les 2 fichiers suivant dans votre dossier projet *.X.

  • C:\Program Files (x86)\Microchip\xc32\v1.20\pic32mx\lib\ldscripts\elf32pic32mx.x
  • C:\Program Files (x86)\Microchip\xc32\v1.20\pic32mx\lib\proc\32MX110F016B\procdefs.ld
Dans la vue Projects de MPLABX cliquez avec le bouton droit sur le dossier linker Files et choississez Add existing Item...
Ajoutez les 2 fichiers que vous venez de copier. Maintenant qu'ils sont ajouter au projet ouvrez-les dans l'éditeur.

Commençons par prodefs.ld à la ligne 9 mettez en commentaire les 2 lignes OPTIONAL comme suit. n'utilisez pas le // pour mettre en commentaire le linker ne reconnait pas cette syntaxe. Descendez maintenant à la ligne 53 section MEMORY. Modifiez la ligne 53 et ajotutez en une autre. le kseg1_data_mem avait une longueur de 0x1000 on la réduit à 0x800.
On a ajouté un nouveau segment kseg1_prog_mem. La mémoire RAM est maintenant partionnée en 2 part égales de 2Ko.

Maintenant on va modifier le fichier elf32pic32mx.x pour y définir une nouvelle section mémoire appellée .ramfunc. Après la ligne 43, apreès la définition de .app_excpt, insérez la section suivante:

Compiler et tester

Maintenant il ne vous reste plus qu'à compiler le projet et à l'injecter dans le PIC32MX110F016B. Avant de démarrer le MCU lancer Terminal et connectez-vous au port série à 115200BAUD. Réinitialisez le MCU.

La LED branchée sur PB6 devrait clignoter. Les 2 autres devraient être éteintes mais vous pouvez basculer leur état à partir du terminial avec les touches 'r' et 'b' comme dans la partie 2. De plus si vous saisissez un nombre entier dans le terminal suivit de la touche ENTER le carré de ce nombre sera affiché dans le terminal. La fonction carre() s'exécute dans la mémoire RAM et non FLASH.

Qu'elle différence ça fait que la fonction s'exécute en RAM? C'est plus rapide que la mémoire flash. Dans le cas de ce démo ça ne fait aucune différence mais il peut y avoir de bonnes raisons d'exécuter en RAM plutôt qu'en flash. Si vous utilisez par exemple un MCU avec 128Ko de RAM et que vous installez RetroBSD dessus vos programmes chargés à partir d'une carte SD seront exécutés en RAM. Une méthode de développement qui est intéressante consiste à installer un petit programme moniteur sur le MCU et de faire du développement en connectant le MCU au PC via un port série. Le moniteur est conçu pour recevoir des fonctions compilées, les installés et exécutés en RAM. De cette façon on peut vérifier la validité du code sans avoir à l'inscrire en flash, réduisant l'usure de celle-ci. C'est aussi plus rapide et plus souple que le cycle compilation + flash.

Présentement je serais tenté d'installer un système FORTH sur un PIC32MX et de faire du développement interactif de cette façon.

Aucun commentaire:

Enregistrer un commentaire