Unity3D, Port-série, Arduino : comment augmenter la rapidité de transmission.

Bonjour,

Je suis parvenus à transmettre des informations d’unity3D en C# vers une UNO r3 en utilisant le port série.
Seulement je peux envoyer qu’une transmission/seconde.
Je présente mon projet:

J’ai une interface 3D représentant deux servo moteur avec deux slider (curseur) pour les piloter. Je souhaite envoyer en temps réel les positions des sliders vers ma carte Arduino. Ceci dans l’objectif de voir se déplacer mes servo-moteurs en même temps que mes curseurs virtuels. Pour cela j’envois un string de format (000000). Les trois première valeur pour le servo 1, les trois autres pour le servo 2.

Du coté Unity3D je peux envoyer autant d’information que mon ordinateur peut à la secondes. De cette façon, Arduino ne réagit pas. J’ai donc bloqué la rapidité de transmission à 1 transmission /sec. J’ai essayé 1 transmission / 1.5 secondes et ça ne marche pas non plus, en revanche 2 secondes 3 sec… fonctionnent.

Pourtant je vois bien RX clignoter, mais alors ma carte sais qu’elle reçoit quelque chose mais n’en fait rien.

Faute que je lui en demande trop ?

Vous avez déjà certainement vus des videos avec des slider virtuel et des servo qui réagissent en même temps ?

Pouvez vous m’aider à résoudre mon problème =) ?

je vous montre mes codes:

Arduino :

//Attention il y un reboot lors d'un serial.print si le port serie n'est pas ouvert.

#include <Servo.h>

Servo myServoA;
Servo myServoB;

const int pinServo1 = 9;
const int pinServo2 = 10;

String incomming;
String maChaine1;
String maChaine2;

int servoPos1;
int servoPos2;
int oldServoPos1 = 0;
int oldServoPos2 = 0;

void setup()
{
  Serial.begin(9600);

  myServoA.attach(pinServo1);
  myServoB.attach(pinServo2);

 // myServoA.write(0);
 // myServoB.write(0);
}

void loop()
{
  if (Serial.available() > 0)
  {
    incomming = Serial.readString();
    delay (10);
    maChaine1 = incomming.substring(0, 3);
    maChaine2 = incomming.substring(3, 6);

    servoPos1 = maChaine1.toInt();
    servoPos2 = maChaine2.toInt();

    oldServoPos1 = servoPos1;
    oldServoPos2 = servoPos2;

    servoPos1 = limitePosServo(servoPos1); //vérifier l'intervalle [0 ; 175]
    servoPos2 = limitePosServo(servoPos2);

    /*  Serial.print ("servoPos1: ");// Serial.print reset la carte quand arduino essaye d'envoyer une info vers unity.
      Serial.println (servoPos1);
      Serial.print ("servoPos2: ");
      Serial.println (servoPos2);
      Serial.println ("");*/


  }
  myServoA.write(servoPos1);
  myServoB.write(servoPos2);
  delay (10);
}//end loop


int limitePosServo(int valeur)
{
  if (valeur < 0)
  {
    valeur = 0;
  }

  if (valeur > 175)
  {
    valeur = 175;
  }
  return valeur;

}

Si cela peux vous permettre de comprendre

Unity3D en C# :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO.Ports;

public class control : MonoBehaviour {

	//communication
	SerialPort serial;
	public string myString;
	float temps = 0.0f;
	float delay = 0.0f;
	public float comRapidity = 2.0f;
	public string portName;

	//Servo 1 
	public int servoDegre1; //Degré value
	public Transform servo1;
	public Slider sliderServo1;
	public Button buttonS1plus;
	public Button buttonS1moins;
	public Text textS1;
	string A;

	// Servo 2
	public int servoDegre2; //Degré value
	public Transform servo2;
	public Slider sliderServo2;
	public Button buttonS2plus;
	public Button buttonS2moins;
	public Text textS2;
	string B;

	// Use this for initialization
	void Start () {
		//communication
		serial = new SerialPort();

		//Button S1
		buttonS1plus.onClick.AddListener(TaskOnClickS1plus);
		buttonS1moins.onClick.AddListener(TaskOnClickS1moins);

		//Button S2
		buttonS2plus.onClick.AddListener(TaskOnClickS2plus);
		buttonS2moins.onClick.AddListener(TaskOnClickS2moins);
	}
	
	// Update is called once per frame
	void Update () {

		//Servo 1 
		if (servoDegre1 != (sliderServo1.value)) 
		{
			if (servoDegre1 < (sliderServo1.value) ) 
			{
				servoDegre1 = servoDegre1 + 1 ;		
			} 
			if (servoDegre1 > (sliderServo1.value) )
			{
				servoDegre1 = servoDegre1 - 1;
			}
		}
		servo1.localRotation = Quaternion.AngleAxis (-servoDegre1, Vector3.up);
		textS1.text = servoDegre1.ToString() + "°";

		//Servo 2 
		if (servoDegre2 != (sliderServo2.value)) 
		{
			if (servoDegre2 < (sliderServo2.value)) 
			{
					servoDegre2 = servoDegre2 + 1;
			} 

			if (servoDegre2 > (sliderServo2.value))
			{
				servoDegre2 = servoDegre2 - 1;
			} 
		}
		servo2.localRotation = Quaternion.AngleAxis (-servoDegre2, Vector3.up);
		textS2.text = servoDegre2.ToString() + "°";
	
		//communication

		A = sliderServo1.value.ToString("000");
		B = sliderServo2.value.ToString("000");
		myString=string.Concat(A,B);

		temps = Time.time;
		if ((delay + comRapidity) < temps)
		{
			Envoyer ();
			delay = temps;
		}
			
	}

