Transmission variable lourd par port série: Serial.write() ou autres?

Bonjour, j'ai un projet qui nécessite l'envoie de nombreuses variables vers un Raspberry Pi 4-4G.

Je suis plutôt à l'aise en Python mais en en C/C++. J'ai donc essayer de créer un petit programme Arduino pour essayer d'introduire mes variables (entier entre 8 et 16bits) dans une chaîne de caractère (principe très différent de C à Python) ayant la même structure qu'une liste en Python pour faire aisément la conversion un fois reçu par le programme Python.
Cependant je n'arrive pas vraiment à tout bien comprendre et je bloque.

Mon code n'a aucune tronche, je pige pas pourquoi je n'ai pas essayer de voir avec une boucle for, je teste dans le motiteur série donc utilisation de Serial.println(), mais pour envoyer cela vers le Raspberry Pi il va me falloir utiliser Serial.write(). Cependant je ne saisi pas totalement l'étendu de la fonction.

Mon code:

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

  // if analog input pin 0 is unconnected, random analog
  // noise will cause the call to randomSeed() to generate
  // different seed numbers each time the sketch runs.
  // randomSeed() will then shuffle the random function.
  randomSeed(analogRead(0));
  Serial.println("essaie dans le setup");
}

void loop() {
  Serial.println("test");
  char text[] = "[";
  int i = 1;
  while (i<9) {
    if (i<5) {
      text[i] = strcat((random(250)), ", ");
    } else if (i<9) {
      text[i] = strcat((random(65500)), ", ");
    } else{
      text[i] = "]";
    }
    int i=+1;
  }
  Serial.println(text);
  
}

Si je pouvais avoir un peu d'aide, des avis, conseils, explications, ce serai vraiment sympas.
Je suis nul en C/C++.

Cordialement

Bonjour,

Je ne vois pas très bien ce que tu veux faire. strcat demande l'adresse d'une chaine de caractères (en fait l'adresse d'un buffer contenant la chaine de caractères) comme premier argument.

Est ce ce que tu veux faire?

void loop() {
  Serial.println("test");
  char text[100];
  char buf[10];
  strcpy(text, "[");
  int i = 1;
  while (i < 9) {
    if (i < 5) {
      strcat(text, ltoa(random(250), buf, 10));
    }
    else {
      strcat(text, ltoa(random(65500), buf, 10));
    }
    if (i < 8)
      strcat(text, ", ");
    else
      strcat(text, "]");

    i++;
  }

  Serial.println(text);
}

Pour faire simple, je veut convertir toute mes variable qui sont des int en des chaînes de caractères et ainsi crée une chaîne de caractère de la forme: "[ 145, 25, 7795, 1476, 9515, 45315, 7454, 741]"
Et ensuite envoyer cette chaîne au Raspberry Pi pour qu'elle soit reçu dans une variable.

Mais il faut que la création et l'envoie de la chaînes soit optimal car l'on parle d'une chaîne contenant 50 variables allant de 8 à 16bits et cette opération de création et envoie devant ce réalisé au moins à 1000Hz.

(Si vous connaissez un moyen simple de convertir un arraw C/C++ en array Python (list), je suis preneur.)

Ton code est vraiment intéressant Kamill. Mon approche était mauvaise. J'essayer d'ajouter à la chaîne de caractères. Alors que fusionner les chaînes est plus pratique.

Je comprend assez mal la règle qui demande de définir le nombres de caractères d'une chaîne. Il doit être possible de crée des chaînes avec un nombres indéfini de caractère à l'initialisation?

Ton code est bien, mais je ne comprend pas à quoi sert buf et 10 dans:

strcat(text, ltoa(random(65500), buf, 10));

De plus les virgule et espace ne sont pas introduit par ce code.
En tout cas merci de m'accorder de ton temps.
Cordialement

Je ne vois pas pourquoi tu crées un nouveau topic ...
J'avais déjà répondu ici :
https://forum.arduino.cc/index.php?topic=661465.msg4455860#msg4455860

On va aller un peu plus loin alors.

Si ce sont des variables 8 et 16 bits une chaîne de caractères ne convient pas.
Il te faut une structure :

struct data
{
  uint16_t d1;
  uint16_t d2;
  uint8_t d3;
  uint8_t d4;
};

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

void loop()
{
  struct data data2send;
  data2send.d1 = 125;
  data2send.d2 = 12;
  data2send.d3 = 1205;
  data2send.d4 = 30000;
  Serial.write((uint8_t *)&data2send, sizeof(data2send));
  delay(1000);
}

Pour transmettre 50 entiers (800 bits) à 1Mbaud il te faudra 800µs.

Si tu transmets des représentations ASCII de tes nombres il te faudra beaucoup plus de temps.
Et de plus la longueur de ta chaîne sera variable en fonction des valeurs.

