mardi 16 avril 2013

Introduction au PIC32, partie 4, assembleur

Même si c'est beaucoup plus simple et rapide de programmer en 'C', il faut avoir une connaissance minimale des instructions machine et de l'architecture des processeurs sur lesquels on travaille. Je jette toujours un coup d'oeil sur le listing assembleur généré par le compilateur. Dans cette chronique je présente un bref aperçu des particularités de l'architecture MIPS32. A ce sujet le premier programme que j'ai écris dans la partie 1 de cette série me réservais des surprises. Si vous vous intéressez à la progammation assembleur sur les PIC32 vous devez au mininum télécharger les 3 documents suivants:

Particularités

Il y a 32 registres généraux de 32 bits. Le registre R0 par contre est en lecture seul et rapporte '0' à la lecture.
Le registre R31 est utilisé pour conserver l'adresse de retour lors de l'appel d'une sous-routine. Appel qui se fait par l'instruction JAL Jump and Link. L'adresse de retour n'est pas l'instruction qui suis le JAL mais la deuxième.
Il n'y a pas de pointeur de pile, le registre R29 est utilisé à cet effet par le compilateur gcc. Il n'y a pas d'instructions POP et PUSH pas plus qu'il n'y a d'auto-incrément/décrément du pointeur de pile. La pile est donc gérée entièrement en software. Il n'y a pas d'instruction RET pour sortir d'une sous-routine il faut utiliser un J RA où RA est le registre qui contient l'adresse de retour, habituellement R31. C'est cohérent avec les instructions JAL et JALR.
Même si les registres à l'exception de R0 peuvent-être utilisés à n'importe quel usage, il y a une convention à respecter si vous voulez que votre code soi compatible avec celui généré par le compilateur. A ce sujet consultez la carte quick reference

Saute, mais pas tout de suite!

Dans la première partie après avoir écris le premier programme dans lequel il y a une boucle de délais for(i=0;i<16000000;i++);, j'ai jeté un coup d'oeil au résultat en assembleur et quelque chose m'a laissé plutôt perplexe. Ça ressemblais à une erreur d'assemblage et pourtant le programme était fonctionnel!

Voici le code source en 'C'
Et voici le résultat en assembleur:

Ce qui m'a laissé perplexe c'est la traduction de la boucle for:
Le compilateur a inversé ma boucle,au lieu de compter de 0 à 16 000 000 il compte à rebours. Pas de problème avec ça. Par contre l'instruction à l'adresse 9D000B64 ressemble à un boucle infinie. En effet la première fois que cette instruction est exécutée le compteur V0 est à 15 999 999. Donc l'instruction branche à l'adresse 9D000B64 c'est à dire sur elle-même. Boucle infinie donc. Le mystère est resté entier jusqu'à ce que je me mette à la lecture du MIPS32 architecture for programmers Volume 2: instructions set.

Avant de faire un branchement ou un saut les cores MIPS32 exécutent l'instruction qui suis immédiatement l'instruction J. Donc dans cette exemple le compteur V0 est effectivement décrémenté par l'instruction à l'adresse 9D000B68.

Si vous faites la lecture de code assembleur pour l'architecture MIPS32, vous constaterez que le compilateur place toujours une instruction NOP après l'instruction J. Et pour cause puisque cette instruction est exécutée avant de faire le saut. Pour toute instruction B* (branch) ou J* (jump) il faut tenir compte de l'avis suivant:
Processor operation is UNPREDICTABLE if a branch, jump, ERET, DERET, or WAIT instruction is placed in the delay slot of a branch or jump. Si on place une des instructions mentionnée après le mot UNPREDICTABLE après une instruction de branchement conditiel ou un saut le résultat est imprévisible. NOP...NOP...NOP.

Autre particularité, il y a 2 types d'instructions addition, ADD,ADDU,ADDI et ADDIU et soustraction SUB et SUBU. Notez que dans le code assembleur ci-haut le compilateur a utilisé la version ADDIU pour additionner une constante à un registre. Les instructions qui n'ont pas de 'U' à la fin génèrent une exception lorsqu'il y a un débordement arithmétique et le résultat de l'opération n'est pas inscris dans le registre distination. Si vous n'avez pas besoin de gérer ces exceptions faites comme le compilateur 'C' et utilisez les versions se terminant par un 'U'. Le résultat est exactement le même pour les 2 types d'instructions et les indicateurs booléens Z,N et OV seront ajustés de la même façon. Comme mentionné dans la litérature fournis par MIPS le suffixe 'U' pour unsigned a été mal choisi, ça veut plutôt dire pas d'exception.

