Liaison série

Bonjour,

Je cherche à acquérir des données venant de deux capteurs en temps réels sur la voie série pour les exporter sur Excel afin de pouvoir réaliser des calculs et tracer des courbes.

Pour cela j'aimerais savoir s'il y a moyen de faire une sorte de tableau à 3 colonnes directement dans la voie série, exemple :

Temps : Capteur 1 : Capteur 2 :
0
0.003 1
0.005 1
0.007 1
0.008 1

OU

Capteur 1 : Capteur 2 :

0.003
0.005
0.007
0.008

Je souhaiterais pouvoir copier coller dans Excel ma fenêtre de liaison série.
J'ignore si c'est faisable, je pense que non mais peut-être qu'il y a des méthodes pour contourner le problème.
Parce que si j'imprime directement dans la voie série j'aurais :

capteur 1 : 0
capteur 1 : 0.003
capteur 1 : 0.005
capteur 2 : 0.007
capteur 1 : 0.008

Et la je ne vois pas comment remettre ça en forme sous Excel.
De plus il faudrait alléger au maximum le code car je vais avoir une interruption pouvant atteindre une fréquence d'environ 250 Hz chacun, je ne sais même pas si l'Arduino sera capable de suivre. A l'oscilloscope on avait mesuré qu'il commençait à avoir des ratés à environ 300Hz mais le code était loin d'être optimisé.

Merci d'avance pour votre aide :slight_smile: !

ce que vous dites c'est qu'à un instant t vous avez une valeur pour 1 2 ou 3 capteurs mais que ça peut varier?

si vous imprimez des tabulations '\t' ou de espace vide séparés par des virgules vous copiez/collez dans un fichier avec suffixe .csv que excel saura importer

le fichier doit avoir cette tête là:

valeur1,valeur2,valeur3
x,,
x,y,
,,z

et ça vous donnera dans excel ce que vous voulez (après avoir fait fichier/importer et choisi le .csv)

sinon votre approche avec une "étiquette, valeur" par ligne fonctionnera aussi de la même façon et vous pouvez bâtir votre analyse en faisant un "tableau croisé dynamique" sous excel

J'ai effectivement deux capteurs qui déclencheront chacun une interruption différente m'envoyant la valeur millis() dans le moniteur série.

J'ai réussi à obtenir quelque chose d'exploitable avec Excel en utilisant la fonction "convertir" de l'onglet "données".

Pour cela j'ai codé ceci :

int i = 0;
void setup()
{
  Serial.begin(9600);
  Serial.print("Capteur1 ");
  Serial.println("Capteur2 ");

}

void loop()
{
  for (i; i < 1; i++)
  {
    Serial.println("0.001 ");
    Serial.print(" ");
    Serial.println("0.002");
    Serial.print("0.001 ");
    Serial.println("0.002");
    Serial.print(" ");
    Serial.println("0.002");
    Serial.println("0.001");
    Serial.println("0.001");
    Serial.print(" ");
    Serial.println("0.002");
    Serial.print("0.001 ");
    Serial.println("0.002");
    Serial.print(" ");
    Serial.println("0.002");
    Serial.println("0.001");

  }


}

Pour illustrer j'ai voulu simuler mes fonctions d'interruptions qui ressemblerais alors à :
Interruption 1 :

Interruption 1 :
println(millis());
Interruption 2 : pas optimisé car j'appelle 2 fois la fonction print
print(" ");
println(millis());

Il y a t'il comme en C avec le printf la possibilité de retourner dans un print du texte + une valeur? Si oui comment?

Je vais essayer le \t que je ne connais pas et voir comment il fonctionne. Merci :slight_smile: .

ça ne pose aucun problème d'appeler deux fois print, c'est même plutôt mieux côté usage mémoire, pas la peine pour le compilateur de bâtir la chaine à envoyer

le séparateur sous forme de '\t' représente une tabulation c'est ce qu'on appelle une escape sequence.

par exemple vous pouvez faire

Serial.println("valeur1\tvaleur2");
Serial.print(15);Serial.print("\t");Serial.println(66);

Je suppose que vous n'utilisez pas de vraies interruptions, si? (millis() n'est pas maintenu à jour dans une interruption, Le compteur n'évolue plus)

D'accord, merci pour tes précisions ;D .

