lire des valeurs sur Arduino en c / c++

Je cherchais un moyen de lire des valeurs sur l'Arduino avec mon Mac depuis quelques temps...

En furetant sur le net j'ai trouvé ça :

How to talk to the USB Arduino board with your C++ Code and Xcode (Comment communiquer via USB avec Aduino en C++ et avec Xcode)

Le problème c'est que ce code montre bien comment envoyer des données à l'Arduino, mais pas comment les lire.

Ensuite j'ai trouvé ça :

Création d'une centrale interactive de capteurs

Projet dans lequel j'ai récupéré un bout de code pour modifier le premier.

Ce qui donne :

#include <iostream>
#include <stdio.h> 
#include <fcntl.h>  
#include <termios.h> 
#include <unistd.h> //write(), read()

using namespace std;



int fd;


/******************************************************************************************************************
      Setting the serial port up to talk to the Arduino board
******************************************************************************************************************/
void init_port(int *fd, unsigned int baud) 
{ 
    struct termios options; 
    tcgetattr(*fd,&options); 
    switch(baud) 
    { 
       case 9600: cfsetispeed(&options,B9600); 
cfsetospeed(&options,B9600); 
break; 
       case 19200: cfsetispeed(&options,B19200); 
cfsetospeed(&options,B19200); 
break; 
       case 38400: cfsetispeed(&options,B38400); 
cfsetospeed(&options,B38400); 
break; 
  default:cfsetispeed(&options,B9600); 
cfsetospeed(&options,B9600); 
break; 
    } 
    options.c_cflag |= (CLOCAL | CREAD); 
    options.c_cflag &= ~PARENB; 
    options.c_cflag &= ~CSTOPB; 
    options.c_cflag &= ~CSIZE; 
    options.c_cflag |= CS8; 
    tcsetattr(*fd,TCSANOW,&options); 
} 

/******************************************************************************************************************
      Main Function
******************************************************************************************************************/
int main() 
{ 
      char USBPort[1000];
      
      cout << "what USB port is your Arduino Board pluged into?" << endl;
      cout << "Example:\n/dev/tty.usbserial-A70041zl\nor\n/dev/tty.usbserial-A1001N2Y" << endl;
      cin >> USBPort;
      
      fd = open(USBPort, O_RDWR | O_NOCTTY | O_NDELAY);
    if(fd == -1) 
      perror("open_port: unable to open port"); 
      
      init_port(&fd,9600);         //set serial port to 9600,8,n,1
      
       
     
      fcntl(fd, F_SETFL, 0);

      char readport();


      int Temp,Temp1,Temp2 = 0;
      
      char table[5]={0};
      
      read(fd,&Val,5);

      cout<<Val<< endl;
      cout<<Val[2]<<Val[3]<<Val[4]<< endl;
      cout<<Val[0]<<Val[1]<<endl;
      
      Temp1 = Val[2]-'0';       // convert ASCII
      Temp2 = Val[3]-'0';       // to
      Temp3 = Val[4]-'0';       //decimal
      
      Temp = Temp1*100 + Temp2*10 + Temp3 ;      


      cout<<endl<<Temp<<endl;

      close (fd)     //close USBPort
      
      return 0;
      
      
 
}

Ainsi cela me permet de lire des données en ASCII envoyée par l'Arduino par ce code :

int Test1 = 0;

void setup()
{

  Serial.begin(9600);
}

void loop () {

Temp1 =123; 
Serial.print("ok");
Serial.println(Test1);
 
}

Et voilà ce qui sort à la console :

[Session started at 2008-03-21 19:49:38 +0100.]
what USB port is your Arduino Board pluged into?
Example:
/dev/tty.usbserial-A70041zl
or
/dev/tty.usbserial-A1001N2Y
/dev/tty.usbserial-A1001N2Y
ok123
123
ok

Executable “digit” has exited with status 123.

C'est peut-être rudimentaire, mais comme je ne connais pas le langage C (je commence à apprendre :)), c'est un grand pas pour moi.

J'ai un peu modifié mes codes sur l'Arduino :

