jeudi 24 avril 2014

PICvision: ajout du jeux 2048

Cette semaine j'ai découvert l'existance du jeux 2048 de Gabriele Cirulli. Après y avoir jouer quelques temps j'ai décidé d'en écrire une version pour la console PICvision.

lien github du projet.

Tous les autres articles du projet PICvision sur ce blog

lundi 14 avril 2014

Comment fonctionne un décodeur d'instruction

Dans un article précédent j'ai expliqué comment fonctionne l'unitée arithmétique et logique d'un microcontrôleur. Dans cette article j'explique comment fonctionne le décodeur d'instruction. On va prendre pour exemple le coeur PIC baseline.

Voici le cycle d'instruction d'un coeur baseline.

Il faut 4 cycles d'oscillateur pour exécuter une instruction. Ces cycles sont appelés Q1,Q2,Q3,Q4 sur le diagramme ci-haut. Commençons par un circuit qui permet de généner ce clocking 4 phases:
Il s'agit d'un compteur binaire à 2 bits réalisé avec 2 bascules JK configurées en mode toggle. Ce compteur boucle de 0 à 3. Il suffit de décoder le code binaire de sorte que la sortie Qx est à 1 seulement lorsque le compteur est à sa valeur correspondante x. A chaque transition montante de Fosc, la sortie Q de FF1 change d'état. La sortie ~Q est le complémentaire de Q et sert à clocker FF2. Il faut 2 cycles de Fosc pour faire basculer FF1 mais il en faut 4 pour FF2. Ça donne la table de vérité suivante.
comptephases
b1b0Q1Q2Q3Q4
001000
010100
100010
110001

Si on regarde encore le diagramme décrivant le cycle d'instruction on constate que le compteur ordinal est incrémenté par la transition montante de la phase Q1. Sur les PIC baseline les instructions sont toutes de la même longueurs soit 1 location mémoire. Donc à chaque instruction le compteur ordinal est augmenté de 1. Dans une telle situation on peut utiliser un compteur binaire synchrone comme compteur ordinal. Cependant pour effectuer des branchements il faut qu'on puisse charger ce compteur avec la valeur de l'adresse de destination. Un tel compteur binaire s'appelle loadable counter. Celui ci-dessous a 4 bits mais le principe peut-être étendu à autant de bits que nécessaires, (9 bits pour les PIC10F202).

Maintenant on a 2 éléments essentiels de notre coeur baseline. Le circuit qui génère les 4 phases et le compteur ordinal.

On va prendre en exemple 2 instructions, GOTO et ADDWF. Commençons par le GOTO. Le code opérationnel de cette instruction est encodée sur les 3 bits les plus significatifs (les instructions sont encodées sur 12 bits) et correspond à 101. Voici comment elle pourrait-être décodée.

Bien que je ne les ai pas représentés sur le schéma il y a des portes de sélection sur tous les bus entre les registres. Les signaux Qx ouvrent et ferment ses portes pour assurer le transfert au moment approprié. Voiçi les étapes pour le GOTO, les étapes 1 et 2 sont communes à toutes les instructions:
  1. Sur la transition Q1 montante on peut transférer le contenu du registre fetch register dans le registre décodeur d'instruction (instructon register). En même temps le compteur ordinal est incrémenté.
  2. Sur la transition descendante de Q1 le contenu du bus data de la mémoire programme peut-être transféré dans le fetch register. Puisque le compteur ordinal été incrémenté l'instruction chargée est celle à l'adresse PC+1
  3. Il s'agit d'un GOTO il faut donc remplacé le contenu du compteur ordinal par les 9 bits faibles du registre d'instruction. On peut faire ce transfert sur la transition montante de Q2. l'instruction qui a été chargée dans le fetch register n'est plus bonne puisqu'il y a changement d'adresse. Il faut donc mettre ce regisre à zéro qui est le code opérationnel pour l'instruction NOP (ne fait rien). Car au prochain cycle on doit charger l'instruction qui se trouve à la nouvelle addresse. On peut-faire cette mise à zéro aussi sur Q2 montante.
  4. Là on a un petit problème. Puisque le compteur ordinal pointe sur la prochaine instruction à exécuter et que celle-ci n'est pas dans le fetch register et puisque que le compteur ordinal est incrémenté dès le début du cycle d'instruction, si on le laisse tel quel il va chargé l'instruction qui se trouve après celle du GOTO. On va donc décrémenter le compteur ordinal avant de terminer ce cycle d'instruction. Cette opération ce fait sur la transition montante de Q3.
  5. La phase Q4 n'est pas utilisée par cette instruction.

