samedi 9 décembre 2017

Busy Bee partie 3, PCA suite

Je poursuis mes expériences avec le PCA Programmable Counter Array du EFM8BB10F8G-A-SOIC6 débuté dans la partie 2 de cette série. J'en suis rendu au mode high speed output.

high speed output

D'emblée je dois dire que la documentation fournie par Silicon Labs laisse à désirer. Voici tout ce qu'a à dire le manuel de référence à propos de ce mode de fonctionnement.
À partir de cette information insuffisante j'ai eu de la difficulté à faire fonctionner ce mode malgré mes recherches sur le site de Silicon labs qui ne m'ont pas permis de trouver un exemple d'application spécifique à ce MCU mais seulement la note applicative AN107 qui concerne les MCU C8051Fxxxx. Après de nombreux essais et erreurs j'ai réussi à faire fonctionner le mode high speed output pour faire du PWM 16 bits. Pour cette expérience j'ai utilisé le même montage que pour la partie 1. Dans cette expérience l'intensité de la LED varie selon une fonction triangulaire sur une période de 2 secondes.

Comme on le voie sur le diagramme bloc ci-dessous ce mode utilise un compteur 16 bits constitué de PCA0H:PCA0L et d'un registre de comparaison de 16 bits constitué de PCA0CPHn:PCA0CPLn. Lorsque le compteur atteint la valeur de comparaison la sortie CEXn est inversée. Le compteur PCA0 marche en boucle de 0 à 65535. Donc la période est fixée par la fréquence Fpca qui alimente le compteur PCA0. Si on configure Fsys à la fréquence maximale de 24,5Mhz et qu'on alimente le compteur PCA0 directement à cette fréquence,la fréquence du cycle PWM sera de 24,5e6/65536=373,8Hertz.

Pour faire du PWM 16 bits avec ce mode il faut activer l'interruption sur débordement du Compteur PCA0 et celle du comparateur et la l'intérieur de la routine d'interruption modifier la valeur de comparaison dans PCA0CP. On va garder le même projet que dans la partie 2 mais en retournant dans le configurateur et on va modifier la configuration pour obtenir les valeurs suivantes.

  • clock à la fréquence maximale pour obtenir un Fsys de 24,5Mhz.
  • PCA0 alimenté par Fsys ce qui va nous donner une fréquence PWM de 24,5e6/65536=373,8Hertz. On va activé l'interruption sur débordement du compteur PCA0
  • PCA0, Channel 0 configuré pour le high speed output avec activation de l'interruption sur le comparateur. La sortie est aussi inversée car la broche est configurée en open drain avec la cathode de la LED sur la broche P1.3. Si la sortie n'est pas inversée l'intensitée sera inversement proportionnelle à la valeur de PCA0CP0 et non proportionnelle mais ça va fonctionner quand même.
  • .

On ferme le configurateur pour mettre à jours les fichiers et on va ajouter quelques lignes de codes.

  • Dans le fichier InitDevice.h on va ajouter les constantes suivantes. La valeur de PWM_MIN a été déterminée par essaie et erreur et dépend de la durée de l'interruption. Plus l'interruption met de temps à s'exécuter plus cette valeur est grande.
  • Dans le fichier Interrupts.c on va ajouter du code dans la routine d'interruption générée par le configurateur.
  • Finalement on va ajouter le code pour contrôler l'intensité de la LED dans la fonction main() du fichier pca_experiments_main.c
    On met les instructions à l'intérieur de la boucle while parce que à chaque interruption le MCU sort du mode idle et que c'est à ce moment qu'on modifie l'intensité avant de remettre le MCU en idle.

Fonctionnement

Commençons par la fonction main(). le code tourne indéfiniment dans une boucle while(1){}. La première instruction de la boucle suspend l'exécution de code en mettant le MCU en mode idle. Lorsqu'une interruption se produit le MCU sort du mode idle et lorsque l'interruption se termine le CPU exécute le code qui suis l'instruction PCON0|=1. Ce code débute par un verrou lock. lock est ce qu'on appelle en informatique une variable mutex (i.e. mutually exclusive). Sont rôle est d'empêcher la routine d'interruption d'accéder la variable pwm_val lorsque la routine main() est en train de la modifier. Comme il s'agit d'une variable 16 bits il faut plus d'une instruction machine pour modifier cette variable. Imaginez qu'une interruption se produit entre le moment où la fonction main() a modifié l'octet faible de pwm_val et celui où elle modifie l'octet fort de pwm_val. Si l'interruption fait une lecture de pwm_val la valeur lue sera erronée. Donc avant de lire pwm_val l'interruption vérifie la valeur de la variable lock et ne lie la variable que si lock est à zéro. Ainsi on est certain que la valeur lue est bonne. Donc la variable lock est mise à 1 avant de débuter le bloc d'instructions qui va modifier la valeur de pwm_val qui contrôle l'intensité de la LED. Elle est remise à zéro lorsque la modification est complétée.

À chaque interruption la fonction main() modifie la valeur de pwm_val en ajoutant ou soustrayant la valeur step qui contrôle la vitesse du cycle fade in, fade out. Plus step est grand plus la LED va varier d'intensité rapidement. Dans ce démo le cycle est régler sur 2 secondes. La fonction main() s'assure que la valeur de pwm_val demeure à l'intérieur des limites valides.

Le rôle de la routine d'interruption

Le compteur PCA0 compte en boucle de 0 à 65535. À chaque débordement de PCA0 ainsi que lorsque PCA0==PCA0CP0 l'interruption SI_INTERRUPT (PCA0_ISR, PCA0_IRQn) est déclenchée. S'il s'agit d'une interruption causée par le débordement du compteur PCA0, c'est la première moitié du cycle PWM. On vérifie si la variable pwm_val est disponible. Si c'est le cas on réinitialise la variable locale brightness avec la valeur de pwm_val. Ensuite on affecte la valeur de brightness au registre PCA0CP0 qui détermine l'intensité de la LED. Par contre si l'interruption est causée par PCA0==PCA0CP0,c'est la deuxième moitié du cycle PWM. Alors PCA0CP0 est mis à 0 pour compléter le cycle en inversant à nouveau la sortie P1.3 lorsque le compteur va revenir à zéro par débordement.

Notez que PWM_MIN ne peut être inférieur au délais de réponse de l'interruption car pendant ce délais le compteur PCA0 continue à avancé. Si on initialise PCA0CP avec une valeur trop petite le compteur aura déjà dépassé cette valeur lorsque PCACP0 sera initialisé et le comparateur ne déclenchera pas l'inversion de la sortie. Ce raisonnement vaut aussi pour la valeur PWM_MAX qui est le complément à 1 de PWM_MIN.

Générateur de fréquence avec diviseur 16 bits

Il est possible d'utiliser le mode high speed output pour générer des fréquences. On retourne dans le configurateur pour désactiver l'interruption sur le débordement du compteur PCA0.


On remplace le code dans la routine d'interruption par ceci

Le principe de fonctionnement est le même que pour le générateur de fréquence de la partie 2 sauf que la mise à jour de PCA0CP doit-être faite manuellement dans la routine d'interruption. Ce qui implique encore une fois une valeur minimum pour FRQ_DIV. Par essaie erreur j'obtiens 48 pour cette valeur minimum. Avec le compteur PCA0 alimenté à 24,5Mhz ça donne une fréquence maximale de 24,5Mhz/2/48=255Khz la valeur de fréquence minimale est de 24,5Mhz/2/65535=187Hertz. La granularité, c'est à dire la variation de fréquence entre 2 diviseurs adjacents, est inversement proportionnelle à FRQ_DIV.

Gestion de la consommation

