[Tutoriel] Communiquez entre caltos en C/C++ !
Posté le 19/03/2014 20:04
Qui n'a jamais rêvé de faire un jeu multijoueurs sur sa calculatrice ? Ou alors un chat pour discuter en cours ? Dans ce tutoriel, nous allons apprendre comment envoyer et recevoir des données via le port série 3 broches ("serial 3-pins" en anglais) pour pouvoir échanger des informations entre deux calculatrices. Bonne lecture !
Sommaire :
1) Comment tester les programmes que je vais créer ?
2) Que faut-il utiliser ?
3) Ouvrir et fermer le port série
4) Émettre et recevoir des variables simples
5) Émettre et recevoir des tableaux de char et des variables "complexes"
6) Annexes
1) Comment tester les programmes que je vais créer ? :
Pour pouvoir communiquer, il faut être deux. Or, en règle générale, il est difficile de trouver chez soi deux Graph 35++/75/85/95. Pour cela, il ne faut pas hésiter à demander à vos amis du collège/lycée/fac, bref, ceux qui sont susceptibles de posséder une Graph "classique". Attention, si celle-ci est une Graph 35+USB non débridée, demandez bien à son propriétaire si il est d'accord pour la modifier en Graph 75. Bon, en règle générale, la promesse de pouvoir jouer à Sims City, Pokémon ou Fruit Ninja dessus est un bon argument, mais soyez responsable du matériel emprunté.
Une fois en possession de vos deux caltos, installez sur les deux l'addin
Serial Monitor de Ziqumu. Il servira à tester la connexion. Ensuite, vous connectez le câble aux deux caltos, puis vous lancez Serial Monitor. Appuyez sur les lettres, si elles sont bien envoyées, tout va bien. Sinon, vérifiez que le câble est bien enfoncé, en bon état, etc.
Vos programmes auront alors toutes les chances de fonctionner, tant que l'erreur ne vient pas de vous.
NB : Je savais pas où mettre ça, mais dans la suite du tutoriel, dès que l'on parlera de taille de données, tout sera exprimé en
octets.
2) Que faut-il utiliser ? :
Comme d'habitude, voici les fichiers contenant les fonctions à inclure dans votre projet (en pièce-jointe). Le fichier .src est à ajouter aux fichiers du programme dans le SDK (comme un .c classique). Il contient des instructions en assembleur.
Voici tout de même le header dont nous commenterons les fonctions :
Serial.h
Cliquer pour enrouler
#ifndef _SERIAL
#define _SERIAL
/**********************************************************/
/** Les syscall suivants servent à l'acces au port série **/
/** Le buffer de réception fait 1ko, **/
/** et le buffer d'envoi fait 256 octets. **/
/**********************************************************/
//Lit un caractère du buffer de réception et le copie a l'adresse pointée par 'dest'
//Retourne 0 en cas de succes, 1 si le buffer est vide, 3 si la connexion n'est pas établie
int Serial_ReadByte(unsigned char *dest);
//Lit 'max' octets du buffer de réception et les écrit dans 'dest'
//'size' donne le nombre d'octets lu
//Retourne 0 en cas de succes, 1 si le buffer est vide, 3 si la connexion n'est pas établie
int Serial_ReadBytes(unsigned char *dest, int max, short *size);
//Ecrit 'byte' dans le buffer d'envoi
//Retourne 0
int Serial_WriteByte(unsigned char byte);
//Ecrit 'size' octets dans le buffer d'envoi depuis l'adresse 'scr'
//Retourne 0 en cas de succes, 2 si le buffer est trop plein, 3 si la connexion n'est pas établie
int Serial_WriteBytes(unsigned char *src, int size);
//FIFO = first in first out
int Serial_WriteByteFIFO(unsigned char byte);
//Retourne la taille des données du buffer de réception
int Serial_GetRxBufferSize(void);
//Retourne l'espace disponible dans le buffer d'envoi
int Serial_GetTxBufferFreeCapacity(void);
//Vide le buffer de réception
//Retourne 0 en cas de succes, 3 si la connexion n'est pas établie
int Serial_ClearReceiveBuffer(void);
//Vide le buffer d'envoi
//Retourne 0
int Serial_ClearTransmitBuffer(void);
//Ouvre et prépare l'interface de communication
//Pour plus de détails, consulter fxreverse-doc-1.pdf ci joint
int Serial_Open(unsigned char *conf);
//Ferme l'interface de communication et vide les buffers d'envoi et de réception
//Si 'mode'==1, la communication est coupée sans regarder s'il reste des données a transmettre
//Si 'mode'!=1, la fonction ne ferme pas l'interface de communication s'il reste des données a transmettre
//et retourne 5
//Retourne 0 en cas de succes (communication terminée) et 5 s'il reste des données a transmettre
int Serial_Close(int mode);
//Copie l'octet numéro 'index' du buffer de réception vers 'dest' si 'index' ne dépasse pas les données du buffer
//Retourne 0 en cas de succes, 1 si 'index' dépasse les données du buffer, 3 si la communication n'est pas établie
int Serial_Peek(int index, unsigned char *dest);
//Récupère le statut de la connexion
//Retourne 1 si la connexion est établie, 3 sinon
int Serial_IsOpen(void);
#endif
3) Ouvrir et fermer le port série :
Encore une fois, on a le code base, mais comment l'utiliser ? Pour envoyer des données via le port série, il faut ouvrir ces ports. Pour cela, on a besoin d'une liste de configuration :
unsigned char config[] = {a, b, c, d, e, f};
Et les valeurs à mettre dans ce tableau :
Source : FxReverse de Andreas Bertheussen et Simon Lothar
Donc pour ouvrir les ports, les options les plus courantes sont :
unsigned char config[] = {0, 5, 0, 0, 0, 0};
// configuration des ports à 9600 bauds, pas de bit de parité, 8 bits de longueur, et 1 bit d'arret
Pour plus d'infos sur les bits de parité/longueur/arrêt, renseignez-vous ailleurs, c'est d'un niveau supérieur et pas forcément intéressant pour un usage courant. Pour les bauds, lisez la note en annexe de ce tutoriel.
Bref, maintenant qu'on a configuré les ports, on va les ouvrir, une seule fois tant que l'on ne les a pas fermés. Donc en début de jeu dans notre cas, en même temps que
srand(), si vous utilisez de l'aléatoire.
Pour cela, exécutez cette fonction, et c'est tout :
erreur = Serial_Open(config);
erreur : 0 si tout s'est bien passé, 3 si c'est déjà ouvert, et 4 si config[0] est différent de 0.
Je vous habitue à utiliser des variables d'erreur pour que vous sachiez comment ça fonctionne, même si ce n'est pas nécessaire.
Pour le fermer, a la fin de votre programme, faites :
erreur = Serial_Close(0); // 0: arrête même si il reste des données à transmettre; 1: envoie les données puis ferme
erreur : 0 si tout s'est bien passé, 5 si il restait des données à envoyer.
4) Émettre et recevoir des variables simples :
On attaque les choses sérieuses : envoyer et recevoir des données. On va commencer avec un exemple simple, envoyer des
(unsigned) char. J'insiste sur le
char car les
int,
short, et
float sont considérés comme des tableaux de
char, et ça risque de ne pas fonctionner comme prévu si vous utilisez cette méthode.
Pour résumer brièvement, la calto possède un
buffer, c'est à dire une zone de mémoire, réservé à l'envoi et à la réception de données. Le buffer d'envoi fait 256 octets, celui de réception 1ko. Lorsque vous écrivez ou lisez dedans, la calto se charge de mettre à jour le buffer correspondant : envoyer ou effacer les données.
Donc, pour écrire une valeur dans ce buffer, il faut faire :
erreur = Serial_WriteByte(monChar); // écrit le [i]char[/i] dans le buffer d'envoi
// celui-ci est automatiquement envoyé dès que la calto le peux
erreur : 0 si tout s'est bien passé, et c'est tout.
Pour les recevoir, c'est presque pareil, sauf qu'il faut un pointeur :
erreur = Serial_ReadByte(&monChar);
erreur : 0 si tout s'est bien passé, 1 si le buffer de réception est vide, 3 si le port est fermé.
Ensuite, vous n'avez plus qu'à traiter les infos de votre coté.
5) Émettre et recevoir des tableaux de char et des variables "complexes" :
Nous allons maintenant passer à la vitesse supérieure : envoyer des tableaux de
char, ainsi que d'autres types de variables et des structures.
Pour envoyer un tableau, il faut copier l'intégralité du tableau dans le buffer d'envoi. On pourrait faire :
void Send_Tab(char *tab, int size)
{
int i;
for(i=0; i<size; i++) Serial_WriteByte(*tab[i ]);
}
Mais il existe une fonction bien plus utile et efficace, qui prend en argument un pointeur sur le tableau, et la taille des données à écrire.
char monTableau[] = {0, 1, 2, 3, 4};
int taille = 5;
erreur = Serial_WriteBytes(monTableau, taille);
erreur : 0 si tout s'est bien passé, 2 si le buffer de transmission est plein, 3 si le port est fermé.
Cette fonction est très utile en ce qu'elle permet de copier tout type de données, y compris des
short,
int,
floats, ou structures. Il suffit encore une fois d'envoyer les données avec un pointeur sur les données à copier :
short monShort = 42;
int monInt = 2048;
struct MA_STRUCTURE maStructure = {0x2A, 0xFF};
erreur1 = Serial_WriteBytes(&monShort, sizeof(short)); // envoie un short
erreur2 = Serial_WriteBytes(&monInt, sizeof(int)); // envoie un int
erreur3 = Serial_WriteBytes(&maStructure, sizeof(maStructure)); // envoie une structure de données
Pour lire ces données, vous pouvez récupérer une zone entière du buffer de réception avec
Serial_ReadBytes(), qui demande en argument un pointeur sur la zone où copier les données, la taille maxi de données à copier, et un pointeur sur
short, qui contiendra la taille des données lues - utile dans le cas où le buffer est vide avant d'avoir atteint la taille max à lire.
unsigned char monBuffer[10];
short tailleLue = 0;
erreur = Serial_ReadBytes(monBuffer, 10, &tailleLue);
erreur : 0 si tout s'est bien passé, 1 si le buffer de réception est vide, 3 si le port est fermé.
De même, on peut utiliser cette fonction pour lire d'autres types de variables :
short monShort = 42;
int monInt = 2048;
struct MA_STRUCTURE maStructure = {0x2A, 0xFF};
erreur1 = Serial_ReadBytes(&monShort, sizeof(short), &tailleLue); // récupère un short
erreur2 = Serial_ReadBytes(&monInt, sizeof(int), &tailleLue); // récupère un int
erreur3 = Serial_ReadBytes(&maStructure, sizeof(maStructure), &tailleLue); // récupère une structure de données
Attention
Il faut d'abord synchroniser la connexion entre les deux caltos avant d'envoyer - recevoir des données par paquets : en effet, si A envoie un
int à B, et que B le lit avant que tout soit transmit, il n'aura qu'une partie des données, qui seront de plus décalées bit à bit !
Faites attention à cet aspect, cela peut provoquer des bugs ! Pour s'en protéger, la fonction
int Serial_GetRxBufferSize(void) peut être utile.
Elle retourne le volume de données présentes dans le buffer de réception.
6) Annexes :
Les bauds :
Le baud est l'unité de mesure de vitesse de communication. Dans notre cas, il est équivalent au nombre d'octets/secondes, car la liaison série ne possède qu'une seule voie. Dans le cas où il existe plusieurs voies (carte SD, etc.) le nombre de bauds n'est pas égal à la vitesse en octets/secondes. Encore une fois, Google sait tout et vous éclairera sur vos questions subsidiaires.
Autres fonctions :
D'autres fonctions existent, la liste complète est dans la documentation
FxReverse de Andreas Bertheussen et Simon Lothar.
Retour au sommaire
Fichier joint
Citer : Posté le 14/08/2014 16:39 | #
Ok je vais voir si j'y arrive
Ajouté le 14/08/2014 à 17:24 :
Il y a un moyen de synchroniser les deux caltos pour que l'une attende l'autre si part exemple elle a plus de calculs a faire ?
Citer : Posté le 14/08/2014 20:10 | #
Oui, c'est ce sur quoi je planchais avant de passer à autre chose
Citer : Posté le 14/08/2014 20:15 | #
Ha oui c'est bon j'ai trouvé mon bonheur, désolé
Mais si t'as une petite fonction sympa a me passer quand même ça m'arrangerait
Ajouté le 15/08/2014 à 14:11 :
Aussi, j'ai une petite question.
Est-ce que c'est nécessaire de mettre Serial_ClearReceiveBuffer() et/ou Serial_ClearTransmitBuffer() après chaque envoie ou les données de l'envoie précédant ne gêneront pas l'envoie suivant ?
Ajouté le 15/08/2014 à 15:24 :
Encore moi
J'ai fait ceci et ça ne fonctionne pas. (une seule des deux caltos me dit qu'elle est connectée)
Serial_ClearReceiveBuffer();
Serial_ClearTransmitBuffer();
ML_clear_vram();
RestoreDisp(2);
ML_rectangle(2,56,125,65,1,ML_BLACK,ML_WHITE);
PrintMini(3,17, "Test de connexion", 0);
PrintMini(3,24, "( [EXIT] pour interrompre )", 0);
ML_display_vram();
communication = 'T';
Serial_WriteByte(communication);
communication = 'N';
i = 500;
while(sizeof(char) != Serial_GetRxBufferSize() && IsKeyUp(KEY_CTRL_EXIT) && i) i--, Sleep(10);
Serial_ReadByte(&communication);
if(communication == 'T'){
ML_clear_vram();
RestoreDisp(2);
PrintMini(3,20, "Connexion reussie !", 0);
ML_display_vram();
Sleep(3000);
// Serial_ClearReceiveBuffer();
// Serial_ClearTransmitBuffer();
}
else{
ML_clear_vram();
RestoreDisp(2);
ML_rectangle(2,56,104,65,1,ML_BLACK,ML_WHITE);
PrintMini(3,17, "Echec de la connexion", 0);
sprintf(buffer, "Reponse trop lente - %c", communication);
PrintMini(3,17, buffer, 0);
ML_display_vram();
while(IsKeyUp(KEY_CTRL_F6) && IsKeyUp(KEY_CTRL_EXIT));
while(IsKeyDown(KEY_CTRL_F6) || IsKeyDown(KEY_CTRL_EXIT));
}
Serial_Close(0);
Citer : Posté le 15/08/2014 16:13 | #
Citer : Posté le 17/08/2014 16:02 | #
Facile : tu lance pas tes caltos en même temps à 1/1000ème de seconde ?
Donc si tu lance la deuxième calto après que la première ai envoyé ses données, vu que tu vide le buffer de reception, c'est normal que la deuxième reçoive rien
Solution : met un Sleep suffisant avant d'envoyer les données, ou un GetKey() après avoir vidé les buffers, puis tu appuie à peu près en même temps sur les deux.
Citer : Posté le 17/08/2014 16:06 | #
Ha oui en effet j'avais pas fait gaffe a ca
Merci :kiss:
Ajouté le 20/09/2014 à 14:18 :
Bon, me revoila
Ca m'enerve un epu parce que j'ai presque terminé le reste mais je bloque sur ca:
// Serial_ClearReceiveBuffer();
// Serial_ClearTransmitBuffer();
ML_rectangle(2,56,104,65,1,ML_BLACK,ML_WHITE);
PrintMini(3,17, "Connectez les 2 calculatrices", 0);
PrintMini(3,24, "puis appuyez sur [EXE]...", 0);
ML_display_vram();
while(1){
GetKey(&key);
if(key == KEY_CTRL_EXIT || key == KEY_CTRL_F6){
Serial_Close(0);
SaveDisp(2);
fenetre_f(0,45,2);
for(i=10;i>=0;i--){
ML_clear_vram();
ML_bmp_or_cl(banniere_mp, 0, 56-i, 128, 8);
ML_bmp_or_cl(banniere_convois, 0, 65-i, 128, 8);
ML_display_vram();
Sleep(50);
}
return;
}
if(key == KEY_CTRL_EXE) break;
}
ML_clear_vram();
RestoreDisp(2);
ML_rectangle(2,56,104,65,1,ML_BLACK,ML_WHITE);
PrintMini(3,17, "Test de connection", 0);
PrintMini(3,24, "( [EXIT] pour interrompre )", 0);
ML_display_vram();
communication = 'T';
Serial_WriteByte(communication);
communication = 'N';
i = 500;
while(sizeof(char) != Serial_GetRxBufferSize() && IsKeyUp(KEY_CTRL_EXIT) && i) i--, Sleep(10);
Serial_ReadByte(&communication);
if(communication == 'T'){
ML_clear_vram();
RestoreDisp(2);
PrintMini(3,20, "Connection reussie !", 0);
ML_display_vram();
Sleep(3000);
// Suite du traitement
}
else{
// Affiche "Probleme"
}
Donc, je connecte mes 2 caltos, je vais jusqu’à ce morceau de code, j’appuie sur EXE avec les deux caltos encore une fois, une seule voit l'autre
Si quelqu'un a la solution ou une idée
Citer : Posté le 20/09/2014 14:29 | #
Pourquoi tu te complique la vie ? Le mieux c'est de faire deux boucles synchronisées. Je suis sur mobile là, je vais essayer de chopper un ordi pour t'expliquer ça en détail
Ajouté le 20/09/2014 à 15:01 :
Bref, en gros tu peux faire un code de ce genre :
i = 100;
whois = 'W'; // W = Waiting
while(i && Serial_ReadByte(&whois) == 1) { i--; Sleep(10); } // On attend pendant 1s des éventuelles données
if(whois == 'R') // Si on est déclaré Receiver
Serial_WriteByte('1'); // On dit qu'on est bien connecté
else if(whois == 'W') // Si on est en mode de réception, mais qu'on a pas reçu de paquets de l'autre calto, on passe en émission
{
whois = 'T'; // T = Transmit
i = 500;
status = '0';
while(i && Serial_ReadByte(&status) == 1) // On essaie de récupérer la réponse de l'autre calto tout en envoyant les paquets
{
Serial_WriteByte('R');
i--;
}
if(status == '0')
return 0; // ça a foiré
// sinon on peut continuer ;)
}
Le code est vraiment brouillon, j'ai pas eu le temps de mieux l'organiser, mais le principe est là : tu envoie des données des deux cotés en boucle jusqu'à ce qu'un signal envoyé par la calto "maître" à la calto "esclave" dise de synchroniser la com'
Citer : Posté le 21/09/2014 14:21 | #
Ok dac, je crois que j'ai compris. Merci
Il manque un Sleep(10) dans le second while
Citer : Posté le 21/09/2014 18:34 | #
Il manque pleins de trucs oui
Comme pouvoir quitter avec EXIT, afficher une barre de chargement, etc.
Citer : Posté le 07/05/2015 09:12 | #
salut !
juste une petit question (surement un peu bête ) :
je vois souvent des gens initialiser la communication à 9600b mais je voulais savoir si il est possible de prendre une valeur plus elevée (sans risquer de griller le câble )
- Un pong multijoueur avec le cable 3pin
- Communication IR entre caltos (Arduino)
Citer : Posté le 07/05/2015 11:55 | #
Regarde la doc de Serial_Open(), l'un des paramètres est cette fréquence.
Citer : Posté le 07/05/2015 17:27 | #
J'arrive a initialiser la communication mais je voudrais juste savoir si il est possible d'initialiser la fréquence à 115200bauds sans risquer de détruire le câble
- Un pong multijoueur avec le cable 3pin
- Communication IR entre caltos (Arduino)
Citer : Posté le 07/05/2015 17:34 | #
La seule manière de griller le cable, c'est de lui mettre du 220V dedans. Donc oui, tu peux mettre 115200 bauds sans soucis (c'est fait pour). La vitesse de communication est totalement indépendante de la tension/intensité qui traverse le cable.
Citer : Posté le 07/05/2015 18:01 | #
ok merci beaucoup comme ça j'aurais pas m'embeter a optimiser la vitesse de co sur mupong en touchant aux variables que j'envoie (j'aurais juste un petit chiffre à changer )
- Un pong multijoueur avec le cable 3pin
- Communication IR entre caltos (Arduino)
Citer : Posté le 10/05/2015 12:57 | #
Après, plus tu transmet vite, plus tu as de chances de perdre des données en route (erreurs de transmission). Ça peut être en partie évité avec un bit de parité et deux bits d'arrêt.
Citer : Posté le 12/03/2017 14:13 | # | Fichier joint
Le baud est l'unité de mesure de vitesse de communication. Dans notre cas, il est équivalent au nombre d'octets/secondes, car la liaison série ne possède qu'une seule voie.
Alors, ici, il me semble que tu as faux. Le Baud rate ne donne pas un équivalant en octets/seconde mais plutôt et seulement très approximativement en bits/seconde.
Si on assume que la valence est égal à 1 (1 baud = 1 état de valence, c'est à dire qu'une modulation ne peut coder qu'un seul bit, 0 ou 1), on a donc 9600 bauds = 9600 états de valence.
Avec la conf que tu as choisis :
On a le calcul suivant :
Plus généralement :
Un petit tableau excel en pj qui résume les vitesses.
Les calculs ne sont que théorique et je me trompe peut être, ce serait cool si quelqu'un pouvait vérifier en balançant un gros fichier entre deux calculatrices et en chronométrant (je n'en ai pas la possibilité malheureusement). Normalement, ça devrait être légèrement plus lent.