	// button S1
	void TaskOnClickS1plus ()
	{
		(sliderServo1.value) = (sliderServo1.value) + 1;
	}

	void TaskOnClickS1moins ()
	{
		(sliderServo1.value) = (sliderServo1.value) - 1;
	}
	// button S2
	void TaskOnClickS2plus ()
	{
		(sliderServo2.value) = (sliderServo2.value) + 1;
	}

	void TaskOnClickS2moins ()
	{
		(sliderServo2.value) = (sliderServo2.value) - 1;
	}

	//communication
	public void Envoyer ()
	{
		serial.PortName = portName;
		serial.Parity = Parity.None;
		serial.BaudRate = 9600;
		serial.DataBits = 8;
		serial.StopBits = StopBits.One;
		serial.Open ();
		serial.Write (myString);
		serial.Close ();
	}
		
}

Regardez la méthode Envoyer (), cela prend t’il du temps à arduino de ferme le port serie à chaque fois ou c’est indépendant ? J’ai essayé de le laisser ouvert mais plus rien ne fonctionne.

Bien à vous.

Salut

En général j'utilise PYTHON et pyserial, et je laisse le port ouvert.
Aucun problème.

@+

Bonsoir,

le problème vient sans doute de l'usage de readString(); dans ton code arduino.

D'après la doc :

Serial.readString() reads characters from the serial buffer into a string. The function terminates if it times out (see setTimeout()).

Une solution, que je ne trouve pas très propre, mais qui devrait fonctionner : Ajouter Serial.setTimeout(10); dans le setup().

Waw vous êtes rapide,

hbachetti

J'ai réussi à laisser le Port ouvert, et effectivement ca fonctionne. En revanche je vais devoir trouver une solution pour le fermer automatiquement quand je quitte l'application, mais ca je ne devrais pas avoir de soucis à trouver l'info.

supercc

Et bien ca fonctionne. Si je comprend bien Serial.Read est une fonction bloquante qui se termine au bout d'un certain temps . Le set time out permet de modifier ce temps. Il est paramétré à 1 seconde. Si je le met à 10 millis, je risque de ne pas avoir le temps de lire toute une donnés si dans l'avenir elles devenait plus grande ? C'est pour cela que tu ne trouve pas génial cette solution ?

Une nouvelle fois, merci, J'espère un jour pouvoir rendre service comme vous.

Bonjour,

il y a un truc qui craint dans ton protocole :
l'arduino reçoit des suites de chiffres au gré de l'humeur de l'OS du PC, et au bout d'un moment ne sait peut-être plus ce qu'il doit en faire
je suggère de rajouter un caractère de début dans tes messages pour clarifier les choses, exemple s000000

cela éliminera les questions liées au time out

concernant la fermeture du port, c'est généralement assez simple : de même que tu l'as ouvert en début de programme, tu le fermes à la fin ...

Si je le met à 10 millis, je risque de ne pas avoir le temps de lire toute une donnés si dans l'avenir elles devenait plus grande ? C'est pour cela que tu ne trouve pas génial cette solution ?

En fait c'est plus pour la raison qu'évoque trimarco232, si l'OS émetteur décide de faire une pause >10 ms au milieu du message ce que lira l'arduino sera incomplet. A l'inverse, si tu augmentes la cadence coté émetteur (C#) qu'est ce qui te garanti que l'OS ne décidera pas de fusionner 2 messages en 1, te faisant perdre du coup un couple de valeur du coté arduino. Enfin, dernier problème du setTimeout (pour ce soir ;-)), c'est qu'il n'est pas optimal (ici il te fait perdre 10 ms à la fin de chaque message). Bref, cette histoire de timeout est bancale de tous les cotés.

Comme trimarco232 je soigne mes protocoles mais d'une autre façon (classique sous *nix) :
Je n'émets et ne reçois que des lignes (chaînes terminées par le caractère '\n').

  • Du coté émetteur un printf('\n') ou println() ou équivalent.
  • Du coté récepteur un fgets() ou Serial.readStringUntil()('\n') ou équivalent.

Et c'est seulement après que je décode la ligne coté récepteur (avec sscanf pour chaînes C, toInt, toFlot, ... pour les String C++).

Sinon, je sais que sous un OS digne de ce nom, quand une application se termine, elle ferme ses fichiers toute seule comme une grande :wink:

Sinon, je sais que sous un OS digne de ce nom, quand une application se termine, elle ferme ses fichiers toute seule comme une grande :wink:

Tout à fait.

Question protocole de transmission, il est assez classique de commencer par un caractère STX (Start of TeXt) et de terminer par un ETX (End of TeXt).
Si l'on veut être très sérieux ETX peut être suivi d'un CRC (classiquement CRC16).
Le récepteur acquitte par un caractère ACK (CRC correct) ou NAK (CRC incorrect).

Si les données transmises sont du texte, il est possible de se passer du mode transparent.
Si les données transmises sont binaires, alors il faut gérer les cas ou un caractère de contrôle (STX, ETX, ACK ou NAK) fait partie des données, en le faisant précéder par un caractère DLE.

Le récepteur reçoit les caractères un par un.
Il attend STX, suivi des données. A la réception de ETX, il attend le CRC sur deux caractères.
Dans le cas nominal il n'y a jamais de time-out.

