Bon, donc c’est parti pour les explications sur le pourquoi du comment
[attention ça va être trèèèès long]
Si je résume tes tests avec leurs résultats en termes de vitesse d’écriture, ça donne ça :
- 1 :
SD.open(FILE_WRITE) + Serial(9600) ? 30 ech/s
- 2 :
SD.open(O_WRITE|O_CREAT) + Serial(9600) ? 145 ech/s
- 3 :
SD.open(O_WRITE|O_CREAT) + Serial(115200) ? 1300 ech/s
- 4 :
SD.open(O_WRITE|O_CREAT) + pas de Serial ? 2300 ech/s
- 5 :
SD.open(FILE_WRITE) + pas de Serial ? 30 ech/s
Comme tu l’as deviné toi-même, envoyer des données via la liaison série est un moyen de debug bien pratique, mais qui peut introduire des délais dont il faut savoir tenir compte. Cependant, la comparaison des tests 1 et 5 montre que ce n’est pas le principal facteur limitant de ton code initial.
Je vais quand même commencer par là parce que c’est peut-être le plus facile à comprendre. Avec une configuration «standard» (que tu utilises parce que tu n’as rien paramétré de spécial), la liaison est en 8N1, ce qui signifie que pour chaque octet envoyé on a :
- 1 bit de start
- 8 bits de données
- aucun bit de parité (le N)
- 1 bit de stop
Soit 10 bits qui circulent sur la ligne pour chaque octet transmis. À 9600 bit/s il est donc possible d’envoyer 9600/10=960 octet/s.
Maintenant, puisque tu utilises Serial.println pour transmettre un entier de 16 bits, c’est sa représentation ASCII qui va transiter, pas la représentation binaire : la majeure partie de tes données étant constitué de nombre à 5 chiffres, chaque appel à println va donc envoyer 5 octets, plus le caractère retour chariot et le caractère saut de ligne, soit 7 octets en tout.
En reprenant le calcul précédent, on en déduit que tu peux envoyer un maximum de 960/7=137 échantillons par seconde. Si on tient compte des approximations que j’ai faites (ie que les nombre sont toujours composés de 5 chiffres, ce qui n’est pas tout à fait vrai), on retrouve bien là la butée que tu as atteinte dans le test numéro 2. On pourrait refaire le même calcul et aboutir à la même conclusion pour le test 3 (toujours aux approximations près, c’est l’ordre de grandeur qui est utile ici, pas la valeur exacte).
Venons en désormais au problème principal, qui est la vitesse d’écriture sur la carte SD. La première chose à savoir est que la communication via le bus SPI est en quelque sorte un mode de fonctionnement dégradé : les lecteurs de cartes/PC/APN/… utilisent en général un bus parallèle à 4 bits beaucoup plus rapide, mais qui nécessite un contrôleur spécifique qu’à ma connaissance aucun microcontrôleur 8bit ne possède. Tout ça pour dire qu’il est illusoire d’espérer atteindre les vitesses de transferts «nominales» des cartes avec un petit atmega. Il n’en reste pas moins que 30 ech/s c’est plutôt très lent, et il y a donc forcément moyen de faire mieux…
La communication avec les cartes SD est assez complexe à mettre en oeuvre, et si on ne veut pas s’embêter avec les détails, il est nécessaire d’utiliser des librairies toutes faites. sdfatlib est une librairie gérant les fichiers et le système de fichiers présents sur la carte. SD est une surcouche à sdfatlib permettant de simplifier encore plus le travail du développeur, mais bien sûr cette simplification a un coût.
Par défaut, la librairie SD est configurée pour écrire les données qu’on lui transmet immédiatement sur la carte. D’un côté c’est pratique parce que cela évite notamment à l’utilisateur/développeur d’avoir à gérer le vidage du tampon, mais de l’autre l’impact sur la vitesse d’écriture est important.
Comme l’a indiqué @fdunews un petit peu plus haut, l’accès aux données d’une carte SD se fait par blocs de 512 octets. Ce qui signifie qu’à chaque fois que l’on souhaite écrire 1 octet, l’ensemble du bloc doit être lu, modifié, puis retransmis en entier. Mais la modification des données d’un fichier modifie aussi le système de fichier : le bloc de données correspondant doit donc lui aussi être lu puis retransmis dans sa totalité. Donc pour résumer, à chaque fois que l’on modifie 1 seul octet sur la carte, il y a 2048 octets qui circulent sur le bus ! Ouch. Et ceci sans même compter les octets de commande qui doivent également être envoyé à la carte pour lui expliquer ce que l’on attend d’elle - mais qui au final comptent pour quantité négligeable dans ce flot de données.
Pire encore : tes échantillons sont des entiers de 16 bits qui sont transmis via println, c’est donc leur représentation ASCII qui circule, et qui comme indiqué dans la partie communication série est en majorité composée de chaînes 7 caractères. Or comme je l’ai dit plus tôt, la librairie SD écrit sur la carte chaque donnée qu’elle reçoit immédiatement : les 7 caractères ne vont donc pas être envoyés «en bloc» mais écrit les uns après les autres, soit 7x2048=14336 octets transmis sur le bus SPI pour chaque échantillons !
Pour résumer, chaque fois que tu envoie vers la carte SD un de tes échantillons qui dans l’AVR tiennent sur seulement 2 octets, il y a au bas mot 14400 octets qui sont transférés… Je te laisse calculer le rendement du bouzin, mais c’est à peu près aussi bon que les panneaux solaires chers à @Batto 
Faisons un petit calcul : par défaut la librairie SD configure l’horloge du périphérique SPI à 4MHz :
- 4 Mbit/s / 8 = 500000 octet/s
- 500000 / (7*2048) = 35 ech/s
Encore une fois, on retrouve (comme par magie) la butée qui a été atteinte dans les test 1 et 5 (aux approximations près, comme toujours).
La modification que j’ai proposée, et qui consiste à utiliser O_WRITE|O_CREATE à la place de FILE_WRITE revient à dire à la librairie de n’écrire physiquement sur la carte que lorsque c’est strictement nécessaire, par exemple lorsque le tampon est plein ou que l’on change de bloc, etc. Ce qui signifie que dans le code que je t’ai mis, la section qui contient dataFile.flush() n’est normalement pas nécessaire, mais comme je le disais je n’ai pas les moyens de tester actuellement.
Une dernière note avant de finir : tel qu’il est mon code n’est bon que pour faire des tests, en conditions réelles il faudrait prévoir un moyen de sortir de la boucle while (bouton+interruption par exemple) et qui ensuite ferme le fichier, sinon les derniers échantillons acquis mais non encore physiquement écrit sur la carte SD seraient perdus.
Voilà, j’arrête là parce que ça commence à être beaucoup trop long. J’avais plus ou moins prévu de répondre également au sujets des buffers (tampons) puisque tu a posé la question, mais je ne vais pas non plus écrire un roman
Pour faire court : les 2 librairies SD et Serial utilisent déjà des tampons mais ils sont globalement inopérants ici parce ce que ça ne correspond pas vraiment à ton problème/besoin. Et si tu veux chercher par toi-même : dans le graph du test 2 que tu as publié, les petits «plateaux» que tu vois au tout début et après chaque remise à zéro du compteur, c’est l’effet du tampon de Serial.