MonSiteWikiNi

DocProc

PagePrincipale :: DerniersChangements :: DerniersCommentaires :: ParametresUtilisateur :: Vous êtes 18-97-9-174.crawl.commoncrawl.org

Implémenter le processeur 68hc11 dans ses programmes


Le processeur est constitué de deux parties distincts:
-Exec, qui contient le processeur en lui même.
-Function, qui contient divers fonctions utiles pour tout programmes exploitant ce processeur(cela évite de réinventer la roue dans chaque programme).

Exec


Obtenir les sources
Vous pouvez vous procurer les sources du processeur de deux façons différentes:
-Soit en téléchargeant directement une archive.
-Soit directement par le svn.
Au jour d'aujourd'hui, aucune version stable n'est sortie. Mais par la suite, l'archive sera pour les versions stables alors que le SVN plutôt pour les versions de développement.
Une archive est disponible sur la page de téléchargement DownLoad
Si vous préférez le SVN, sous Linux, pour obtenir les sources; tapez ceci dans une console:
$ mkdir 68hc11 $ mkdir 68hc11/proc $ cd 68hc11/proc $ svn co svn://svn.tuxfamily.org/68hc11/proc_68hc11
Normalement, il créera votre dépot local et téléchargera la dernière version de développement(ie non stable) des sources.
Par la suite, pour le mettre à jour vers la dernière version:
$ svn up

Toutefois, le développement s'effectue de la façon suivante:
-Développement de fonctionnalités.
-Cycle de Bêta tests, dans le but de trouver les éventuels bugs et de les corriger
-Sorties d'une version dites stable, qui devrait correctement marcher.
-Et retour au début, avec le développement de nouvelles fonctionnalités.
Par conséquent, il est recommandé de prendre les versions stables, qui auront été testés, les versions de développements pouvant contenir d'énormes bugs.


Premier pas
Pour ce processeur, 3 fichiers sont requis:
-Exec.cpp: contient le code source.
-Exec.h: L'header du processeur.
-Param.h: Divers constantes(tel que les adresses des ports/registres, ...)

Il vous faudra donc, dans votre makefile, inclure le Exec.cpp. Et dans les fichiers de codes sources utilisant le 68hc11, mettre une directive #include "Exec.h".

Noté que l'espace de nom du processeur est proc_68hc11.
La classe principale se nomme c_Exec. Il vous faudra donc dans votre code créer une instance de la classe:

int main(...)
{
proc_68hc11::c_Exec *proc=new proc_68hc11::c_Exec();
}