Tous les MCU possèdent un mécanisme de gestion de la consommation électrique. Les EFM8BB1xxx ne font pas exceptions. Quelques explications sur les modes disponibles. Il y en a 4.

  1. Normal, dans ce mode le CPU et les périphériques activés fonctionnent normalement. C'est le maximum de consommation électrique. On peut réduire la consommation en réduisant la fréquence de l'horloge.
  2. Idle, dans ce mode le CPU est désactivé donc aucune instructions n'est exécutée. Par contre les périphériques continues à fonctionner comme ils ont été configurés. On économise donc l'énergie dépensée par le CPU. Ce mode est utilisé dans les applications pilotées par interruption. Sitôt qu'une interruption est déclenchée le CPU se remet en fonction pour exécuter le code de l'interruption. À la fin de l'interruption il ne retombe pas en mode idle il faut l'y remettre. L'exemple ci-haut est un exemple de ce mode de fonctionnement. La dépense d'énergie est principalement celle causée par les périphériques. Dans ce mode la mémoire RAM est conservée et les états du CPU sont conservés. A la sortie de l'interruption c'est l'instruction qui suis celle qui a mis le CPU en idle qui est exécutée.
  3. stop, arrête tous les signaux d'horloges. Donc le CPU et les périphériques nécessitant un signal d'horloge sont désactivés. Par contre les entrés/sorties demeure dans leur état. Donc si une LED branchée sur une broche est allumée elle demeure allumée. Les comparateurs analogiques continus de fonctionner et peuvent-être configurer pour déclencher un reset. La seule façon de sortir de ce mode est par un reset ou une remise sous tension. Notez que si la détection missing clock est activée elle va générer un reset après son délais normal et redémarrer le MCU. Pour garder le MCU en mode stop il faut donc que la détection missing clock soit désactivée. Lorsque le MCU sort de ce mode il exécute le code à partir de l'adresse 0x0000 comme s'il venait juste d'être mis sous tension.
  4. shutdown, semblable au mode stop mais en plus le régulateur de tension du CPU est désactivé. Consommation minimale assurée. Le CPU fonctionne à 1,8 volt et puisque le MCU doit-être alimenté avec une tension entre 2,2 et 3,6 volt il faut un régulation de tension interne fournissant 1,8 volt au CPU.
La figure suivante montre par code de couleur de qui est actif dans chaque mode.

Il n'y a que 2 registres pour gérer la consommation
  • PCON0, Contient les bits IDLE et STOP. Les 6 autres bits peuvent-être utilisés par les application comme indicateurs booléens et sont nommés GF0 à GF5.
  • REGCN0, Il n'y a qu'un seul bit d'utilisé dans ce registre STOPCF. Si ce bit est mis à 1 avant de mettre le bit STOP à 1 alors le MCU va passé en mode SHUTDOWN et seule la broche ~reset ou un cyclage de l'alimentation pourra le redémarrer.

mardi 5 décembre 2017

Busy Bee partie 2, PCA

En lisant le manuel de référence du EFM8BB10F8 mon attention a été attiré par le périphérique PCA Programmable Counter Array. Chaque fabriquant nomme ses périphériques comme sa lui chante d'autres appelleraient ça Advance Capture/Compare. Cependant le PCA possède un mode de fonctionnement que je n'ai pas vue ailleurs. Silicon Labs appelle-ça le mode Frequency output. Dans cette deuxième partie mon objectif est d'expérimenter cette fonctionnalité.

PCA experements

Le PCA possède un compteur 16 bits et 3 canaux comparateurs qui peuvent-être utilisés selon 11 modes. J'ai créé un nouveau projet appelé PCA experements dans simplicity studio.

problème avec le configurateur

le PCA a 3 canaux j'ai choisi le canal 0 pour ma sortie en fréquence. L'entrée/sortie CEX0 peut-être assignée à n'importe qu'elle broche des ports 0 ou 1 mais j'ai découvert qu'il n'y a pas de registres pour sélectionner la broche désirée. Le crossbar choisi lui-même la broche en fonction de celles qui sont disponibles et d'un système de priorité par périphérique1.

Le crossbar assigne les broches par ordre croissant en commençant à P0.0 jusqu'à P1.7 pour les broches qui ne sont pas skipped. L'UART0 par contre est toujours assigné aux borches P0.4 pour TX et P0.5 pour RX. Le périphique SPI0 a la deuxième priorité il sera donc assigné aux broches de plus petite numérotations même s'il est activé après un autre périphérique. Comme dans mon projet je ne configure que CEX0 toutes les broches sont disponibles le crossbar a donc mis CEX0 sur la broche P0.0. Ça ne faisait pas vraiment mon affaire je voulais la sortie sur P1.3. Pour régler le problème j'ai tenté de sélectionner chaque broche l'une après l'autre dans la perpsective Configurator pour mettre la propriété skip à skipped. Le crossbar n'utilise pas les broches marqués skipped. Problème, le configurateur ne me laisse pas sélectionner les broches du port 1, elles sont tracées en gris pâle.

Au diable le configurateur

Au diable le configurateur, on n'est jamais mieux servit que par soi-même. J'ai configuré ce que j'ai pu dans le configurateur et j'ai ajouter le code suivant dans la fonction void SiLabs_Startup (void) du fichier pca_experements_main.c.


void SiLabs_Startup (void)
{
 // empêche le crossbar d'utiliser le port 0.
 P0SKIP=255; 
 // bloc pour le crossbar toutes les broches du port 1
 // sauf P1.3
 P1SKIP=~(1<<3); 
}

L'idée est de skipper toutes les broches des ports 0 et 1 sauf P1.3 obligeant ainsi le crossbar à utiliser cette broche.

A part ça il n'y a rien d'autre à faire sinon d'ajouter une seule ligne de code dans la fonction main. En mode Frequency output, la valeur dans le registre PCA0CPHx détermine la fréquence du générateur selon la formule:

Fcex=Fpca/2/PCA0CPHx
Fpca est la fréquence du signal qui alimente la minuterie du PCA
PCA0CPHx  est la valeur dans le registre PCA0CPHx. 
Ici x représente le canal, i.e. {0,1,2}

int main (void)
{
  // Call hardware initialization routine
  enter_DefaultMode_from_RESET();

 //fréquence de sortie sur Fcex0=Fpca/2/PCA0CP0H
        // Fsys=24,5Mhz/32
 // Fpca=Fsys/12=63802Hertz
 // Fcex0=63802/2/10=3190Hertz
 PCA0CPH0=10;
  while (1) 
  {
    // $[Generated Run-time code]
    // [Generated Run-time code]$
  }                             
}
Pour changer la fréquence il suffit de changer la valeur dans PCA0CPH0. Notez que la plus basse fréquence est obtenue avec la valeur zéro qui correspond à un diviseur de 256. La plus haute fréquence est obtenue avec le diviseur 1 qui donne une fréquence de Fpca/2. Si on utilise Fsys à sa fréquence maximale de 25Mhz et qu'on configure le diviseur du PCA0CPH0 à 1 on obtient une fréquence de sortie de 12,5Mhz. Le fonctionnement du mode frequency output est le suivant. La valeur du registre PCA0L est comparée avec la valeur de PCA0CPL0. Lorsque c'est 2 valeurs sont égales la sortie CEX0 est inversée et le registre PCA0L est rechargée avec la valeur PCA0CPL0+PCA0CPH0. Ainsi le compteur PCA0L doit toujours être incrémenté du même nombre de cycles pour atteindre l'égalité avec PCA0CPL0. Ainsi on obtient une onde carrée.

Expérience #2

Dans le configurateur si on active les interruptions sur PCA0


On doit aussi activé Cycle overflow interrupt

Ainsi que dans l'onglet channel 0 on doit activé la propriété capture/compare flag Interrupt.

Maintenant dans la routine d'interruption du périphérique PCA0 du fichier Interrupts.c on ajoute le code suivant:

SI_INTERRUPT (PCA0_ISR, PCA0_IRQn)
{
 static unsigned char hcycles=0;
 PCA0CN0&=0xfe; // remet à zéro le bit CCF0
 hcycles++;
 if (hcycles==2){
    hcycles=0;
           // truc pour commuter la valeur de PCA0CPH0 entre 10 et 20.
    PCA0CPH0=30-PCA0CPH0;
 }
}
Maintenant la fréquence de sortie du générateur change à chaque cycle entre 3190Hertz et 1595Hertz. Cet exemple peut-être facilement modifié pour fabriquer un modulateur FSK.

Conclusion

Grâce au configurateur et malgré ses défauts on a presque pas eu de code à écrire pour obtenir ce résultat.

  1. pas de récompense pour l'ingénieur qui a imaginé ce système.

dimanche 3 décembre 2017

abeille laborieuse et luciole désespérée

Introduction

Jusqu'à tout récemment je connaissais Silicon Labs pour leur produits radio-fréquences et je ne savais pas qu'ils vendaient aussi des microcontrôleurs. Il vendent entre autre des MCU 8 bits affublés du nom commercial de busy bee que je traduirais par abeille laborieuse plutôt que abeille occupée comme le suggère un certain traducteur en ligne. Il n'existe pas de version DIP de ces microcontrôleurs par contre il existe une version SOIC-16, le EFM8BB10F8G-A-SOIC-16. Très économique, actuellement 0,86cent chez digikey.ca. Évidemment il me fallait aussi un programmeur que Sillicon Labs appelle USB Debug Adapter. Il existe 2 versions de ce débogueur une pour les MCU 8 bits et une autre pour les MCU 32 bits. D'apparence ils sont identiques sauf pour la couleur, à ne pas confondre.

