Projet de programmation - scheme 2003 Documentation du "PIC simulator" Ce programme est une interface d'apprentissage, de développement et de fami- liarisation pour le microcontrolleur 8 bits PIC16F84 fabriqué par MICROCHIP. FONCTIONALITES : Programmation en assembleur (PICasm), avec définitions de constantes. Affichage des ports A et B, possibilité de les faire varier pour des inputs. Affichage des registres principaux de controle, de l'état (sleep/int) et de l'accumulateur. Variation de la vitesse d'execution avec une molette en haut. Edition, sauvegarde, chargement de fichiers de code assembleur. Sous linux, avec un PC classique ix86, on peut afficher PORTB sur des leds (8) connectées au port parallele, sur les pins (D0 - D7) avec une resistance d'en- viron 400 ohms. Ceci est géré par un programme écrit en C (output.c), plus simple que de le faire en scheme apparemment. Il prend 2 arguments sur la ligne de commande, la valeur à écrire et l'adresse en décimal du port; il doit etre executé en root ou avoir le mode +s et root comme owner. Gestion des interruptions TODO : Faire defiler le code pour voir ou on en est dans l'execution. Génération du code binaire du programme et programmation par port série ou parallele au moyen d'un programmeur de PIC, ce qui en ferait un outil idéal de développement. Support de l'EEPROM. Interruptions pendant le SLEEP, WatchDog Timer (WDT). La plupart des bits de OPTION_REG ne sont pas pris en compte... C'est en rapport avec le WDT. LE PIC : C'est un CPU 8 bits de type RISC, il connait 35 instructions, il dispose de 2 ports d'entrée-sortie, un de 5 et un de 8 bits. Il peut etre connecté a une eeprom, sa fréquence habituelle de fonctionnement est 4 MHz mais peut monter si besoin est à 10 MHz maxi. Le PIC16F84 possede 68 octets de RAM, et une mémoire flash pour les programmes de 1 kilo mots de 14 bits. On peut aussi étudier le PIC16F83 qui est le meme avec 36 o de RAM et 512 mots de memoire flash. Il est habituellement utilisé en robotique pour de petits robots, par exemple un robot suiveur de ligne ou un des coupes de robotiques des écoles, ou bien pour des automates de tout genre. Son concurrent en robotique est le Motorola 68HC11 qui est bien plus puissant. Pour plus d'informations a propos de ces microcontrolleurs, j'ai inclus la datasheet (référence technique) prise sur le site du constructeur, qui est le fichier PIC16F84.pdf . Parties utiles : Organisation de la mémoire (file registers) : pages 13 - 17 Entrées/Sorties : pages 21 - 25 Horloge et prescaler : pages 27 - 31 Interruptions : pages 48 - 53 Instructions (liste p. 56) : pages 55 - 70 Le reste concerne des fonctionalitées non supportées ou la partie electronique et cablage du µC. REGISTRES : On appelle file registers les cases mémoires du PIC, il en compte 12 pour le controle de l'execution (p. 14) et 68 pour la mémoire vive. La RAM commence donc à 12 soit 0C en hexa. Toutes les valeurs auxquelles ont fait allusion sont en hexadécimal. Simplement parce que FF vaut 255 en décimal, ce qui est la plus grande valeur présente dans les registres d'un microcontrolleur 8 bits. On dispose d'un accumulateur W qui stoque les variables courantes ou les resultats des calculs, on a aussi une pile de 8 éléments de 13 bits a laquelle on n'a pas acces, qui garde en mémoire les appels aux fonctions, 13 bits car on a un compteur de programme qui sert a savoir quelle ligne du programme est en exécution (le Program Counter PC composé de 8 bits du PCL et 5 du PCLATH) qui a une taille de 13 bits, pour permettre un adressage jusqu'a 2 kilo mots, au cas ou il y aurait 2 K mots de programme dans une future version du PIC, (1 K ici). Certains registres sont seulement lisibles, d'autres en écriture seulement, d'autres les 2. Dans mon logiciel, rien n'empeche d'écrire dans un registre ou on n'a pas le droit ni de lire un en lecture seule. Ce serait trop compliqué et trop lent de vérifier les arguments de toutes les fonctions qui peuvent écrire dans les registres. Un programmeur lit la documentation sur le microcontrol- leur avant de le programmer, donc il doit savoir tout ca et y faire attention ! On a un registre qui représente l'horloge, a l'adresse 01, TMR0. Un registre est destiné a recevoir un pointeur et un autre a l'adressage indirect par ce pointeur, il s'agit respectivement de 04 FSR et 00 INDF. Dans les registres on retrouve les ports d'entrée sortie PORTA (05) et PORTB (06) respectivement de 5 et 8 bits. On peut savoir si chaque bit est soit configuré pour etre une entrée, soit une sortie par les bits correspondant dans les registres TRISA et TRISB. Si le TRIS est a 0 c'est une sortie, si il est a 1 c'est une entrée. Les autres registres sont ceux affichés dans l'interface, ils sont la pour indiquer diverses choses comme l'apparition d'une interruption, un dépassement de taille pour une opération arithmétique. FICHIERS : asm_func.scm Ce fichier contient toutes les définitions des fonctions assembleur du PIC. Les fonctions modifient au besoin tout ce qu'il faut de sorte que dans la boucle d'éxecution du programme on ne s'occupe de rien de ce coté. Il y a 3 types d'instructions comme on le voit page 56 du pdf: - les byte oriented file register operations opèrent sur un octet et une adresse mémoire. - les bit-oriented n'affectent ou ne testent qu'un bit d'un registre. - les literal and control operations sont la pour charger une valeur dans l'accumulateur, appeler une sous-fonction, retourner une valeur, etc. registers.scm On trouve ici les définitions concernant les registres et les variables globales, et les fonctions suivantes : - PC-ref et PC-set! qui lisent ou écrivent le PC sur les 2 registres PCL et PCLATH - TOS et push pour la pile, TOS signifie Top Of Stack, cela correspond au pop - file-set! et file-ref qui ont le meme comportement que vector-ref et vector-set! a la différence près qu'ils prennent en argument aussi bien un nombre qu'une représentation par une string d'un nombre en hexa. Finalement ce n'est pas necessaire car le "compilateur" remplace les expressions hexa en decimal, mais pour débugger et pour un eventuel changement dans le fonctionnement du reste du programme. - is-bit-set? regarde si un bit est a 1 dans un nombre - bit-set bit-clear et les autres du meme genre mettent un bit a 0 ou a 1 dans un nombre - hexa-string rajoute un 0 devant la string du nombre hexa si il est plus petit que 16 - binary-string de meme, rajoute assez de 0 pour que la représentation binaire de n fasse len caracteres. Utile pour PORTA et PORTB qui ne font pas la meme taille. - remove-0x renvoie la sous-string "0c" de "0x0c" , qui est la syntaxe utilisée en C pour représenter un nombre hexa. (string->number str 16) renvoie #f si str n'est pas la string d'un nombre en hexa, on suppose alors que on a 0x devant. - Z et C donnent les états des bits correspondant dans le registre STATUS. La mémoire est représentée par un tableau de taille 80 (12 + 68), mem, pour un acces rapide. main.scm C'est ici que sont définies les fonctions principales du programme. On y retrouve les variables necessaires à la simulation, et les plus grosses fonctions : - catch-interrupt renvoie le symbol auquel l'interruption est due, #f si il n'y en a pas; Les interruptions gérées sont : TMR0 a atteint sa valeur maximale si le bit 5 du INTCON est a 1, un changement d'état du port B sur les pins 4 5 6 et 7 en fonction de leur activation sur les bits 4 à 7 de TRISB en entrée et le bit 4 du INTCON, le bit 0 du port B, en fonction de son bit de changement d'état (front montant ou descendant) et du bit 4 de INTCON. Les bits de INTCON sont mis a jour meme si les interruptions ne sont pas activées, et cette fonction ne renvoie une valeur que si le GIE (Global Interrupt Enable bit, bit 7 de INTCON) est mis a 1. Il manque les inter- ruptions qui viennent de l'eeprom, mon programme ne gere rien en rapport avec ceci pour l'instant. - runtime est la boucle principale de l'execution du programme. Elle rafraichit l'affichage, fait avancer TMR0, gere le cas du sleep et des sauts d'interruption, et execute la fonction du programme. Pour le rafraichissemnt du port parallele, il a fallu mettre un détecteur de changement, car si on le fait a tous les tours de boucle pour rien, PLT scheme ouvre trop de fichiers et il plante. On s'occupe aussi ici du cas ou il ne se passe rien pendant un tour de boucle, une instruction qui effectue un saut conditionnel ne fait rien pendant le 2e top d'horloge comme décrit dans la datasheet. Les deux plus grosses fonctions: celles dédiées a la transformation du code assembleur vers les fonctions scheme définies dans asm_func.scm. Description rapide: - pre-compile: cette fonction construit 3 types de données : - une liste d'association pour les labels de forme ((label . ligne) ...), en effet on a besoin du numero de ligne ou plutot de l'adresse de la mémoire du programme ou il faut se brancher en cas de GOTO ou CALL. Typiquement, les labels se trouvent en début de ligne, sans rien devant. - une deuxieme AListe, cette fois ci pour les symboles (constantes). En effet, avec le mot clé EQU, on a le droit de définir des constantes (ex: PORTB EQU 0x0C ; ou 0C). On a donc ((PORTB . 12) ...) comme deuxieme donnée construite pour faciliter le travail du "compilateur". - pour finir, un tableau qui fait la correspondance entre une ligne du programme et une ligne du fichier. C'est utile car si on a des définitions (EQU), des labels seuls sur une ligne, des lignes vides ou des commentaires, il y a un décalage. On sait ensuite quelle ligne du fichier on doit transformer en expression scheme ou afficher pendant l'execution. Il y a aussi le mot spécial ORG qui prend un argument nombre x et qui force la prochaine ligne de code à etre a la ligne x du programme. Utile pour les interruption dont la valeur de branchement du PC est 004 (on met alors "org 004" avant le code voulu). On renvoie aussi la taille du programme (en lignes) qui sert pour la suite. Il y a une syntaxe a respecter, j'ai essayé de faire quelque chose de semblable a ce qu'on trouve sur internet pour du PICasm pour plus de confort : * Labels: Tout mot en début de ligne - si la ligne ne contient pas EQU - est un label. Par convention, on les écrit en minuscule mais ca n'a pas d'importance. Il peut etre seul sur la ligne ou bien classiquement a coté de code comme: ou | |label2 |label1 MOVLW a3 | MOVLW a3 | | * Commentaires: Tout ce qui suit un ; est considéré comme un commentaire jusqu'a la fin de la ligne. Par contre ce qui est avant est pris en compte. * Arguments d'instructions: Pour les fonctions a 2 arguments, il est possible de séparer le premier du deuxieme par une suite de , et d'espaces; pour la destination, on peut utiliser les arguments de base du microcontrolleur ou bien comme en PICasm W ou w pour 0 et f ou F pour 1. Ainsi toutes ces écritures sont identiques: MOVF 0c,1 | MOVF 0x0C f | MOVF 0c, 1 | MOVF 0C , F On peut aussi avoir des argument d'un chiffre de 0 a 7 pour les opération binaires. * Majuscules: Il n'est pas necessaire d'écrire les instructions en majuscules grace a la solution de "Katsmall T. Wise" de la mail liste PLT. * Format des nombres: Les nombres écrits dans le code sont interprétés en hexadécimal, on peut les écrire de deux facons différentes : brut (4f) ou formaté C (0x4F). Les majuscules ici ne sont pas importantes, on peut aussi utiliser le format que l'on veut tant qu'il fait une taille de deux caracteres comme hx4f ou $h4f peu importe. On a a disposition une forme spéciale, le bit pattern qui permet de rentrer directement du binaire a la place de l'hexa; pour cela il faut préfixer de b et mettre entre '. Ex: MOVLW b'11110000' Il ne doit pas y avoir d'espace, et obligatoirement 8 bits. * Nom des symboles: Les symboles (et labels) ne doivent pas contenir de ',' ni de ';'. On peut utiliser les memes noms pour les labels et les constantes. * Fin du fichier: Le fichier (ou le code dans l'éditeur) doit finir par un saut de ligne, sauf si la derniere ligne n'est pas une instruction. Je n'ai pas trouvé comment le rajouter automatiquement... - compile: en se basant sur les données de pre-compile, elle construit un tableau représentant la mémoire du programme du PIC, dans lequel on retrouve les instructions pretes a etre évaluées par (eval). Si une fonction n'est pas reconnue au parsage, il met NOP a la place. La fonction runtime est lancée dans un thread, de facon a pouvoir la stopper facilement. interface-dessin.scm On y définit la géométrie de la fenetre de l'interface et tous les dessins. J'utilise le bitmap-canvas% du memento du schemer pour que la fenetre se rappelle de ce qui y est dessiné si on passe quelque chose dessus. draw-registers-content est la fonction appelée a chaque tour de boucle pour rafraichir les données affichées. Elle redessine tout, ce serait trop compliqué de ne rafraichir que ceux qui ont changé. Il faudrait la refaire en plus jolie, avec un vrai séparateur et des ascenceurs entre la ram et la partie du milieu, pour pouvoir rétrécir la fenetre. interface-editeur.scm Définitions pour l'éditeur, boutons au dessus. Pas grand chose finalement. Tout ce qu'il y a de plus classique dans les interfaces MrEd. BUG: il semblerait que le fait se sauvegarder un fichier le modifie au sens du texte brut, mais on ne le voit pas dans l'éditeur. interface-inputs.scm La petite fenetre définie ici s'ouvre quand on clique sur le bouton INPUTS de la fenetre principale. Les boutons ne sons actifs que si les bits qu'ils représentent sont configurés en entrée. On le relit a chaque ouverture. Donc il faut la fermer et réouvrir pour rafraichir. BUG: quand on clique plusieurs fois sur INPUTS, ca ouvre plusieurs fenetres. Comment la redessiner alors ? opcode.scm C'était fait pour générer le code binaire a rentrer dans le microcontrolleur mais j'ai pas eu le temps de faire cette partie la. A partir du code compilé cela ne doit pas etre compliqué. Dans le fichier il y a les codes binaires de chaque instruction, que l'on appelle l'opcode. Il faut le completer avec les arguments. UTILISATION : En premier, il faut charger le fichier main.scm dans DrScheme puis l'executer. La fenetre du programme s'ouvre et il est pret à fonctionner. Pour la configuration du port parallele si besoin, voir les premieres lignes de interface-dessin.scm (conf:use-parport et mon-port-parallele pour l'adresse). A gauche, on retrouve les 68 registres de la RAM. L'adresse est inscrite à coté de chaque case. Au milieu, on a l'accumulateur W, le program counter qui indique le numero de la ligne qui va s'executer ou qui est en train, en dessous, TMR0 l'horloge. Ensuite, il y a quelques registres interessants en binaire car on a besoin de voir chaque bit. Enfin les leds, les deux premieres symbolisent l'état de veille et si un flag de INTCON est a 1, ce qui signifie qu'une interruption est intervenue, ou qu'elle aurait pu si elles ne sont pas activées. Les leds en bas sont les ports A et B. Les boutons: Exit ferme la fenetre. INPUTS ouvre la fenetre avec les check-box pour simuler les entrées sur les ports. RUN/STOP lance la simulation ou la stoppe, on peut en faire varier la vitesse avec le slider. STEP execute la ligne spécifiée par le PC et s'arrete ensuite. RESET est l'équivalent du hardware reset qui remet des registres un état d'origine, ou fait sortir le microcontolleur d'un SLEEP. load charge un fichier dans l'éditeur. save sauvegarde le contenu de l'éditeur dans un fichier. make "compile" le code contenu dans l'éditeur. On peut charger un fichier ou en taper un nouveau, avant de débuter la simulation, il faut cliquer sur make pour le charger dans la mémoire du PIC virtuel. Ensuite on joue avec RUN/STOP et STEP pour controller son execution, et si besoin, INPUTS pour faire varier soi meme les entrées. EXAMPLES count.asm Ce fichier a été trouvé sur le net, il ne fait qu'afficher sur PORTB un compteur binaire. J'ai enlevé 4 appels a pause et je l'ai rétréci car il fallait plusieurs heures ou jours pour qu'il en sorte. C'est bien le probleme avec ce logiciel, il est lent. Les deux lignes list p=16F84 et radix hex sont recouvertes par la suite a cause du org 0x000, sinon on les verrait dans le code et cela poserait des problemes. helloLed.asm Aussi trouvé sur le net, il montre comment se servir de la forme binaire b'xxxxxxxx'. Il ne fait que mettre 170 sur PORTB et boucler indéfiniment. int.scm Ce programme allume les leds du PORTB un court instant si une interruption est détectée. Il les attend dans la boucle infinie, il vaut mieux regler la vitesse à une petite valeur si on veut voir quelque chose. Quand une interruption intervient, il se passe 4 tops de clock sans instruction. Pour résumer ce qui est dit plus haut, on déclenche une interruption quand on fait varier l'entrée 0 du PORTB, celle ci sur front montant ou descendant dépendem- ment du bit 6 d'OPTION_REG, quand on fait varier les bits 4 5 6 et 7 du PORTB, ceux la théoriquement dans n'importe quel sens a part que bizarrement ca ne fonctionne que sur front montant (BUG), et quand TMR0 atteint ff. Le PC est alors chargé a 004 et reste dans la routine d'interruption jusqu'au RETFIE. C'est un programme amusant pour tester les interruptions :) chenillard.asm Juste un aller de droite a gauche de deux leds. Elles s'allument toutes à la fin. my-big-example.asm Ca se complique... On effectue une multiplication de deux données lues sur PORTB. Il vaut mieux aller lentement meme plutot en step, sinon on n'a que peu de temps pour entrer les données que l'on souhaite. Pour corser le tout, on a une interruption si on essaie de rentrer un nombre impair - totalement inutile mais c'est pour la beauté de la chose. La multiplication est appelée par CALL, on retrouve le resultat dans le registre RES. Les données A et B ne sont pas modifiées, on utilise I pour controler les opérations. Il y a quand meme de quoi s'amuser, malgré ce qu'il manque (voir TODO en haut). Je suis assez content du résultat, surtout qu'il a demandé beaucoup de travail. En espérant que la doc soit assez claire... Vincent HOURDIN - 2003 vinvin @ vinvin.dyn...