Fichier en assembleur dans un projet XC32

Pour des raisons de performances il se peut que certaines parties de votre projet soit en assembleur. Si c'est le cas évitez de farie ceci:

Si vous faites ça MPLABX va créé un fichier avec l'extension .s. Si vous voulez vous simplifier la vie créer un fichier vide (empty file...) et nommez le avec une extension .S. Un 'S' majuscule plutôt que minuscule. Les fichiers avec un 'S' majuscule sont traités par le pré-processeur du compilateur ce qui n'est pas le cas des fichiers avec un 's' minuscule. Le traitement par le pré-processeur 'C' va vous permettre d'utiliser les macros et fichiers d'entête des librairie 'C' sans vous casser la tête. Même si votre projet XC32 ne contient qu'un fichier assembleur .S le même d'initialisation du processeur que celui utilisé dans un projet écris en 'C' sera utilisé. Vous n'aurez donc pas plus d'initialisation à faire que si vous écriviez en 'C'.

exemple de projet

Je ne vous présenterai pas de code exemple personnel puisque Microchip en fourni un exemple que vous pouvez télécharger à partir de cette page.

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.

mardi 9 avril 2013

Introductuion au PIC32, partie 2

Quelques précisions

Le datasheet intitulé PIC32MX1xx/2xx family datasheet qu'on retrouve en haut de cette page n'est qu'un résumé, pour chaque section il y a un autre document PDF disponible plus bas sur cette même page. Heureusement grâce aux librairies on n'a pas besoin de lire toute cette documentation en détail, il suffit de si référer pour éclaircir certains points lorsque la lecture des fichiers d'entête n'est pas suffisante.

En ce qui concerne la fréquence maximale, d'après la page mentionnée ci-haut la fréquence maximale des MCU des familles PIC32MX1xx/2xx est de 40Mhz pourtant dans le datasheet à la page 1 c'est indiqué que le core peut fonctionné à 50Mhz. Que faut-il croire? Quoi qu'il en soit j'ai configuré le PLL pour 50Mhz et ça semble fonctionné sans problème pour le moment mais ça ne veut rien dire, pour un programme plus complexe ça pourrait posé problème, c'est donc à surveiller.

En bas de cette page il y a des notes applicatives avec le code source disponible. Inutile de téléchargé le document pdf car il est inclus avec la package source.

J'ai oublié de mentionner certains faits importants à propos des PIC32 dans la première partie. Premièrement ces MCU fonctionnent à une tension maximale de 3,6 Volt et le core lui-même fonctionne à 1,8 Volt. Il y a donc à l'intérieur du MCU un régulateur de tension de type LDO (Low Drop Output) et la broche marquée Vcap (20 sur les DIL-28) est la sortie de ce régulateur. Il faut mettre un condensateur de 100nF entre les broches 19 (Vss) et 20 (Vcap) le plus prêt possible des broches. J'ai vu sur certains schéma un condensateur de 10uF, mais ce n'est pas nécessaire. Si vous voulez mettre un électrolytique entre 19 et 20 mettez quand même un 100nF céramique. Voir la section Quickstart dans le datasheet.

Les broches 26 et 27 nommées AVss et AVdd correspondent à l'alimentation pour les circuits analogiques, comparateurs et convertisseur A/N. Encore une fois on doit-mettre un condensateur 100nF entre 26 et 27 le plus près possible des broches. De plus au lieu de raccorder la broche 27 (AVdd) directement à V+ de l'alimentation on peut mettre une petite résistance de 10 ohm pour améliorer le filtrage. Un MCU qui fonctionne à 50Mhz ça produit beaucoup de fluatuations parasites sur l'alimentation.

Certaines broches tolèrent une tension de 5 volt. Celle-ci sont indiquées en gris sur l'image suivante:

Certaines sorties numériques peuvent-être configurées en mode drain ouvert. En plus des résistances pullup configurables il y a aussi des pulldown configurables. Voir la section I/O ports du datasheet à ce sujet.

