Probleme limite memoire arduino+Sd+Bluetooth+Lecteur code barre

Bonjour,

J’ai besoin de votre aide :wink:

Voici mon projet: Je souhaite gérer un gestionnaire de stock de matériel avec Arduino, lorsqu’on scanne le code barre d’un produit, on l’enlève (ou on ajoute) 1 sur fichier sur la carte SD, en parallèle il y a une application faites avec app inventor qui permet de voir en temps réel le stock (lire le fichier sur la carte SD) (image 1), cette appli permet aussi d’ajouter des produits qui n’y sont pas: code, nom, quantité (image 2). Mais également d’enlever des produits qu’on ne veut plus (image 3).

Dans ce but j’écris sur ma carte SD de la manière suivante: nom ; codebarre ; quantité…

Exemple:
coca ; 253361155 ; 2…
fanta ; 11225533 ; 5…
tournevis ; 122595445 ; 7…
banane ; 60225103 ; 3…

Et ainsi de suite.

Les … et les ; permettent naviguer dans le texte pour le modifier “facilement”.

La partie pour ajouter fonctionne très bien, celle pour retirer un produit aussi.

Cependant lorsque le stock commence à être important (plus de 5 lignes), des problèmes apparaissent pour la lecture de la carte SD, je suis quasi persuadé que c’est lié à la mémoire de l’Arduino qui sature. J’utilise 90% de l’espace de stockage de programme et 70% de la mémoire dynamique, je suis sur arduino uno.

Les problèmes sont les suivants: non envoi par le module bluetooth et lorsque je retire des produits, il y a un moment ou je dois supprimer le fichier et le rajouter juste après (sans la ligne supprimée), cependant ce qui s’enregistre n’est que très partiel (juste la fin du fichier ou partie).

J’aimerai avoir de l’aide pour résoudre ces problèmes: gagner de la place sur l’arduino, trouver un autre moyen de modifier le texte du fichier sans avoir à enregistrer tout le contenu dans une variable, envoyer le document ligne par ligne plutôt qu’en une seule fois.

Si l’un d’entre vous à du temps pour m’aider, ça serait génial ! Merci d’avance !

Voici mon programme:

#include <SoftwareSerial.h>
#include<SD.h>
#include<SPI.h>
#include <usbhid.h>
#include <hiduniversal.h>
#include <Usb.h>
#include <usbhub.h>
#include <hidboot.h>

USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);

String data = "";

class KbdRptParser : public KeyboardReportParser {
   void PrintKey(uint8_t mod, uint8_t key);
 protected:
   virtual void OnKeyDown  (uint8_t mod, uint8_t key);
   virtual void OnKeyPressed(uint8_t key);
};

KbdRptParser Prs;

SoftwareSerial HC06 (7, 5);
File fichier;

byte buzz = 3;
byte cs = 6;
String messageRecu;
String messageEnvoye; //Lecture de carte sd

void setup() {
 Serial.begin(9600);
 Usb.Init();
 delay(200);
 Hid.SetReportParser(0, (HIDReportParser*)&Prs);

 HC06.begin(9600);
 pinMode(cs, OUTPUT);
 pinMode(buzz, OUTPUT);

 while (!Serial) {
   ; // wait for serial port to connect. Needed for native USB port only
 }
 Serial.print(F("Initializing SD card..."));

 if (!SD.begin(6)) {
   Serial.println(F("initialization failed!"));
   tone(buzz, 500, 200);
   while (1);
 }
 Serial.println(F("Carte SD prête"));
 buzzer();
 lectureCarte();
 delay(1000);
}

void loop() {
 Usb.Task();
 while (HC06.available())
 {
   char c = HC06.read();
   delay(3);
   messageRecu += c;
 }

/*****************Si le message recu par bluetooth est "maj" on envoi le contenu du fichier par bluetooth*************/
 if (messageRecu == "maj") {
   lectureCarte();
   HC06.print("Début \n" + messageEnvoye + "\n Fin");
 }
/*****************Si le message recu commence par "ajo" on ajoute le suite du message au fichier*************/
 if (messageRecu.substring(0, 3) == "ajo") {
   fichier = SD.open("inv.txt", FILE_WRITE);
   if (fichier) {
     Serial.println(F("Écriture du fichier..."));
     fichier.println(messageRecu.substring(4));
     fichier.close();
     Serial.println(F("Fait"));
   }
   fichier.close();
   buzzer();
 }

/*****************Si le message recu commence par "sup" on supprime le produit du fichier*************/
 if (messageRecu.substring(0, 3) == "sup") {
   lectureCarte();
   messageEnvoye.toLowerCase(); //Pour le comparaison on met en minuscule
   messageRecu.toLowerCase();
   int i = messageEnvoye.indexOf(messageRecu.substring(4));
   int j = i;
   if (messageRecu.substring(4).length() > 2 && i > -1) {
     while (messageEnvoye.substring(j, j + 3) != "...") {
       j++;
       Serial.println(messageEnvoye.substring(j, j + 3));
       delay(50);
     }
     if (i - 1 < 0) {
       i++;
     }
     else if (j + 5 >= messageEnvoye.length()) {
       j--;
     }

     Serial.print(messageEnvoye.substring(i - 1, j + 5));
     messageEnvoye.remove(i - 1, j - i + 5);
     Serial.println(messageEnvoye);
     messageEnvoye.remove(messageEnvoye.length() - 1);

     SD.remove("inv.txt");
     delay(1000);
     fichier = SD.open("inv.txt", FILE_WRITE);
     if (fichier) {
       Serial.println(F("Écriture du fichier..."));
       fichier.println(messageEnvoye);
       delay(1000);
       fichier.close();
       Serial.println(F("Fait"));
       buzzer();
     }
   }
 }

 messageEnvoye = "";
 messageRecu = "";
}