Présentation des Busy Bee

Comme mentionné en introduction il s'agit de MCU1 8 bits. Ils sont basés sur un CPU1 appelé CIP-51 par Silicon Labs. Il s'agit d'une version modernisé et 100% compatible avec le vénérable CPU d'Intel MCS-51 qui était au coeur des premiers microcontrôleurs mise en marché en 1980, les Intel 8051. Intel a abandonné la commercialisation des i8051 il y a longtemps mais de nombreux fournisseurs ont prie le relais. Les Busy Bee sont donc des descendant direct mais plus performant du i8051. Les Busy Bee fonctionnent jusqu'à 25Mhz. Il y a aussi des Universal Bee pour les application USB ainsi que les Laser Bee qui peuvent fonctionner à 72Mhz. Dans cette introduction il sera question du Busy Bee modèle EFM8BB10F8G-A-SOIC16.

Particularités des MCU de la famille Bee

Le CPU a quelques particularités qu'il est bon de souligner par exemple il utilise les 32 premiers octets de la RAM comme 4 banques de 8 registres. Une seule banque est active à la fois. Pour l'assembleur ces registres sont nommés R0 à R7. l'intérêt d'avoir 4 banques se situe au niveau des appels de sous-routines et surtout des interruptions. Le délais de réponse d'une interruption peut-être réduit si elle n'a pas besoin de sauvegarder les registres sur la pile. Elle peu s'éviter cette tâche en utilisant une autre banque de registres. Autre particularité les adresses RAM entre 32 à 48 sont accessibles par certaines instructions au niveau du bit. Pour ces instructions il ne s'agit pas d'octets mais d'un tableau de 128 bits adressable de 0 à 127. Il faut aussi mentionner que même plus économique des Busy Bee possède un multiplicateur en hardware. Je ne connais aucun autre MCU 8 bits à moins de 1$ qui en possède un.

Première étape

Silicon Labs fournis gratuitement l'environnement de développement appelé simpliciy studio. Il faut créer un compte pour le télécharger. Je l'ai installé sur mon laptop qui est en Windows 10 mais comme vous le verrez sur le site il est aussi disponible pour OSX et Linux.

Une fois le logiciel installé on découvre qu'il utilise le compilateur C/C++ de Keil et qu'il faut enregistrer son installation auprès de Silicon Labs pour bénéficier de toutes les fonctionnalités du compilateur gratuitement. Avant de lancer l'IDE2 ne pas oublier de brancher le USB Debug Adapter à l'ordinateur, ainsi il sera vu par l'IDE.

Simplicity studio

Même si cet environnement s'appelle Simplicity Studio ce n'est pas simple à utiliser comme l'IDE de Arduido. Il faut un certain temps pour se familiariser mais heureusement il y a un tour guidé. C'est basé sur l'environnement Eclipse CDT donc plus facile d'accès si vous avez déjà travaillé avec Eclipse. L'environnement utilise le concept de perspective. Selon la tâche à accomplir on passe d'une perspective à l'autre. Dans Symplicity Studio les boutons pour passer d'une perspective à l'autre sont dans la barre d'outils en haut à droite. Il y en a 5, Launcher, Configurator, Simplicity IDE, C/C++ et Debug. Au premier démarrage de l'application on se retrouve dans la perspective Launcher et par la suite dans la perspective visible lors de la fermeture de l'application.

Ma première tentative de créer un projet fut un échec. J'ai eu l'idée de créer un projet vide. Quand Simplicity Studio dit Empty C project il veut vraiment dire projet vide. Lorsqu'on clique avec le bouton droit de la souris sur le nom du projet dans la perspective Simplicity IDE la fenêtre des propriétés du projet s'ouvre. Cette fenêtre contient plusieurs onglets et de nombreuses options dans chaque onglet. Si vous avez choisi empty C project vous devez définir chacun de ces paramètres. Disons que c'est pour les utilisateurs avancés.

On retourne donc à la perspective Launcher et on clique sur le bouton new solution.


On inscris le nom du MCU sur la ligne sous le bouton new solution, EFM8BB10F8G-A-SOIC16. Un dossier jaune apparaît avec le nom du MCU sous lui. On peut renommé le dossier en cliquant bouton droit dessus. Appelons le luciole désespérée

Maintenant on va cliquer sur le bouton new project.

Le nom du MCU qu'on a sélectionné apparaît, on clique sur le bouton next.
On conserve la sélection par défaut soit Simplicity Configurator Program et on appuie encore sur next.
À cette étape on nomme notre projet, les espaces ne sont pas permis dans le nom: luciole_désespérée donc. next
On garde ça tel quel et on clique sur finish. C'est lent avant de passer automatiquement à la perspective Configurator.

Il s'agit d'un outil de configuration visuel. On nous présente le brochage du composant sélectionné pour ce projet. Pour ce projet on va utilisé seulement la broche 8/P1.3. On clique dessus pour la sélectionner. A droite la fenêtre Properties nous permet de configurer cette broche. Pour ce projet on doit avoir la configuration suivante:

On va brancher une LED3 à cette broche on l'a donc nommé ainsi.
En bas de la figure du composant on va maintenant cliquer sur DefaultMode peripherals.
Dans cette nouvelle vue on configure des valeurs par défaut des périphériques. J'ai sélectionné Clock Control et dans les propriétés j'ai choisis pour la propriété Select clock source, Low Frequence Oscillator. Il s'agit d'un oscillateur interne fonctionnant à 80Khz. Pour la propriété Clock Source Divider, j'ai choisi SYSCLK/32.
Ensuite j'ai sélectionné LFOSC
Il s'agit d'activé l'oscillateur et de choisir de diviser sa fréquence par 8 tel qu'indiqué dans la fenêtre properties.
Il nous reste maintenant à désactivé le Watchdog Timer

Problèmes

Lorsqu'on est dans le configurateur et qu'on clique sur PB2 dans le fenêtre Outline la fenêtre properties demeure vide. Hors le registre PB2 est celui qui permet d'activer le crossbar. Le crossbar est le multiplexeur qui permet de sélectionner quel périphérique est relié à quel broche. Il faut qu'il soit activé même si dans cette application on n'utilise aucun périphérique. Heureusement dans la fenêtre Problèmes un avertissement apparaît pour nous dire que le crossbar n'est pas activé. En double cliquant sur cet avertissement la fenêtre propriété du registre PB2 s'affiche et on peut activé le crossbar.

Pour ce projet simple c'est tout ce que nous avons à faire comme configuration mais ça nous évite pas mal de travail de codage manuel. Le configurateur va générer automatiquement le code nécessaire dans le fichier InitDevice.c. Le fichier qui contient l'information de configuration matérielle s'appelle luciole_desespérée.hwconf.

Luciole désespérée

Lorsqu'on travaille pour la première fois avec un MCU il est bon de commencer avec le projet le plus simple qu'on appelle habituellement blinky et qui consiste à faire clignoter une LED. Puisque Silicon Labs a décidé de donner un nom d'insecte à ses MCU, je joue le jeu en nommant mon blinky luciole désespérée. Désespérée parce qu'elle ne se contente pas de clignoter à vitesse régulière mais émet plutôt répétitivement un SOS en morse. Montage super simple pour un projet super simple:

schématique

montage expérimental
Notez que le débogueur n'utilise que 3 fils, GND position 2 sur le connecteur du débogueur, C2CLK position 7 sur le connecteur du débogueur et C2D position 4 sur le connecteur du débogueur. Lors du développement il il n'y a donc que 5 broche du MCU qui sont utilisées, 4=GND,5=+3Volt,6=C2CK,7=C2D et 8=LED. Les broches 6 et 7 sont débranchées lorsque le développement est complété.

Le code source en C

Puisque le configurateur a fait une bonne partie du travail pour nous il ne nous reste que très peu à écrire dans le fichier luciole_desespérée_main.c lui aussi créé par le configurateur.

C'est simple je cré un tableau appelé sos qui contient 4 octets. en fait ce tableau est lu comme une série de bits. Un bit à 0 signifie LED éteinte et un bit à 1 signifie LED allumée. La variable b est incrémentée de 0 à 27 et est utilisée pour lire séquentiellement les bits et lorsque b=28 on réinitialise b à zéro et le cycle recommence. Comme sos est un tableau d'octets on accède le bon octet en divisant b par 8 et le bon bit en prenant le modulo de b par 8. Les bits sont numérotés à l'inverse donc on soustrait le modulo de 7. Le code morse est fait de signaux courts dit et longs dah. Les dah sont 3 fois la longueur des dit. Donc en prenant le signal dit comme unité de temps pour produire un dah il suffit de mettre 3 bits consécutifs à 1. Les 5 derniers bits de la séquence sont a zéro pour produire une pose entre chaque appel.

