UNO-R3 -- Carte µSD, Vitesses d'escargot dans le transfert des données !?

Bonjour à tous,

je travail depuis une semaine sur un système d'acquisition de données constitué de:

  • un accéléromètre ADXL345,
  • un Arduino UNO-R3,
    -un shield µSD SparkFun.

...rien d'extraordinaire donc !

Néanmoins, le but est de faire tourner l'ADXL345 à sa vitesse maximum l'acquisition,
en réccupérant les valeurs brutes d'accélérations X,Y,Z sans traitement,
et de stocker ces valeurs sur une carte µSD à la volé !

... et c'est là que les choses se compliquent ! , en effet

L'ADXL345 est capable de fournir jusqu'à 800 acqs/sec avec son bus I2C cadencé à 400 Khz,
et 3200 acqs/sec avec son bus SPI cadencé à 5 Mhz.
Pour l'instant j'utilise le bus I2C à 400 Khz car je n'ai pas trouvé de librairie SPI pour ADXL345.

Le transfert des données UNO-R3 vers µSD se fait quand à lui par bus SPI.
Pour gérer ce transfert j'utilise la librairie SdFat.h plus performante que la bibrairie SD.h,
elle permet notament de paramètrer la vitesse du bus SPI à sa valeur max, soit 16Mhz/2=8Mhz avec un UNO-R3.

Malgrés ce paramétrage je me heurte à un problème de vitesse d'écrire des données sur la carte µSD.
Il y a deux aspects dans la vitesse d'écriture sur la carte:
1 - la vitesse intrinsèque de la carte en écriture, voir les taux de transfert en classe 10 par exemple,
2 - la vitesse d'éxécution du morceau de code qui gère l'écriture.
Pour l'instant ce qui m'intéresse c'est le point N°2, car j'observe que cette vitesse d'éxécution est très basse,
à tel point que les performances de la librairie SdFat.h semblent incompatible avec mon application !?l

Pour tester cette vitesse j'ai écris le petit code ci-joint.
Pour simplifier les choses l'acquisition de l'accéléromètre est remplacée par des valeurs fixes,
ce qui m'interesse c'est de voir combien de temps met le programme d'écriture des données pour se dérouler,
Pour cela je ne met même pas la carte µSD en place dans son connecteur pour m'affranchir du temps d'écrire (point N°1).
Cela ne pose pas de problème, le maître (UNO-R3) cause sans avoir de retour de l'esclave ( µSD)...
Pour mesurer le temps d'éxécution je place un oscilloscope sur la pin 7 qui me sert de marqueur ( voir le code dans la loop() ).
Dans ces conditions la durée de l'écriture est de 550 µs entre High et Low, ce qui fait grosso modo une fréquence de 1818 hz.
Cette valeur est peut-être acceptable pour faire de l'acquisition à 800 hz, quoi que, il faut voir une fois la carte µSD en place ? mais certainement pas pour fonctionner à 3200 hz !!
J'ai donc tendence à conclure que les librairies disponibles ( SD.h, SdFat.h ...)
ne donnent pas la possibilité d'atteindre des cadences de stockage élevées !?
Cela est peut-être dû à l'utilisation du bus SPI,
pour aller plus vite il faudrait travailler en mode SDIO, mais ça c'est impossible avec UNO !

Quel est votre avis sur cette question ?
Quelqu'un a-t-il des idées pour résoudre ce problème ?
mais peut-être faut-il que je m'oriente vers un autre système ? ...pcduino3 par exemple, cadencé à 1 Ghz !

Voici le code, il est utilisable en l'état, si vous avez le matériel vous pouvez faire l'expérience vous même.

/*
==============================================
 le 08 juillet 2014

µC UNO-R3 avec SD Shield SparkFun
===============================================
* Carte SD est connectée au bus SPI de la façon suivante:
** MOSI  - pin 11
** MISO  - pin 12
** CLK   - pin 13
** CS    - pin 8
*/

// pour la carte SD
// Sur le SD Shield SparkFun, CS est la pin 8.
const int chipSelect = 8;
#include <SdFat.h>
SdFat sd;
SdFile myFile;

// Valeurs à ecrire dans la carte SD, ax,ay,az sont sur 16 bits, donc deux mots de 8 bits sur le bus SPI
int16_t ax = B10101011;  //en binaire pour des raisons de facilité de lecture de la trame SPI à l'oscilloscope 
int16_t ay = B10101111;
int16_t az = B10111011;