int pinCapteur1 = 0;
int pinCapteur2 = 1;

void setup()
{
  Serial.begin(250000);
  Serial.print("Capteur_1 ");
  Serial.println("Capteur_2");
  attachInterrupt(pinCapteur1, capteur1, RISING);
  attachInterrupt(pinCapteur2, capteur2, RISING);
}

void loop()
{
}

void capteur1()
{
  Serial.println(millis());
}


void capteur2()
{
  Serial.print(" ");
  Serial.println(millis());
}

Voici mon code que je pense être réduit au maximum pour espérer pouvoir récupérer mes données qui vont arriver à des fréquences assez élevées (250Hz pour un capteur et pour l'autre entre 45 et 275 Hz). Ça parait viable d'acquérir une information toute les 0.004s sur 1 capteur? Si oui, je ne pense pas qu'il sera capable en plus de suivre un deuxième capteur travaillant à cette vitesse. Changer de matériel, oui mais quoi?
Je n'avais pas pensé à l'interruption qui stoppait millis() :confused: .
Il y a une solution à ce soucis?

l'interruption stoppe le compteur qui incrémente la valeur de millis(), mais millis() retournera toujours une valeur : la dernière à avoir été mise à jour juste avant l'interruption. ce sera donc quand même relativement précis.
le compteur repart ensuite dès que la routine d'interruption est terminée. Tu n'aura perdu que le nombre de cycles d'horloge necessaire à la routine d'interruption, d'où l'intéret de la rendre la plus courte possible.

et donc utiliser serial dans une routine d'interruption, il vaut mieux éviter, déja parceque c'est long, et puis aussi toujours parceque le passage en interruption désactive les interruptions, et que serial les utilise.

ce qui me semblerait plus fonctionnel serait que la routine d'interruption attribue la valeur de millis() à une variable (et c'est tout) et que ce soit ensuite dans ta boucle loop que tu te charge de faire l'envoi des données sur le port série si la variable a été modifiée. Ce serait également plus facile pour faire la mise en forme ainsi.

250Hz ce n'est pas énorme ça doit passer sans problèmes, en revanche le point limitant ici ça va être la vitesse de la liaison série, sinon tu va perdre des mesures.

Il faut aussi que le capteur réponde vite - certains ne sont pas très véloces.

Mettre la liaison série au minimum à 115200 voire 230400 avec Le PC aidera aussi à ne pas saturer le petit buffer de 64 octets en sortie. s'il est plein l'appel print devient bloquant (et print pas possible bien sûr dans une interruption)

édit : j'ai enlevé car effectivement sans interêt dans ce contexte

bah ça ce n'est envoyé qu'une fois au début de l'execution, ça ne devrait pas poser trop de problèmes de puissance de calcul :stuck_out_tongue:

J-M-L:
Mettre la liaison série au minimum à 115200 voire 230400 avec Le PC aidera aussi à ne pas saturer le petit buffer de 64 octets en sortie. s'il est plein l'appel print devient bloquant (et print pas possible bien sûr dans une interruption)

D'accord j'ai cru lire que 250 000 baud était un quelque sorte une limite en USB mais je n'ai pas trop approfondi ma recherche pour le moment je me suis contenté des résultats de recherche de Google sans même ouvrir les pages :-[ . Donc je note pour le 230 400 baud.

Je viens de retrouver le bout de programme qu'on m'avait donné à l'époque ou je ne savais rien de rien concernant l'Arduino.

/*
mesure de fréquence avec envoi sur la liaison série0 TX0 -> USB
Frequency Input Pin : 49 (PL0, ICP4)timer4
Pins Unusable with analogWrite() : 6, 7 et 8 (OCR4 tmer4)
carte Mega 2569



*/

#include "FreqMeasure.h"

#define Led_13 13

unsigned long time ,
              time_init;
String TEXTE;

void setup() 
 {
  pinMode(Led_13,OUTPUT);
  
  pinMode(49,INPUT_PULLUP); 
  
  Serial.begin(250000);
  
  Serial.println("temps (s) ; frequence (Hz)");
  
  FreqMeasure.begin();
 }