Si le CRC est incorrect le récepteur envoie NAK et l'émetteur renvoie la même trame. On fixe généralement le nombre de tentatives à 3.

@+

Bonjour, et merci pour l’initiation à la transmission de données :slight_smile: .

Trimarco232

Au départ j’envoyais A000 pour le servo 1 puis B000 pour le servo 2, mais j’ai pensé que cela me faisait perdre du temps et j’avais toujours un problème de timeout à 1000 milli. J’ai donc décidé d’envoyer 000000. Mais sans amélioration. Donc la solution dans premier temps c’est bien d’avoir un Serial.Timeout(10).

Pour le fermeture du port, lorsque je quitte la simulation d’unity 3D le port reste ouvert. Certainement parce qu’il s’agit d’une simulation; je ne quitte par réellement l’application. En effet je ne peux plus utiliser le moniteur ou encore téléverser un code dans Arduino.
J’utilise alors cette solution en C# propre à unity me semble t’il :

void OnApplicationQuit()
	{
		Debug.Log("Application ending after " + Time.time + " seconds");
		serial.Close ();
	}

Si non je vous avoue que je suis un peut perdu avec ces nouveaux termes. Ce sont les premiers symbole de la table ascci. Mais je ne vois pas comment les utiliser, il faut tous les utiliser ?

J’envois ^B pour pour STX
puis S pour servo : S000000\n. Cela me permettra d’envoyer un message A B ou C … pour une autre fonction.

serial.Write (^B + myString + "\n" );

Si je comprends bien, au lieu d’attendre 10 milli secondes arduino va attendre \n .Je n’aurais donc plus besoin de Serial.setTimeout(10); .Cela met beaucoup moins de temps n’est ce pas ?

Je vais me contenter d’envoyer du texte, on verras pour le binaire plus-tard.

Du côté Arduino je valide ça avec des condition if ?

Je lis un caractère