Les entrées/sorties numériques ont 3 registres associés SET, CLEAR, INVERT. Ces registres fonctionnent de la façon suivante, les bits qui sont mis a 1 sont affectés par l'opération ceux qui sont à zéro demeurent inchangés. Pour chacune de ces fonctions il y a une macro. Dans le programme présenté dans la première partie, la macro mPORTBToggleBits() prend simplement la valeur qu'on lui donne en argument pour la mettre dans le registre INVERT. les 2 autres macros mPORTBSetBits() et mPORTBClearBits() font la même chose pour les registres SET et CLEAR. La première mets les bits sélectionnés à Vdd et la deuxième les mets à Vss. Donc dans le programme de la première partie mPORTBToggleBits(BIT_6|BIT_7|BIT_8) inverse ces bits chaque fois qu'elle est invoquée.

Il y a dans le PIC32MX110F016B beaucoup plus de périphériques qu'il n'y a de broches. On ne peut donc pas tous les utilisés en même temps. Associé à chaque port il y a un mutliplexeur d'entrées et un autre pour les sorties. Le rôle de ces multiplexeurs est d'assigner les périphérique aux broches. La déclaration des macros nécessaires à cette configuration se trouve dans le fichier pps.h (Peripheral Pin Select). Dans cette deuxième partie nous allons configurer le périphérique UART2 sur les broche PB10 (21) et PB11 (22).

Matériel requis

En plus du matériel de la première partie on a besoin d'un adapteur USB - rs232 - TTL. Si vous avez déjà un adapteur USB-RS232 il ne vous manque que l'adapteur de niveaux de tension entre celui-ci et le PIC. Le programme démo de cette partie va communiqué avec le PC hôte dans les 2 sens. A la réinitialisation il va lui envoyé le message "hello from blinky!". Ensuite il va répondre à des commandes pour allumer et éteindre les LEDS.

interface UART

Un nouvel élément est apparut au début de la procédure main. SYSTEMConfig() permet d'optimiser la configuration du MCU en fonction de la fréquece Fsys. Le premier argument est la fréquence en Hertz et le deuxième argument représente des valeurs de configurations combinées avec l'opérateur booléen OU. Il y a 3 paramètres qui peuvent-être ajustés. SYS_CFG_WAIT_STATES configure les cycles d'attente pour l'accès à la mémoire flash. Cette mémoire a une vitesse d'accès maximale de 30Mhz donc si le MCU fonctionne à plus de 30Mhz il faut insérer des cycles d'attente entre chaque lecture de la mémoire flash. SYS_CFG_PB_BUS sert à ajuster le diviseur de l'horloge du bus périphérique qui est limité à 80Mhz. Utile donc seulement pour les MCU qui fonctionnent au delà de cette fréquence. SYS_CFG_PCACHE configure la mémoire cache pre-fetch ceci ne concerne pas vraiment les 1xx/2xx car ils ne possèdent pas de cache pre-fetch seul des 3xx/4xx/5xx/6xx/7xx ont une telle mémoire. Vous trouverez plus d'information à ce sujet dans le fichier d'entête system.h et la documentation fournie par Microchip.

Voyons voir maintenant l'assignation des périphériques aux broches du MCU. N'importe quel périphérique ne peut pas être assigné à n'importe quel broche, il y a des groupes de périphériques. Chaque groupe correspond à un multiplexeur. Il a 4 multiplexeurs d'entrées et 4 pour les sorties. La façon la plus rapide de savoir sur quel broche on peut assigné un périphérique particulié est de regarder dans le fichier d'entête pps.h. Nous voulons utilisé le UART2. Commençon par la sortie TX. En ce qui concerne le PPS les périphériques s'appelle des fonctions. On commence donc par rechercher à quel multiplexeur (groupe) appartient la fonction U2TX et découvre cette fonction dans le groupe 4, OUT_FN_PPS4_U2TX. OUT pour sortie, FN pour fonction, PPS4 le multiplexeur, et U2TX la fonction. Il suffit ensuite de choisir sur quel broche du groupe OUT_PIN_PSS4_ on veut assigné cette fonction. Dans notre programme on a choisi OUT_PIN_PPS4_RBP10. Donc on a dans le programme:

PPSOutput(4, RPB10, U2TX);
Le premier argument est le numéro du multiplexeur, le deuxième la broche et le 3ième la fonction
Si on regarde pour U2RX on a que le groupe est 2 et parmis les broches d'entrée disponible on a IN_PIN_PSS2_RBP11. Donc dans le programme on a:
PPSInput(2, U2RX, RPB10);
Ici le premier argument est le groupe, le deuxième la fonction et le 3ième la broche. Pourquoi est-ce que ce n'est pas dans le même ordre que pour PPSOutput? mystère!.