int CaptPin05 = 5;

void setup()
{
pinMode(CaptPin05,INPUT);
  Serial.begin(9600);
}


void loop () 
{
Serial.print("ok");
Serial.println(analogRead(CaptPin05));
 
}

J'utilise un capteur de luminosité sur un pin analogique, donc je devrais avoir une valeur comprise entre 0 et 1023
à lire.

J'ai deux problèmes, le premier est que la lecture n'est pas régulière et de temps en temps un chiffre manque ou alors c'est une lettre du "ok" qui sort et je ne comprend pas pourquoi.

Le deuxième problème est qu'avec la méthode que j'ai utilisé pour convertir mes "lectures" d'Ascii à Décimal, lorsque je dois afficher des valeurs en dessous du millier, la valeur s'affiche, mais elle est suivie d'un ou plusieurs chiffres incohérent et je n'arrive pas à résoudre cela.

Salut,

a mon avis, ton probleme vient du fait que ton soft sur PC lit 5 caraecters puis s'arrete (je ne me trompe pas ? )
sauf que l'arduino, elle, crache en continue des choses sur son port série. si je regarde le code arduino, ca devrait ecrire :
'ok' suivi d'une valeur entre 0 et 1023, suivi d'un retour chariot.

une valeur entre 0 et 1023, ca correspond a 1, 2 3 ou 4 caracteres...

donc il faut que tu lises ton port serie juqu'a recevoir un retour chariot.

Ensuite, il y a des buffers sur la liaison serie, donc si tu lis systematiquement 5 caracteres, ben tu va avoir
un premier message plus le debut du suivant, puis a la lecture suivante, tu auras la fin du second message et le debut du 3eme, etc...
tous tes soucis viennent du fait que tu lis 5 caracteres, et non pas caracteres par caracteres en regardant si c'est le carac lu est le dernier d'un message...

OK Pascal, merci pour la réponse.

Donc si j'ai bien compris je suis obligé de lire le flux caractère par caractère jusqu'à trouver un \n ou \r ?

Est ce qu'en utilisant un fgets() à la place de read() cela marcherait, normalement le fgets devrait lire mon string en entier et s'arrêter à \n ?

oui en theorie fgets doit suffire je pense...

J'ai fait des essais avec fgets, mais ça ne renvoit aucune valeurs du flux.
La communication avec l'arduino n'est même pas amorcée, les leds Rx/Tx restent éteintes.

Voici la façon dont j'essaye de l'utiliser :

      fd = open(USBPort, O_RDWR | O_NOCTTY | O_NDELAY);
    if(fd == -1) 
      perror("open_port: unable to open port"); 

      init_port(&fd,9600);         //set serial port to 9600,8,n,1
      
     
      fcntl(fd, F_SETFL, 8);
      


      char readport();

      
      
      
      char table[100]={0};
            
      
      
      char fgets(table,99,fd);

pourquoi tu passes 8 comme argument a ton fcntl ?

autre chose : tu n'as pas besoin de mettre char readport();,
readport(); suffit, pareil pour le fgets...
fgets(able,99,fd); suffit.

Je devrais mettre quoi pour F_SETFL ?

Si je ne mets pas les char ça plante , si j'ai de la chance ça me sort de l'application, autrement je suis obligé d'éteindre mon ordi de façon barbare... et j'ai la même chose lorsque la communication avec l'arduino est coupée au milieu d'un flux, je suis planté... planté.

J'ai repris mes codes et j'ai essayé de rendre plus propre celui en C, j'ai rajouté une partie pour commencer la lecture et pour l'arrêter à la fin du string.
J'ai rajouté une ouverture de fenêtre (merci le site du zéro) pour avoir les valeurs imprimées dessus en parallèle à la console.
Le problème de la position des milliers, centaines et dizaines est traité directement sur l'arduino en ajoutant des "0" suivant l'échelle dans laquelle on se trouve, la lecture est ainsi stabilisée.
J'ai rajouter une fonction qui me permet de détecter le(s) port(s) USB utilisé(s), cela s'affiche dans la console.
L'application est toujours démarrée depuis la console.