Si j'ai ^B je lis
 {
     Si ce que je lis commence par S
         {
          Je lis jusqu'à ^C 
          Je stock laValeur en int
          J'attend le CRC     /*mais comment je fais ça et comment je l'envoi. Un CRC, c'est bien la taille du message envoyer ?*/
          Je converti le CRC en int
          Si CRC == laValeur 
               {
                    J'envois ACK 
                    Je traduit le texte
                    Je positionne les servos
               }
          Si CRC != la valeur 
               { J'envois NAK }
         }
 }

Et du côté émetteur ?

J'envois mon string : ^B000000^CCRC  désolé mais je ne visualise pas le CRC
J'attend une réponse ...Attendre, je ne sais pas attendre en programmation à moins de mettre un bool attendre sur tout le programme .
Si la réponse est ACK ou nombre d'envois >=3 
        Je n'attend plus le bool attendre est false je passe à la suite.
Si la réponse est NAK et nombre d'envois < 3
       Je renvois ce que je viens d'envoyer.

J’ai remarqué que ma carte redémarre quand elle tente de transmettre. J’espère que le faite de ne plus fermer résous ce problème.

Un CRC, c’est bien la taille du message envoyer ?

Non. C’est un mot sur 16bits qui permet de s’assurer que les données transmises sont correctes.

Un CRC est différent d’un checksum.
Le checksum est une somme. Si l’on permute deux octets dans la trame, le checksum donnera le même résultat.
Le CRC donnera un résultat différent.

Il faut disposer du même code de calcul de CRC des deux côtés.
Cela ne devrait pas être très dur de trouver cela sur le WEB.

J’ai une implémentation du CRC16 CCITT dans ma société, mais pas ce ne sera pas avant demain.

@+

Je poste un exemple modifié de ce que j’utilise. Par curiosité j’ai fait le code C# (en mono sous GNU/Linux). Désolé s’il ne répond pas aux standards (je ne développe pas en C#). Tu devras adapter pour Windows le nom du port série.

Ce code est adapté, je pense, à ton problème (ligne au format “val1 val2” pour les valeurs des 2 moteurs).

Je sécurise par un CRC8 (plus léger pour l’arduino) et un accusé de réception indiquant l’éventuel problème.

A la vitesse de 115200 bauds le programme est capable d’émettre (et de recevoir l’acquittement) de 500 couples de valeurs (lignes) par seconde.

Le programme C# :

using System;
using System.IO.Ports;
using System.Threading;

/*
   Émission en boucle (1000) de lignes contenant 2 entiers.

   Un CRC8 est ajouté en début de ligne pour validation par le récepteur.

   Le récepteur renvoie :

 * "0" : ok
 * "1" : mauvais format de ligne.
 * "2" : mauvais CRC

 Arrêt du programme si erreur détectée.
*/

public class TestUsb {

 // from pololu.com (https://www.pololu.com/docs/0J44/6.7.6)

 static byte GetCRC(String message) {
 byte j, crc = 0;
 const byte CRC7_POLY = 0x91;
 int i;

 int length=message.Length;

 for (i = 0; i < length; i++)
 {
 crc ^= (byte) message[i];

 for (j = 0; j < 8; j++)
 {
 if ((crc & 1) == 1)
 crc ^= CRC7_POLY;
 crc >>= 1;
 }
 }
 return crc;
 }

 static public void Main ()
 {

 Random rnd = new Random();

 SerialPort serial;
 serial = new SerialPort();

 serial.PortName = "/dev/ttyUSB0";
 serial.Parity = Parity.None;
 serial.BaudRate = 115200;
 serial.DataBits = 8;
 serial.StopBits = StopBits.One;
 serial.Open ();

 // Pour laisser le temps à la carte arduino de rebooter

 Console.WriteLine ("Wait arduino reboot (2s) ...");
 Thread.Sleep(2000); 

 // On émet (à fond) 1000 couples (m1 m2)

 for(int i=0; i<1000; ++i) { 

 int m1=rnd.Next(0,255);
 int m2=rnd.Next(0,255);

 string msg=m1+" "+m2;

 // Calcul du CRC 8

 byte crc=GetCRC(msg);


 // falsifie le CRC pour test détection mauvais crc
 // crc++;

 // falsifie le message pour détection mauvais format
 //msg="123 bad";


 // CRC en hexa sur 2 octets (simplification du code coté arduino)
 string crcHex = crc.ToString("X");
 if(crcHex.Length==1) crcHex="0"+crcHex;

 // Ajout du CRC en début de message
 msg=crcHex+" "+msg;

 Console.WriteLine ("Envoi : \""+msg+"\"");

 // envoi 
 serial.WriteLine(msg);

 // attente de la réponse (on retire '\n' final).
 string response=serial.ReadLine().TrimEnd();

 // Affichage de la réponse 
 Console.WriteLine ("Recu ("+i+") : \""+response+"\"");

 if(response != "0") {
 Console.WriteLine ("Erreur !");
 Environment.Exit(1);
 } 

 // pour une attente entre les messages
 //Thread.Sleep(500);
 }
 serial.Close ();
 }
}

Le programme Arduino :

/*
Lecture en boucle sur le port série de lignes ASCII au format :

CRC8 VAL1 VAL2

  * CRC8 la représentation ASCII en hexadécimal sur 2 caractères du CRC 8
    de "VAL1 VAL2".
  * VALn la représentation ASCII d'un entier.

A l'issue d'une lecture, un code de retour est émis sur le port série :

  * "0" : ok
  * "1" : mauvais format de ligne.
  * "2" : mauvais CRC
*/

const int maxChars=20; // nombre de caractères maxi d'une ligne

// CRC8 from pololu.com (https://www.pololu.com/docs/0J44/6.7.6)

const unsigned char CRC7_POLY = 0x91;

unsigned char getCRC(char *message, int length)
{
    unsigned char i, j, crc = 0;

    for (i = 0; i < length; i++)
    {
        crc ^= message[i];
        for (j = 0; j < 8; j++)
        {
            if (crc & 1)
                crc ^= CRC7_POLY;
            crc >>= 1;
        }
    }
    return crc;
}


// Fonction fgets inspirée de la version POSIX.
// Évite de manipuler des String (fragmentation du tas).

char *fgets(char *s, int size, Stream &stream) {
    int cpt=0;
    char c;

    while(1) {
        if(stream.available()) {
            c = stream.read();
            if(c=='\r') continue; // skip Windows \r
 if(cpt<size-1) s[cpt++]=c;
            if(c=='\n') break ;
        }
    }
 s[cpt]=0;
    return s;
}

// Initialisation du port série

void setup() {
    Serial.begin(115200);
    while (!Serial);
}

// lecture en boucle des lignes

void loop() {
 char line[maxChars]; // espace pour stocker la ligne lue
 int val1, val2, crc, crc2;

 // Lecture (bloquante) d'une ligne en provenance du port série
 fgets(line, maxChars, Serial);

 // retire l'\n' final

 line[strlen(line)-1]=0;

 // décodage de la ligne reçue

 if(sscanf(line, "%x %d %d", &crc, &val1, &val2) !=3) {
 // renvoie "1" : ligne au format invalide
 Serial.println("1");
 return;
 }
 else { 
 // vérification du CRC (+3 car le CRC ne s'applique qu'aux données 
 // utiles)

 crc2=getCRC(line+3, strlen(line+3));
 if(crc!=crc2) {
 // renvoie "2" : mauvais CRC
 Serial.println(2);
 return;
 }
 else {
 // renvoie "0" : tout est ok
 Serial.println(0);  
 }
 }

 // arrivé à ce point du programme il y a une forte probabilité que 
 // val1 et val2 soient valides.

 // ...
}

Tu en fait ce que tu veux, mais méfie toi, je ne garanti pas qu’il est exempt d’erreurs de codage.

Sinon oui, sous GNU/Linux la carte reboot aussi à l’ouverture du port série par défaut en C#. J’évite cela sous GNU/Linux en C en fixant des valeurs précises de configuration via la commande stty. Si cela te dérange il faudra que tu te renseignes pour C# sous Windows.

PS 1 : désolé pour l’indentation qui disparaît a la moindre édition du post…?!?

PS 2 : je n’ai pas testé sous Windows (mais ai pris en compte, normalement, la terminaison de ligne différente par rapport à GNU/Linux).

A+

J'ai remarqué que ma carte redémarre quand elle tente de transmettre. J'espère que le faite de ne plus fermer résous ce problème.

Oui, je n'avais pas réagi.
En fait c'est la montée du signal DTR qui fait rebooter la carte, donc sur Open().

@+

Comme promis, une implémentation de CRC16 légère.
Ce genre de chose tourne sur des projets à vase de processeurs MSP430 bien moins puissants qu’un ATMEGA.

#define update_crc(crc, c)    crc = (crc >> 4) ^ crctbl[(c ^ crc) & 0x0F]; \
                              crc = (crc >> 4) ^ crctbl[((c >> 4) ^ crc) & 0x0F];

static const uint16_t crctbl[] =
{
     0x0000, 0x1081, 0x2102, 0x3183,
     0x4204, 0x5285, 0x6306, 0x7387,
     0x8408, 0x9489, 0xA50A, 0xB58B,
     0xC60C, 0xD68D, 0xE70E, 0xF78F
};


// utilisation :
   uint16_t crc;
   update_crc(crc, c);  // étant le caractère à émettre ou reçu

// émission du CRC
// send est la fonction d'émission, à remplacer par la votre
   send((crc & 0xFF00) >> 8);    // MSB
   send(crc & 0x00FF);              // LSB

// réception du CRC
// c est le caractère reçu. il y en a deux donc
    uint16_t crc;
// 1er caractère du CRC
    crc = ((uint16_t)c << 8) & 0xFF00;    // MSB
// 2ème caractère du CRC
    crc |= (uint16_t)c & 0x00FF;              // LSB

En espérant que cela soit suffisamment clair.

@+

Bonjour, et merci c’est vraiment sympa.

Je vous avoue avoir un très petit niveau en informatique. Je vais mettre du temps à décortiquer, voir digérer ^^. Ne m’en voulez pas si dans un mois je repose quelques questions .

Je vous informe dès que j’arrive à appliquer un de vos exemples.

Cordialement

@proautobot , pas de souci :wink:

@hbachetti, merci aussi pour le CRC16 léger !

Bonjour,

Je commence par décortiquer ton code supercc. Il y plain de choses que je ne connais pas et qui m’empêche de comprendre. Aussi seriez vous m'orienter vers un cours sur le CRC abordable par un débutant. Jusqu'à présent j'ai trouvé des explications, des exemples mais je ne comprend pas. Sans doute qu'il me manque des bases.
Voici mes questions pour vous rendre compte de mon niveau :

byte j, crc = 0;

c'est un peu comme:

byte j = 0;
byte crc = 0 ;

Si non le j n'est jamais remis à 0 lors de l'appel de la fonction GetCRC ?
D'ailleur La fonction c'est une static byte. Cela fait quoi ? Jusqu’à présent j'utilisais que des "void" quelque chose.

//////////////////////////////

CRC7_POLY = 0x91;

Ca veux dire quoi CRC7_POLY
Je ne connais 0x91 c'est un format, un nombre ?

/////////////////////////////

crc ^= (byte) message*;*
J'applique un "xor au byte message et j’attribue cette valeur au byte crc. Pour "i" = 1 je lit le byte 1 du message. J'ai soit 1 soit 0. Mais comment fonctionne ce xor,
d'habitude il y a un tableau de ce type:
S A B
0 0 0
1 1 0
1 0 1
0 1 1
La j'ai rien à comparer...
///////////////////////////////
if ((crc & 1) == 1)
si ((crc et 1) sont égale à 1 )
Pourquoi "& 1)" ?
///////////////////////////////
crc >>= 1;
Je me décale de 1 vers la droite, oui mais il resemble a quoi mon CRC ?
à ca 00000000 ?
Donc je me décale la 0[0]00000 ?
///////////////////////////////
A la suite tu créer une génération aléatoire de position. A moi de modifier cette partie pour piloter mes servos.
///////////////////////////////
Passage du CRC en Hexa
Je croyais que ToString c'était une pour une chaîne de caractère.
string crcHex = crc.ToString("X");
if(crcHex.Length==1) crcHex="0"+crcHex;
la chaîne de caractère crcHex = le byte crc transformé en String au format ("X")
Je ne comprend pas pa le ("X").
Si la taille de crcHex est de 1 caractère, le crcHex est transformé à "0"+crcHex
Mais ca donne "0" + "1 caractère" ?
///////////////////////////
msg=crcHex+" "+msg;
Pourquoi tu met un espace, c'est pris en compte dans le calcule de CRC ?
///////////////////////////
WriteLine(msg);
C'est quoi la différence avec Write(msg) ?
////////////////////////////
(on retire '\n' final). avec .TrimEnd(); ?
////////////////////////////
if(response != "0") {
Console.WriteLine ("Erreur !");
Environment.Exit(1);
}
Pourquoi on arrête le programme si il y a une erreur, c'est pas mieux de renvoyer le message au moins 3 fois ?
C'est pour cette raison que ton code n'est pas dans "le standard" ?
///////////////////////////
// pour une attente entre les messages
//Thread.Sleep(500);
Thread.Sleep(500); c'est un peu comme un delay ? j'ai pas cette bibliothèque sur unity.
Je suis obligé d'utiliser des co-routines ou des Time.time ...
Voilà, ça en fait un paquet de questions désolé. Mais ca m'aideras à aborder le CRC16 de hbachetti.
Je suis dans le dure je croix =) à bientôt.