void loop() 
 { 
  
  while(!FreqMeasure.available()) {}         //attente première mesure
  
  time = micros();
  time_init = time;
                          //
  double frequency = FreqMeasure.read();
  frequency = F_CPU / frequency;
  
  TEXTE = String(1e-6 * (time - time_init),6);     //conversion un nombre à virgule en chaine (6 chiffres après la virgule)
  TEXTE.replace(".",",");                                 //remplace dans la chaine le caractère  le "." en ","
  Serial.print(TEXTE);
  
  Serial.print(";");
  
  TEXTE = String(frequency);
  TEXTE.replace(".",",");
  Serial.println(TEXTE);
     
      
  while(1) {                                //boucle mesure de fréquence
      if (FreqMeasure.available()) 
        {
         time = micros();
         double frequency = FreqMeasure.read();
         frequency = F_CPU / frequency;
         
         TEXTE = String(1e-6 * (time - time_init),6);     //conversion un nombre à virgule en chaine (6 chiffres après la virgule)
         TEXTE.replace(".",",");                                 //remplace dans la chaine le caractère  le "." en ","
         Serial.print(TEXTE);
         
         Serial.print(";");
         
         TEXTE = String(frequency);
         TEXTE.replace(".",",");
         Serial.println(TEXTE);
        } 
    } 
      
  }

On avait des soucis d'acquisition à plus de 300 Hz pas avant (on avait comparé les valeurs avec celles d'un oscilloscope au lycée). Du coup au début n'ayant qu'un seul capteur je m'étais dis que ça irait et puis maintenant que je me débrouille plus avec Arduino et que je sais un peu me débrouiller avec le code quand je vois ce code ça me semble pas être le chemin le plus court.
Petite question, l'interruption nécessite apparemment 2 à 3 ms soit 0.002s à 0.003s seconde hors j'ai deux capteurs qui pourrait arriver à envoyer une information toutes les 0.004s. Ça va le faire?

trimarco232:
Bonjour,

il faudrait éviter de faire faire 99% du travail à l'appareil qui n'a que 1% de la puissance

remplace "Capteur_1 " par "C_1", puis fait Edition, Remplacer C_1 par Capteur_1 .

Cette partie du code est dans le setup au moment de l'initialisation de l'Arduino :wink: . Pour la suite il ne m’envoie que la valeur de millis(). J'aime bien l'idée du 99% du travail à celui qui a 1% de puissance!

bricofoy:
l'interruption stoppe le compteur qui incrémente la valeur de millis(), mais millis() retournera toujours une valeur : la dernière à avoir été mise à jour juste avant l'interruption. ce sera donc quand même relativement précis.
le compteur repart ensuite dès que la routine d'interruption est terminée. Tu n'aura perdu que le nombre de cycles d'horloge necessaire à la routine d'interruption, d'où l'intéret de la rendre la plus courte possible.

et donc utiliser serial dans une routine d'interruption, il vaut mieux éviter, déja parceque c'est long, et puis aussi toujours parceque le passage en interruption désactive les interruptions, et que serial les utilise.

ce qui me semblerait plus fonctionnel serait que la routine d'interruption attribue la valeur de millis() à une variable (et c'est tout) et que ce soit ensuite dans ta boucle loop que tu te charge de faire l'envoi des données sur le port série si la variable a été modifiée. Ce serait également plus facile pour faire la mise en forme ainsi.

250Hz ce n'est pas énorme ça doit passer sans problèmes, en revanche le point limitant ici ça va être la vitesse de la liaison série, sinon tu va perdre des mesures.

D'accord, donc je vais modifier mon code en conséquence.

Du coup je m'y remet et je repasse pour voir avec vous si c'est bon :smiley: .

l'interruption nécessite apparemment 2 à 3 ms

Non une interruption se déclenche en quelques microsecondes, pas millisecondes.
Il faut effectivement mémoriser avec un drapeau (un booleen) le fait que vous avez eu l'interruption et lire micros() par exemple à ce moment là puis ensuite dans la boucle faire l'affichage en limitant au max les caracteres envoyés. Pas de fioritures, imprimez en hexadécimal ça prend moins de place, Excel se débrouillera :slight_smile:

après, est-ce que le moniteur arrive à suivre (afficher) avec ce flux ?