// La pin 7 sert de marqueur pour visualisation à l'oscilloscope
int  MarqPin = 7;

void setup() {

    // On met la pin 7 en sortie
    pinMode(MarqPin, OUTPUT); 
    //------------------------------------------------------------------------------------------------ 
    // Initialisation de SdFat
    // SPI_FULL_SPEED pour plus de performance, 
    // le bus SPI est donc à sa cadence maximum, Fosc/2 soit 16Mhz/2 = 8 Mhz.
    sd.begin(chipSelect, SPI_FULL_SPEED);       
    //------------------------------------------------------------------------------------------------
}

void loop() {
                       
      digitalWrite(MarqPin, HIGH);  // La pin 7 passe à High, débute de cycle d'écriture
      // stokage des valeurs sur la carte SD
      myFile.open("test.csv", O_RDWR | O_CREAT | O_AT_END); // Ouverture du fichier      
      myFile.print(ax); // on ecrit ax à l'intérieur du fichier
      myFile.print(";"); // on ecrit ; à l'intérieur du fichier  
      myFile.print(ay); // on ecrit ay à l'intérieur du fichier
      myFile.print(";"); // on ecrit ; à l'intérieur du fichier
      myFile.println(az); // on ecrit az à l'intérieur du fichier       
      myFile.close(); // fermeture du fichier
      digitalWrite(MarqPin, LOW);  // La pin 7 passe à Low, fin de cycle d'écriture
      //on recommence indéfiniment pour une nouvelle écriture.
}

Merci pour votre retour,
Cordialement, Thierry.

Bonjour
Essai de ne pas faire de OPEN et CLOSE a chaque fois.
Essai aussi de construire un tableau de char que tu envoi d'un coup plutôt que d'envoyer avec plusieurs print().

Si ça peut t'aider.
a+

Salut,

Essaye :

void loop() {
                       
      digitalWrite(MarqPin, HIGH);  // La pin 7 passe à High, débute de cycle d'écriture
    
      myFile.open("test.csv", O_RDWR | O_CREAT | O_AT_END); // Ouverture du fichier      
      myFile.close(); // fermeture du fichier

      digitalWrite(MarqPin, LOW);  // La pin 7 passe à Low, fin de cycle d'écriture
      
}

Bonjour, et merci pour ta réponse.

caape:
... Essai de ne pas faire de OPEN et CLOSE a chaque fois....

Il faut absolument ouvrir et fermer le fichier pour écrire chaque ligne...

caape:
...Essai aussi de construire un tableau de char que tu envoi d'un coup plutôt que d'envoyer avec plusieurs print()...

Compte tenu de ce qui précède cela ne change donc rien, hélas.

+Thierry.

Bonjour, et merci pour ta réponse.

B@tto:
...Essaye :

void loop() {

digitalWrite(MarqPin, HIGH);  // La pin 7 passe à High, débute de cycle d'écriture
   
      myFile.open("test.csv", O_RDWR | O_CREAT | O_AT_END); // Ouverture du fichier     
      myFile.close(); // fermeture du fichier

digitalWrite(MarqPin, LOW);  // La pin 7 passe à Low, fin de cycle d'écriture
     
}

Le problème c'est que tu n'écris rien dans le fichier avec cette solution, tu te contentes de l'ouvrir et de le refermer aussi tôt.

  • Thierry.

Bin oui, on est à tester le timing et le temps pris par open() et close () ... Tu verrais surement que c'est la que le temps est bouffé. Auquel cas il faut éviter de passer par ces méthodes qui plombent les perf. Ce qui se fait facilement :

digitalWrite(MarqPin, HIGH);  // La pin 7 passe à High, débute de cycle d'écriture
myFile.open("test.csv", O_RDWR | O_CREAT | O_AT_END); // Ouverture du fichier  

for(int i=0;i< nbDeMesure;i++) {
      myFile.print(ax[i]); // on ecrit ax à l'intérieur du fichier
      myFile.print(";"); // on ecrit ; à l'intérieur du fichier  
      myFile.print(ay[i]); // on ecrit ay à l'intérieur du fichier
      myFile.print(";"); // on ecrit ; à l'intérieur du fichier
      myFile.print(az[i]); // on ecrit az à l'intérieur du fichier   
      myFile.print('\r'); // on ecrit az à l'intérieur du fichier 
      myFile.print('\n'); // on ecrit az à l'intérieur du fichier     
}