J'ai fait un nouvelle essaie brute de transmission. Ca fonctionne sur le moniteur arduino. Mais c'est un échec en C# sur unity 3D.

J'arrive à envoyer ce format 000000 avec C# , serte avec le Serial.setTimeout(10); du côté arduino

J'ai voulu augmenter le nombre de servo à 6 mon nouveau format est : 000000000000000000

Je tape 175090080100010000 avec le moniteur Arduino et les servo se déplacent.

J'envoie ca avec mon interface unity 3D et j'ai à nouveau un problème, aucune réaction.

J'ai essayé d'augmenter le Serial.setTimeout(); je l'ai même suprimmé
J'ai essayé de diminuer à une émission par seconde
J'ai essayé avec trois servo moteur 000000000.
Aucun résultat.

C'est quoi le secret du moniteur Arduino que je n'ai pas. Pourquoi ça marche la et pas autrement. Il envoi comment le moniteur ?

Ah et aussi, si j'envois 0 mes deux servos ce place à 0. Hors j'ai demander de lire le 4ème caractère pour le deuxième servo. 4ème caratère qui n'existe pas si j'envois 0. mon code retourne t'il ce fameux "null" qui vos 0 ?

help =).

proautobot:
Bonjour,

Je commence par décortiquer ton code supercc. Il y plain de choses que je ne connais pas et qui m’empêche de comprendre. Aussi seriez vous m’orienter vers un cours sur le CRC abordable par un débutant. Jusqu’à présent j’ai trouvé des explications, des exemples mais je ne comprend pas.