10 en binaire = 2 octets.
"10" en string = 2 octets.
mais :
10000 en binaire = 2 octets.
"10000" en string = 5 octets.
Donc comme tu vises un envoi en 1ms -> impossible !

Et en plus tu ajoutes des virgules !

Le seul moyen est de transférer en binaire (ou de changer de technologie, Ethernet par exemple).

Si tu transfère les données en binaire il te faudra une structure regroupant tes 50 données.
Du côté PYTHON, la chaîne reçue peut être découpée avec le module struct.

J'espère que c'est plus clair.

Je te ressors ceci de ton autre post :

*Je suis entrain de voire si une carte Arduino Mega peut transmettre à 600'000 Bauds en valence 1.

Où en est-tu ?

600000 n'est pas une valeur standard. Ce sera soit 500000 soit 1000000.

D'autre part quand je parle de 800µs pour envoyer 50 entiers, si les données sont à 50/50 en 8 bits et 16 bits on sera plus proche de 600µs (600 bits).
Mais à cela il faut ajouter 1 start bit et un stop bit, donc 10 bits par octet transmis.
Donc au final on aura plutôt besoin de 800µs.
Une MEGA est-elle capable de faire tout ça en 1ms ?

  • affectation des éléments de la structure
  • envoi sur la ligne série

A voir ...

Plus précisément, avec 25 variables 8 bits + 25 variables 16 bits à 1Mbaud :

((25×10)+(25×2x10))÷1000000 = 0,00075 donc 750 µs.

Kyusuke:
Je comprend assez mal la règle qui demande de définir le nombres de caractères d'une chaîne. Il doit être possible de crée des chaînes avec un nombres indéfini de caractère à l'initialisation?

Il existe des class qui gèrent les chaines de caractères, par exemple String sur arduino. Cependant l’allocation, désallocation de mémoire fragmente la mémoire et il est (très) déconseillé de les utiliser sur les processeurs avec très peu de mémoire vive comme les processeurs avr.

Kyusuke:
mais je ne comprend pas à quoi sert buf et 10

buf c'est le buffer où mettre la chaine de caractères issue de la conversion long -> ASCII et 10 c'est la base de la conversion.

Merci beaucoup pour les réponse extrêmement clair et utile.

Je suis en effet renseigner sur la communication série et pensé en effet utiliser du 1Mbauds voire supérieur.

