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.
Tous les autres articles du projet PICvision sur ce blog
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.
Tous les autres articles du projet PICvision sur ce blog
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.compte | phases | ||||
---|---|---|---|---|---|
b1 | b0 | Q1 | Q2 | Q3 | Q4 |
0 | 0 | 1 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 0 | 0 |
1 | 0 | 0 | 0 | 1 | 0 |
1 | 1 | 0 | 0 | 0 | 1 |
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: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.
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.
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.
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.
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.
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.
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.
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
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
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:
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.
LPC_SWM->PINENABLE0 = PINENABLE0_RESRVD|DIS_ACMP_I1|DIS_ACMP_I2|
DIS_SWCLK|DIS_SWDIO|DIS_XTALIN|DIS_XTALOUT|
DIS_CLKIN|DIS_VDDCMP;
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:
Les fonctions GPIOinit(), GPIOSetDir() et GPIOSetBitValue() sont définies dans lpc8xx_gpio.c
LPC_SYSCON->SYSAHBCLKCTRL |= ENABLE_MRT;
LPC_SYSCON->PRESETCTRL |= MRT_RESET;
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.
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.
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:
Car on veut obtenir un fichier rgb_blinky.hex pour utilisation dans flash magic.
arm-none-eabi-objcopy -v -O ihex "${BuildArtifactFileName}"
"${BuildArtifactFileBaseName}.hex"
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é.
Le compilateur me donne encore une erreur:
J'ai oublié quelque chose dans la configuration:
../src/main.c:18:30: fatal error: lpc8xx_clkconfig.h:
No such file or directory
#include "lpc8xx_clkconfig.h"