====== Communication série : Arduino et Pure Data ====== Pour communiquer entre une carte Arduino et le logiciel Pure Data, il existe plusieurs solutions, listées [[http://playground.arduino.cc/Interfacing/PD|ici]]. La plus répandue est d'utiliser l'objet **[arduino]** dans Pure Data avec le firmware **Firmata** dans Arduino. Voir aussi la page [[http://fr.flossmanuals.net/puredata/ch048_arduino-et-pd|Arduino et pd]] sur flossmanuals. Cependant il peut arriver que nous ayons besoin de plus de fonctionnalités comme l'utilisation des capteurs de distance avec la bibliothèque UltraSonic, des capteurs capacitifs, d'utiliser la bibliothèque Tone pour changer la fréquence PWM, etc. Donc, il faut soit reprendre et donc comprendre les exemples de Firmata, soit comprendre la base de la communication série. Vous l'aurez compris, on va plutôt choisir la deuxième solution. **Téléchargement** de tous les codes de cette page : {{.:arduino-pd-serial.zip|}} \\ ** Un autre tutoriel (en anglais) détaillant la plupart des cas :** {{:logiciels:serial:arduino_for_pders.tar.gz|}} ===== Prérequis ===== Si vous n'êtes pas à l'aise avec les notions de **liaison série**, je vous invite à lire le tutoriel d'[[http://fr.openclassrooms.com/sciences/cours/arduino-pour-bien-commencer-en-electronique-et-en-programmation/generalites-4#r-690110|openclassrooms]]. Une autre notion technique très utilisée ici est celle de **String**, littéralement "chaîne" en français. Il s'agit d'un type de données comme les nombres entiers (int), nombres à virgule flottante (float) ou caractère (char) que l'on retrouve en programmation. On l'appelle chaîne de caractères, c'est une suite de caractères, l'équivalent d'un tableau de caractères. Par exemple, le mot "BONJOUR" est une String composée de sept caractères 'B', 'O', 'N', 'J', 'O', 'U', 'R' auquel il est parfois ajouté au niveau informatique un caractère de fin, en langage C par exemple. Avec les Arduino Leonardo, il y a une petite différence, c'est pourquoi les exemples doivent être un peu modifiés. Il faudra peut-être ajouter ajouter un while (!Serial) ; dans le setup. Voir cette page : [[https://www.arduino.cc/en/Guide/ArduinoLeonardoMicro?from=Guide.ArduinoLeonardo#toc11|Différences avec Arduino UNO]] ===== Une valeur ===== L'exemple le plus simple est d'envoyer une valeur avec Pure Data et de la recevoir après être passée par la carte Arduino. Une boucle en somme. Quand il s'agit d'un nombre pas de problème, mais on peut aussi vouloir envoyer des caractères. Pour ce faire le caractère est converti en nombre entre 0 et 255 soit 8 bits. La conversion suit le standard [[http://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange|ASCII]] que nous utiliserons souvent. Une [[http://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/ASCII-Table.svg/1000px-ASCII-Table.svg.png|table]] permet de visualiser ces correspondances. {{.:serial-1.png|}} // Serial messages (1) // Recevoir un nombre et l'envoyer // conteneur (int) pour recevoir la donnée série int incomingByte = 0; void setup() { // ouverture du port série avec un taux de 9600 bauds Serial.begin(9600); } void loop() { // on reçoit au moins 1 octet dans le buffer (.available()) if (Serial.available() > 0) { // lecture d'un octet et effacement dans le buffer incomingByte = Serial.read(); // ecriture d'un octet Serial.write(incomingByte); } } ===== Une commande ===== En utilisant les opérateurs de comparaisons, on peut déclencher une fonction très simplement. Ici, on allume et éteind la LED 13 de la Arduino avec l'envoi d'un chiffre ou d'une lettre. {{.:serial-2.png|}} // Serial messages (2) // Allumer une LED avec un nombre // 72 pour allumer, 76 pour éteindre const int ledPin = 13; // pin de la LED int incomingByte; // variable void setup() { Serial.begin(9600); // port série } void loop() { // on reçoit quelque chose if (Serial.available() > 0) { incomingByte = Serial.read(); // allumer la LED (H=72 en ASCII) if (incomingByte == 'H') digitalWrite(ledPin, HIGH); // eteindre la LED (L=76 en ASCII) if (incomingByte == 'L') digitalWrite(ledPin, LOW); } } ===== Serial.available() ===== La fonction **Serial.available()** est toujours utilisée pour connaître combien d'octets restent dans le buffer. Celui-ci est limité à 63 octets et si il n'y pas de Serial.read() ou de Serial.parseInt() pour enlever petit à petit les octets, alors il atteindra son maximum. Dans le code Pure Data, il y a un petit algorithme très pratique pour afficher dans Pure Data les données venant de la Arduino. Il consiste à stocker dans un objet **[list]** toutes les données les unes à la suite des autres ([list prepend]), puis de l'envoyer sous forme de liste quand arrive le chiffre 10 équivalent au retour à la ligne dans Arduino, le **ln** dans **Serial.println()**. L'objet **[bytes2any]** convertit cette liste en caractères courant. {{.:serial-3.png|}} // Serial messages (3) : test available // imprime le nombre de caractères reçus int inBytes = 0; int lastInBytes = 0; void setup() { Serial.begin(9600); // port série } void loop() { // nombre d'octets reçus dans le buffer inBytes = Serial.available(); // pour n'imprimer le nombre d'octets reçus // que lorsque ce nombre a changé if (inBytes != lastInBytes && inBytes > 0) { Serial.println(inBytes); } lastInBytes = inBytes; delay(10); } Méthodes pour recevoir les caractères ASCII {{:logiciels:serial:arduino-to-pd-serial.png|}} ===== serialEvent() ===== C'est l'occasion d'introduire la fonction **serialEvent()** du langage Arduino qui permet de récupérer les données séries, très pratique pour ne pas avoir du code éparpillé. La méthode pour recevoir un message consiste à concaténer (ajouter les unes à la suite des autres) les données pour former une chaine de caractères (String). On définit un caractère de fin de message pour pouvoir ensuite l'utiliser. Il est souvent convenu que celui-ci soit le caractère de retour de ligne "\n", équivalent à 10 en ASCII. L'étape suivante est d'extraire du message la fonction et l'argument à l'aide des méthodes **indexOf(' ')** et **substring()**. {{.:serial-4.png|}} /* * Serial messages (4) * Réception des données avec Serial Event et commande avec un argument */ String inputString = ""; // chaine de caractères pour contenir les données boolean stringComplete = false; // pour savoir si la chaine est complète void setup() { Serial.begin(9600); // port série pinMode(13,OUTPUT); pinMode(9,OUTPUT); } void loop() { // 2 - Utilisation du message if (stringComplete) { //Serial.println(inputString); // on récupère la position du séparateur (l'espace " ") int index = inputString.indexOf(' '); // on coupe la chaine en deux : la fonction d'un côté et l'argument de l'autre String fct = inputString.substring(0,index); String arg = inputString.substring(index,inputString.length()); // appel de ma fonction en transformant la chaine en nombre if (fct == "LED13") { light(13, arg.toInt()); } else if (fct == "LED9") { light(9, arg.toInt()); } // on vide la chaine pour utiliser les messages suivants inputString = ""; stringComplete = false; } } /* 1 - Réception des données SerialEvent est déclenchée quand de nouvelles données sont reçues. Cette routine tourne entre chaque loop(), donc utiliser un delay la fait aussi attendre. */ void serialEvent() { while (Serial.available()) { // récupérer le prochain octet (byte ou char) et l'enlever char inChar = (char)Serial.read(); // concaténation des octets reçus inputString += inChar; // caractère de fin pour notre chaine if (inChar == '\n') { stringComplete = true; } } } // fonction personnalisable void light(int pin, int brightness) { Serial.print("Light function : "); Serial.print(pin); Serial.print(", "); Serial.println(brightness); analogWrite(pin,brightness); } ===== Avec deux arguments ===== On découpe encore une fois les données avec les espaces. {{.:serial-5.png:|}} Pour le code Arduino, on découpe une nouvelle fois pour récupérer le second argument. On ajoute aussi nos fonctions. void loop() { if (stringComplete) { ... // deuxième découpage pour le second argument index = arg.lastIndexOf(' '); String arg2 = arg.substring(index,arg.length()); arg = arg.substring(0,index); // appel des fonctions en transformant la chaine en nombre if (fct == "LED") { light(arg.toInt(), arg2.toInt()); } else if (fct == "MODE") { mode(arg.toInt(), arg2.toInt()); } ... // fonctions personnalisables void light(int pin, int brightness) { ... } void mode(int pin, int state) { ... } ===== Découper le message (parser) ===== Jusqu'ici, le nombre d'arguments est fixe et la méthode n'est pas modulaire. On peut concevoir une fonction qui parcourt le message et le découpe à chaque espace, qui sera le séparateur. Ainsi, le message "LED 9 120", pourra être décomposé en trois "bouts" : "LED", "9", "120". Le premier sera le sélecteur de la commande et les deux autres, les arguments dans Arduino. Le patch Pure Data est le même que précédemment : {{.:serial-6.png|}} Le code Arduino est aussi presque le même, nous ajoutons la fonction **splitString()** : ... int cnt = 0; // nombre de données découpées String data[10]; // stockage des données découpées ... void loop() { // si le message est complet if (stringComplete) { // on le découpe à chaque espace ' ' // et on stocke les bouts dans un tableau splitString(inputString, ' '); // appel des fonctions selon le premier sélecteur if (data[0] == "LED") { light(data[1].toInt(), data[2].toInt()); } else if (data[0] == "MODE") { mode(data[1].toInt(), data[2].toInt()); } // vide la chaine inputString = ""; stringComplete = false; } } ... // méthode pour découper le message avec un séparateur (ou "parser") void splitString(String message, char separator) { int index = 0; cnt = 0; do { index = message.indexOf(separator); // s'il y a bien un caractère séparateur if(index != -1) { // on découpe la chaine et on stocke le bout dans le tableau data[cnt] = message.substring(0,index); cnt++; // on enlève du message le bout stocké message = message.substring(index+1, message.length()); } else { // après le dernier espace // on s'assure que la chaine n'est pas vide if(message.length() > 0) { data[cnt] = message.substring(0,index); // dernier bout cnt++; } } } while(index >=0); // tant qu'il y a bien un séparateur dans la chaine } ... ==== Autres exemples ==== * http://www.dyadica.co.uk/journal/simple-serial-string-parsing/ * [[http://kiilo.org/tiki/tiki-index.php?page=Arduino-PureData-MessageSystem |Arduino-PureData-MessageSystem]] * http://mchobby.be/wiki/index.php?title=SerialCommand * http://forum.arduino.cc/index.php/topic,41215.0.html * http://robotic-controls.com/book/export/html/10 // Exemple avec strtok // http://forum.arduino.cc/index.php/topic,41215.0.html #include char sz[] = "Here; is some; sample;100;data;1.414;1020"; void setup() { char *p = sz; char *str; Serial.begin(9600); while ((str = strtok_r(p, ";", &p)) != NULL) // delimiter is the semicolon Serial.println(str); } void loop(){} ===== SerialCommand ===== Télécharger la bibliothèque Arduino[[https://github.com/kroimon/Arduino-SerialCommand|SerialCommand]] et la placer dans le dossier "~/sketchbook/libraries". {{.:serial-7.png|}} // Demo Code for SerialCommand Library - Steven Cogswell - May 2011 #include #define arduinoLED 13 // Arduino LED on board SerialCommand sCmd; // The demo SerialCommand object void setup() { pinMode(arduinoLED, OUTPUT); // Configure the onboard LED for output digitalWrite(arduinoLED, LOW); // default to LED off Serial.begin(9600); // Setup callbacks for SerialCommand commands sCmd.addCommand("ON", LED_on); // Turns LED on sCmd.addCommand("OFF", LED_off);// Turns LED off sCmd.addCommand("HELLO", sayHello);// Echos the string argument back sCmd.addCommand("P", processCommand);// Echos two arguments converted to integers sCmd.setDefaultHandler(unrecognized);// Handler for command that isn't matched Serial.println("Ready"); } void loop() { sCmd.readSerial(); // We don't do much, just process serial commands } void LED_on() { Serial.println("LED on"); digitalWrite(arduinoLED, HIGH); } void LED_off() { Serial.println("LED off"); digitalWrite(arduinoLED, LOW); } void sayHello() { char *arg; arg = sCmd.next(); // Get the next argument from the SerialCommand object buffer if (arg != NULL) { // As long as it existed, take it Serial.print("Hello "); Serial.println(arg); } else { Serial.println("Hello, whoever you are"); } } void processCommand() { int aNumber; char *arg; Serial.println("We're in processCommand"); arg = sCmd.next(); if (arg != NULL) { aNumber = atoi(arg); // Converts a char string to an integer Serial.print("First argument was: "); Serial.println(aNumber); } else { Serial.println("No arguments"); } arg = sCmd.next(); if (arg != NULL) { aNumber = atol(arg); Serial.print("Second argument was: "); Serial.println(aNumber); } else { Serial.println("No second argument"); } } // This gets set as the default handler, and gets called when no other command matches. void unrecognized(const char *command) { Serial.println("What?"); } ===== CmdMessenger ===== Plus compliqué mais à noter : http://playground.arduino.cc/Code/CmdMessenger ===== Réception de valeurs ===== Même technique, l'espace est un séparateur et le retour à la ligne le caractère de fin de message {{.:serial-8.png|}} /* * Serial messages (8) * Envoie de données de trois capteurs */ void setup() { Serial.begin(9600); } void loop() { Serial.print(analogRead(0)); Serial.print(" "); Serial.print(analogRead(1)); Serial.print(" "); Serial.println(analogRead(2)); delay(20); }