Le main sur mon mac (Xcode) :

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL_ttf/SDL_ttf.h>



#include <fcntl.h>
#include <termios.h>   
//#include <memory.h>
#include <unistd.h> //write(), read()

using namespace std;

void Output();          // pour envoyer à l'arduino
void readport();        // pour recevoir de l'arduino
void init_port();       //initialise le portUSB
void Ouverture_port();  //ouvre le portUSB


char USBPort[1000];                  // pour le nom du portUSB
char temps[20] = "";            //pour déterminer le temps
char Temperature [5]={0};      //pour la valeur écrite dans la fenêtre
char table[7]={0};                  //pour les valeurs en sortie d'arduino

int fd = 0;
int continuer = 1;
long Temp,Temp1,Temp2,Temp3 ,Temp4,Temp5  = 0;




int main(int argc, char *argv[])
{
      /******************************************
      détection du port usb
      *******************************************/
      FILE* myPipe=NULL;
      char buffer[1000];
      myPipe = popen("ls /dev/tty.usbserial*", "r");
      if(myPipe==NULL){
            //errorstuff
      }
      while(fgets(buffer, 1000, myPipe)!=NULL){
            (void) printf("\n\nvotre usb est : %s \n", buffer);
      }      
      
      
      pclose(myPipe);
      
      /*******************************************
      fin de détection
      ********************************************/
                  

      
      cout << "what USB port is your Arduino Board pluged into?" << endl;
      cout << "Example:\n/dev/tty.usbserial-A70041zl\nor\n/dev/tty.usbserial-A1001N2Y" << endl;      
      
      cin >> USBPort;

      

    SDL_Surface *ecran = NULL, *texte = NULL;
    SDL_Rect position;
    SDL_Event event;
    TTF_Font *police = NULL;
    SDL_Color couleurNoire = {0, 0, 0}, couleurBlanche = {255, 255, 255};


    SDL_Init(SDL_INIT_VIDEO);
    TTF_Init();

    ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);
    SDL_WM_SetCaption("Mesure de luminosité", NULL);


    police = TTF_OpenFont("angelina.ttf", 65);

    tempsActuel = SDL_GetTicks();



    while (continuer)
            {
        SDL_PollEvent(&event);
        switch(event.type)
        {
            case SDL_QUIT:
                continuer = 0;
                        close(fd);
                break;
        }

        SDL_FillRect(ecran, NULL, SDL_MapRGB(ecran->format, 255, 255, 255));

            
      Ouverture_port();
      Output();
      readport();
      close(fd);
            

                  sprintf(Temperature, "%ld%ld%ld%ld", Temp1, Temp2, Temp3, Temp4);      
      
      cout<<table[0]<<table[1]<<Temperature<<endl;
            
            sprintf(temps, "Temp : %s", Temperature); 
            SDL_FreeSurface(texte); 
            texte = TTF_RenderText_Shaded(police, temps, couleurNoire, couleurBlanche); 
           

        position.x = 180;
        position.y = 210;
        SDL_BlitSurface(texte, NULL, ecran, &position); /* Blit du texte contenant le temps */
        SDL_Flip(ecran);

            }
      
      
      
            TTF_CloseFont(police);
            TTF_Quit();

            SDL_FreeSurface(texte);
            SDL_Quit();


    return EXIT_SUCCESS;
      }

Les fonctions :

void init_port(int *fd, unsigned int baud) 
      { 
    struct termios options; 
    tcgetattr(*fd,&options); 
    switch(baud) 
    { 
       case 9600: cfsetispeed(&options,B9600); 
      cfsetospeed(&options,B9600); 
      break; 
       case 19200: cfsetispeed(&options,B19200); 
      cfsetospeed(&options,B19200); 
      break; 
       case 38400: cfsetispeed(&options,B38400); 
      cfsetospeed(&options,B38400); 
      break; 
      default:cfsetispeed(&options,B9600); 
      cfsetospeed(&options,B9600); 
      break; 
    } 
    options.c_cflag |= (CLOCAL | CREAD); 
    options.c_cflag &= ~PARENB; 
    options.c_cflag &= ~CSTOPB; 
    options.c_cflag &= ~CSIZE; 
    options.c_cflag |= CS8; 
    tcsetattr(*fd,TCSANOW,&options); 
      } 
      
      