ADDWF

Voici un schéma partiel d'un décodeur pour cette instruction.

Comme mentionné précédemment la phase Q1 est identique pour toutes les instructions.

  1. Pendant la phase Q2 la valeur contenu à l'adresse indiqué dans le code opérationnel (5 bits faibles) est utilisé pour adresser le file register. S'il y a indirection, le file register doit-être adressé une deuxième fois avec le contenu du registre FSR
  2. Pendant la phase Q3 la valeur de W et le contenus adressé dans le file register sont présentés aux entrées de l'UAL et l'addition effectuée.
  3. Durant la phase Q4 le résultat de l'opération est retourné soit dans W soit dans le file register. Notez la façon dont le bit d du code opérationnel est utilisé pour sélectionner la destination. Si d est zéro le résultat est retourné dans W sinon il est retourné dans le file register. Un petit rond à l'entrée d'une porte indique que le signal est inversé (active low) il faut donc que W soit à zéro pour que le signal de contrôle passe.

décodeur ROM

Créer un décodeur pour chaque instruction peut nécessité beaucoup de portes logiques même si les instructions peuvent partager des portes, par exemples les inverseurs sont communs à tous les décodeurs. Il y a une autre façon de procéder, il s'agit d'utiliser un décodeur ROM (Read Only Memory). Les bits data de la ROM sont fabriqués avec un masque métallique plutôt que des portes et ont un faible coût. L'idée est simple à implanter, les bits de l'instruction register qui contiennent le code opérationnel sont utilisés comme adresse d'une mémoire ROM. Les lignes de sortie de donnée de la ROM sont utilisés comme signaux pour contrôler les portes, en plus certaines lignes de données sont retournées vers le bus d'adresse de la ROM ce qui permet de faire évoluer l'automate de manière simple d'une étape à l'autre.

Supposons que le opcode est 5 et que les bits à la sortie du latch sont toujours à zéro au début du cycle d'instruction. À l'adresse 5 de la mémoire ROM on met les bits data aux valeurs qu'on a besoin sur le bus de contrôle et on envoie dans le latch les bits qui combinés avec les bits qui proviennent de l'instruction register vont donner l'adresse en ROM qui contient les valeurs de bits qu'on aura besoin à la prochaine étape du décodage. Ce genre de state machine est fréquemment utilisé, particulièrement dans les FPGA.

dimanche 6 avril 2014

Un Boitier pour PICvision

Quand viens le temps de trouver un boitier pour mes projets j'essaie le plus souvent de faire de la récupération plutôt que d'acheter un boitier préfabriqué. Pour mon projet PICvision j'ai décidé de tenter l'expérience d'en fabriquer un avec du carton ondulé.

Les dimensions extérieures sont de 6-1/4" (159mm) en largeur, 3" (76mm)en hauteur et 4-1/4" (108mm) en profondeur. J'ai découper les panneaux pour chaque face en les doublant en épaisseur et j'ai assemblé le tout avec de la colle chaude. j'ai trouvé à la quincaillerie une équerre en plastique de 1/2" (12,7mm) que j'ai découpé en sections pour protéger les angles de la boite. Les faces extérieures sont recouvertes de papier glacé destiné à l'impression photographique.

Ceci est une solution temporaire, je planifie d'en fabriquer un en bois d'érable ou de chêne rouge.

Il me reste encore du développement logiciel à faire sur ce projet.

samedi 5 avril 2014

LPC810, partie 5, module SCT et PWM