myFile.close(); // fermeture du fichier
digitalWrite(MarqPin, LOW);  // La pin 7 passe à Low, fin de cycle d'écriture

Question subsidiaire :
Quelle est la fréquence du bus SPI ?
La valeur de cette fréquence est-elle toujours la valeur configurée par les bib Arduino ou a-t-elle été changée ?

Salut,

B@tto:
Bin oui, on est à tester le timing et le temps pris par open() et close () ... Tu verrais surement que c'est la que le temps est bouffé. Auquel cas il faut éviter de passer par ces méthodes qui plombent les perf. Ce qui se fait facilement :

digitalWrite(MarqPin, HIGH);  // La pin 7 passe à High, débute de cycle d'écriture

myFile.open("test.csv", O_RDWR | O_CREAT | O_AT_END); // Ouverture du fichier

for(int i=0;i< nbDeMesure;i++) {
      myFile.print(ax[i]); // on ecrit ax à l'intérieur du fichier
      myFile.print(";"); // on ecrit ; à l'intérieur du fichier 
      myFile.print(ay[i]); // on ecrit ay à l'intérieur du fichier
      myFile.print(";"); // on ecrit ; à l'intérieur du fichier
      myFile.print(az[i]); // on ecrit az à l'intérieur du fichier 
      myFile.print('\r'); // on ecrit az à l'intérieur du fichier
      myFile.print('\n'); // on ecrit az à l'intérieur du fichier   
}

myFile.close(); // fermeture du fichier
digitalWrite(MarqPin, LOW);  // La pin 7 passe à Low, fin de cycle d'écriture

Ben... pas vraiment, ce n'est pas si simple, ce n'est pas uniquement Open() et Close() qui consomment du temps,
mais toutes la procédure d'écriture sur la carte !
En faite ta proposition de code ne resoud rien,
premièrement, l'indiçage des ax,ay,az ne sert à rien, on peut s'en passer pour construire le fichier,
et de toute façon au final le temps pour faire un tour de boucle for(){...} prend exactement 540 µs, donc grosso modo le même temps, ce n'est donc pas la solution.
Merci quand même pour l'idée que j'avais par ailleurs déjà testé...

  • Thierry.

Salut,

68tjs:
Question subsidiaire :
Quelle est la fréquence du bus SPI ?
La valeur de cette fréquence est-elle toujours la valeur configurée par les bib Arduino ou a-t-elle été changée ?

La réponse est dans le code dans la void setup(), regardes bien

void setup() {

    // On met la pin 7 en sortie
    pinMode(MarqPin, OUTPUT); 
    //------------------------------------------------------------------------------------------------ 
    // Initialisation de SdFat
    // SPI_FULL_SPEED pour plus de performance, 
    // le bus SPI est donc à sa cadence maximum, Fosc/2 soit 16Mhz/2 = 8 Mhz.
    sd.begin(chipSelect, SPI_FULL_SPEED);       
    //------------------------------------------------------------------------------------------------
}

Dans cette lib SdFat.h la vitesse est configurée par défaut à Xtal/4, soit 4 Mhz,
en prenant SPI_FULL_SPEED tu fixes la fréquence du SPI à Xtal/2, soit 16 Mhz/2=8 Mhz pour un UNO.

  • Thierry.

Et ça ? :

digitalWrite(MarqPin, HIGH);  // La pin 7 passe à High, débute de cycle d'écriture

myFile.open("test.csv", O_RDWR | O_CREAT | O_AT_END); // Ouverture du fichier 

char buffer[10];

sprintf (buffer, "%d;%d;%d\r\n", ax, ay, az);
myFile.print(buffer);

myFile.close(); // fermeture du fichier
digitalWrite(MarqPin, LOW);  // La pin 7 passe à Low, fin de cycle d'écriture

Booffff... pas vraiment top.

B@tto:
Et ça ? :

digitalWrite(MarqPin, HIGH);  // La pin 7 passe à High, débute de cycle d'écriture

myFile.open("test.csv", O_RDWR | O_CREAT | O_AT_END); // Ouverture du fichier
char buffer[10];
sprintf (buffer, "%d;%d;%d\r\n", ax, ay, az);
myFile.print(buffer);
myFile.close(); // fermeture du fichier
digitalWrite(MarqPin, LOW);  // La pin 7 passe à Low, fin de cycle d'écriture