void Ouverture_port()
            {
            fd = open(USBPort, O_RDWR | O_NOCTTY | O_NDELAY);
    if(fd == -1) 
      perror("open_port: unable to open port"); 

      init_port(&fd,9600);         //set serial port to 9600,8,n,1
      fcntl(fd, F_SETFL,3);
      }


void Output()
      {
      
      write(fd,"&",1);
      }
            
      
void readport()
      {
      char table[7]={0};
      {      
      while(table[6]!='

Ainsi j'obtiens un renouvellement de l'affichage des mesures environ toutes les secondes.

J'aimerais savoir si c'est possible de rendre cela plus rapide ?)
     {
     read(fd,&table,sizeof(table));
     
     Temp1 = table[2]-'0';
     Temp2 = table[3]-'0';
     Temp3 = table[4]-'0';
     Temp4 = table[5]-'0';

}
     }

}


Ainsi j'obtiens un renouvellement de l'affichage des mesures environ toutes les secondes.

J'aimerais savoir si c'est possible de rendre cela plus rapide ?

Le .pde sur l'Arduino

int CaptPin05 = 5;
//int Temp1 = 0;
char incoming = 0;
void setup()
{
pinMode(CaptPin05,INPUT);
  Serial.begin(9600);
}



void loop () {
//Temp1 = analogRead(CaptPin05);

//Temp1 =123; 

incoming=Serial.read();
if (incoming = '&')
{
Serial.print("ok");
//Serial.print("yes");
/* mise à niveau des chiffres */
if(analogRead(CaptPin05)<1000)
{
  Serial.print("0");
}
if(analogRead(CaptPin05)<100)
{
  Serial.print("0");
}
if(analogRead(CaptPin05)<10)
{
  Serial.print("0");
}
/* fin de mise à niveau */

Serial.print(analogRead(CaptPin05));
Serial.print("$");
incoming = 0 ;
}
}

Ainsi j'obtiens un renouvellement de l'affichage des mesures environ toutes les secondes.

J'aimerais savoir si c'est possible de rendre cela plus rapide ?

oui, ca peut etre plus rapide,

Tout d'abord, tu peux augmenter la vitesse de ton port serie. Moi, je tourne en 19200.

Mais ca peut surtout etre plus efficace :

Perso, j'ai ajouté une condition : je ne remonte la valeur au PC que si elle a changé.
Tu peux meme introduire une notion de seuil : ne remonter la valeur qui si elle a change de plus de XX%..
mais la, ca depend de ce que tu veux faire...

Merci pour ces réponses, je vais tester ça...

Après essais ça ne change rien à la vitesse d'affichage des mesures.

Je pense que c'est le fait d'ouvrir et fermer le port usb à chaque boucle qui ralenti le tout.

Si je déplace la fonction d'ouverture et celle de fermeture ça plante.

En mettant la lecture du port dans un thread et l'affichage dans le main, je peux ouvrir le port en début d'application et le fermer à la fin de l'application.
Sans l'affichage graphique (affiché dans la console) la fréquence de mesure est de 130 par seconde, cette fréquence tombe à 50 par seconde avec l'affichage dans une fenêtre graphique.

la, ca devient un probleme de programmation mac... :-X

tu dois surement avoir un mecanisme de synchro entre ta thread de lecture de l'arduino, et la partie graphique qui fait que ca ralentit... (mutexs, sections critique ou autre).
si tu veux plus de vitesse, va falloir que tu t'embringues dans des traitements asynchrones, et je te souhaite bien du plaisir....

tu as vraiment besoin de lire si vite ?
et si tu ne remontes la valeur que si elle change, ca ne resoud pas ton probleme ?

au fait, c'est quoi le but final de ton projet ? (si c'est pas indiscret)