Pour compiler le projet on utilise le marteau. et pour programmer le MCU avec le fichier binaire on utilise l'icône flèche verte au dessus d'un circuit intégré. Il faut choisir le fichier binaire à programmer.

Le fichier généré utilise 257 octets de mémoire programme et 13 octets de mémoire RAM. On peu demander au compilateur de généner le code assembleur. On va allez dans les propriétés du projet et cocher assembly code dans l'option listing du compilateur Keil.

Maintenant si on recompile on va voir à la fin du fichier luciole_désespérée_main.lst qui se trouve dans le dossier src, le code assembleur. J'ai oublié de mentionner que le configurateur génère 2 configurations une configuration release et une configuration debug. Lorsqu'on modifie les propriétés du projet ça s'applique seulement à la configuration active.

Conclusion

Quelques problèmes se sont présentés lors de cette prise en main de Simplicity Studio.

  • J'ai été capable de créer une variable de type bit mais lorsque j'ai voulu convertir le tableau const char sos[4] en const bit sos[28] le compilateur m'a donné une erreur! Il est possible de créer une variable scalaire de type bit mais pas un vecteur de ce type.
  • J'ai essayé d'imbriquer du code assembleur à l'intérieur du code C sans succès. La simple ligne de code suivante:
    
      asm("nop");
    
    suffie pour déplaire au compilateur qui répond avec l'erreur suivante:
    
    *** ERROR C272 IN LINE 50 OF C:\Users\Jacques\SimplicityStudio\v4_workspace\
    luciole_desespérée\src\luciole_desespérée_main.c:
     '__asm' requires src-control to be active
    
    Je n'ai pas encore trouvé la solution de ce problème.
  • Lorsque j'essaie de déboguer le programme pour suivre l'exécution en pas à pas la session plante, le programmeur s'appelle pourtant USB Debug Adapter!
    Erreur fatale lorsque j'essaie d'utiliser le débogueur

Bien sur je ne suis encore qu'un néophyte avec cet environnement j'ai encore beaucoup à apprendre.

Petite expérience

J'ai décidé de créer une version plus permanente de la luciole désespérée et de l'alimenter avec une pile lithium CR2032. L'objectif est de voir combien de temps va duré la pile. Le premier décembre à 15:00 EST j'ai mis une pile neuve et je surveille la luciole. Je vais compter le nombre de jours qu'elle va clignoter. Vous avez noté que j'ai inséré une résistance de 1Kohm en série avec la LED pour réduire le courant à 1mA environ. De plus le CPU fonctionne à une fréquence à 312,5Hertz pour réduire la consommation de celui-ci à environ 120µA comparativement à environ 5mA à la configuration par défaut qui utilise l'oscillateur haute fréquence avec un diviseur à 8 ce qui donne Fsys de 3,0625Mhz. Vous avez aussi notez qu'à fréquence de 312,5Hertz je n'ai pas besoin d'un boucle de délais pour prolonger la duré entre chaque bit. En fait à cette fréquence la durée entre chaque bit est d'environ 0,12 seconde. Il résulte donc un code morse facile à lire.


Le montage est fait directement au dos du porte pile. Le MCU est collé à celui-ci avec de la colle chaude et la résistance ainsi que la LED sont collés au dos du MCU avec de la colle cyanate (Creazy glue). D'après mes calculs luciole désespérée devrait clignoter environ 20 jours. Je vais mettre à jour cet article lorsque j'aurai le résultat final.

mise à jour 2017/12/04

J'ai trouvé comment inclure du code assembleur dans un fichier en C. Dans la fenêtre Project explorer, dans le dossier src du projet il faut cliquer bouton droit sur le fichier dans lequel on veut inclure du code assembleur et dans le menu surgissant il faut sélectionner Properties.

Dans les options générales il faut cocher l'option Generate assembly SRC from "*.c" file.
Pour inclure du code assembleur dans le fichier en question il encadrer les instructions assembleurs par les directives #pragma asm/#pragma endasm.
Curieusement le fait d'ajouter 2 instructions nop a fait passer la taille du code binaire de 257 octets à 268 octets alors que chacune de ces instructions n'occupe qu'un octet.

J'ai trouvé la solution sur le site de Keil dont l'aide est beaucoup plus complète que l'aide qui viens avec simplicity studio.

mise à jour 2017-12-17

La luciole s'est éteinte au cours de la nuit. Elle aura donc vécue moins de 16 jours. 4 de moins que ce que j'avais estimé sur la base d'une capacité de la pile de 240mah. Possiblement que la pile avait une capacité inférieure à celle utilisée pour mon calcul. La pile utilisé était sans spécification et j'avais utilisé la valeur d'une marque connue.


  1. MCU signifie Micro Controller Unit, tandis que CPU signifie Central Processing Unit. Il ne faut pas confondre les deux. tout MCU a en son coeur un CPU mais ne se limite pas à celui-ci elle possède aussi de la mémoire et des périphériques programmables.
  2. IDE Integrated Development Environment est une application utilisée pour le développement de logiciel qui contient un éditeur de texte mais aussi le support intégré au compilateur ainsi que d'autre outils nécessaire à la réalisation d'un projet. Il permet donc d'écrire le code source, de le compiler, de l'injecté dans le microcontrôleur et aussi de le déboguer sans avoir à sortir de l'environnement. D'où son nom d'environnement intégré.
  3. LED Light Emitting Diode ou Diode Electro Luminescente en français.

mercredi 8 novembre 2017

minix 3

Aujourd'hui j'ai appris que minix 3 est un système d'exploitation des plus utilisés au monde. En fait même Andrew S. Tanenbaum le créateur de minix l'ignorais jusqu'à tout récemment. Depuis 2006 tous les microprocesseurs Intel Core contiennent un processeur secondaire que le fabriquant appelle Management Engine. Ce processeur fonctionne en dehors du processeur principal et du BIOS avec son propre logiciel et a accès à toutes les fonctionnalité de l'ordinateur même s'il n'y a pas de système d'exploitation installé ou même de disque dur. Il y a eu beaucoup de critiques concernant le management Engine et récemment des hackers ont trouvé une méthode pour le désactiver et par la même occasion étudier le logiciel qu'il utilise. C'est là qu'ils ont découvert qu'il utilise une version customisé par Intel du système d'exploitation minix 3. Puisque les processeurs Intel Core sont les plus utilisés dans les ordinateurs de bureau et les laptops ça signifie que minix 3 est un des systèmes d'exploitations les plus utilisés dans le monde et personne n'était au courant!

Qu'est-ce que minix 3

Minix 3 se distingue des systèmes d'exploitation plus connus comme Windows, OSX et GNU/Linux d'une façon remarquable. Contrairement à ces 3 derniers c'est un système d'exploitation à micro noyau, alors que les 3 autres sont basés sur des noyaux monolithiques.

Noyau monolithique

Tous les systèmes basés sur Unix comme OSX, ainsi que ceux basé sur Linux ou encore le noyau NT4 de Windows possèdent un noyau monolithique. Pour comprendre ce qu'est le noyau d'un système d'exploitation vous pouvez consulter l'article suivant: Qu'est-ce qu'un système d'exploitation. Un noyau monolithique comme Linux est un gros module logiciel comprenant des centaines de milliers de lignes de codes. Plus on ajoute de fonctionnalités plus il grossie. Hors Linux existe depuis longtemps et il est devenu très gros.

Micro noyau

Un micro noyau comme celui de minix au contraire implémente un minimum de fonctionnalité et renvoie tous le reste dans l'espace utilisateur. Alors qu'avec Linux, Unix et NT4 tous les pilotes de périphériques sont liés directement au noyau et fonctionne dans l'espace protégé de celui-ci.

Le problème que tente de résoudre les micro noyaux.

Le principal problème que les micro noyaux essaient de résoudre est celui de la sécurité. En effet plus un système contient de lignes de codes plus il a de chance de contenir des erreurs qui peuvent-être exploités de façon malveillante. Comme le noyau d'un système d'exploitation fonctionne au niveau de privilège le plus élevé du processeur. Les erreurs logicielles qui se trouvent dans son code posent une menace majeure à la sécurité mais aussi à la stabilité du système.