J-M-L:
Non une interruption se déclenche en quelques microsecondes, pas millisecondes.
Il faut effectivement mémoriser avec un drapeau (un booleen) le fait que vous avez eu l'interruption et lire micros() par exemple à ce moment là puis ensuite dans la boucle faire l'affichage en limitant au max les caracteres envoyés. Pas de fioritures, imprimez en hexadécimal ça prend moins de place, Excel se débrouillera :slight_smile:

Ah! Pour moi ms c'est milliseconde du coup j'en étais pas du tout à µs. 10-6s c'est tout a fait acceptable effectivement ça ne va pas changer grand chose au court du temps :smiley: . L’hexadécimal je vais me renseigner la dessus du coup (le pourquoi du comment, j'aime bien savoir pourquoi mais je dirais pour une simplicité de conversion binaire vers hexadécimal :P).

Bon du coup voilà où j'en suis arrivé :

int pinC1 = 0; // PinCapteur1
int pinC2 = 1; // PinCapteur2
long C1 = 0; // VariableCapteur1
long C2 = 0; // VariableCapteur2
int bol_C1 = 0; // BooléenCapteur1
int bol_C2 = 0; // BooléenCapteur2

void setup()
{
  Serial.begin(250000);
  Serial.print("Capteur_1 ");
  Serial.println("Capteur_2");
  attachInterrupt(pinC1, capteur1, RISING);
  attachInterrupt(pinC2, capteur2, RISING);
}

void loop()
{
  if (bol_C1 == 1) //Si Capteur1 a délenché une interruption
  {
    Serial.print(C1, HEX); // alors on envoit la valeur de C1=millis() en héxadécimal sur la voie série
    bol_C1 = 0; // puis on remet l'état du booléen C1 à 0.
  }

  if (bol_C2 == 1) //Si Capteur2 a délenché une interruption
  {
    Serial.print(" "); // Alors on envoit un espace
    Serial.println(C2, HEX); // puis on envoit la valeur de C2=millis() en hexadécimal sur la voie série
    bol_C2 = 0; // et enfin on remet l'état du booléen C2 à 0.
  }
}

void capteur1()
{
  C1 = millis();
  bol_C1 = 1;
}

void capteur2()
{
  C2 = millis();
  bol_C2 = 1;
}

Si vous voyez un endroit où je pourrais encore améliorer la chose n'hésiter pas à me le faire remarquer :wink: .

Par contre en testant des nombres bidon par exemple :
Arduino 152366(DEC) -> 2532E (HEX)
Excel 2532E (HEX) -> 152366(DEC)
MAIS
Arduino 4871517412(DEC) -> 225D74E4 (HEX)
Excel 225D74E4 (HEX) -> (DEC) 576550116
Excel 4871517412(DEC) -> 1225D74E4(HEX)
Du coup ...

Edit : ah bah oui j'ai dépassé la taille de ma variable honte à moi !

:slight_smile:

Le hex parce que c'est plus compact pour représenter des chiffres que le décimal - donc sature moins le port série

les variables pour stocker les temps doivent être des unsigned long et pas seulement des long sinon pour de grands valeurs tu risques de te retrouver avec une valeur négative qui n'aura rien à voir...

pour tes drapeaux bol_C1 et bol_C2 utilises donc plutot des boolean que des int, ça prends moins de place en RAM et c'est fait pour ça

toutes les variables utilisées en interruption doivent être déclarée avec le mot clef "volatile" avant la déclaration :

volatile unsigned long C1 = 0; // VariableCapteur1
volatile boolean bol_C1=false;

ceci afin de les garder en RAM tout le temps et non pas sur la pile.

sinon ça me semble plutot bien :slight_smile:

trimarco232:
après, est-ce que le moniteur arrive à suivre (afficher) avec ce flux ?

il lag un peu parfois mais ça finit par s'afficher :slight_smile:

et puis rien n'empèche d'utiliser une autre appli comme moniteur série, qui va être capable d'enregistrer directement tout ce qui est reçu dans un fichier txt (ou csv). Dans ce cas ce serait même plus simple pour récupérer les données.

moi j'utilise cutecom sous linux, mais je sais qu'il y en a plein aussi sous windows capable de faire ça.

J-M-L:
:slight_smile:

Le hex parce que c'est plus compact pour représenter des chiffres que le décimal - donc sature moins le port série

D'accord merci :slight_smile: .