bonne chance.

En fait j'ai plusieurs projets:

-Domotique ou similaire, les vitesses de lecture/affichage que j'ai actuellement sont bien au delà de ce que j'ai besoin pour ce projet.

-Un contrôle de déplacement et de vitesse sur un objet (genre CNC), là suivant la vitesse de l'objet la lecture n'est pas assez rapide pour avoir une précision suffisante pour le situer et inter-agir avec lui.

sur un site spécialisé Mac on me propose de daemoniser la partie lecture du port pour libérer le main.
J'essaye de coder ça mais plusieurs choses m'échappent et je n'y arrive pas.

Domotique ou similaire, les vitesses de lecture/affichage que j'ai actuellement sont bien au delà de ce que j'ai besoin pour ce projet.

ok

-Un contrôle de déplacement et de vitesse sur un objet (genre CNC), là suivant la vitesse de l'objet la lecture n'est pas assez rapide pour avoir une précision suffisante pour le situer et inter-agir avec lui.

dans ce cas la, il va surement falloir que tu deportes l'intelligence dans la carte arduino... Depuis le PC, tu vas envoyer une consigne a la carte arduino, et l'arduino s'occupera de gerer la commande du moteur et le retour du capteur afin de correspondre à la consigne recue, un peu comme avec un servo-moteur.

Enfin, a mon humble avis...

sur un site spécialisé Mac on me propose de daemoniser la partie lecture du port pour libérer le main. J'essaye de coder ça mais plusieurs choses m'échappent et je n'y arrive pas.

l'idée est interressante, mais selon moi, ca n'ira pas dans ton cas : daemoniser, fera que ton process d'acquisition tournera vite .. ok super... sauf que le process "main" qui va gerer le reste n'arrivera pas a suivre... resultat : il va falloir bufferiser les valeurs remontée entre le daemon et le main... et donc tu vas les traiter en retard... je ne pense pas que ca resolve ton pb, ca va juste le deporter...

mais je peux me tromper...

dans ce cas la, il va surement falloir que tu deportes l'intelligence dans la carte arduino... Depuis le PC, tu vas envoyer une consigne a la carte arduino, et l'arduino s'occupera de gerer la commande du moteur et le retour du capteur afin de correspondre à la consigne recue, un peu comme avec un servo-moteur.

C'est un peu ce que je pensais devoir faire, de toute façon je me vois mal réagir au 1/1000 de seconde , ce qui implique que déroulement du "mouvement" doit être programmer à l'avance, donc que ce soit coté carte ou coté PC ça change pas grand chose.
.... mais j'en suis pas encore là ;D

Est-ce que c'est possible d'envoyer un "tableau" de donnée à l'arduino et qu'il l'interprète... il me semble que oui, mais de quelle taille ?

Est-ce possible d'avoir deux tableaux un qui mets les valeurs en reçues et les mets en attentes et l'autre qui est lu directement pour le "protocole", puis les tableaux seraient inversés.... ?

J'ai encore pas mal de chemin à faire, mais ça devient de plus en plus passionnant.

Est-ce que c'est possible d'envoyer un "tableau" de donnée à l'arduino et qu'il l'interprète... il me semble que oui, mais de quelle taille ?

Ben en gros tu n'es limité que par la taille de la ram dispo au moment de recevoir ton tableau.
aprés, ca depend ce que tu veux envoyer comme valeurs dans ton tableau...

Tu envoies valeur par valeur, et quand tu as fini, tu envoies un message indiquant que tu as fini d'envoyer les valeurs. Ou encore, tu commence par envoyer un chiffre qui indique le nombre de valeurs à enregistrer..., comme ca l'arduino connait la longueur de la boucle de lecture du port serie pour remplir son tableau...

Est-ce possible d'avoir deux tableaux un qui mets les valeurs en reçues et les mets en attentes et l'autre qui est lu directement pour le "protocole", puis les tableaux seraient inversés.... ?

bien sur, rien n'empeche d'avoir autant de tableaux que tu le veux, tant que ca rentre en ram...

Merci pour ces précisions.