Donc la philosophie du micro noyau est de réduire le code qui tourne au niveau de privilège le plus élevé du processeur pour réduire au minimum le risque d'erreurs logicielles. Mais il y a un inconvénient, celui de la vitesse d'exécution. En effet pour accéder aux périphériques les applications doivent faire un appel au noyau de type syscall pour appeler une fonction matérielle. Un syscall est une exception logicielle ça implique un délais qu'on appelle latence. Prenons un exemple, une application veut écrire un bloc de données dans un fichier sur le disque dur. Dans un système monolithique, l'application fait un syscall en passant l'information requise au noyau. l'interruption qui répond au syscall identifie le type de demande et transmet l'information au pilote du disque dur qui effectue l'opération et le résultat de l'opération est retourné à l'application dans le bloc d'information. Dans un micro noyau le pilote est aussi une application donc pour parler au noyau il doit lui aussi faire un syscall donc chaque transaction entre une application et un périphérique implique 2 systcall. Un pour la requête et un pour la réponse du pilote. On double le nombre de syscall et comme je viens de l'écrire chaque syscall implique un délais de réponse. De plus dans un système d'exploitation chaque application existe dans un espace mémoire virtuel inaccessible aux autres applications. Donc lorsque une application veut envoyer de l'information à un périphérique le noyau fait une copie vers l'espace de l'application vers l'espace du pilote et lorsque le pilote répond le noyau copie la réponse dans l'autre sens. Ça fait beaucopu de données à copier.

Cette façon de faire est très sécuritaire mais très lente. J'ai l'occasion d'expérimenter cette lenteur chaque fois que j'utilise mon PlayBook qui fonctionne avec le système d'exploitation QNX qui est basé sur un micro noyau.

Les recherches se poursuivent pour améliorer la performance des systèmes d'exploitations à micro noyau mais ils ne seront jamais aussi rapides que les systèmes monolithiques. Ils sont utilisés principalement pour leur sécurité mais aussi pour leur modularité qui permet de réduire la taille du système en enlevant simplement les modules qui ne sont pas nécessaires à l'application. C'est sans doute ce qui a incité Intel à utiliser minix 3 pour son Management Engine.

Cette découverte va peut-être intéresser plus de développeurs au projet minix 3 qui pour le moment est plutôt anémique.

mercredi 9 août 2017

détecteur de métal

Dans cet article je décris la construction d'un détecteur de métal construit autour d'un PIC10F322 et d'un oscillateur Colpitts en base commune.

Origine d'un projet

La semaine dernière je sors de la maison pour aller prendre l'allée qui se trouve à l'arrière. J'aperçois mon voisin qui a l'air de chercher quelque chose.
- Salut Jacques!
- Salut Robert! Qu'est-ce que tu cherches?

Il ouvre la main pour me montrer plusieurs vis et clous.
- J'ai fais une crevaison ici hier.
- Ha! Moment d’embarras...

En juin j'ai refais la clôture qui borde cette allée. Hors je n'avais pas ramassé toutes les clous et vis qui y sont tombés. J'aurais du être plus attentif à la récolte des vieux clous et vis. Mais ce n'est pas si facile de les trouver à travers l'herbe et le gravier. Je me suis dis qu'un détecteur de métal serais utile dans cette opération de nettoyage. Plutôt que d'en acheter un j'ai décider de le fabriquer. Projet de fin de semaine.

Schématique

Montage carte

Assemblage final

Mise à l'essaie

Dans ce bref vidéo les objets détectés sont dans l'ordre, une rondelle en acier, 1 pièce de 5 cent en nickel et 1 pièce de 1 cent en cuivre.

Description

Le détecteur de métal est constitué d'un oscillateur Colpitts en montage base commune. Il comprend le transistor Q1 2N3904, l'inductance L1 et les condensateurs C4 à C7. R1,R2 ainsi que C2 ne servent qu'à polariser le transistor dans sa zone de conduction. C6 et R3 sont les éléments qui permettent la rétro-action positive entre le collecteur et l'émetteur. Le circuit accordé est formé de L1, C4,C5 et C7. C4 et C7 en parallèles forment l'équivalent d'un condensateur de 4,4nF. Je n'avais pas de 4,7nF en main mais si vous avez un, remplacez C4 et C7 par un seul condensateur de 4,7nF. Une fraction du signal du circuit accordé est renvoyée à l'émetteur de Q1 à travers le condensateur C6. La fréquence de cet oscillateur mesurée à l'oscilloscope est de 460Khz. Mais la valeur exacte n'est pas importante car c'est la variation de fréquence que le logiciel va mesurer. La sortie de l'oscillateur est envoyée au microcontrôleur PIC10F322 sur la broche 3, T0CKI à travers C8 et R4. La logique de détection est simple, il s'agit simplement de détecter les variations de fréquence de l'oscillateur causées par la présence prêt de la bobine L1 d'un objet métallique. Dans ce but la minuterie TIMER0 est utilisée pour compter les cycles de l'oscillateur pendant un intervalle de 32 millisecondes a répétition en comparant chaque mesure avec la précédente. Si cette valeur varie dans la même direction 2 mesures consécutives on considère qu'il y a présence d'un objet métalique et l'alarme sonore est activée.

Construction de la bobine L1 et assemblage.

Dans une planche de pin de 3/4" d'épaisseur (19mm) j'ai découpé un disque de 6" (15cm) de diamètre. Avec une lime queue de rat j'ai creusé une cannelure sur toute la circonférence du disque afin d’accueillir le bobinage. Le bobinage est comprend 17 tours de fil émaillé numéro 22 AWG (0,6mm). Mesuré avec un appareil LCR j'obtiens une valeur de 115µH pour cette bobine tel qu'indiqué sur la schématique.

Pour le manche j'ai utilisé une tige de peuplier à section carrée de 1/2" (12,5mm) qui est maintenue au disque avec une vis et de la colle.

Un petit boîtier en contre-plaqué 1/8" (3mm) a été fabriqué et fixé au manche pour contenir le circuit électronique.

Le montage électronique lui-même est fait sur une plaquette de bakélite de 7cm x 9cm à pastille de cuivre sur une seule face.

Description logicielle

Donc le signal de l'oscillateur est capturé à l'entrée T0CKI (broche 3) et les cycles sont comptés par le TIMER0 sur un intervalle de 32 msec. La minuterie TIMER2 est utilisée comme compteur de période pour la génération de la tonalité d'alerte de 500 hertz générée par le périphérique PWM1. La routine débutant à init: initialise les périphériques et active l'interruption sur le TIMER2. Cette minuterie possède un diviseur en sortie qui permet de déclencher l'interruption après un certain nombre de cycles. Le post-diviseur est configuré pour une interruption à tous les 16 cycles de TIMER2. Une fois l’initialisation complétée le logiciel tombe dans une boucle infinie goto $ car la détection se fait à l'intérieur de la routine d'interruption qui débute à ìsr:. À chaque interruption on fait une lecture de TMR0 qui représente le nombre de cycles de l'oscillateur comptés sur une durée de 32 msec. En fait il s'agit du nombre de cycles modulo 256 car on n'a pas besoin du compte complet puisque les variations d'une interruption à l'autre sont nettement inférieures à 256. Pour chaque interruption on conserve cette valeur dans la variable last. La variable run est une variable temporaire qui représente la dernière lecture de TMR0 faite au début de la routine d'interruption. On compare donc cette dernière valeur avec la valeur de last et on incrémente ou décrémente la variable slope en fonction de la différence entre les 2 valeurs. Si run est plus grand que last on incrémente slope car ça signifie que la fréquence de l'oscillateur a augmentée. Au contraire si run est plus petit que last ça signifie que la fréquence à diminuée et donc slope est décrémentée. Si run et last ont la même valeur slope est ramenée à zéro et la tonalité d'alarme est désactivée. Pour que la tonalité d'alarme soit réactivée il faut que la valeur absolue de slope soit égale ou supérieur à la constante TR_LVL. Ce code très simple qui n'occupe que 66 instructions donne de bons résultats.

J'ai testé différentes valeurs pour la durée d'échantillonnage et pour la constante TR_LVL c'est avec ces valeurs que j'obtiens le meilleur résultat.

Technique de balyage

La technique de balayage consiste à tenir le disque de la sonde 1 ou 2 cm au dessus du sol dans le plan horizontal et balayer de gauche à droite ni trop vite ni trop lentement. Avec un peu de pratique on trouve rapidement la bonne vitesse de balayage.

Lorsqu'un objet est détecté il ne faut pas arrêter le balayage de la sonde au dessus de l'objet mais plutôt faire des aller-retour. Un arrêt du balayage permet à l'oscillateur de se stabiliser sur une nouvelle fréquence et donc rend le détecteur silencieux.

Notez que la détection se produit lorsque l'objet est au plus proche du rebord du disque et non en son centre.