Au niveau mathématique, pour ce que j’en sais, c’est à dire pas grand chose ;-), le CRC est le reste de la division des bits du message (qui pour les mathématiciens est un polynome binaire) par une suite de bits de petite longueur (8 pour un CRC8, 16 pour un CRC16, …) (qui pour les mathématiciens est le polynome binaire générateur). Le CRC (reste de la division) à les propriétés recherchées : court, détecte avec une grande probabilité les erreurs, et est “relativement” simple à calculer par un processeur (la division est faite par enchainements de de ou exclusifs). Le choix du polynome générateur n’est pas trivial et est déterminé en fonction de de la longueur des messages, des longueurs des potentielles erreurs, … Bref c’est vraiment un travail de “mathématiciens”. Pourtant les informaticiens les utilisent partout (en réseaux, sur les DVD, …). Si la page wikipedia en français est trop succincte regarde la page en anglais.

Quand j’ai constaté que j’avais des d’erreurs dans certaines trames (dues principalement à des vitesses de transmission trop élevées via la bibliothèque softwareSerial) j’ai décidé d’intégrer un CRC dans chacun de mes messages. Après quelques recherches sur le net je suis tombé sur le CRC8 proposé dans le lien indiqué et l’ai recopié tel quel sans chercher à comprendre dans le détail son fonctionnement exact (comment il faisait la division polynomial par enchainements de XOR et de AND). J’ai pris la fonction et l’ai considérée comme une boite “noire” : je lui donne une chaîne de caractère et elle me retourne son CRC.

Maintenant je vais tenter de répondre à tes questions qui sont plus liées à de la programmation.

byte j, crc = 0;

c’est un peu comme:

byte j = 0;
byte crc = 0 ;

Non, c’est plutôt comme :

byte j;
byte crc=0;

Si non le j n’est jamais remis à 0 lors de l’appel de la fonction GetCRC ?

Si, à chaque début de boucle “for (j = 0; j < 8; j++)”

D’ailleur La fonction c’est une static byte. Cela fait quoi ? Jusqu’à présent j’utilisais que des “void” quelque chose.

Là c’est parce que je voulais en faire une méthode de classe, au même titre que la méthode main qui elle aussi est static (static = méthode de classe). Je détaille plus si tu veux mais c’est plus un concept de programmation objet.

//////////////////////////////

CRC7_POLY = 0x91;

Ca veux dire quoi CRC7_POLY
Je ne connais 0x91 c’est un format, un nombre ?

CRC7_POLY est le nom de la variable mémorisant le polynome binaire générateur (d’où son nom)
0x91 est la valeur binaire des coefficients non nul du polynome générateur exprimé en base 16 (0x… est le préfixe des nombres exprimés en hexadécimal, valable aussi en C/C++/…).

/////////////////////////////