Maintenant que le périphérique a été assigné à des broches il faut le configuré. On consulte uart.h. Avec un nom comme UARTConfigure() on ne risque pas de se tromper c'est surement ce qu'il nous faut. Le premier argument est l'id du périphique ici UART2 et le deuxième est le mode de fonctionnement UART_ENABLE_PINS_TX_RX_ONLY, on n'utilise pas les lignes CTS et RTS seulement TX et RX. Maintenant il faut déterminé le nombre de bits , la parité et les stops.

UARTSetLineControl(UART2, UART_DATA_SIZE_8_BITS | UART_PARITY_NONE | UART_STOP_BITS_1);
On a choisi 8 bits, pas de parité et 1 seul stop bit.
La vitesse de communication est sélectionnée avec:
UARTSetDataRate(UART2, mGetPeripheralClock(), BAUD_RATE);
la fréquence du bus périphérique est nécessaire pour programmer le baud rate generator, d'ou le deuxième argument mGetPeripheralClock(). BAUD_RATE est défini à 38400 plus haut dans le fichier.
Finalement il ne reste qu'à activer le périphérique avec UARTEnable(UART2, UART_ENABLE_FLAGS(UART_PERIPHERAL | UART_RX | UART_TX)); la transmission et la réception sont activés indépendemment l'un de l'autre.

Une partie du code de ce démo a été emprunté sur le forum de eevblog. C'est le cas de la function serial_print(). Dans la boucle while externe qui transmet le contenu du buffer, on attend que le périphérique soit disponible pour la transmission, while(!UARTTransmitterIsReady(UART2)); et on transmet un caractère à la fois, UARTSendDataByte(UART2, *buffer++);. On sort de la bouche while externe lorsque le caractère est nul. Finalement on transmet un CRLF pour terminer la ligne.