Conclusion

De réalisation économique et rapide il ne faut cependant pas s'attendre à ce qu'il est la sensibilité d'un bon détecteur vendu dans le commerce.


  1. sources du projet sur github

lundi 10 juillet 2017

ForthEx (partie 3)

Depuis le dernier article sur le projet ForthEx, je n'ai cesser d'y travailler et je peux dire qu'à ce point ci le projet est pas mal abouti. ForthEx peut maintenant être contrôlé en remote via le port RS-232 et un émulateur VT102 sur le PC. J'ai créé un éditeur de texte simple et un système de blocs pour la sauvegarde sur EEPROM externe ou carte SD. De plus la documentation html est complète avec description de chacun des 445 mots qui sont dans le dictionnaire en mémoire FLASH du MCU, sans oublier un tutoriel. Le tutoriel consiste en la description du code source d'un jeu classique appellé snake. Voici donc un démo du jeu en action.

Conclusion

Le temps passé sur ce projet m'a permis de mieux connaître le langage Forth au point ou maintenant je peux lire un programme Forth aussi facilement que s'il était écris en C et je n'ai pas eu plus de difficulté à écrire le jeu snake en Forth que j'en aurais eu en C.

Comme n'importe quoi d'autre il suffit d'y mettre le temps et les efforts pour maîtriser ce langage. Ceux qui prétendent que ce langage est plus difficile à comprendre ou lire qu'un autre ne se sont tout simplement donner la peine de l'étudier sérieusement.

samedi 27 mai 2017

Swift Forth et SwiftX Forth

Si vous avez envie d'essayer la programmation Forth sur votre ordinateur ou sur votre microcontrôleur préféré la compagnie Forth inc. offre en téléchargement gratuit une version d'évaluation de leur systèmes SwiftForth et SwiftX. Aucune limite de temps.

Forth inc.

Forth incorporated est la compagnie fondée par Chuck Moore1 et Elisabeth Rather2.

SwiftForth

SwifthForth est un système de programmation en Forth natif. C'est à dire pour créer des applications pour le système d'exploitation sur lequel il est installé. Il est disponible pour les plateformes Windows,OSX et Linux. Vous pouvez donc télécharger la version pour votre système d'exploitation préféré et étudier les exemples et écrire vos premières applications Windows,OSX ou Linux en Forth.

SwiftX

SwiftX est la version cross compiler de leur système Forth. C'est à dire que SwiftX permet de créer et compiler des applications pour différent microcontrôleurs. La liste est assez longue, en autre sont supportés les AVR,MSP430,ARM. Il est possible de développer des applications Arduido avec SwiftX.