void buzzer() {
 tone(buzz, 4000, 100);
 delay(200);
 tone(buzz, 4000, 100);
}

void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key) {
 uint8_t c = OemToAscii(mod, key);

 if (c)
   OnKeyPressed(c);
}

void KbdRptParser::OnKeyPressed(uint8_t key) {
 char c = (char)key;

 if (key == 19) {
   String ret = data;
   data = "";
   barcodeCallback(ret);
 } else {
   data += c;
 }
}

void barcodeCallback(String data) {
 Serial.println(data);
}

void lectureCarte() {
 messageEnvoye = "";
 fichier = SD.open("inv.txt", FILE_READ);
 if (fichier.available()) {
   Serial.println(F("Lecture:"));
   while (fichier.available()) {
     messageEnvoye += char(fichier.read());
   }
   Serial.println(messageEnvoye);
   fichier.close();
 }
}

Combien de fois a t-on rabâché : l'utilisation de la classe String fragmente la mémoire.

1000 ou 10000 fois ?

Lis ceci : arduino-la-fragmentation-memoire

juste pour vous assurer que c’est un souci de mémoire, avez vous essayé sur un Arduino Mega ?

hbachetti:
Combien de fois a t-on rabâché : l'utilisation de la classe String fragmente la mémoire.

1000 ou 10000 fois ?

Lis ceci : arduino-la-fragmentation-memoire

Merci pour votre réponse agréable.

Cependant, je ne connais pas d'avance la taille maximum du fichier. Je souhaite avoir des alternatives pour modifier le fichier sans avoir à le mettre dans string.

Eventuellement créez un fichier par référence au lieu de tout mettre dans le même fichier ? si le code barre est assez court, ça pourrait être le nom de fichier (ou "Long File Name" dans SDFat). ce fichier contiendrait toutes les infos concernant cette référence. (nom, stock, ...). ça pourrait même être un dump binaire d'une structure.

Eventuellement maintenez un fichier de l'ensemble des référence mais structurez le pour que les champs aient tous la même taille et dise si une référence est encore active ou pas de manière à ne jamais avoir à détruire une ligne au milieu et ré-écrire un fichier

Cependant, je ne connais pas d'avance la taille maximum du fichier. Je souhaite avoir des alternatives pour modifier le fichier sans avoir à le mettre dans string.

Faux.
Pour ajouter une ligne dans un fichier il est possible de l'ouvrir en APPEND.

Pour supprimer une ligne il est parfaitement possible de lire un fichier ligne par ligne, et écrire chaque ligne dans un deuxième fichier, sauf la ligne que l'on veut supprimer.
Ensuite on détruit le premier fichier et on renomme le deuxième.

Cela n'empêchera pas la fragmentation, mais ça la retardera, au mieux.
Se former aux C strings est fondamental en logiciel embarqué, l'allocation dynamique est à proscrire.
A moins de disposer au minimum d'un ESP8266 (64Ko de RAM est un peu plus confortable que 2Ko) ...

Je n'ai pas de méga sous la main, mais j'ai eu des messages d'erreur de stabilité à cause de la memoire en effectuant des tests avec un string de 5 lignes.

[/quote]

J-M-L:
Eventuellement créez un fichier par référence au lieu de tout mettre dans le même fichier ? si le code barre est assez court, ça pourrait être le nom de fichier (ou "Long File Name" dans SDFat). ce fichier contiendrait toutes les infos concernant cette référence. (nom, stock, ...). ça pourrait même être un dump binaire d'une structure.

Eventuellement maintenez un fichier de l'ensemble des référence mais structurez le pour que les champs aient tous la même taille et dise si une référence est encore active ou pas de manière à ne jamais avoir à détruire une ligne au milieu et ré-écrire un fichier

Merci pour ces pistes, je n'ai pas précisé, mais de temps à autre je recupère la carte SD pour la lire dans un excel. Ainsi plusieurs fichiers me semble complexe.

De plus pour envoyer les informations sur le téléphone ça ne me parait pas simple avec plusieurs fichiers.