crc ^= (byte) message*;*
[
J’applique un "xor au byte message et j’attribue cette valeur au byte crc. Pour “i” = 1 je lit le byte 1 du message. J’ai soit 1 soit 0. Mais comment fonctionne ce xor,
d’habitude il y a un tableau de ce type:
S A B
0 0 0
1 1 0
1 0 1
0 1 1
La j’ai rien à comparer…
[/quote]
En fait, l’écriture “a op= b”, en C#/C/C++/… est équivalente à “a = a op b”. Donc en fait ici on fait : “crc = crc ^ (byte) message_”. Le XOR est fait entre tous les bits de crc et tous les bits de l’octet message .
Exemple
*_</em></em> <em><em><em>*crc =     1001 1010 Xor byte[i] = 1100 0011 ==============          01011001*</em></em></em> <em><em>_*
> ///////////////////////////////
> if ((crc & 1) == 1)
>
> si ((crc et 1) sont égale à 1 )
>
> Pourquoi “& 1)” ?
& est l’opération “et binaire” (idem xor ci dessus). En fait permet ici de savoir si le bit de poids faible du crc vaut 1.
> ///////////////////////////////
>
> crc >>= 1;
>
> Je me décale de 1 vers la droite, oui mais il resemble a quoi mon CRC ?
> à ca 00000000 ?
> Donc je me décale la 0[0]00000 ?
Même explication que plus haut : crc >>= 1 est équivalent à crc = crc >> 1 et donc si crc vaut initialement 10101111 il deviendra 01010111 après.
> ///////////////////////////////
>
> A la suite tu créer une génération aléatoire de position. A moi de modifier cette partie pour piloter mes servos.
Oui, en fait c’était pour simuler différentes valeurs de moteurs.
> ///////////////////////////////
>
> Passage du CRC en Hexa
> Je croyais que ToString c’était une pour une chaîne de caractère.
>
> string crcHex = crc.ToString(“X”);
> if(crcHex.Length==1) crcHex=“0”+crcHex;
>
> la chaîne de caractère crcHex = le byte crc transformé en String au format (“X”)
> Je ne comprend pas pa le (“X”).
Le format X permet d’obtenir la valeur hexadécimal. J’ai décidé d’envoyé mon crc dans le message sous la forme d’une valeur hexa sur 2 digits (puisque c’est un CRC8 → 8bits → 2 digits hex).
> Si la taille de crcHex est de 1 caractère, le crcHex est transformé à “0”+crcHex
> Mais ca donne “0” + “1 caractère” ?
Si crc est <16 alors sa représentation hexa ne contient qu’un digit or j’en veux 2 pour simplifier le travail de l’arduino. Je pense que crc.ToString(“X2”); permet d’avoir directement le résultat sur 2 digits.
> ///////////////////////////
>
> msg=crcHex+" "+msg;
>
> Pourquoi tu met un espace, c’est pris en compte dans le calcule de CRC ?
En fait mon message avant cette ligne est une chaine de caractères contenant 2 nombres (les valeurs de M1 et de M2) genre “23 112”
Je veux préfixer mon message par le crcHex, 2 digits hexa pour obtenir le message final à envoyer. Si crcHex vaut “A8” :
“A8 23 112”
Ce message sera décodé sur l’arduino (cf. le programme arduino) :
if(sscanf(line, “%x %d %d”, &crc, &val1, &val2) !=3) { … }
> ///////////////////////////
>
> WriteLine(msg);
>
> C’est quoi la différence avec Write(msg) ?
Elle rajoute un retour à la ligne. Mon protocole est basé sur l’envoi de lignes terminées par un retour chariot (’\n’).
> ////////////////////////////
>
>
> (on retire ‘\n’ final). avec .TrimEnd(); ?
Oui, cf la doc de C#. Je te rappelle que c’est toi qui fait du C#, pas moi :wink:
> ////////////////////////////
>
> if(response != “0”) {
> Console.WriteLine (“Erreur !”);
> Environment.Exit(1);
> }
>
> Pourquoi on arrête le programme si il y a une erreur, c’est pas mieux de renvoyer le message au moins 3 fois ?
> C’est pour cette raison que ton code n’est pas dans “le standard” ?
Si c’est mieux de réessayer, j’ai juste voulu te donner un point de départ :wink:
> ///////////////////////////
>
> // pour une attente entre les messages
> //Thread.Sleep(500);
>
> Thread.Sleep(500); c’est un peu comme un delay ? j’ai pas cette bibliothèque sur unity.
> Je suis obligé d’utiliser des co-routines ou des Time.time …
Oui, c’est comme un delay. Fait avec ce que tu as ou ne fait rien (les messages défileront à toute allure).
> Voilà, ça en fait un paquet de questions désolé. Mais ca m’aideras à aborder le CRC16 de hbachetti.
>
> Je suis dans le dure je croix =) à bientôt.
Bon courage pour la suite :wink:_

Merci ,

J’ai essayé l’idée de la boite noir ^^.

j’ai donc ce nouveau code qui fait planter le logiciel Unity 3D u__u

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO.Ports;
using System.Threading;



public class control : MonoBehaviour {

	//communication
	SerialPort serial;
	public string myString;
	float temps = 0.0f;
	float delay = 0.0f;
	public float comRapidity = 2.0f;
	public string portName;
	bool setPort = true;

	//Servo 1 
	public int servoDegre1; //Degré value
	public Transform servo1;
	public Slider sliderServo1;
	public Button buttonS1plus;
	public Button buttonS1moins;
	public Text textS1;
	string A;

	// Servo 2
	public int servoDegre2; //Degré value
	public Transform servo2;
	public Slider sliderServo2;
	public Button buttonS2plus;
	public Button buttonS2moins;
	public Text textS2;
	string B;

	// Autre servo test


	// Use this for initialization
	void Start () {
		//communication
		serial = new SerialPort();

		//Button S1
		buttonS1plus.onClick.AddListener(TaskOnClickS1plus);
		buttonS1moins.onClick.AddListener(TaskOnClickS1moins);

		//Button S2
		buttonS2plus.onClick.AddListener(TaskOnClickS2plus);
		buttonS2moins.onClick.AddListener(TaskOnClickS2moins);
	}
	
	// Update is called once per frame
	void Update () {

		//Servo 1 
		if (servoDegre1 != (sliderServo1.value)) 
		{
			if (servoDegre1 < (sliderServo1.value) ) 
			{
				servoDegre1 = servoDegre1 + 1 ;		
			} 
			if (servoDegre1 > (sliderServo1.value) )
			{
				servoDegre1 = servoDegre1 - 1;
			}
		}
		servo1.localRotation = Quaternion.AngleAxis (-servoDegre1, Vector3.up);
		textS1.text = servoDegre1.ToString() + "°";

		//Servo 2 
		if (servoDegre2 != (sliderServo2.value)) 
		{
			if (servoDegre2 < (sliderServo2.value)) 
			{
					servoDegre2 = servoDegre2 + 1;
			} 

			if (servoDegre2 > (sliderServo2.value))
			{
				servoDegre2 = servoDegre2 - 1;
			} 
		}
		servo2.localRotation = Quaternion.AngleAxis (-servoDegre2, Vector3.up);
		textS2.text = servoDegre2.ToString() + "°";
	
		//communication

		A = servoDegre1.ToString("000");
		B = servoDegre2.ToString("000");

		myString=string.Concat(A,B);

		temps = Time.time;
		if ((delay + comRapidity) < temps)
		{
			Main (A, B);
			delay = temps;
			//Debug.Log (myString);
		}
			
	}