Générer un signal PWM sur un AVR ou un PIC est simple mais pour arriver à le faire sur un LPC810 j'ai du lire et relire avec beaucoup d'attention le chapitre 10 du LPC81x user manual concernant le module SCT (State Configurable Timer). C'est le plus long chapitre du manuel. Ce périphérique utilise 116 registres de configuration. Générer un signal PWM est l'utilisation la plus simple qu'on peut faire de ce périphérique. Si ce périphérique est si complexe c'est qu'en fait il s'agit d'un automate programmable. Notez que les LPC8xx sont limités à 2 états et 6 événements. Dans l'utilisation que j'en fais dans les 2 démos qui suivent il n'y a pas de transition d'état, le SCT demeure dans l'état 0. Néanmoins si vous jetez un coup d’œil à la procédure PWM_init()du premier démo, 30 assignations à divers registres du module SCT sont nécessaires pour générer 3 signaux PWM.

LED RGB contrôlée par PWM 16 bits

Pour ce démo j'ai conservé le montage de la LED RGB tel quel mais chaque anode est contrôlée en intensité par un signal PWM à une fréquence de 183 Hertz. La résolution PWM est de 16 bits par canal. A chaque seconde la fonction rand() est utilisée pour générer 3 nouvelles valeurs pour le rapport cyclique de chaque canal. Il en résulte donc que la LED change de couleur 1 fois par seconde de façon aléatoire.

Comparez la simplicité de la fonction delay_ms() avec la complexité de PWM_init(). Le module MRT (Multi-Rate Timer) est un des plus simple à utiliser.

2ième démo

Dans ce démo j'utilise le module SCT d'un autre autre façon. En utilisant 4 événements le module SCT peut séquencer les 3 couleurs primaires de la LED sans intervention logicielle. Une fois le module SCT paramétré et démarré, le MCU tourne dans une boucle while vide et il n'y a pas non plus de routine d'interruption qui intervient.

Conclusion

Les fabricants de MCU ajoute des modules de plus en plus complexes à leur produit. Ceux-ci demandent plus d'effort d'apprentissage mais une fois maîtrisés ils permettent de libéré l'unité centrale au profit d'autres tâches.

mercredi 2 avril 2014

LPC810, partie 4, programmation

Les 3 premiers articles présentaient l'environnement et le LPC810. Dans cette partie je vais discuter de la programmation en analysant l'exemple de la partie 3.

Après avoir créer le projet rgb_blinky dans la partie 3 on avait 4 fichiers dans le dossier src

  • cr_startup_lpc8xx.c, ce fichier contient le code d'initialisation du MCU et est généré automatiquement. Normalement il n'est pas nécessaire de modifier son contenu.
  • crp.c, Si on a sélectionné l'option "Enable Code Read Protect" dans les options du projets ce fichier contient le code de protection. Normalement on ne touche pas à ce fichier.
  • mtb.c, correspond à la fenêtre d'options Micro Trace Buffer lors de la création du projet. On n'a pas à toucher à ce fichier.
  • main.c, c'est le fichier principal de notre application, celui sur lequel on travaille.

Le squelette de l'application

Si on ouvre main.c dans l'éditeur on voit qu'il y a 2 fichiers d'entête inclus dans le squelette de l'application.

LPC8xx.h contient toutes les définitions nécessaire pour accéder les modules internes du MCU. On va y revenir.

cr_section_macros.h ne contient que des macros qui définissent les sections de mémoire. Ces informations sont utilisées par le compilateur et le linker mais normalement on n'a pas besoin de s'en préoccuper.

On pourrait écrire notre application sans inclure d'autres entête mais ça augmenterais notre charge de travail. C'est pourquoi on a inclus dans notre projet les librairies CMSIS_CORE_LPC8xx et lpc8xx_driver_lib.

CMSIS_CORE_LPC8xx contient le code d'initialisation du MCU ainsi que les fonctions de base qu'on retrouve dans toute application 'C' tel que stdio.h, stdlib.h, string.h, math.h, etc. On inclus donc dans notre projet les entêtes dont on a besoins pour référencer les fonctions de ces modules.