Allez directement à la page de téléchargement et vérifiez les options disponibles.


  1. Chuck Moore est le créateur du langage Forth.
  2. Elisabeth Rather est la première programmeuse du langage Forth. Elle devait faire la maintenance d'un système écris par Chuch Moore lorsqu'elle a découvert ce curieux langage (What's that mess?) et elle ne disposait d'aucune documentation. Elle a donc entrepris de créer toute la documentation. Plus tard elle s'est associée à Chuck Moore pour créer la compagnie Forth inc.

mercredi 3 mai 2017

magnétron

Le four micro-ondes est tombé en panne. C'est un appareil assez simple, une minuterie électronique, 4 micro-switches pour la sécurité, un ventilateur, un moteur pour la rotation du plateau, un transformateur haute-tension, un diode rectificatrice haute-tension et finalement le magnétron. C'est le seul composant intéressant dans cet appareil et c'est ce dont je vais parler dans cet article.

ATTENTION! Si vous avez envie d'ouvrir un micro-ondes pour en examiner le contenu, sachez qu'il y a des précautions à prendre.
  1. Débranchez l'appareil du secteur avant d'ouvrir le boitier.
  2. Il est possible que le condensateur haute-tension soit encore chargé. Un magnétron ça fonctionne à un voltage d'environ -2500 VDC. Pour éviter l'électrocution déchargez le condensateur en branchant une résistance de 1 Mohm entre la cathode du magnétron et l'anode de celui-ci avec des fils à pinces alligator. Laissez ce circuit en place quelques secondes avant de le retirer.

Schéma bloc d'un micro-ondes

schéma bloc d'un four micro-ondes

magnétron

Un tube à vide qui produit des micro-ondes

Un magnétron ce n'est qu'un tube à vide contenant que 2 électrodes comme les rectificateurs utilisés à l'époque des tubes électroniques, et pourtant il produit des micro-ondes. Alors quel est la différence entre un tube à vide rectificateur et un magnétron?

Attention ça tourne

Comme on peut le constater à partir de la photo, son apparence est très différente des diodes en tube de verre. La partie extérieur c'est l'anode avec les ailettes de refroidissement. Remarquez les 2 disques noirs à chaque extrémité du cylindre d'anode. Ce sont des aimants. Comme dans tout tube à vide la cathode contient un filament chauffant. La chauffe libère des électrons de la surface. Pour que ces électrons soient accélérés vers l'anode il faut un champ électrique. Il faut que l'anode soit positive par rapport à la cathode. Pour des raisons pratique et de sécurité puisque l'anode est un gros bloc de métal à l'extérieur du tube on conserve l'anode à 0 volt en vissant le magnétron au chassie. La cathode par contre étant à l'intérieur du tube est mise à un voltage négatif d'environ -2500 VDC. Donc même si l'anode est à zéro volt elle est positive par rapport à la cathode. Les électrons sont fortement accélérés vers l'anode. C'est là que le fonctionnement diverge d'une diode rectificatrice. Dans un rectificateur les électrons s'en vont directement vers l'anode par le chemin le plus court.

vue schématique d'un magnétron
Comme je l'ai mentionné il y a 2 aimants au extrémités du tube. Il y a donc un champ magnétique perpendiculaire au déplacement des électrons. Ce champ magnétique exerce une force sur les électrons qui les fait déviés de leur trajectoire rectiligne. Les électrons au lieu d'aller directement vers l'anode décrivent une trajectoire en spirale dans l'espace entre la cathode et l'anode. Les électrons subissent donc une accélération et tout électron accéléré émet des ondes électromagnétiques. A la périphérie de l'anode il y a des cavités résonnantes. Lorsque les électrons passent devant ces cavités il se produit un phénomène de résonance. La dimension des cavités est calculée pour que la fréquence de résonance soit de 2 450 MHz. Cette fréquence est choisie parce qu'elle correspond aussi à la fréquence de résonance des molécules d'eau. Comme la nourriture est faite de matière vivante et que toute matière vivante contient de l'eau. En faisant vibrer les molécules d'eau contenues dans la nourriture on produit la chaleur de cuisson.

Cyclotron

Il existe un type d'accélérateur de particule qu'on appelle un cyclotron, si vous étudiez le fonctionnement d'un cyclotron vous allez constater la très grande similitude de fonctionnement entre le cyclotron et le magnétron. Il y a une différence de taille et aussi que dans un cyclotron on utilise les électrons eux-même pour bombarder des cibles plutôt que d'utiliser les ondes électromagnétiques émient par les électrons accélérés.

mardi 28 mars 2017

ForthEx (partie 2)

Dans cette partie je décris la structure générale du système logiciel.

structure logicielle

Il s'agissait donc de construire un système à partir de zéro. Pour utiliser le MCU il faut d'abord effectuer une configuration matérielle de celui-ci en fonction de l'usage des différents périphériques. J'ai conçu le système pour entrer le plus rapidement possible dans le système forth mais cependant il y a plusieurs fonctions logicielles qui ne font pas parti du système forth à proprement parler puisque ces fonctions ne sont pas intégrées au fonctionnement de la machine virtuelle forth tel qu'expliqué plus bas dans cet article. On pourrait appeler ces fonctions de base BIOS pour Basic Input Output Services ou encore HAL Hardware Abstraction Layer puisqu'elles sont spécifiques à cette plateforme matérielle. Si on dessine un schéma de la structure logicielle ça ressemble à ceci, l'information circulant entre les couches adjacentes seulement.

Abstraction matérielle

Les routines de service d'interruptions ainsi certaines routines qui peuvent-être appelées par des mots forth mais qui ne sont pas intégrés à la machine virtuelle font partis de cette couche logicielle. Les procédures qui ne font pas partie du système forth sont appelées par l'instruction assembleur call et se termine par l'instruction assembleur return. Les routines en codes qui font parti de la machine virtuelle sont liées les unes aux autres par un système d’enfilage (threading) assuré par une macro appelée NEXT qui est définie dans le fichier macros.inc.

La machine virtuelle

Il s'agit d'une machine virtuelle 16 bits, donc les adresses sont dans l'intervalle {0..65535} et les entiers dans l'intervalle {-32768..32767}. Dans le jargon forth une unité élémentaire de donnée s'appelle une cellule (CELL). ForthEx utilise des cellules de 16 bits. La machine virtuelle est constituée de 3 piles et d'un interpréteur interne qui a pour fonction de lire une liste d'adresses qui correspond au code à exécuter. Les données stockées sur les piles sont en unité cellule. Les entiers doubles (32 bits) occupe donc 2 cellules sur la pile. Il existe des mots spécialisés pour faire de l'arithmétique sur les cellules et s'assurer qu'une adresse est alignée sur le début d'une cellule.

La première pile est la pile des arguments appelée pstack dans le fichier core.s. Cette pile sert à passer les arguments entre les fonctions (appelés mots en forth).

La deuxième pile sert principalement à conserver les adresses de retour pour les appels de mots de haut-niveaux. Cette pile s'appelle rstack dans le fichier core.s

La 3ième pile n'est utilisée que par le compilateur et s'appelle cstack dans le fichier core.s. Le compilateur utilise cette pile pour résoudre des adresses de sauts. En dehors de la compilation cette pile n'est pas utilisée.

En plus de ces 3 piles la machine virtuelle a un certain nombre de registres. La machine virtuelle a donc la structure suivante:

  • IP Instruction Pointer. Joue le même rôle que le compteur ordinal (Program counter) d'un CPU. IP pointe la prochaine adresse de code à exécuter.
  • DSP Data Stack Pointer. Pointeur pour la pile pstack. Pointe à la position de l'avant dernier élément empilé, le dernier élément étant conservé dans le registre T.
  • RSP Return Stack Pointer. Pointeur pour la pile rstack. Pointe l'adresse après le dernier élément empilé. Notez la différence entre le pstack et le rstack. DSP est incrémenté avant d'empiler et décrémenté après le dépilement. RSP est incrémenté après l'empilement et décrémenté avant le dépilement.
  • VP. Variable Pointer. Le système utilise un certain nombre de variables. Ces variables sont rangées en un seul bloc en mémoire RAM. VP pointe sur l'adresse du début du bloc. Les variables système sont référencées par leur position relative à VP.
  • I et LIMIT. Ces 2 registres sont utilisés ensemble par les boucles DO ... LOOP et DO ... +LOOP. I est le compteur de boucle incrémenté à chaque itération de la boucle et LIMIT est la valeur limite du compteur. Lorsque I atteint LIMIT le bouclage se termine.
  • WP Working Pointer. Après l'appel d'un mot ce registre pointe vers le champ des paramètres du mot.
  • T est un registre qui contient la valeur au sommet du pstack.

plan de la mémoire

Ce plan de la mémoire est appelé à varier avec chaque version du système.

Mémoire RAM

  • 0x0000-0x1000 Bloc d'adresse réservés pour les SFR Special Function Register. Les périphériques sont accédés via ce bloc d'adresses. C'est spécifique à ce MCU.
  • 0x1000-0x107F rstack, Pile des retours.
  • 0x1080-0x10BF pstack, Pile des arguments.
  • 0x10C0-0x10FF cstack, Pile de contrôle.
  • 0x1100-0x104F TIB, Terminal Input Buffer. Tampon pour le clavier ou autre source d'entrée.
  • 0x1050-0x109F PAD, Tampon pour le formatage des textes en sortie.
  • 0x10A0-0x10C7 Tampon d'entrée pour le port série.
  • 0x10C8-0x11ED Variables système.
  • 0x11EE-0x1211 Variables et tampon utilisé par le USART du clavier.
  • 0x1212-0x1233 Bloc de variables utilisées par les fonctions HAL.
  • 0x1234-0x12F7 Bloc libre.
  • 0x12F8-0x12FF Variables utilisées par le mécanisme de sauvegarde/restauration d'image boot.
  • 0x1300-0x7FFF Espace données utilisateur, dictionnaire utilisateur. Ce bloc mémoire contient les fonctions, les variables et les constantes définis par l'utilisateur.
  • 0x8000-0xC9FF Mémoire EDS, Extended Data Space, réservée pour l'allocation dynamique.
  • 0xCA00-0xCFFF Mémoire vidéo, contient les caractères affichés à l'écran.

Mémoire FLASH

  • 0x000000-0x000003 vecteur de réinitialisation.
  • 0x000004-0x0001FF Table des vecteurs d'interruptions.
  • 0x000200-0x000229 Code machine _reset.
  • 0x00022A-0x0003A9 Code machine des routines ISR.
  • 0x0003AA-0x00235D Dictionnaire système. Contient les mots définis lors de la création du système.
  • 0x00235E-0x00275D Police de caractères, table ASCII.
  • 0x00275E-0x0027C3 Chaînes de caractères constantes, messages système.
  • 0x0027C4-0x00396D Code machine des mots forth définis avec les macros DEFCODE et HEADLESS.
  • 0x00396E-0x003C82 Code machine fonctions HAL.
  • 0x007800-0x007823 Constantes pour l'initialisation des variables système.
  • 0x008000-0x0557EC Mémoire libre. l'image boot est enregistrée au début de cette plage et peu occupée jusqu'à ~28000 octets.

lexique de la machine virtuelle

D'abord un lexique pour comprendre la machine virtuelle.

  • dictionnaire Le dictionnaire est une base de donnée qui fait le lien entre les noms des routines et l'adresse du code. La structure d'une entrée dans le dictionnaire est comme suis:
    +-----+-----+-----+-----+
    | LFA | NFA | CFA | PFA |
    +-----+-----+-----+-----+
    
    Le dictionnaire est utilisé par l'interpréteur/compilateur. Une partie du dictionnaire est dans la mémoire FLASH et l'autre correspondant aux mots définis par l'utilisateur est en mémoire RAM.
    • LFA Link Field Address, est le champ dans une entrée dictionnaire qui contient l'adresse du prochain mot. Le dictionnaire étant une liste chaînée. Occupe 2 octets.
    • NFA Name Field Address, contient la chaîne comptée du nom. Le premier octet contient la longueur du nom et les suivants le nom lui-même. Si le nombre d'octets occupés par ce champ est impaire, 1 octet est ajouté pour aligner le CFA sur une adresse paire. Occupe un nombre pair d'octets.
    • CFA Code Field Address, ce champ contient l'adresse du point d'entrée de la routine à exécuter pour ce mot. Occupe 2 octets.
    • PFA Parameter Field Address, ce champ contient les paramètres utilisés par la routine. Occupe 0 ou un nombre quelconque d'octets. Le champ est complété pour assurer une adresse paire sur l'entrée suivante.
  • Interpréteur interne, Une fois qu'une définition est compilée, le code à exécuter est une liste d'adresses lues par l'interpréteur interne ou machine virtuelle. Chacune de ces adresses est appelée XT pour eXecution Token.
  • XT, eXecution Token, est un pointeur qui contient l'adresse du code à exécuter. Le modèle d'exécution de cette machine virtuelle est appellée ITC.
  • ITC, Indirect Threaded Code, est un modèle d'exécution dans lequel le programme est une liste pointeur ou chaque pointeur contient l'adresse de la routine en code machine à exécuter. C'est le modèle d'exécution utilisé par ForthEx.
  • Interpréteur/compilateur, C'est l'interface utilisateur qui analyse la ligne d'entrée fournie par l'utilisateur ou provenant d'un fichier source et qui interprète ou compile le texte lu selon l'état de la variable système STATE.
  • STATE, est la variable système qui indique à l'interpréteur/compilateur si les mots lus dans le flux d'entré doivent-être exécuter immédiatement ou compilés dans une nouvelle entrée du dictionnaire. Lorsque cette variable est à zéro les mots sont interprétés sinon ils sont compilés.
  • Colon Definition, Est un mot définit par :, ce caractère étant appelé colon en anglais. Les Colon definition sont des listes d'adresses qui débutent par l'exécution du code ENTER et se terminent par l'exécution du code EXIT. Pour la machine virtuelle ces 2 mots sont l'équivalent des instructions machine CALL et RETURN respectivement. C'est à dire que ENTER empile la valeur de IP sur rstack avant d'initialiser IP avec la valeur qui est dans le CFA du mot. La dernière adresse est celle de EXIT qui a pour effet de prendre la valeur qui est au sommet de rstack pour la remettre dans IP ce qui correspond à une sortie de sous-routine. donc les colon definition sont comme des sous-routines, tandis que les mots codes sont comme des instructions machine pour la machine virtuelle forth.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;    
; interpréteur interne
; exécute l'instruction suivante
; de la machine virtuelle 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;    
.macro NEXT
    mov [IP++], WP  ; WP=CFA, IP pointe vers item suivant
    mov [WP++], W0  ; W0= adresse code, WP=PFA
    goto W0         ; saut vers code routine
.endm    

Comme on le voit la machine virtuelle est très simple puisqu'elle ne comprend que 3 instructions machine. Chaque mot en code machine doit se terminer par cette macro pour assurer le fonctionnement de la machine virtuelle.

démarrage de la machine virtuelle

Voici le code qui s'exécute lors de la réinitialisation de l'ordinateur. La réinitialisation se produit lors de la mise sous tension de l'ordinateur mais aussi s'il y a une exception logicielle, une commande reboot ou une des combinaisons de touches suivante: CTRL-C pour un warm reset ou CTRL-ALT-DEL pour un cold reset.


; vecteur de réinitialisation du processeur    
.section .start.text code address(0x200)
.global __reset    
__reset: 
    clr ANSELA    ; désactivation entrées analogiques
    ; priorité 6 pour _INT1Interrupt
    mov #6, W0
    ior IPC5
    mov #rstack, RSP
    mov #pstack, DSP
    mov DSP, W0
    sub #RSTK_GUARD, W0
    mov W0, SPLIM
    movpag #1,DSWPAG
    btsc RCON,#SWR
    bra 1f
    ; power on reset
    movpag #psvpage(_cold),DSRPAG
    mov #psvoffset(_cold),IP
    NEXT
    ; réinitialisation logicielle
1:  movpag #psvpage(_reboot),DSRPAG
    mov #psvoffset(_reboot),IP
    NEXT
    
.text

   
_reboot:
    .word QCOLD,TBRANCH,_cold-$
_warm:
    .word LIT,fwarm,FETCH,LIT,_DP,FETCH,LIT,_LATEST,FETCH
    .word CLS,CLR_LOW_RAM
    .word HARDWARE_INIT,VARS_INIT
    .word LATEST,STORE,DP,STORE
    .word DUP,LIT,USER_ABORT,EQUAL,ZBRANCH,1f-$
    .word DROP,LIT,_user_aborted,BRANCH,8f-$
1:  .word DUP,LIT,MATH_EXCEPTION,EQUAL,ZBRANCH,2f-$
    .word DROP,LIT,_math_error,BRANCH,8f-$
2:  .word DUP,LIT,STACK_EXCEPTION,EQUAL,ZBRANCH,3f-$
    .word DROP,LIT,_stack_reset,BRANCH,8f-$
3:  .word DROP,LIT,_unknown_reset    
8:  .word COUNT,TYPE,CR,QUIT
  
_cold:
    .word CLR_RAM,HARDWARE_INIT,VARS_INIT
    .word VERSION,COUNT,TYPE,CR 
    ; autochargement système en RAM à partir
    ; d'une image en FLASH MCU ou EEPROM
    .word BOOTDEV,FETCH,BOOT
    .word QUIT ; boucle de l'interpréteur

Le code assembleur exécute un minimum de code qui consiste à désactiver les entrées analogiques et à initialiser les pointeurs de piles DSP et RSP ainsi que le compteur ordinal de la machine virtuelle IP. A partir de la macro NEXT c'est la machine virtuelle forth qui contrôle l'exécution.

La machine virtuelle forth a 2 points d'entrés, _cold et _reboot. Le point d'entrée est sélectionné en fonction de l'état dut bit SWR dans le registre RCON du MCU. Si ce bit est à 1 il s'agit d'une réinitialisation logicielle donc IP est initialisé pour exécuter le code _reboot sinon IP est réinitialiser pour exécuter le code _cold.

A partir du moment où la machine virtuelle est en action il n'agit plus d'exécuter directement du code machine mais du code forth. Donc _reboot et _cold sont des mots forth mais sans entête de dictionnaire car l'interpréteur/compilateur n'a pas besoin de les voir. Donc chacun des noms qui suis une directive assembleur .word correspond au CFA d'un mot forth. QCOLD est le CFA du mot défini plus bas:

; est-ce un cold reboot    
HEADLESS QCOLD,HWORD
    .word LIT,fwarm,FETCH,ZEROEQ,EXIT
HEADLESS est une macro pour définir les mots forth qui ne sont pas dans le dictionnaire. Dans le fichier macros.inc il y a plusieurs macros pour faciliter la définition des mots forth lors de la construction du système. Tous les mots forth qui sont dans la mémoire FLASH du MCU sont définis à l'aide de ces macros. Donc chaque fois qu'il y a dans les fichiers sources une des macros suivantes: HEADLESS, DEFCODE, DEFWORD, DEFUSER, DEFCONST il s'agit d'une macro qui simplifie la création de définitions forth. La lecture des fichiers source révèle donc que la plus grosse part du système est en forth. Bien sur écrire des définitions forth de cette façon est différent que d'écrire des définitions forth pour l'interpréteur/ compilateur par l'utilisateur de l'ordinateur. Pour quelqu'un qui n'est pas habitué cette différence peu prêter à confusion. J'ai passé tellement de temps à définir des mots de cette façon que les premières fois que j'utilisais l'ordinateur il m'arrivais de taper par exemple sur la ligne de commande le mot MINUS au lieu du caractère -.

Utilisation de l'ordinateur

Cet ordinateur fonctionne seulement en mode texte, affichant 25 lignes de 64 caractères en monochrome. La table des caractères utilise le code ASCII qui est un code à 7 bits. Le 8ème bit, le plus significatif, est utilisé pour inverser le caractère à l'écran. C'est à dire que si ce bit est à 1 le caractère est affiché noir sur fond blanc au lieu de blanc sur fond noir. Dans le fichier TVout.S se trouve les mots forth pour l'affichage. On y trouve entre autre le mot TGLCHAR qui inverse le caractère à la position du curseur. Comme les mots SETX et SETY permettent de positionner le curseur il est possible de manipuler l'affichage pour souligner certains mots en les inversant.

Le système n'a pas d'assembleur donc les seuls mots qui peuvent-être définis par l'utilisateur sont des colon definition. Ces définitions sont sauvegardées en mémoire RAM et perdues lorsque l'utilisateur éteint l'ordinateur, à moins qu'il n'utilise une sauvegarde >BOOT.

Sauvegarde >BOOT et BOOT

Le MCU possède 512Ko de mémoire FLASH mais seulement 64Ko sont réservés pour le système forth. Le reste peu donc être utilisé pour la sauvegarde de données utilisateur. Les fichiers flash.s et store.s contiennent des définitions qui permettent de sauvegarder des données dans la mémoire flash. Deux mots sont d'intérêts pour sauvegarder les définitions de l'utilisateur pour qu'elles soient rechargées automatiquement au démarrage. Le mot >BOOT sert à sauvegarder une image des définitions utilisateur et le mot BOOT est utilisé pour recharger cette image en RAM. Le mot BOOT s'exécute automatiquement lors d'un démarrage à froid et récupère l'image s'il y en a une mais il peut-être lancé manuellement. Voici un exemple d'utilisation.


: p2 ( n -- n^^2 ) \ puissance de 2 
    dup * ;
: p3 ( n -- n^^3 ) \ puissance de 3
    dup p2 * ;
\ sauvegarde de l'image RAM
MFLASH >BOOT
Dans cette exemple 2 définitions ont été créées par l'utilisateur. Pour que ces définitions soit sauvegardées en permanence l'utilisateur a décidé de créer une image boot dans la mémoire flash du MCU, de sorte qu'à chaque démarrage cette image sera rechargée en mémoire RAM. MFLASH est le nom du périphérique de sauvegarde. l'image aurait pu être sauvegardée dans l'EEPROM mais dans ce cas il aurait fallu la recharger manuellement avec la commande:

EEPROM BOOT

On peut donc étendre les fonctionnalités du système en créant de nouvelles définitions en RAM et en sauvegardant avec la commande:


MFLASH >BOOT
L'image modifiée sera disponible à chaque démarrage.

démo d'utilisation

Conclusion

Je n'ai pas l'intention de donner un cours complet sur le forth sur ce blog ce serait redondant, de nombreuses ressources sont disponibles dans l'internet ( en anglais du moins) . Ce qu'il me reste à faire c'est d'écrire un lexique complet des mots définis dans forthex. Ce document sera disponible dans le dossier documentation du github.

Le système de base sera sans doute élargie, par exemple par l'ajout d'un gestionnaire de mémoire dynamique ainsi qu'un système de fichiers pour la carte SD. Il faudrait aussi écrire un éditeur de texte.


Liens