	// button S1
	void TaskOnClickS1plus ()
	{
		(sliderServo1.value) = (sliderServo1.value) + 1;
	}

	void TaskOnClickS1moins ()
	{
		(sliderServo1.value) = (sliderServo1.value) - 1;
	}
	// button S2
	void TaskOnClickS2plus ()
	{
		(sliderServo2.value) = (sliderServo2.value) + 1;
	}

	void TaskOnClickS2moins ()
	{
		(sliderServo2.value) = (sliderServo2.value) - 1;
	}

	//communication

	/*
   Émission en boucle (1000) de lignes contenant 2 entiers.
   Un CRC8 est ajouté en début de ligne pour validation par le récepteur.
   Le récepteur renvoie :
 * "0" : ok
 * "1" : mauvais format de ligne.
 * "2" : mauvais CRC
modifier => Arrêt du programme si erreur détectée.
*/



	 // from pololu.com (https://www.pololu.com/docs/0J44/6.7.6)

	 static byte GetCRC(string message) 
		{
		 byte j, crc = 0;
		 const byte CRC7_POLY = 0x91;
		 int i;

		 int length = message.Length;

		for (i = 0; i < length; i++)
			 {
			 crc ^= (byte) message[i];

			 for (j = 0; j < 8; j++)
				 {
				 if ((crc & 1) == 1)
					 crc ^= CRC7_POLY;
				 crc >>= 1;
				 }
			 }
		 return crc;
		 }

	static public void Main (string A, string B)
	 {

		 SerialPort serial;
		 serial = new SerialPort();
		 serial.PortName = "COM3" ;
		 serial.Parity = Parity.None;
		 serial.BaudRate = 115200;
		 serial.DataBits = 8;
		 serial.StopBits = StopBits.One;
		 serial.Open ();

		 // Pour laisser le temps à la carte arduino de rebooter

		Debug.Log ("Wait arduino reboot (2s) ...");
		 Thread.Sleep(2000); 

		 // On émet (à fond) 1000 couples (m1 m2)

		 for(int i=0; i<1000; ++i) { 

			 string msg=A+" "+B;

			 // Calcul du CRC 8

			 byte crc=GetCRC(msg);

				 // falsifie le CRC pour test détection mauvais crc
				 // crc++;

				 // falsifie le message pour détection mauvais format
				 //msg="123 bad";


			 // CRC en hexa sur 2 octets (simplification du code coté arduino)
			 string crcHex = crc.ToString("X");
			 if(crcHex.Length==1) crcHex="0"+crcHex;

			 // Ajout du CRC en début de message
			 msg=crcHex+" "+msg;

			Debug.Log ("Envoi : \""+msg+"\"");

			 // envoi 
			 serial.WriteLine(msg);

			 // attente de la réponse (on retire '\n' final).
			 string response=serial.ReadLine().TrimEnd();

			 // Affichage de la réponse 
			Debug.Log ("Recu ("+i+") : \""+response+"\"");

			 if(response != "0")
			{
				Debug.Log ("Erreur !");
			} 
			 // pour une attente entre les messages
			 //Thread.Sleep(500);
			 }
		 serial.Close ();
		 }

	void OnApplicationQuit()
	{
		Debug.Log("Application ending after " + Time.time + "seconds");
		serial.Close ();
	}
		
}

J’ai remplacé les console.writeline qui ne sont pas reconnu. J’utilise Debug.Log à la place, cela permet d’afficher le message dans la console d’unity 3D.

Bonjour,

Je reviens avec ma question :

Pourquoi le format 000000 fonctionne avec le moniteur et avec mon code unity.

Pourquoi le format 0000000 et plus fonctionne avec le moniteur et non avec unity.

J'utilise le code suivant pour déchiffrer 0000000 :

 maChaine1 = incomming.substring(1, 4);//lire uniquement les caractères dans l'interval ]1, 4] 
 maChaine2 = incomming.substring(4, 7);//lire uniquement les caractères dans l'interval ]4, 7]

C'est trop rapide contrairement aux nombre d'envois lent du moniteur ? ou c'est autre chose ?

Désolé je n'arrive pas à intégrer vos exemples. Je pense aussi qu'il y a des particularités entre le C# UNITY 3D et le C# . Aussi je me retourne uniquement vers ce que j'ai sus faire.

Pour retourner vers le format 0000000 ; il fonctionne mais le déplacement des servos n'est pas fluide. Il y a des ratés de + ou moins 10°.

Si j'utilise le format 00000000 ; les servos réagissent mais c'est n'importe quoi. Inutile de pousser la barre plus haute ^^ avec ce protocole.

L'objectif reste de pouvoir transmettre le format 000000000000000000 pour six servo voir plus.

Je n'ai pas compris pourquoi le Serial.SetTime n'est plus utile si j'utilise une lettre au début et une lettre à la fin d'un message.

Je vais essayer d'utiliser ce type de message.

A000/n
B000/n
C000/n
... sans vraiment comprendre pourquoi je met /n XD.

Comme je le fessai avec le moniteur au début, seulement je n'avais pas réussi a l'envoyer avec Unity 3D.

Cordialement. :slight_smile:

Salut, micro UP,

Je n'