lpc8xx_driver_lib contient les pilotes de périphériques disponibles dans le microcontrôleur. Il faut inclure le(s) fichier(s) d'entête pour les périphériques qu'on utilise dans notre application.

Dans le projet rgb_blinky j'ai inclus les 2 fichiers d'entête suivant:

#include "lpc8xx_gpio.h"  // utilisation des entrées/sorties sans périphérique
#include "lpc8xx_mrt.h"   // utilisation du multi-rate timer

Si on se réfère à la partie 2 de cette série, où j'explique les modules interne du LPC810 et qu'on regarde le contenu du fichier d'entête LPC8xx.h on constate que pour chaque bloc du schéma on a une structure définie comme typedef. par exemple:
typedef struct
{
  union {
    __IO uint32_t PINASSIGN[9];
    struct {
      __IO uint32_t PINASSIGN0;
      __IO uint32_t PINASSIGN1;
      __IO uint32_t PINASSIGN2;
      __IO uint32_t PINASSIGN3;
      __IO uint32_t PINASSIGN4;
      __IO uint32_t PINASSIGN5;
      __IO uint32_t PINASSIGN6;
      __IO uint32_t PINASSIGN7;
      __IO uint32_t PINASSIGN8;
    };
  };
  __I  uint32_t  RESERVED0[103];
  __IO uint32_t  PINENABLE0;
} LPC_SWM_TypeDef;

et plus bas on retrouve une variable de type pointer dont le nom est LPC_SWM. Chaque périphérique utilise un certains nombre de registres SFR et les éléments de la structure correspondent à ces registres. Donc dans le programme on retrouve:

 LPC_SWM->PINENABLE0 = PINENABLE0_RESRVD|DIS_ACMP_I1|DIS_ACMP_I2|
                       DIS_SWCLK|DIS_SWDIO|DIS_XTALIN|DIS_XTALOUT|
                       DIS_CLKIN|DIS_VDDCMP;
Le registre PINENABLE0 permet d'activer/désactiver certaines fonctions pour libérer les GPIO. Par défaut SWCLK et SWDIO accaparent les broches 3 et 4, donc si on veut utiliser ces broches pour alimenter la LED RGB on doit désactiver ces 2 fonctions.

La fonction delayMs() qui est définie dans lpc8xx_mrt.c utilise le périphérique Multi-rate Timer on doit donc l'activer et le réinitialiser:

   LPC_SYSCON->SYSAHBCLKCTRL |= ENABLE_MRT;
   LPC_SYSCON->PRESETCTRL |=  MRT_RESET;
Les fonctions GPIOinit(), GPIOSetDir() et GPIOSetBitValue() sont définies dans lpc8xx_gpio.c
  • GPIOinit(), initialise les GPIO.
  • GPIOSetDir(), configure une broche entrée ou sortie. Le premier argument est le no de port. Le deuxième est le no de bit et le troisième est 0 pour entrée et 1 pour sortie.
  • GPIOSetBitValue(), fixe la valeur d'un broche en sortie. Le premier argument est le no de port. Le deuxième est le no de bit et le troisième est la valeur de sortie.

Dans la boucle while(1) on contrôle la valeur des sorties en affectant les bits directement dans le registre NOT0 ce registre inverse les bits de sortie dont la valeur est mis à 1 dans le registre. C'est une registre toggle. Il y a aussi un registre SET0 pour mettre les sorties à 1 et un registre CLR0 pour mettre les sorties à 0. Ces registres permettent de modifier simultanément plusieurs sorties.

Conclusion

Dans ce programme exemple on voit donc 2 façons d'utiliser les périphériques. Soit on utilise des fonctions de la librairie lpc800_driver_lib soit on accède directement les SFR à partir des structures définies dans LPC8xx.h Il faut noter que la librairie lpc800_driver_lib est fournie à titre d'exemple et qu'en conséquence est n'est pas très bien documentée et incomplète. Cependant la licence en début de fichier mentionne qu'on a le droit de modifier le code source de cette librairie à condition d'y laisser l'information de licence et de ne l'utiliser que pour des MCU NXP. Il faut considérer cette librairie comme un point de départ qui reste à développer.