bricofoy:
les variables pour stocker les temps doivent être des unsigned long et pas seulement des long sinon pour de grands valeurs tu risques de te retrouver avec une valeur négative qui n'aura rien à voir...

Bah du coup quand j'ai vu que je débordais en mémoire je me suis dis que même si de toute façon je n'y arriverais jamais un unsigned long c'était quand même, les temps négatifs... Du coup oui ça c'est bon.

bricofoy:
pour tes drapeaux bol_C1 et bol_C2 utilises donc plutot des boolean que des int, ça prends moins de place en RAM et c'est fait pour ça

D'accord je modifie ça :slight_smile: .

bricofoy:
toutes les variables utilisées en interruption doivent être déclarée avec le mot clef "volatile" avant la déclaration :

volatile unsigned long C1 = 0; // VariableCapteur1

volatile boolean bol_C1=false;



ceci afin de les garder en RAM tout le temps et non pas sur la pile.

sinon ça me semble plutot bien :)

Je modifie ça aussi et je vais aller rejeter un œil à la RAM et la pile histoire d'avoir bien ça en tête au passage.

bricofoy:
il lag un peu parfois mais ça finit par s'afficher :slight_smile:

et puis rien n'empèche d'utiliser une autre appli comme moniteur série, qui va être capable d'enregistrer directement tout ce qui est reçu dans un fichier txt (ou csv). Dans ce cas ce serait même plus simple pour récupérer les données.

moi j'utilise cutecom sous linux, mais je sais qu'il y en a plein aussi sous windows capable de faire ça.

J'aimerais à la fin réussir à directement acquérir les données sous Excel et pourquoi pas tracer les courbes en directes! :sunglasses: Mais bon la j'ai du boulot car le VBA et moi on s'entend pas super bien en dehors des manipulations simples. Je trouve le C beaucoup plus simple.

Edit :

int pinC1 = 0; // PinCapteur1
int pinC2 = 1; // PinCapteur2
volatile unsigned long C1 = 0; // VariableCapteur1
volatile unsigned long C2 = 0; // VariableCapteur2
volatile boolean bol_C1 = false; // BooléenCapteur1
volatile boolean bol_C2 = false; // BooléenCapteur2

void setup()
{
  Serial.begin(250000);
  Serial.print("Capteur_1 ");
  Serial.println("Capteur_2");
  attachInterrupt(pinC1, capteur1, RISING);
  attachInterrupt(pinC2, capteur2, RISING);
}

void loop()
{
  if (bol_C1 == true) //Si Capteur1 a délenché une interruption
  {
    Serial.print(C1, HEX); // alors on envoit la valeur de C1=millis() sur la voie série
    bol_C1 = !bol_C1; // puis on remet l'état du booléen C1 à 0.
  }

  if (bol_C2 == true) //Si Capteur2 a délenché une interruption
  {
    Serial.print(" "); // Alors on envoit un espace
    Serial.println(C2, HEX); // puis on envoit la valeur de C2=millis() sur la voie série
    bol_C2 = !bol_C2; // et enfin on remet l'état du booléen C2 à 0.
  }
}

void capteur1()
{
  C1 = millis();
  bol_C1 = !bol_C1;
}

void capteur2()
{
  C2 = millis();
  bol_C2 = !bol_C2;
}

Tadaaaa!!! :slight_smile:

ceci afin de les garder en RAM tout le temps et non pas sur la pile.

Non ce n'est pas exactement ça - les variables sont toujours en ram (la pile c'est aussi de la ram). C'est pour vous assurer que le
Compilateur ne fasse pas des optimisations avec des registres. Avec un volatile la lecture et l'écriture de font toujours avec la valeur de référence en ram

Bonjour,

Parmi vous, est-ce qu'il y'a une personne qui a réussit à sauvegarder les données lues dans la liaison série directement dans un fichier (EXCEL, DOCUMENT TEXTE , ETC)?

Merci.

Écrivez un petit programme en processing qui écoute sur le port série et écrit le fichier au fur et à mesure ou si vous êtes sur mac/Linux vous pouvez directement lancer une commande qui va logger ce qui arrive dans un fichier (l'article date un peu il faut sans doute modifier un peu pour tenir compte des dernières règles de sécurité) ou utiliser coolterm je crois qu'il le fait