coca ; 253361155 ; 2...
fanta ; 11225533 ; 5...
tournevis ; 122595445 ; 7...
banane ; 60225103 ; 3...

100 octets au total.

J'utilise 90% de l'espace de stockage de programme et 70% de la mémoire dynamique

2Ko * 70% = 1400 octets.
Il en reste 600, pile comprise.

Donc normalement cela devrait suffire (pour 4 lignes). Si cela ne marche pas c'est bien que l'espace libre n'est pas le problème.

En laissant 100 octets de pile (c'est peu) il reste 500 octets.
Donc cela veut dire que la RAM disponible suffira théoriquement pour 20 lignes.
Est-tu sûr que cela suffise ?

hbachetti:
Faux.
Pour ajouter une ligne dans un fichier il est possible de l'ouvrir en APPEND.

Pour supprimer une ligne il est parfaitement possible de lire un fichier ligne par ligne, et écrire chaque ligne dans un deuxième fichier, sauf la ligne que l'on veut supprimer.
Ensuite on détruit le premier fichier et on renomme le deuxième.

Cela n'empêchera pas la fragmentation, mais ça la retardera, au mieux.
Se former aux C strings est fondamental en logiciel embarqué, l'allocation dynamique est à proscrire.
A moins de disposer au minimum d'un ESP8266 (64Ko de RAM est un peu plus confortable que 2Ko) ...

Que signifie ouvrir en APPEND ?

Comment lire ligne par ligne ?

Il y a vraiment aucun moyen d'éviter d'arriver à la mémoire saturée ?

Répond d'abord à la question :

Donc cela veut dire que la RAM disponible suffira théoriquement pour 20 lignes.
Est-tu sûr que cela suffise ?

hbachetti:
Répond d'abord à la question :

20 lignes ne suffisent pas, c'est pour gérer les stock d'une association on a au moins 100 produits...

20 lignes ne suffisent pas, c'est pour gérer les stock d'une association on a au moins 100 produits...

25 octets par ligne * 100 = 2500. L'ATMEGA328 n'en dispose même pas.
Donc lire le fichier en entier est impossible.

Que signifie ouvrir en APPEND ?

Cela veut dire en AJOUT.
Avec le librairie SD :

src/SD.h:#define FILE_WRITE (O_READ | O_WRITE | O_CREAT | O_APPEND)

Donc cela veut dire qu'un fichier est ouvert avec O_APPEND par défaut.
Mais comme tu détruis le fichier avec de l'écrire ... c'est comme si tu partais de ZÉRO.

Comment lire ligne par ligne ?

Avec la librairie SdFat je dirais fgets().

Mais comme tu utilises la librairie SD : lire caractère par caractère jusqu'à rencontrer '\n'.
Rien ne t'empêche de recréer fgets() pour la librairie SD:
Voir ici : SdFat/FatFile.cpp at master · greiman/SdFat · GitHub
ligne 228

Il me semble quand même que la librairie SdFat est préférable.

hbachetti:
Il me semble quand même que la librairie SdFat est préférable.

+1

Arrivé là, on peut se demander s'il n'y aurait pas une erreur de casting....
Est-ce que la carte choisie est un bon choix.
Si on veut pas mal de mémoire, une liaison sans fil, un accès à une base de données sans faire des manipulations de carte mémoire hasardeuse, je me demande s'il ne faudrait pas regarder du côté ( des cartes Arduino un peu musclées ) ou d'un Raspberry Pi

fdufnews:
Arrivé là, on peut se demander s'il n'y aurait pas une erreur de casting....

Oui vu le besoin et toutes les bibliothèques (y compris String) utilisées, un UNO n'est pas adapté ou il faudrait une grosse ré-écriture.

en dégageant le port série matériel pour mettre le bluetooth sur le port hardware on gagnerait un peu en qualité et code, mais Dès que la base de donnée va devenir importante, toute commande va commencer à prendre du temps car il faudra en permanence parcourir le fichier à la recherche de la bonne référence, régénérer le fichier etc...

Arrivé là, on peut se demander s'il n'y aurait pas une erreur de casting....

N'est-ce pas ?

Une appli sur un tel mobile envoie une requête BT à un ARDUINO, qui est utilisé ensuite pour scanner un code barre pour ajouter ou retirer un produit d'un stock.
On fait mieux comme architecture.

Personnellement je ne penserais jamais à gérer un stock avec un ARDUINO.
Un fichier tableur partagé, ou une base de données hébergée sur le WEB, etc.

Je suppose qu'un téléphone sait scanner des codes barre, pas seulement des QRCodes. Donc cette application, logiquement, est une appli Android.

hbachetti:
Donc cette application, logiquement, est une appli Android.

Ou iOS :slight_smile:

Aussi, mais je crois qu'AppInventor tourne sur les deux.

effectivement pas de inventor sur iOS

Il m’avait semblé pourtant : MIT App Inventor for iOS version 0.9 takes off in TestFlight