Non mais sans plaisanter le fonctionnement est hératique, avec des raffales,
je ne vois pas très bien ce que tu as voulu faire.
De toute façon tu peux bien tourner cette chose dans tous les sens,
si tu restes dans la "logique" actuelle je pense qu'il n'y a pas d'amélioration possible,
c'est la lib SdFat.h elle même qui est en cause !?

  • Th.

Justement, l'écriture se faisant par bloc, il n'est pas impensable qu'une optimisation soit possible et qu'écrire une grande chaine d'un coup fasse justement gagner plusieurs bytes par transfert.

C'est comme les RAM sur écran TFT par exemple : tu envois la commande et défini l'adresse de départ dans la RAM et stream ensuite les données et l'adresse en RAM s'incrémente automatiquement au fur et à mesure des envois. Maintenant si tu t'amuses à définir l'adresse, à envoyer les données RGB, définir une autre adresse, charger les données RGB etc ... Tu perds un temps fou. Il n'est pas improbable d'imaginer que du temps soit perdu. Car effectivement, 8 mhz pour 20 bytes ça fait 1µs, donc il y a du temps perdu quelque part ... Mais je ne connais pas bien le protocole des cartes flash et vu la taille de la lib je vais pas me lancer tout de suite dans l'aventure xD

C'est une quelle classe ta SD ?

D'autres piste à explorer : ne pas utiliser digitalWrite() mais une manipulation directe des port te permettra de gratter 40-50 µs

Travailler en binaire aussi au lieu d'utiliser print() : Maximum speed that the Arduino can read an SD card - Storage - Arduino Forum

D'autres trucs : Optimizing SD Card Writes – CalSol

Je pense qu'il y a une mauvaise connaissance du système à la base.

Les échanges de données entre la carte SD et son hôte se font comme pour n'importe quel disque par lecture/écriture de secteurs. Sur la SD un secteur c'est 512 octets. Donc à chaque fermeture du fichier ou lorsque le tampon d'échange est plein la librairie écrit 512 octets dans la carte.
Avec une horloge à 8MHz, il faut 1µs pour envoyer un octet par le SPI. Pour transférer le tampon complet il faut donc, au mieux, 512µs. Comme il y a une boucle avec des tests pour gérer tout ça on comprend un peu mieux que tu trouves dans les 550µs.

Moralité, pour optimiser les transferts entre l'Arduino et la carte mémoire, je pense qu'il serait préférable de travailler par bloc de 512 octets.

Salut,

B@tto:
Justement, l'écriture se faisant par bloc, il n'est pas impensable qu'une optimisation soit possible et qu'écrire une grande chaine d'un coup fasse justement gagner plusieurs bytes par transfert.

Non je t'assure ce n'est pas la solution, les choses sont beaucoup plus complexes que cela...

B@tto:
... Mais je ne connais pas bien le protocole des cartes flash et vu la taille de la lib je vais pas me lancer tout de suite dans l'aventure xD

Je comprend...

B@tto:
C'est une quelle classe ta SD ?

à terme ce serait une classe 10, mais pour l'instant le problème n'est pas là, commençons d'abord par accélérer le code...
car si la durée d'éxécution du code est déjà supérieur à la cadence d'acquisition des données ce n'est même pas la peine d'aller plus loin.

B@tto:
D'autres piste à explorer : ne pas utiliser digitalWrite() mais une manipulation directe des port te permettra de gratter 40-50 µs