Le processeur n'inclut pas directement la gestion de la mémoire, c'est donc à vous de la gérer. Ceci car dans un système réelle, une grande partie de la mémoire est externe au processeur. Inclure la gestion de la mémoire directement dans le processeur reviendrait à ce priver de possibilité que le processeur réelle offre.
Par la suite, un fichier spécial en contiendra plusieurs pré-écrite. Mais actuellement, vous devez vous-même les écrire. Rassurez-vus, rien de bien compliqué, deux fonctions(l'une pour gérer la lecture, l'autre l'écriture) suffisent.
-Pour la lecture, une fonction prenant comme argument l'adresse mémoire(de type unsigned int), et renvoyant la valeur de cette adresse mémoire(de type unsigned char).
-Pour l'ecriture, une fonction prenant comme argument l'adresse mémoire(unsigned int), et la valeur à y écrire(unsigned char), et ne renvoyant aucune information(void)
Ainsi, un simple tableau de char suffit pour stocker les valeurs.
Voici un exemple de code:

unsigned char _mem[64*1024]; // Mémoire enregistrée sur 16 bits, un bus de donné de 8 bits, ce qui fait 64Ko de mémoire adressable.
unsigned char mem_read(unsigned int Addr)
return mem[Addr];
void mem_write(unsigned int Addr, unsigned char Val)
mem[Addr]=Val;


Puis, il faut indiquer au processeur où trouver ces fonctions. Ceci se fait par pointer, le proc contenant deux pointers de fonctions nommé hard_mem et hard_setmem:

proc->hard_mem=mem_read;
proc->hard_setmem=mem_write;



puis, on initialise le processeur ainsi:
proc->Init();
Cette function, de type void, remet le 68hc11 à 0.

Une fois ceci fait, il ne reste plus qu'à charger le programme en mémoire, et à lancer l'exécution:
Une série de proc->setmem permet d'écrire, octet par octet, le contenu:
proc->setmem(Addr, Octet);
Addr: un entier non signé(unsigned int)
Octet: un octet non signé(unsigned char). Pour entrer la valeur d'un hexa, mettre 0xVAL(exemple, pour le code FF: 0xFF).
Exemple de codes:
proc->setmem(0x00F0,0x86) // LDAA(chargement Accu A) immédiat)
proc->setmem(0x00F1,0x55) // La valeur qui sera chargé dans l'accu A
proc->setmem(0x00F2,0x4C) // INCA(Incrément l'Accu A).
proc->setmem(0x00F3,0x3F) // SWI(fin du programme).


Ci dessus, le code commence à l'adresse 00 F0, et se termine à l'adresse 00 F3. Nous allons donc placer le pointer sur l'adresse de départ. La variable "pointer" est l'adresse courante du pointer du 68hc11, donc de type unsigned int:
proc->pointer=0x00F0;

Puis, il ne nous reste plus qu'à exécuter le programme. Il existe plusieurs méthodes permettant de gérer l'exécution, qui seront vues plus tard. Pour l'instant, nous nous contenterons de la fonction execInstr(), qui exécute une instruction:
proc->execInstr()
Ainsi que de la variable End, qui indique si le programme est ou non terminé:
if(proc->End)
est_terminee();

Dans notre exemple, nous allons inclure iostream afin de bénéficier de la sortie standard:
#include <iostream>

Nous allons créer une boucle, qui bouclera jusqu'à ce que le programme se termine. Ainsi, tant que proc->End vaudra False, la boucle continuera. Et nous afficherons, après chaque instruction, l'adresse du pointer et la valeur de l'accu A:

while(!(proc->End))
{
proc->execInstr(); // Exécute l'instruction suivante de votre programme Hexa
std::cout << "pointer: " << exec->Pointer << "; Accu A: " << proc->Accu_A() << "\n"; /Renvoie les valeurs sur la sortie standard
}



Vous l'aurez compris, Accu_A() renvoie la valeur de l'accu A(de type unsigned char).
Toutefois, en l'état actuel, la valeur affichée pour l'Accu A ne sera pas exploitable. Le problème provient du type char, qui est un "caractère". Dans l'état actuel donc, il ne renverra pas une valeur numérique mais le caractère correspondant au code ASCII de la valeur. C'est là qu'intervient la seconde partie(Function.h/Function.cpp), qui nous permettra de faire les conversions nécessaires. En début de votre source:
#include "function.h"

Les fonctions se trouvent dans l'espace de nom proc_68hc11::Function. Nous allons utiliser la fonction Hex, qui prend en argument une valeur de type unsigned char, et renvoie une valeur de type std::string, chaine de caractère contenant le code Hexa. Nous allons adapter le code pour afficher sous forme hexadécimale la valeur de l'accu(puis, tant que nous y sommes, le pointer sera aussi plus lisible en hexa: la fonction peut aussi prendre un unsigned int en paramètre(surcharge)):

while(!(proc->End))
{
proc->execInstr(); // Exécute l'instruction suivante de votre programme Hexa
std::cout << "pointer: " << proc_68hc11::Function::Hex(exec->Pointer) << "; Accu A: " << proc_68hc11::Function::Hex(proc->Accu_A()) << endl;
}


Notez qu'un "using namespace proc_68hc11" en début de votre source vous éviterait d'avoir à spécifier l'espace de nom du processeur à chaque fois.

Voilà, le programme est enfin prêt :)

Le code complet:

#include // Lib Standard: nous permet d'utiliser la sortie pour écrire les valeurs des pointers/Accu A(std::cout)

#include "Exec.h" // Nécessaire pour créer un 68hc11 virtuel.
#include "Function.h" // Pour ne pas réinventer la roue, nous utilisons ses fonctions de conversion de base.

//Gestion mémoire
unsigned char _mem[64*1024];
unsigned char mem_read(unsigned int Addr)
return mem[Addr];
void mem_write(unsigned int Addr, unsigned char Val)
mem[Addr]=Val;

// Programme principal
int main(...)
{
proc_68hc11::c_Exec *proc=new proc_68hc11::c_Exec();

proc->hard_mem=mem_read;
proc->hard_setmem=mem_write;

proc->Init();

proc->setmem(0x00F0,0x86) // LDAA(chargement Accu A) immédiat)
proc->setmem(0x00F1,0x55) // La valeur qui sera chargée dans l'accu A
proc->setmem(0x00F2,0x4C) // INCA(Incrémente l'Accu A).
proc->setmem(0x00F3,0x3F) // SWI(fin du programme).

while(!(proc->End)) // Tant que programme non terminé
{
proc->execInstr(); // Exécute l'instruction suivante de votre programme Hexa
std::cout << "pointer: " << proc_68hc11::Function::Hex(exec->Pointer) << "; Accu A: " <<
proc_68hc11::Function::Hex(proc->Accu_A()) << endl;
}
}