Avant de réinitialisé le MCU utilisé un émulateur de terminal (il s'appelle simplement terminal dans windows 7) et connectez le au port sur lequel est branché le MCU. Au démarrage le programme envoie le message "hello from blinky!" et ensuite allume la LED qui est sur PB6 et dans la boucle while attend simplement une commande. Utilisez les touches 'r' et 'b'. Il n'y a que ces 2 commandes. 'r' bascule l'état de la LED branchée à PB7 et 'b' celle sur PB8.

Formidable nous venons de tuer un moustique avec un bazooka! En pratique ce genre de micro-contrôleur est assé puissant pour hébergé un petit système d'exploitation et dans un boitier plus compacte que le DIL-28 ferait très bien pour fabriquer un lecteur MP3 et autre gadget plus exigeant que de faire clignoter des LEDs. On pourrait l'utiliser pour fabriquer un petit ordinateur dans le style 1980 mais qui serait dopé comme un cycliste professionel. A ce sujet je vous invite à consulter la page de Goeff Graham au sujet du maximite, construit avec un PIC32MX795F512.

lundi 8 avril 2013

introduction au PIC32MX

C'est en 2007 que Microchip a débuté la mise en marché des ses microcontrolleurs 32 bits. Contrairement à ses autres MCU le core des PIC32 n'a pas été développé à l'interne mais ils ont plutôt choisi d'acheter une licence d'utilisation du core M4K de MIPS technologies. C'est microcontrolleurs n'ont donc rien à voir avec les autres PIC en ce qui concerne l'architecture du core et des instructions assembleurs. Le core M4K est de type LOAD/STORE comme les AVR, les MSP430 et les ARM. c'est à dire que les opérations arithmétiques et logiques sont effectuées sur des arguments stockés dans la banque de registres internes au core. Cette banque comprend 32 registres généraux de 32 bits. Donc pour additionner par exemple 2 variables il faut d'abord aller les chercher dans la mémoire RAM pour les mettre dans deux deux registres, ensuite faire l'addition et réécrire le résultat dans une variable en RAM.

Ce qui est intéressant pour les hobbyistes c'est que Microchip fournis plusieurs modèles de PIC32MX en format DIL-28 broches 300mil. Ce qui n'est pas le cas des contrôleurs à core ARM. A ma connaissance il n'y a que NXP qui vend un modèle de MCU 32 bits(core ARM) en format DIL soit le LPC1114FN28. Mais il n'est jamais en inventaire chez Digikey ou Newhark. Donc si on veut avoir un MCU 32 bits en format DIL il faut se tourner vers les PIC32MX1xxxx/2xxxx. Les PIC32MX sont groupés en familles identifiées par le premier chiffre après le 'X'. Présentement il y en a 7. Les seules familles qui ont des représentants au format DIL-28 sont les familles 1xxx et 2xxx. Le plus plus économique est le PIC32MX110F016B-I/SP. Les 3 chiffres après le 'F' indiquent la quantité d'octets flash, dans cette exemple il y en a 16Ko + 3Ko flash boot. Et ce MCU contient 4Ko de RAM. Présentement il se vend 3,26CAN$ chez Digikey.ca à l'unité.

Pour les premières expérimentations avec ce MCU le matériel suivant est requis.

  • 1 MCU PIC32MX110F016B-I/SP
  • 1 résistance 10 ohm
  • 3 résistances de 470 ohm
  • 1 résistance 10 Kohm
  • 3 LED
  • 2 condensateurs 20pF
  • 3 condensateurs 100nF céramique
  • 1 crystal 10Mhz
  • 1 petite alimentation 3.3Volt DC
  • 1 programmeur pickit3 ou ICD2 ou IDC3

Montage sans-soudures

Problème possible avec le Pickit 3

Si vous utilisez un Pickit 3 dont le numéro assy# est 10-000424-R4 ou antérieur il est probable que vous n'arriverez pas à programmer les PIC32MX. Le message d'erreur est le suivant: pickit 3 missing memory object. J'ai cherché longtemps avant de trouver la solution. Heureusement il y en a une mais elle exige de hacker le pickit 3. Il faut court-circuiter 3 résistances de 100 ohm sur la plaquette de circuit. Pour plus d'information téléchargez le document pdf ETN 32 pickit 3 operation at low voltage - Modification. Ce n'est qu'après avoir effectué cette modification que j'ai réussi à programmer un PIC32MX. Bizarrement il n'y a qu'avec les PIC32 que j'ai eu ce problème, jamais avec des PIC10,12,14,18,24. Il est moins risqué de souder un petit bout de fil entre les 2 extrémités des résistances plutôt que d'essayer de les enlever pour les remplacer par un court-circuit. Ces résistances SMD sont très petites, il faut une pointe de fer à souder très fine pour faire ce travail.

Environnement de travail

Que ce soit sous windows ou linux l'environnement de travail est le même, il faut télécharger et installer la dernière version de MPLABX et le compilateur XC32 qui se trouve sur la même page de téléchargement.

MPLABX est gratuit mais pour XC32 c'est quelque peut différent. Vous pouvez l'utiliser gratuitement mais dans cette version gratuite le compilateur ne fait pratiquement pas d'optimisation. Vous pouvez aussi utiliser la version professionnelle avec l'optimisation complète gratuitement pour 60 jours. Après cette échéance le logiciel retourne en mode gratuit à moins que vous n'achetiez une licence. 3 différentes options vous seront présentés lors de l'installation. Enregistrement immédiat d'une licence. Version d'essai de 60 jours et version gratuite à utilisation illimitée mais sans optimisation.

Présentation du PIC32MX110F016B

Les PIC32MX1xxx et 2xxx peuvent fonctionner avec une fréquence d'horloge de 50Mhz. Contrairement aux autre PIC qui utilient 4 cycles d'horloge pour chaque instruction, les PIC32MX en utilisent seulement 2 et grâce à un pipiline à 5 étages il peut exécuter les instructions plus rapidement.
Voici les principales caractéristiques du PIC32MX110F016B

  • Fréquence maximale de fonctionnement 50Mhz
  • mémoire flash de 19Ko, 16Ko général + 3Ko boot
  • mémoire RAM 4Ko
  • 5 timers de 16bits, les timers 2,3 et 4,5 peuvent-être groupés pour former deux timers 32 bits.
  • 2 UART
  • 3 comparateurs
  • 2 SPI/I2S
  • 2 I2C
  • 1 PMP (parrallel master port)
  • 4 canaux DMA (Direct Memory Access)
  • 10 canaux de conversion analogique/numérique de 10bits
  • 1 circuit RTCC avec l'entrée pour le crystal 32768Khz
  • 1 circuit JTAG pour le déboggage
  • reconfiguration des périphériques sur différentes broches

Premier projet

Créez un nouveau projet appellé blinky et choississez le compilateur XC32.

Comme d'habitude pour apprivoiser un MCU on commence par le plus simple, question de savoir si on a vraiment compris la documentation concernant la configurations des bits de démarrage et des entrés/sorties numériques. On va donc configurer les bits de démarrage pour utiliser l'oscillateur externe 1 et que le PLL multiplie la fréquence du crystal par 5. Nous aurons donc Fsys de 50Mhz. On va faire clignoté les 3 LED à l'unisson à un fréquence d'environ 1Hz dans un premier temps en utilisant un délais par boucle simple et dans un deuxième temps on va utilisé le`CoreTimer avec un interruption et un fonction delay().

La façon la plus simple de programmer les bits de démarrage est d'utiliser l'outil disponible dans MPLABX. Menu Windows - Pic Memory Views - Configuration bits

Dans la fenêtre configuration bits on peut sélectionné pour chaque option la valeur désirée dans une liste de choix. Ensuite on clique sur generate source code output. Le résultat apparaît alors dans la fenêtre output et de là on sélectionne et colle le résultat dans le fichier source.

Blinky version 1

Dans tout projet on commence par include le fichier plib.h, qui contient toutes les macros et fonctions qui permettent de configurer le MCU et les périphériques. Pour voir le contenu d'un fichier inclus in clique avec le bouton droit de la souris sur le nom du fichier et dans le menu surgissant on choisi navigate - goto declaration

Cette fonction est très pratique pour découvrir les macros et fonctions disponibles dans les librairies. Lorsqu'il s'agit d'une macro le nom commence par m sinon c'est une fonction. Les constantes sont en majuscules.

Donc dans procédure main on commence par initialiser le PORT B pour avoir des sorties numérique sur les bits 6,7 et 8 là où sont connectés les 3 LED. la macro mPORTBWrite(0) mets tous les bits du port à zéro. Ensuite mPORTBDirection() mets les bits 6,7,8 à zéro (mode sortie). Dans la boucle while mPORTBToggleBits() inverse la valeur des bits 6,7,8 à chaque appel. Lorsque les bits passe à Vdd les leds alluments et éteignent lorsqu'ils passe à Vss. La boucle for est utilisé pour le délais. Avec une valeur de 16 millions la fréquence de clignotement est d'environ 0,8Hz.

Blinky version 2

Le PIC32MX110F016B possède 5 périphériques timers de 16 bits mais le core M4K lui-même possède un compteur 32 bits qui est incrémenté à tout les 2 cycles de Fsys. Donc pour un Fsys de 50Mhz, il est incrémenté à tous les 40nSec. Dans blinky 2 on va se servir de ce compteur appellé CoreTimer pour créer un compteur sys_tick qui lui va incrémenté à toutes les milli-secondes. On va initialiser ce compteur avec la valeur Fsys/2/1000 et générer une interruption chaque fois qu'il arrive à zéro. De plus on va ajouter une fonction delay() qui va utiliser sys_tick. Avec ça on va contrôler précisément la vitesse de clignotement des leds à 1Hz.

Les PIC32 sont des MCU très complexe mais heureusement les librairies fournies par Microchip nous simplifient grandement le travail. La façon de déclarer les interruptions ressemble beaucoup à ce qu'on doit faire dans Atmel studio à la différence que les PIC32 possède un gestionnaire d'interruption programmable. Chaque interruption a un niveau de priorité de 1 à 7 et un sous-niveau de priorité de 0-3. Plus le chiffre est élevé plus la priorité est grande. Lorsque 2 interruptions de même priorité sont en concurence c'est le niveau de sous-priorité qui décide laquelle sera exécutée en premier. Dans notre exemple la macro mConfigIntTimer() défini que l'interruption pour le CoreTimer aura une priorité de 2 et une sous-priorité de 0.

Dans la routine ISR du CoreTimer on incrémente la variable sys_tick, on réinitialise le compteur du CoreTimer et finalement on remet à zéro l'indicateur d'interruption pour éviter que cette interruption se redéclenche en boucle infinie.

Dans la boucle while de la routine main on a remplacé la boucle for par un appel à la fonction delay() que nous avons ajouté au code. Cette routine utilise simplement la variable sys_tick pour compter les milli-secondes écoulées. Avec un argument de 500msec les leds clignotes à 1Hz.

Dans une prochaine chronique on verra comment utiliser le UART et assigner les périphériques aux broches car les PIC32 permettent de choisir sur quel broche sera connecté un périhérique.