mardi 1 avril 2014

LPC810, partie 3, création d'un projet

Dans la première partie nous avons compiler un projet téléchargé de github après l'avoir modifié légèrement. Dans cette partie je montre comment créer un nouveau projet. Actuellement la fenêtre project explorer devrait contenir les projets contenu dans LPCXpresso Sample Code Bundle for the LPC8xx. Si ce n'est pas le cas il faut télécharger et importer (comme expliquer dans la partie 1) le fichier zip dans project explorer. Au final la fenêtre devrait ressemblée à ceci:

Pour créer un nouveau projet on peut passé par File - new - project ou cliquez sur new project dans la fenêtre Quickstart panel.

Étape 1, type de projet:

Étape 2, nom du projet: on va l'appellé rgb_blinky
Étape 3, sélection du microcontrôleur: LPC810
Étape 4, sélection de la librairie: on laisse ça tel quel et on clique sur next
Étape 5, sélection de la librairie DSP: on en utilise aucune, on laisse à none et on clique next
Étape 6, débogage: on n'a pas de débogueur donc on décoche enable definition of buffer array for MTB et on met le champ number of byte to reserve for the MTB à zéro. next
Étape 7, other options: on peut décoché enable CRP in target image et faire finish

Maintenant notre projet rgb_blinky apparaît dans project explorer. On le sélectionne et en cliquant sur son nom avec le bouton droit de la souris on choisi properties tout en bas du menu surgissant. Dans c/c++ build - settings on sélectionne l'onglet build steps on modifie le champ command du groupe post-build steps en ajoutant au texte existant la ligne suivante:

arm-none-eabi-objcopy -v -O ihex "${BuildArtifactFileName}"
 "${BuildArtifactFileBaseName}.hex"
Car on veut obtenir un fichier rgb_blinky.hex pour utilisation dans flash magic.

Personnellement j'aime bien avoir un listing assembleur, donc avant de fermer les propriétés on va aller dans c/c++ build - Setting onglet Tool settings, dossier miscellaneous et on va ajouter à la fin du champ other flags

-Wa,-a,-ad="$*.lst"

On peut maintenant travailler sur le code source. Dans le dossier src de notre projet on double clique sur main.c pour l'ouvrir dans l'éditeur. On a déjà un squelette de l'application voici mon code finalisé. Ce programme fait simplement clignoté la LED RGB en blanc à 1 rythme d'une fois par seconde. Faites un copier-coller à partir de cette page et on va compiler en version release puisque de toute façon on n'a pas de débogueur.
Il est possible que lors de la compilation le linker affiche des messages d'erreur disant qu'il ne trouve pas les librairies CMSIS_CORE_LPC8xx et lpc800_driver_lib. C'est probablement parce que ces 2 projets non pas été compilés en version release. Vérifiez s'il y a un dossier release dans chacun des ces 2 projets. Si ce n'est pas le cas compilez les en version release.

Pour rappel la compilation se fait en sélectionnant le nom du projet et ensuite la petite flèche à droite du marteau dans la barre d'outils. Choisir 2 Release (Releas build).

Le compilateur me donne encore une erreur:

../src/main.c:18:30: fatal error: lpc8xx_clkconfig.h: 
    No such file or directory
 #include "lpc8xx_clkconfig.h"
J'ai oublié quelque chose dans la configuration:
lpc800_driver_lib n'était pas coché dans Project reference. Deuxième tentative même erreur. Il manque encore quelque chose dans la configuration des propriétés. S'il ne trouve pas le fichier d'entête c'est qu'il ne connait pas le chemin on va donc ajouter ce chemin dans C/C++ general - Path and symbols onglet includes. On sélectionne GNU C et on clique le bouton add...
Cette librairie dans est dans le dossier workspace donc on coche is a workspace path et on donne le chemin relatif. Dans la même fenêtre dans l'onglet library paths on clique sur add...
Et finalement dans l'onglet libraries on ajoute lpc800_driver_lib
Compilation! Cette fois ça marche. Ne reste plus qu'à flasher rgb_blinky.hex tel qu'expliqué en partie 1.