Ces lignes de code ne sont là que pour le debugg,, à terme elles doivent disparaître... ( mais pour info les deux digitalWrite() c'est 1.3 µs)

B@tto:
Travailler en binaire aussi au lieu d'utiliser print() : Maximum speed that the Arduino can read an SD card - Storage - Arduino Forum
D'autres trucs : http://calsol.berkeley.edu/news/2011/optimizing-sd-card-writes/

Merci pour ces deux liens je vais regarder dans le détail...

  • Th.

Salut,

et merci pour tes remarques très pertinentes,

fdufnews:
Je pense qu'il y a une mauvaise connaissance du système à la base.

Oui je suis d'accord, en ce qui me concerne c'est le cas, j'ai une connaissance très superficielle des spécifications de cartes SD.

fdufnews:
Les échanges de données entre la carte SD et son hôte se font comme pour n'importe quel disque par lecture/écriture de secteurs. Sur la SD un secteur c'est 512 octets. Donc à chaque fermeture du fichier ou lorsque le tampon d'échange est plein la librairie écrit 512 octets dans la carte.
Avec une horloge à 8MHz, il faut 1µs pour envoyer un octet par le SPI. Pour transférer le tampon complet il faut donc, au mieux, 512µs. Comme il y a une boucle avec des tests pour gérer tout ça on comprend un peu mieux que tu trouves dans les 550µs.

Effectivement, cette l'analyse est interéssante.

fdufnews:
Moralité, pour optimiser les transferts entre l'Arduino et la carte mémoire, je pense qu'il serait préférable de travailler par bloc de 512 octets.

OK, mais que veux-tu dire précisément ?

Je vous rappel que l'objectif intermédiaire est de faire l'acquisition et le transfert en mémoire de 800 acq/sec,
soit une toutes les 1250 µs...
et que l'objectif final serait de transférer 3200 acq/sec, soit une toutes les 312.5 µs !! et sans en rater une si possible...
Je ne suis pas convaincu que la chaîne ADXL345 - I2C - UNO - SPI - µSD soit capable de faire cela,
c'est pour cela que je parlais de pcduino-v3 cadencé à 1 Ghz dans mon premier post.

  • Th.

Ce que dit fdufnews c'est que tu écrives 1 octet ou 512 ça nécessitera le même temps, donc autant stocké le plus de mesure possible dans 512 octet et écrire sur la carte. C'est ce que je te proposais plus haut

lectronlibre:

B@tto:
C'est une quelle classe ta SD ?

à terme ce serait une classe 10, mais pour l'instant le problème n'est pas là, commençons d'abord par accélérer le code...

En fait la classe de la carte est sans importance. Le goulot d'étranglement n'est certainement pas là.
Avec une horloge à 8MHz, tu écris 1 octet par µs soit 1Mo/s ce qui correspond à une classe 1. C'est pas les cadences infernales.

B@tto:
Ce que dit fdufnews c'est que tu écrives 1 octet ou 512 ça nécessitera le même temps, donc autant stocké le plus de mesure possible dans 512 octet et écrire sur la carte. C'est ce que je te proposais plus haut

Excuses-moi je n'avais pas compris ce que tu voulais me dire.

fdufnews:

lectronlibre:

B@tto:
C'est une quelle classe ta SD ?

à terme ce serait une classe 10, mais pour l'instant le problème n'est pas là, commençons d'abord par accélérer le code...

En fait la classe de la carte est sans importance. Le goulot d'étranglement n'est certainement pas là.
Avec une horloge à 8MHz, tu écris 1 octet par µs soit 1Mo/s ce qui correspond à une classe 1. C'est pas les cadences infernales.

Oui je suis d'accord, pour l'instant le problème n'est pas la carte, le problème c'est la façon dont est goupillée la lib SdFat.
(PS: si tu regardes la trame du SPI à l'oscilloscope, tu observes 8 coups d'horloge SPI en 1µs puis rien pendant 3µs, et tu recommences, donc tu ne transferts pas à 1Mo/s mais à 250 Ko/s)

  • Th.

lectronlibre:
Oui je suis d'accord, pour l'instant le problème n'est pas la carte, le problème c'est la façon dont est goupillée la lib SdFat.

Non. Le problème est d'utiliser une carte SD en mode SPI qui est un mode intrinsèquement "lent".

De plus je ne suis pas certain que le fait de faire des essais sans que la carte ne soit présente constitue un test valide, et je ne serais pas étonné que les résultats soit encore plus décevant une fois la carte SD mise en place.

Dans la même veine, il n'est pas du tout certains que le fait de passer l'horloge SPI à 8MHz soit une bonne chose. Le mode SPI est un mode de fonctionnement "dégradé", et certains contrôleurs SPI ont tendance à perdre les pédales si on tente de les forcer à aller trop vite. Du coup un bus à 8MHz est parfois plus lent qu'à 4MHz. C'est d'ailleurs pour ça que la valeur par défaut est 4 et non pas 8. Mais cela dépend des cartes (d'ailleurs les "anciennes" SD " ont tendances à être plus rapides en SPI que les récentes...).