En exécutant ce programme, vous devriez avoir en sortie:
$ ./MonProg?
pointer: 00 F0; Accu A: 55
Pointer: 00 F2; Accu A: 56
Pointer: 00 F3; Accu A: 56
$

Félicitation, votre premier programme exploitant ce 68hc11 vient d'être terminé :).
Une version un peu plus complète de ce programme se trouve sur le svn, elle se nomme simu.cpp. Elle permet, entre autre, de charger son programme depuis un fichier binaire, de définir une liste d'adresse à surveiller, et à un affichage plus complet en sortie.

Listes des fonctions

Nous allons désormais lister chacune des fonctions d'API de ce 68hc11, avec les paramètres, et une brève description. Une page dédiée à chacune d'elle contiendra une description plus poussée et des exemples d'utilisations.

Mémoire
- void setmem(unsigned int Addr, unsigned char Val)
Ecrit un octet en mémoire.
Addr: adresse mémoire où l'octet doit être écrit.
Val: valeur à écrire.
Renvoie rien(void)
DocSetmem?
- unsigned char mem(unsigned int Addr)
Lit un octet en mémoire
Addr: octet à lire
Renvoie la valeur de l'octet.
DocMem?
- unsigned int longmem(unsigned int Addr)
Lit une valeur étalée sur deux octets mémoires.
Addr: Adresse du premier octet. Le second étant Addr 1
Renvoie la valeur numérique des 16 bits de Addr:Addr (interwiki) 1 ([Addr]*0xFF [Addr 1])
DocLongmem?

Variable d'execution
- unsigned int pointer
Cette variable permet au programme de connaitre ou de définir l'adresse courante du pointer.
DocPointer?
- bool End
Cette variable vos True si le programme est terminée.
DocEnd?
- void Init()
Cette fonction remet le 68hc11 virtuel à 0. La mémoire est complètement effacé, les Accus remis à 0, ...

Gestion des Accus/Registres
- unsigned char Accu_A/B()
Ces deux fonctions renvoient la valeur le l'accu A ou B.
DocAccuAB?
- void SetAccu_A/B(unsigned char val)
Ces deux fonctions renvoient permettent de définir la valeur de l'accu A ou B.
DocSetAccuAB?
Note: les registres X et Y ne sont pas encore implémentés, et les registres de ports sont détaillés à la section "Gestion des ports" ci dessous

Gestion des ports
Le port A:
Le port A contient des bits bi-directionnels(que le programme devra alors paramétrer comme entrée ou comme sortie), et des bits prédéfini. Un registre permet de définir l'état des bits bi-directionnels.
- unsigned char RegPort_A()
Renvoie une valeur de 8 bits représentant la direction des bits de sorties du port A. ATTENTION: Il ne s'agit pas forcément de la valeur que le programme a enregistrée dans le registre du port A, mais le mode réelle de chacun des bits du port. Ainsi, les bits non bidirectionnels sont fixes, seuls les bits des ports bidirectionnels sont pris de la valeur que le programme enregistre.
DocRegportA?
- unsigned char RegPortVal_A()
Renvoie la valeur du registre du port A, et bien désormais la valeur du registre. Ainsi, c'est exactement la valeur que le programme enregistre dans le registre, y compris pour les ports non bi directionnels(attention: ses bits sont ignorés par le processeur, préférer la fonction RegPort_A() pour savoir quels bits sont en sorties, et lesquels sont en entrées).
DocRegportvalA?
- unsigned char Port_A()
Renvoie la valeur du port A.
DocPortA?
- void SetPort_A(unsigned char val)
Permet de définir les bits de sorties du port A. C'est donc équivalent à ce qu'un programme hexa provoquerait en écrivant à l'adresse de ce port. Les autres bits sont ignorés
DocSetportA?
-void SetPortEnter_A(unsigned char val)
Permet de définir les bits d'entrées du port A. Cette fonction permet de définir l'état(binaire) de chacune des entrées du port. Les autres bits sont ignorés.
DocSetportenterA?
Il n'y a pas de commentaire sur cette page. [Afficher commentaires/formulaire]