Je commence à douter des capacité du Mega de supporter ces calculer et manipulations (sachant qu'il pas mal d'autres blocs d'instructions dans le code finale). Sinon je verrai pour passer sur du plus puissant, genre STM32 ou autres.

La remarque sur Ethernet est intéressante (mais la consommation risque d'être bien supérieur, et faudra que je me renseigner sur cette méthode de communication que je n'ai jamais utilisé sur µC).

Pourrais-je avoir un lien qui me dirigerai vers un site qui explique comment crée sa propre structure de donnés en C?

Il y a autre chose à prendre en considération.

Le driver Serial a un buffer circulaire de 256 octets, c'est à dire que si l'on écrit 256 octets à l'aide de la méthode write(), ceux-ci sont copiés dans le buffer et émis sous interruption.

Si l'on écrit 600 octets d'un coup, la méthode write() sera suspendue jusqu'à ce que 600 octets - 256 octets soient émis, c'est à dire 344 octets. Ensuite la méthode write() rendra la main.

Ceci fait que l'émission de 600 octets prendra moins de temps que prévu, vu du côté de l'application.
C'est peut être une bonne raison d'en profiter pour envoyer des paquets de 600 / 3 = 200 octets :

  • préparation du premier paquet
  • envoi du paquet
  • préparation du deuxième paquet pendant que le premier est émis
  • envoi du paquet
  • préparation du troisième paquet pendant que le deuxième est émis
  • envoi du paquet

Ceci afin de tenir dans une durée d'envoi < 1ms.

Je commence à douter des capacité du Mega de supporter ces calculer et manipulations (sachant qu'il pas mal d'autres blocs d'instructions dans le code finale). Sinon je verrai pour passer sur du plus puissant, genre STM32 ou autres.

S'il y a beaucoup de calcul, il faudra bien.

Pourrais-je avoir un lien qui me dirigerai vers un site qui explique comment crée sa propre structure de donnés en C?

Mon exemple avec 4 données ne suffit pas ?

struct data
{
  uint16_t d1;
  uint16_t d2;
  uint8_t d3;
  uint8_t d4;
};

Il suffit d'ajouter celles qui manquent et leur donner un nom parlant.

Ok, je vais me penché sur toutes ces bonnes informations. Je vais coder tout ça et puis j'aviserai.

Merci beaucoup pour tout vos réponse avisé.

Cordialement

C'est de nouveau moi. J'ai codé ceci:

#include <stdio.h>

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

  // if analog input pin 0 is unconnected, random analog
  // noise will cause the call to randomSeed() to generate
  // different seed numbers each time the sketch runs.
  // randomSeed() will then shuffle the random function.
  randomSeed(analogRead(0));
  Serial.println("essaie dans le setup");
}

void loop() {
  struct define_data
      {
        uint8_t d1;
        uint8_t d2;
        uint8_t d3;
        uint8_t d4;
        uint16_t d5;
        uint16_t d6;
        uint16_t d7;
        uint16_t d8;
      };
   
   struct define_data data;

  data.d1 = random(250);
  data.d2 = random(250);
  data.d3 = random(250);
  data.d4 = random(250);
  data.d5 = random(65500);
  data.d6 = random(65500);
  data.d7 = random(65500);
  data.d8 = random(65500);

  Serial.println(define_data);

Mais j'ai cette erreur:

arduino_random_test:38:27: error: expected primary-expression before ')' token

Serial.write(define_data);

^

exit status 1
expected primary-expression before ')' token

J'ai essayé avec Serial.println(data); mais marche pas également.
Je suis vraiment nul en C, donc j'ai suivi ce cours:

Pourrais-je avoir un petit peu d'aide sur la fin pour que ça marche avec Serial.println() et Serial.write()?

Cordialement

Pas println() : plutôt write()
write demande deux arguments : adresse et taille.

  Serial.write((uint8_t *)&data, sizeof(data));

Le cast (uint8_t *) est là pour faire accepter au compilateur l'adresse &data comme adresse de tableau de bytes.

   // arguments de write :
   virtual size_t write(const uint8_t *buffer, size_t size);

Oups. dsl erreur de manip

hbachetti:

   // arguments de write :

virtual size_t write(const uint8_t *buffer, size_t size);

Je n'ai pas compris l'utilité de cette ligne de code.

De plus du côté de Python, je reçois cela:

b'!\xa5\xb0~B6\x87\x1e\x13.~\x88'
b'S\xda\xe9S(\x1d3\xe4\xe6\xe9\xa2\xd5'
b'KU\xd2i8+\xdd\xeft\xc2K\xc5'
b'\x97\xc0ZZa\xa4\xa9\xef\xdc\xba\xb4\x10'

On devine que c'est la structure qui s'affiche mais elle n'est pas lisible. Un problème de donné, decimal, binaire, ASCII ? J'ai essayé avec une simple valeur égal à 250 et elle renvoyait b'\n

Un peu d'aide? --> Gratitude pour le temps accordé.

Python interprète les données reçues comme des caractères (de l'ASCII)
Mais ces données ne sont pas des caractères, ce sont des entiers 8 ou 16 bits à la queue leu leu.
Il faut expliquer au programme Python ce que sont ses données, lui donner un plan des données.
Ce plan, c'est la struct define_data que tu as définie.

On devine que c'est la structure qui s'affiche mais elle n'est pas lisible. Un problème de donné, decimal, binaire, ASCII ? J'ai essayé avec une simple valeur égal à 250 et elle renvoyait b'\n

Non, c'est du binaire pur.
Tu ne peux pas afficher simplement avec print().
Il faut décoder d'abord avec struct.unpack().
l'AVR est un processeur little endian.

Mon exemple précédent était un peu faux. Je plaçais des entiers trop grands dans d3 et d4 :

  data2send.d1 = 125;
  data2send.d2 = 12;
  data2send.d3 = 1205;
  data2send.d4 = 30000;

Voici une version corrigée. J'ai ajouté attribute((packed)) pour éviter les problèmes d'alignement.

struct __attribute__((packed)) data
{
  uint8_t d0;
  uint16_t d1;
  uint16_t d2;
  uint8_t d3;
  uint8_t d4;
};

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

void loop()
{
  struct data data2send;
  data2send.d0 = 1;
  data2send.d1 = 1205;
  data2send.d2 = 30000;
  data2send.d3 = 125;
  data2send.d4 = 12;
  Serial.write((uint8_t *)&data2send, sizeof(data2send));
  Serial.print('\n');               // pour dire à python : c'est fini
  delay(1000);
}

Le code python :

#!/usr/bin/env python

import time, serial, struct

if __name__ == "__main__":
	ser = serial.Serial('/dev/ttyUSB1', baudrate=115200)
	ser.timeout = 3
	time.sleep(1)
	data = ser.read_until('\n').strip('\r\n')
	print len(data)
	(d0, d1, d2, d3, d4) = struct.unpack('<BHHBB', data)
	print d0, d1, d2, d3, d4
$ python struct-python.py 
7
1 1205 30000 125 12

C'est tout bon ...

	(d0, d1, d2, d3, d4) = struct.unpack('<BHHBB', data)

< : indique à unpack que l'émetteur est little endian
BHHBB : un byte + un entier non signé + un entier non signé + un byte + un byte

Si tu veux émettre des entiers signés il faudra utiliser :

  • int8_t, int16_t côtè ARDUINO
  • 'biibb' côté PYTHON