SD-Karte: Dateinamen als String öffnen

Guten Abend Forum!!

Ich laboriere jetzt schon geraume Zeit an einem Problem 'rum, das ich trotz intensivem googeln nicht so richtig lösen kann.

Folgende Situation: Ich möchte eine Datei von einer SD-Karte öffnen. Die entsprechenden Dateinamen sind in einem String-Array gespeichert.

Leider erwartet die Software aber ein char* und lässt sich durch nichts dazu bringen einfach mal meinen String oder eine Konvertierung davon zu fressen.

Ich habe mittlerweile herausgefunden, dass das Thema auch andere beschäftigt hat und in verschiednen Arduino-Versionen unterschiedlich behandelt wurde (Convert String string1 to char* - Syntax & Programs - Arduino Forum). Jedenfalls bin ich momentan bei der Funktion oder besser gesagt der Methode toCharArray hängen geblieben, wende sie aber offensichtlich falsch an.

Hat das jemand von euch schon gemacht und kann mir sagen, wie ich den Compiler überreden kann eine Datei zu öffnen, deren Namen ich als String vorliegen habe?

Hier mein verwendeter Code:

#include <SD.h>

File root, myFile;
char* datei;
String array_dateinamen[12];

void setup()
{
  Serial.begin(9600);
  Serial.print("Initializing SD card...");

  // uno:   4, mega: 53 
  pinMode(4, OUTPUT);

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  root = SD.open("/");
  dateien_anzeigen();
}

void loop()
{
// nothing happens after setup finishes.
}

void dateien_anzeigen()
{
  for (byte i = 0; i < 12; i++)
  {
    if (array_dateinamen[i] != "") 
    {
      array_dateinamen[i].toCharArray(datei, 100);
      myFile = SD.open(datei, FILE_WRITE);
      myFile.close();
    }
  }
}

Vielleicht ist's ja ausnahmsweise mal ganz einfach ...
Ich muss ja echt sagen: Ich mag C, aber C mag MICH nicht. Ständig solche Dinger, die in anderen Sprachen ganz einfach sind ... :confused:

Danke für den Schubs in die richtige Richtung!!

Kuahmelcher!

P.S.: Das Array "array_dateinamen" ist nur scheinbar leer. Ich habe den Teil der Übersichtlichkeit halber 'rausgenommen, damit ihr euch nicht durhc 1000 Zeilen quälen müsst. Es ist aber gefüllt und enthält nur gültige Dateinamen.

Strings in C sind nichts anderes als Character Arrays mit einem NULL am Ende. String Arrays sind zwei-dimensionale Arrays auf char, bzw. die Variable ist ein char**.

Wenn du das machst:
char str[10];

Dann hat str den Typ str* da Arrays Pointer auf das erste Element sind.

Du darfst das nicht mit der Arduino String Klasse verwechseln. Das ist was ganz anderes.

Hier sein ein paar Grundlagen dazu:

Dein Problem hier ist die Kombination aus String Objekten mit C-Strings. Dazu gibt es aber wie du erkannt hast toCharray() zur Konvertierung. Du musst dafür aber einen Puffer anlegen. Nicht einfach einen Pointer der auf nichts zeigt:

char datei[13];
array_dateinamen[i].toCharArray(datei, 12);

Rein in C geht das so:

char dateinamen[12][13] =          //Array aus 12 Strings mit Länge 12 (+NULL)
{
    "file1.txt",
    "file2.txt",
};

for (byte i = 0; i < 12; i++)
{
   if(dateinamen[i] != NULL)
        myFile = SD.open(*dateinamen[i], FILE_WRITE);     //dateiname[i] alleine geht hier nicht, da der Pointer nie NULL ist!
}

*dateinamen[j] ist dabei das gleiche wie dateinamen[j][0]. Der erste char im j-ten string des Arrays.

Zum Befüllen des Arrays muss man dann das machen:
strcpy(dateinamen[4], "file5.text");

Das ist nötig weil das Array einen kontinuierlichem Speicherbereich abdeckt und man daher nicht einfach nur die Pointer kopieren kann.

Man kann string Arrays auch so definieren:

char* dateinamen[12] =
{
    ...
};

Dann hat man aber nur Pointer auf strings die wo anders stehen. Und wenn man das macht:

char str[] = "Test1";
dateinamen[1] = str;
strcpy(str, "Test2");

...steht da am Ende "Test2" drin! Das Array zeigt nur auf eine andere Variable und wenn man diese ändert, ändert sich auch der string. Du muss man auch aufpassen wo der Speicher steht, nicht dass man da Pointer auf Speicher zuweist der am Ende einer Funktion nicht mehr gültig ist.

Dateinamen sind bei den Arduino SD-Libraries immer 8.3 - Namen.
Wenn man ein ganzes Array davon hat, bringt es überhaupt nichts, ein Array von String-Objekten anzulegen,
lieber gleich ein zeidimensionales array wie char name[][13]
Evtl. haben ja alle dieselbe Extension oder können einfacher als durch solch ein Array bestimmt werden.

Deine Voraussetzung, dass du
String array_dateinamen[12];hast, ( woher eigentlich ?) ist dein Problem :wink:

Es ist halt das übliche:

1.) Benutzter finden die String Objekte in der Referenz, welche anfangs relativ einfach zu handhaben sind wenn man von anderen Sprachen kommt
2.) Die meisten Libs und anderen Funktionen erwarten aber C-Strings/char* als Argumente

Dann beginnt die Verzweiflung wie man das konvertiert weil die Arduino Leute das relativ komplizierte und ungewohnte C-String Handling gezielt verstecken.

Serenifly:

char str[] = "Test1";

dateinamen[1] = str;
strcpy(str, "Test2");

Hallo Serenifly,

danke für deine Geduld! Ich bin leider heute nicht dazu gekommen den Code in Gänze einzubauen. Erst mal hat nichts funktioniert, aber dafür brauche ich ZEIIIIIT! Vielleicht heute Nacht. Ich melde mich dann (hoffentlich mit einer Erfolgsmeldung! :))

Ich sehe langsam ein, dass es so nicht weitergeht! Ich muss mir irgendwann die C-Datentypen und deren Konvertierungen mal grundsätzlich aneignen.

Danke und Gruß, kuahmelcher!

Ja, das ist Anfangs gewöhnungsbedürftig. Um das zu Üben würde ich dir auch Visual C++ empfehlen. Da kannst du auch mal auf NULL-Pointer zugreifen und du merkst es gleich. Und du kannst Debuggen.

Den Code den du da kopiert hast war nur zur Veranschaulichung gedacht. Und für die andere Version. Vergiss das :slight_smile:

Du solltest dein Array so definieren:

char dateinamen[12][13] =          //Array aus 12 Strings mit Länge 12 (+NULL)
{
    "file1.txt",
    "file2.txt",
     ....
};

Oder leer einfach:
char dateinamen[12][13];

Dann ist die Frage wie dein Array füllst. Du hast das mal angedeutet mit:

Das Array "array_dateinamen" ist nur scheinbar leer. Ich habe den Teil der Übersichtlichkeit halber 'rausgenommen

Wenn du da mal was postest wie du das füllen willst, da kann man dir auch sagen wie das mit C-Strings geht.

Aber wenn man es nicht beim Initialisieren macht - sondern zur Runtime - muss man in in diesem Fall immer strcpy() nehmen, egal ob die Quelle in Literal oder ein anderer String ist.

Wenn es doch gar nicht geht, kannst du mal das probieren:

char datei[13];
array_dateinamen[i].toCharArray(datei, 12);

Hab das aber nicht getestet. Das ist generell wie in C Strings von anderen Funktionen gefüllt werden. Man kann keine Pointer auf lokale Variablen zurückgeben. Deshalb wird ein Puffer angelegt und der Puffer (bzw. ein Pointer auf ihn) und die Größe als Parameter übergeben. Das ist bei Standard C Funktionen wie sprintf() genauso.

Serenifly:
Wenn es doch gar nicht geht, kannst du mal das probieren:

char datei[13];

array_dateinamen[i].toCharArray(datei, 12);

Hallo Serenifly, hallo alle Helfer.

Dank eurer Hilfe funktioniert mein SD-Kartenzugriff jetzt. Ich habe alle Tipps verwertet und zwei wichtige Fehler 'rausgefunden:

  1. Im Codebeispiel oben habe ich den Puffer nicht als Char-Array angelegt
  2. vollkommen kryptisch und für mich zunächst total unverständlich: Jeder Serial.print-Befehl braucht Arbeitsspeicher!!! Da ich die Werte von der SD-Karte alle über den seriellen Monitor ausgegeben habe ist da eine Menge zusammengekommen und hat mir den RAM blockiert. Leider gab es keine oder keine eindeutige Fehlermeldung dazu.

Falls es der Nachwelt nutzt hier jetzt mein funktionierender Code:

#include <SD.h>

File root, myFile;
int counter = 0;
String dateinamen[20];
char charBuf[13];
char Zeichen;

void setup()
{
	// Serielle Kommunikation starten:
	Serial.begin(115200);

	// **********************************************************
	// Initialisierung der SD-Karte:  
	// **********************************************************

	Serial.print("Initializing SD card...");

	// mega: 53
	// uno:   4
	pinMode(53, OUTPUT);

	if (!SD.begin(53)) {
		Serial.println("initialization failed!");
		return;
	}
	Serial.println("initialization done.");

	root = SD.open("/");

	// **********************************************************

	dateien_einlesen(root);

	dateien_auflisten();

	erste_datei_anzeigen();
}

void loop()
{
  //  Hier passiert man gaaa nix!
}

void dateien_einlesen(File dir) {

	Serial.println("Es wurde die Funktion >>dateien_einlesen<< aufgerufen!");

	counter = 0;

	while(true) 
	{
		File entry =  dir.openNextFile();

		// Wenn keine Datei mehr vorliegt: ABBRECHEN!
		if (! entry) { break; }

		// Wenn eine Datei vorliegt:
		if (!entry.isDirectory()) {

			if (entry.size() > 0 && entry.size() != 4096) {

				// Hier wird mein String-Array gefüllt:
				dateinamen[counter] = entry.name();
				counter++;
			}
		}

		// Datei wieder schließen:
		entry.close();
	}
}

void erste_datei_anzeigen()
{	
	Serial.println("Es wurde die Funktion >>erste_datei_anzeigen<< aufgerufen!");

	counter = 0;

	breite = 0;

	// erst muss aus dem String-Array ein Dateiname 
	// in den Char-Puffer gelesen werden:
	dateinamen[0].toCharArray(charBuf, 13);

	Serial.print("Es wird die folgende Datei geoeffnet: ");
	Serial.println(dateinamen[0]);

	File myFile = SD.open(charBuf);

	if (myFile) 
	{
		while (myFile.available()) 
		{
			// Array, in dem jeweils eine ganze 
			// Spalte rgbwerte-Werte gespeichert werden: 
			rgbwerte[counter][0] = myFile.parseInt();
			rgbwerte[counter][1] = myFile.parseInt();
			rgbwerte[counter][2] = myFile.parseInt();

			// nächste LED
			counter++;
		}

		// Datei schließen:
		myFile.close();
	} else {
		// Falls was schiefläuft:
		Serial.println("Dateifehler!");
	}
}




void dateien_auflisten()
{
	// *********************************

	Serial.println("Es wurde die Funktion >>dateien_auflisten<< aufgerufen!");

	Serial.println("\n+-----------------+");

	byte i = 0;

	while (dateinamen[i]){

		Serial.println(dateinamen[i]);
		i++;

	}  

	Serial.println("+-----------------+\n");

	// *********************************
}

Danke für eure kompetente Hilfe,

Gruß, kuahmelcher!

Ja. String-Literale landen im RAM. Das hängt damit zusammen, dass der AVR eine Harvard-Architektur hat und damit Programm und Daten getrennte Adress-Räume haben. Normale Pointer können also nicht in Flash zeigen

Man kann strings vor allem bei println() aber einfach ins Flash schreiben:
println(F("String im Flash Speicher"));

nennt sich F-Makro. Schon braucht das kein RAM mehr :slight_smile:

kuahmelcher:
rgbwerte[counter][0] = myFile.parseInt();
rgbwerte[counter][1] = myFile.parseInt();
rgbwerte[counter][2] = myFile.parseInt();

Wenn ich mal querlese zu Deinem anderen Thread, dass das Lesen von Werten aus einer Datei auf SD-Karte angeblich so langsam sei, und dann das lese, wie Du offenbar RGB-Werte aus einer Datei liest, wundert mich nichts mehr.

jurs:
Wenn ich mal querlese zu Deinem anderen Thread, dass das Lesen von Werten aus einer Datei auf SD-Karte angeblich so langsam sei, und dann das lese, wie Du offenbar RGB-Werte aus einer Datei liest, wundert mich nichts mehr.

... wie würde es denn deutlich schneller gehen??

Danke und Gruß, kuahmelcher

kuahmelcher:
... wie würde es denn deutlich schneller gehen??

Oh, oh, wenn Du schon so fragst, brauchst Du wohl umfangreiche Erklärungen.

Zwei Dinge fallen mir an den drei Zeilen Einlesecode auf:

  1. Du verwendest das ASCII-Klartextformat zur Speicherung binärer Daten. Das ist zwar gut für Menschen lesebar, z.B. in einem Texteditor, aber zur Abspeicherung von Datensätzen fester Länge verschwendet es massenhaft Speicherplatz. Und je mehr Speicherplatz das Datenformat in der Datei belegt, desto mehr Speicherplatz muß beim Einlesen durch den Arduino geschleust werden. Viel speichersparender in der Datei wäre es, die Daten binär zu speichern.

Beispiel: Du möchtest immer 59 RGB -LED-Werte nacheinander speichern, dann werden bei der binären Speicher in der Datei die RGB-Werte einfach als Bytes nacheinander weggespeichert:

Die ersten 18 Bytes einer Datei:

abcdefghijklmnopqr  <== die binären Daten als Bytes in der Datei gespeichert
RGBRGBRGBRGBRGBRGB  <==  R, G oder B-Wert ergibt sich aus der Position innerhalb der Datei
000111222333444555  <== Nummer der LED ergibt sich ebenfalls aus der Position innerhalb der Datei

D.h. anhand der Position innerhalb der Datei steht für jedes Byte fest, zu welchem Frame der Animation das Byte gehört, zu welcher LED der Anzeigeleiste das Byte gehört und ob es ein R, G oder B-Byte ist.

Nach den Bytes für die ersten 59 LEDs folgen die nächsten für den zweiten Frame etc.

Prinzip einer "Binären Datei mit Datensätzen fester Länge" verstanden?
Kannst Du die Daten in solchen binären Dateien bereitstellen?

  1. Die Anzahl der Dateizugriffe muß minimiert werden.
    Also nicht für jedes einzelne Byte eine Leseoperation auf die Datei anwenden, sondern die Datei möglichst "blockweise" auslesen. Das sind bei SD-Karten immer 512 Bytes am Stück. Also liest Du am besten immer mit EINER EINZIGEN Dateioperation 512 Bytes am Stück in einen Puffer ein, diese werden verarbeitet und erst danach erfolgt die nächste Leseoperation, mit der wieder exakt 512 Bytes eingelesen werden.

Das sind zwei Optimierungsmöglichkeiten, die mir so beim Ansehen Deiner drei Einlesezeilen auffallen, um den Datendurchsatz zu erhöhen.

Wie groß ist denn der notwendige Datendurchsatz, d.h. wie viele Bytes oder RGB-Werte pro Sekunde müssen verarbeitet werden?

jurs:
Oh, oh, wenn Du schon so fragst, brauchst Du wohl umfangreiche Erklärungen.

Tjahaaa, was soll ich sagen ... :slight_smile:

jurs:
D.h. anhand der Position innerhalb der Datei steht für jedes Byte fest, zu welchem Frame der Animation das Byte gehört, zu welcher LED der Anzeigeleiste das Byte gehört und ob es ein R, G oder B-Byte ist.

Kannst Du die Daten in solchen binären Dateien bereitstellen?

OK. Habe ich verstanden. Ich generiere mir mit PHP die RGB-Daten: http://raspberrypi.fmhmamehlauorxfo.myfritz.net/rgb/sd.php (Ich war schon so stolz, dass ich überhaupt an die RGB-Werte komme ...) :slight_smile: Aber mal sehen. Vielleicht geht das ja mit PHP irgendwie ... Umwandeln in Binärzahlen geht wohl mit decbin() aber ich glaube, dass die Datei selbst irgendwie als Binärdatei angelegt werden muss ...?

jurs:
2. Die Anzahl der Dateizugriffe muß minimiert werden.
Also nicht für jedes einzelne Byte eine Leseoperation auf die Datei anwenden, sondern die Datei möglichst "blockweise" auslesen. Das sind bei SD-Karten immer 512 Bytes am Stück. Also liest Du am besten immer mit EINER EINZIGEN Dateioperation 512 Bytes am Stück in einen Puffer ein, diese werden verarbeitet und erst danach erfolgt die nächste Leseoperation, mit der wieder exakt 512 Bytes eingelesen werden.

Das bedeutet, dass ich momentan pro Byte einen Dateizugriff auslöse, sinnvoll wären aber ein Dateizugriff pro 512 Bytes. Sehe ich ein. Weißt du, was nur das Problem sein wird? Wenn dann nach 512 Bytes der Dateizugriff tatsächlich stattfindet wird es einen kleinen Ruckler geben, weil ja dann nicht mit der gleichen Geschwindigkeit "abgespielt" werden kann.

Aber egal. Trotzdem ein wichtiger Hinweis. Dazu habe ich im Arduino Playground auch 'ne Anleitung gefunden. Steht auf meiner ToDo-Liste!! Selbst wenn ich am Ende die Farbwerte aus einem externen RAM-Speicher oder etwas ähnlichem abspiele scheint das sinnvoll zu sein um einfach nicht so lange warten zu müssen bis ein Bild von der SD-Karte geladen ist.

Danke, ich habe wieder was zu tun!!! Gruß, kuahmelcher

kuahmelcher:
OK. Habe ich verstanden. Ich generiere mir mit PHP die RGB-Daten: http://raspberrypi.fmhmamehlauorxfo.myfritz.net/rgb/sd.php (Ich war schon so stolz, dass ich überhaupt an die RGB-Werte komme ...) :slight_smile: Aber mal sehen. Vielleicht geht das ja mit PHP irgendwie ... Umwandeln in Binärzahlen geht wohl mit decbin() aber ich glaube, dass die Datei selbst irgendwie als Binärdatei angelegt werden muss ...?

Wenn Du Deinen Arduino besser programmieren kannst als Dein PHP kannst Du es im Endeffekt auch mit Arduino machen und Dir einen kleinen Sketch erstellen, der Dir Deine Datei "Bild.txt" einliest und eine "Bild.bin" Datei auf die SD-Karte zurückschreibt. Laufzeit ist ja egal, da das nur einmalig zur Vorbereitung der Datei gemacht werden muss.

Dein Programm arbeitet dann mit der viel kleineren Bild.bin Datei, die dann immer eine durch 177 teilbare Dateigröße aufweisen sollte (177 = 59 LEDs mal 3 Bytes RGB).

kuahmelcher:
Das bedeutet, dass ich momentan pro Byte einen Dateizugriff auslöse, sinnvoll wären aber ein Dateizugriff pro 512 Bytes. Sehe ich ein. Weißt du, was nur das Problem sein wird? Wenn dann nach 512 Bytes der Dateizugriff tatsächlich stattfindet wird es einen kleinen Ruckler geben, weil ja dann nicht mit der gleichen Geschwindigkeit "abgespielt" werden kann.

Wegen dem "Ruckler" beim Nachladen: Das kommt ganz darauf an, wie das Programm läuft. Ein MP3-Player macht ja auch keine Ruckler beim Abspielen, wenn er den Sound aus der Datei nachlädt. Es kommt nur auf die richtige Programmlogik an. Mir schwebt da folgendes vor:

a) Du definierst nicht einen, sondern ZWEI Lesepuffer a 512 Bytes für das Lesen von SD-Karte
b) Das Befeuern der LEDs findet in einem Timer-Interrupt alle 2 Millisekunden statt.

Und nun läuft es von der Programmlogik her wie folgt:
Zu Beginn werden beide Lesepuffer vollgeladen und dann der 2ms-Timer-Interrupt zum Abspielen gestartet. Nennen wir die Lesepuffer mal LP0 und LP1.

Eine globale Variable im Programm legt fest, welches gerade der "aktive" Lesepuffer ist, aus dem der Timer-Interrupt die Daten holt. Beginn mit LP0.

Nun feuert der Timer-Interrupt, die ISR holt 177 (593) Bytes aus dem LP0 Puffer, setzt die LEDs.
Nach 2 ms feuert wieder der Timer-Interrupt, die ISR holt 177 (59
3) Bytes aus dem LP0 Puffer, setzt die LEDs.
Das geht 2 mal gut. Beim 3. mal sind nicht mehr genügend Daten in Lesepuffer LP0.
Die ISR holt sich nun die fehlenden Bytes aus dem Lesepuffer LP1, setzt das Flag für "LP1 ist aktiv".

So werkelt der Timer mit seinen Interrupts vor sich hin, holt mal die Daten aus dem einen, dann aus dem anderen Lesepuffer, und setzt jeweils das Flag, welcher Lesepuffer aktiv ist.

Nun zur loop-Funktion: Die loop-Funktion achtet in einer ganz engen Schleife darauf, ob sich das Flag für den Lesepuffer ändert. Sobald das Flag für den Lesepuffer wechselt, lädt die loop-Funktion neue 512 Bytes an Daten in den jeweils nicht aktiven Puffer. Und lauert anschließend wieder darauf, dass das Flag für den aktiven Lesepuffer wechselt.

Das läuft vom Prinzip her vollkommen ruckelfrei. Allerdings läuft es nur wie beabsichtigt, wenn die loop die Daten schnell genug nachladen kann. Zeitabschätzung: Da 2 mal 177 Bytes auf jeden Fall komplett in 512 Bytes hineinpassen, hat die loop-Funktion immer mindestens 2* 2ms = 4 Millisekunden Zeit, um einen 512 Byte großen Puffer nachzuladen.

Genauer gesagt: 4 Millisekunden abzüglich der Zeit, die die 2 Timer-Interrupts zwischendurch verbraten zum Setzen der 59 LEDs jedesmal.

Wenn Du das hinbekommst, kann es von SD-Karte laufen. Und auch ruckelfrei.

Aber ich befürchte: 4 ms sind schon sehr knapp zum Nachladen von SD-Karte.

jurs:
Wenn Du Deinen Arduino besser programmieren kannst als Dein PHP kannst Du es im Endeffekt auch mit Arduino machen

Nee, da ich PHP ja eh brauche um irgendwie an die RGB-Daten 'ranzukommen kann ich die auch sofort in Binärdaten umrechnen.

Ich habe ein bisschen rumprobiert. Ich habe jetzt mit der PHP-Funktion pack() die folgende Bytefolge

46,117,86,58,119,85,58,119,84,56,113,78,50,105,71,44,93,62,41,72,46,29,73,50,36,101,73,49,111,74,47,117,82,54,121,81,56,138,95,61,87,70,54

umgewandelt in:

0000 002e 7556 3a77 553a 7754 3871 4e32
6947 2c5d 3e29 482e 1d49 3224 6549 316f
4a2f 7552 3679 5138 8a5f 3d57 4636

Lediglich die ersten sechs Nullen kommen mir noch "spanisch" vor, aber das kriege ich bestimmt auch noch hin. Jedenfalls kann ich damit jetzt sicherlich was anfangen. Ich brauche kein Komma mehr und muss die Binärzahlen (eigentlich sind es ja Hexadezimalzahlen?) nur noch in 512 Byte-Blöcken auslesen.
Eine ähnliche Datei (nur mit 506 Bytes drin) ist als BIN-Datei 510 Bytes groß und als utf8-Datei 1396 Bytes groß. Das ist doch schon mal was!! Das muss ich alles nicht einlesen - das ist mal der Faktor 2,7!

Wenn die SD-Ausleserei mal schnell funktioniert dann schreibe ich mal, um wieviel Prozent ich die Geschwindigkeit steigern konnte!

jurs:
Wegen dem "Ruckler" beim Nachladen: Das kommt ganz darauf an, wie das Programm läuft. Ein MP3-Player macht ja auch keine Ruckler beim Abspielen, wenn er den Sound aus der Datei nachlädt. Es kommt nur auf die richtige Programmlogik an. Mir schwebt da folgendes vor:

a) Du definierst nicht einen, sondern ZWEI Lesepuffer a 512 Bytes für das Lesen von SD-Karte
b) Das Befeuern der LEDs findet in einem Timer-Interrupt alle 2 Millisekunden statt.

Jurs, das ist lieb von dir aber das kriege ich auf die Schnelle nicht hin. Du merkst ja, dass viele Dinge hier Neuland für mich sind und deshalb fange ich erst mal damit an die Daten bzw. den Einlesevorgang zu verschlanken. Ich beginne also mit den beiden Maßnahmen von deinem vorherigen Post, da steckt ordentlich Potenzial drin. Wenn das mal zuverlässig läuft und BEVOR ich mich um externe Speicherlösungen bemühe probiere ich deinen Vorschlag mit dem Timer-Interrupt aus. Das wäre natürlich die Königsklasse!

Danke und Gruß, kuahmelcher!

kuahmelcher:
Lediglich die ersten sechs Nullen kommen mir noch "spanisch" vor, aber das kriege ich bestimmt auch noch hin.

Ja, sieht gut aus, wenn man sich die ersten sechs Nullen wegdenkt.
2e 75 56 sind 46,117,86, das scheint zu stimmen.

kuahmelcher:
Jedenfalls kann ich damit jetzt sicherlich was anfangen. Ich brauche kein Komma mehr und muss die Binärzahlen (eigentlich sind es ja Hexadezimalzahlen?) nur noch in 512 Byte-Blöcken auslesen.
Eine ähnliche Datei (nur mit 506 Bytes drin) ist als BIN-Datei 510 Bytes groß und als utf8-Datei 1396 Bytes groß. Das ist doch schon mal was!! Das muss ich alles nicht einlesen - das ist mal der Faktor 2,7!

Genau! Mit Speicherung in Binär statt ASCII belegt die Datei viel weniger Platz und beim Einlesen der gesamten Datei braucht man weniger Zeit zum Einlesen, bei fest vorgegebener maximaler Datenrate. Das ist die Idee dahinter.

kuahmelcher:
Wenn die SD-Ausleserei mal schnell funktioniert dann schreibe ich mal, um wieviel Prozent ich die Geschwindigkeit steigern konnte!

Ich habe mal kurz mit der SD-Library getestet, die mit Arduino mitgeliefert wird: Das Ding fällt raus, weil es die geforderte Datenrate nicht bringt.

Meine Tests ergeben, dass mit der standardmäßigen SD-Library maximal 24 bis ca. 27 KBytes pro Sekunde Leserate machbar sind, je nach Chichi beim Einlesen und Timen. Diese SD-Library unterstützt nicht das blockweise Einlesen kompletter 512-Byte Blöcke mit einem Funktionsaufruf, und mit dem zeichenweisen Einlesen ist das Ding nicht schnell genug.

Da müßte man mal eine schnellere SD-Library auftun. So wie ich das sehe, darf die Library ja gerne andere Mankos haben, aber sie muß wenigstens rasend schnell sein und muss das einlesen kompletter 512-Byte Blöcke aus einer Datei ermöglichen.

Mal schauen, was es da an Alternativen zur standardmäßigen SD-Library gibt und um wie viel schneller die dann ggf. sind.

Wenn Du was schnelles findest, kannst Du ja mal Bescheid geben.
Ich würde wohl als erstes mal die "SdFat" Library gegenchecken, was die an Einlesegeschwindigkeit und Funktionen bietet.

Edit/Nachtrag: Der oben gestrichene Absatz stimmt so nicht. Auch die standardmäßige Arduino SD-Library unterstützt ein "buffered read", und das ist viel schneller als das Einlesen einzelner Zeichen in einer Schleife. Wenn ich das richtig überblicke, sind mit "buffered read" etwas über 200 KBytes pro Sekunde mit der SD-Library machbar. Das könnte für Deine Zwecke doch brauchbar sein.

Nachtrag-2: Ich habe nochmal weiter getestet, wie schnell man beim Lesen sein kann. Und zwar mit 177 Byte großen Puffern, die 59 LEDs mal 3 Bytes RGB entsprechen. Das Ergebnis zeigt, dass die Einlesezeit beim Einlesen eines einzelnen 177-Byte-Puffers sehr stark schwanken kann, wenn man eine große Datei liest:

  • Maximale Einlesezeit: 5880 usec,
  • Minimale Einlesezeit: 120 usec
  • Durchschnittliche Einlesezeit: 799 usec
    Was auch klar ist, denn die SD-Karte wird Sektorenweise zu je 512 Bytes gelesen. Mal kommen die 177 Bytes aus einem bereits gelesenen Sektor, das geht schnell. Mal muß ein neuer 512 Byte Sektor eingelesen werden. Und mal muß sogar im FAT-Dateisystem ein neuer FAT-Sektor eingelesen werden um nachzuschauen, welches der nächste zur Datei gehörende Sektor ist.

D.h. "im Durchschnitt" reicht eine Einlesezeit von 0,8 Millisekunden aus, um alle 2 Millisekunden einen kompletten Puffer ausgeben zu können. Aber die maximale Einlesezeit von fast 6 Millisekunden reicht bei weitem nicht, nicht mal mit 2 oder 3 Puffern, um die Darstellung flüssig zu halten. Mein Vorschlag: 8 Puffer a 177 Bytes. Mit einem MEGA sollte das kein Problem sein., und im Schnitt hast Du dann alle 2 Millisekunden im Timer-Interrupt bis zu 1 Millisekunde = 1000 Mikrosekunden Zeit, um die 59 LEDs zu setzen. Das sollte passen. Falls Du Hilfe bei der Umsetzung brauchst, melde Dich, ich habe hier zum Testen der Geschwindigkeit schon einiges zusammengestrickt, was sich ggf. als Ansatz verwenden läßt.

jurs:
Nachtrag-2: Ich habe nochmal weiter getestet, wie schnell man beim Lesen sein kann. Und zwar mit 177 Byte großen Puffern, die 59 LEDs mal 3 Bytes RGB entsprechen. Das Ergebnis zeigt, dass die Einlesezeit beim Einlesen eines einzelnen 177-Byte-Puffers sehr stark schwanken kann, wenn man eine große Datei liest:

  • Maximale Einlesezeit: 5880 usec,
  • Minimale Einlesezeit: 120 usec
  • Durchschnittliche Einlesezeit: 799 usec
    Was auch klar ist, denn die SD-Karte wird Sektorenweise zu je 512 Bytes gelesen. Mal kommen die 177 Bytes aus einem bereits gelesenen Sektor, das geht schnell. Mal muß ein neuer 512 Byte Sektor eingelesen werden. Und mal muß sogar im FAT-Dateisystem ein neuer FAT-Sektor eingelesen werden um nachzuschauen, welches der nächste zur Datei gehörende Sektor ist.

D.h. "im Durchschnitt" reicht eine Einlesezeit von 0,8 Millisekunden aus, um alle 2 Millisekunden einen kompletten Puffer ausgeben zu können. Aber die maximale Einlesezeit von fast 6 Millisekunden reicht bei weitem nicht, nicht mal mit 2 oder 3 Puffern, um die Darstellung flüssig zu halten. Mein Vorschlag: 8 Puffer a 177 Bytes. Mit einem MEGA sollte das kein Problem sein., und im Schnitt hast Du dann alle 2 Millisekunden im Timer-Interrupt bis zu 1 Millisekunde = 1000 Mikrosekunden Zeit, um die 59 LEDs zu setzen. Das sollte passen. Falls Du Hilfe bei der Umsetzung brauchst, melde Dich, ich habe hier zum Testen der Geschwindigkeit schon einiges zusammengestrickt, was sich ggf. als Ansatz verwenden läßt.

Hallo jurs,
vielen Dank für dein Angebot. Ich bin nicht abgetaucht, sondern fleißig dabei die Todo-Liste umzusetzen um die Lesegeschwindigkeit zu erhöhen. Es sieht nämlich - wie du vermutet hast - so aus, als wäre doch alles sehr komfortabel nur mit SD-Karte möglich.

Außerdem habe ich durch die Umstellung von UTF8 auf Binärdaten festgestellt, dass das Grafikformat "BMP" nichts anderes ist als ein kurzer Dateiheader gefolgt von RGB-Daten. Es spricht also nun tatsächlich nichts mehr dagegen den PHP-Teil einzusparen und direkt von einer BMP-Datei zu belichten.

Das direkte Abspielen einer BMP-Datei direkt von einer SD-Karte würde für das RGB-Drucker-Projekt einen Quantensprung in Punkto Komfort bedeuten!!!

Dummerweise steckt der Teufel mal wieder im Detail und ich brauche für alles sehr lange. Dafür ist es aber auch sehr lehrreich und ich freue mich immer diebisch wenn ich ein Schrittchen weiter komme.

Ich habe mich bemüht aus dem Zwischenstand ein lauffähiges Minimalbeispiel zu basteln, hier das Ergebnis (ist trotzdem noch lang, aber es sind jede Menge Kommentare dabei, die BMP-Analyse ist nur angedeutet, der gelöste Teil "Dateoperation" fehlt komplett):

#include <SdFat.h>

// MEGA = 53, UNO = 4!
int chipSelect = 53;

SdFat sd;
SdFile myFile;


#define BUF_SIZE 512
uint8_t buf[BUF_SIZE];


void setup() {
  Serial.begin(9600);
  while (!Serial) {}  // wait for Leonardo
  Serial.println("Press Play on Tape");
  while (Serial.read() <= 0) {}

  if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt();

  // ************************
  // BINÄRDATEN  S C H R E I B E N :
  // ************************

  // ist eigentlich nicht notwendig, kann aber evtl. DOCH notwendig 
  // werden, weil nämlich das Grafikformat BMP von der linken 
  // unteren Ecke ZEILENWEISE aufgebaut ist. Das muss ich 
  // wahrscheinlich noch aufbereiten, weil ich ja SPALTENWEISE abspiele

  if (!myFile.open("binaer.bin", O_RDWR | O_CREAT | O_AT_END)) {
    sd.errorHalt("opening binaer.bin for write failed");
  }

  Serial.print("SCHREIBEN in die Datei binaer.bin ... ");

// Puffer testweise mit Nonsense füllen:
  buf[0] = 66; 
  buf[1] = 77;
  buf[2] = 136;
  buf[3] = 255;

  myFile.write(buf, sizeof(buf));

  // Datei schließen:
  myFile.close();
  Serial.println("fertig!\n\n\n");



  // ************************
  // BMP  L E S E N:
  // ************************

  if (!myFile.open("test3.bmp", O_READ)) {
    sd.errorHalt("opening test3.bmp for read failed");
  }
  // Es werden zunaechst die ersten 54 Bytes der Datei test3.bmp 
  // ausgewertet, weil dass der wichtige Dateiheader ist:

  myFile.read(buf, sizeof(buf));

  if(buf[0] == 66 && buf[1] == 77){

    Serial.print("In Byte 0+1 liegt die BMP-Kennung vor!");

    Serial.print("Bildbreite (Byte 18): ");
    Serial.println(buf[18]);

    Serial.print("Bildhoehe (Byte 22): ");
    Serial.println(buf[22]);

    Serial.print("Byte 10: Die Pixeldaten beginnen bei Byte: ");
    Serial.println(buf[10]);

    Serial.print("Zeilenendbytes: ");

    // Jede Zeile wird mit einer durch vier teilbare Anzahl 
    // Bytes beschrieben. Der Rest wird mit Nullen aufgefüllt! 

    byte tmp = (buf[18]*3) % 4;
    Serial.println(tmp);

    Serial.print("Bits pro Pixel (Byte 28): ");
    Serial.println(buf[28]);
  }  

  // Datei schließen:
  myFile.close();
}

void loop() {  // Tu nix! 
}

Was ich jetzt noch NICHT gemacht habe ist deinen Vorschlag bezüglich mehrerer Puffer anzugehen. Wenn du da ein paar Denkanstöße hättest wäre ich dankbar.

Wobei ich mal echt sagen muss: Ich bin übermüdet, aber mir scheint die Sonne aus dem A... und ich bin echt glücklich mit dem Zwischenstand. :slight_smile: Ich habe mit dem Code gekämpft wie ein Tiger aber ich hatte und habe hier im Forum tolle Hilfe! Lob und Dank an die Helfer!

Gute N8, kuahmelcher!

kuahmelcher:
Außerdem habe ich durch die Umstellung von UTF8 auf Binärdaten festgestellt, dass das Grafikformat "BMP" nichts anderes ist als ein kurzer Dateiheader gefolgt von RGB-Daten. Es spricht also nun tatsächlich nichts mehr dagegen den PHP-Teil einzusparen und direkt von einer BMP-Datei zu belichten.

Verstehe. Ja, das unkomprimierte BMP-Datenformat für 24-Bit RGB-Bilder besteht im Prinzip aus einem Header und danach den RGB-Daten zeilenweise.

kuahmelcher:
Das direkte Abspielen einer BMP-Datei direkt von einer SD-Karte würde für das RGB-Drucker-Projekt einen Quantensprung in Punkto Komfort bedeuten!!!

Das könnte man ggf. in einen AVR-Sketch einbauen: Header überlesen und dann die RGB-Daten auslesen. Das einzige Handycap ist wohl, dass die RGB-Zeilen immer an einer durch 4 teilbaren Bytegrenze ausgerichtet werden. D.h. bei 59 Bytes*3=177 Bytes müßten tatsächlich immer 180 Bytes (durch 4 teilbar) eingelesen werden, wovon 177 Bytes zum Setzen der LEDs verwendet werden.

kuahmelcher:
// ************************
// BINÄRDATEN S C H R E I B E N :
// ************************

// ist eigentlich nicht notwendig, kann aber evtl. DOCH notwendig
// werden, weil nämlich das Grafikformat BMP von der linken
// unteren Ecke ZEILENWEISE aufgebaut ist. Das muss ich
// wahrscheinlich noch aufbereiten, weil ich ja SPALTENWEISE abspiele

Normalerweise ist es nicht notwendig, die Datei gesondert aufzubereiten, wenn Du das Bild in einem Grafikprogramm entsprechend abspeicherst: Du kannst ein im falschen Zeilen/Spalten-Format vorliegendes Bild durch Drehen um 90 oder 270 Grad immer so hinbekommen, dass die Spalten/Zeilen passen. Unter Windows ab WIN7 kann das mit dem Drehen sogar die "Bildanzeige" erledigen, so dass gar kein richtiges Grafikprogramm dafür starten braucht..

kuahmelcher:
Was ich jetzt noch NICHT gemacht habe ist deinen Vorschlag bezüglich mehrerer Puffer anzugehen. Wenn du da ein paar Denkanstöße hättest wäre ich dankbar.

Das mit dem Einlesen der Puffer muß in der loop gemacht werden. Deklaration bei mir:

#define BUF_SIZE 177   // 59 x 3 Bytes RGB
byte buffers[8][BUF_SIZE];
volatile byte bufferstatus=0b00000000; // eight bits for eight buffers
File file;

Ich habe 8 Puffer a 177 Bytes und eine Statusvariable "bufferstatus", die ich als "volatile" deklariert habe.
Jedes einzelne Bit in bufferstatus steht für den Zustand eines 177-Byte Puffers ("0=Bytes gelesen", "1=LEDs gesetzt").
Das Lesen der Puffer erfolgt in der loop (und ist etwas holperig/unregelmäßig).
Das Setzen der LEDs in einem Timer-Interrupt (exakt alle 2 Millisekunden).

Die Puffer werden in der loop eingelesen, und jedes Bit in "bufferstatus" steht für den Status eines Puffers, wobei ich es so handhabe: Sobald in der loop ein Puffer eingelesen wird, wird das entsprechende Statusbit in bufferstatus auf "0" gesetzt. D.h. wenn alle acht Puffer eingelesen sind, ist bufferstatus=0b00000000;

Das Abspielen der Pufferinhalte erfolgt in einer Timer-Interruptroutine, daher ist bufferstatus auch "volatile" deklariert: Der Timer-Interrupt wird programmiert, dass er alle 2 Millisekunden läuft. Immer wenn ein Timerinterrupt auftritt, wird einer der Puffer verwendet, um die 59 LEDs zu setzen, und danach wird das zu diesem Puffer gehörende Bit in bufferstatus auf "1" gesetzt.

Die loop kann nun ihrerseits erkennen, welche Bits nacheinander alle auf "1" wechseln und macht nun während des Abspielens nichts anderes als: Wenn ein Bit in der Variablen bufferstatus auf "1" wechselt, lade den Puffer neu und lösche das Bit des dazugehörenden Puffers wieder auf "0". So lange, bis die Datei zuende abgespielt ist.

Nachtrag:
Ich habe mal etwas mit dem Einlesen von 24-Bit BMP-Bilddateien experimentiert.
Also das direkte einlesen unkomprimierter 24-Bit RGB BMPs in Arduino funktioniert.

Wie von Dir schon geschrieben, entsprechen die ersten gelesenen Bytes einer BMP-Datei "links unten" im Bild, die letzten Bytes in der Datei "rechts oben". D.h. Du müßtest Deine BMP-Datei einmal um 90 Grad nach links drehen, damit sie in der gewünschten Reihenfolge abgespielt werden kann. Also statt z.B. Bildgröße 1000 59 (BreiteHöhe) das Bild einmal um 90 Grad nach links drehen auf 591000 (BreiteHöhe), dann passt es.

Als Anhang ein experimenteller Code zum Einlesen von 59 Pixel breiten 24-Bit RGB BMP-Dateien.
Das ist nur Proof-of-Concept Code, es sind noch keine Timer-Interrupts drin, sondern nur ein Test zum Auslesen.

Da BMP-Bildzeilen immer auf vollen 4-Byte Grenzen gespeichert werden, wird ein 59 Pixel breites RGB-Bild (177 RGB-Bytes) immer in 180 Bytes breiten Zeilen gespeichert (180 ist die nächste durch 4 teilbare Zahl).

Als Dateipuffer habe ich ein "union" angelegt, das gleichermaßen zum Einlesen

  • des Datei-Headers
  • des BMP-Headers
  • der Bildzeilen
    dienen kann. Von den acht deklarierten Puffern für Bildzeilen wird in diesem Programm nur der erste genutzt.

So wie ich den Code drangehängt habe, wird beim Auslesen nur ein Geschwindigkeitstest gemacht und Du kannst die maximale Leserate ermitteln.

Wenn Du die Kommentarstriche vor
// if (bildbuffers.buffers[0][start+2]==255) digitalWrite(13,true); else digitalWrite(13,false);
entfernst, kannst Du sehen, wie die Geschwindigkeit einbricht, wenn mit den gelesenen Daten etwas gemacht wird, z.B. per digitalWrite eine LED ein- und ausschalten. Die Leserate bricht mit digitalWrite sofort weit unter 90 kB/s ein. D.h. wenn Du LEDs mit 90 kB/s setzen möchtest, müßtest Du dafür eine Möglichkeit (Fast SPI?) haben, die deutlich(!) schneller ist als digitalWrite.

Wenn Du die Kommentarstriche vor
// #define DEBUG
entfernst, werden alle Bytes mit ausgelesen und über Serial angezeigt.
(Dann wird's trotz Baudrate von 11520 richtig langsam, weil die Datenrate der seriellen Schnittstelle bremst.)

Wie setzt Du denn die LEDs auf ihre RGB-Werte, wie schnell ist das?
Mit welchem Datendurchsatz kannst Du die LEDs einstellen, wenn nichts aus Datei gelesen werden muß?
Du müßtest auf jeden Fall die 59 LEDs in weniger als einer Millisekunde auf ihre RGB-Werte setzen können, sonst reicht es von der Geschwindigkeit her nicht.

BMPread.ino (5.46 KB)

jurs:
Das könnte man ggf. in einen AVR-Sketch einbauen: Header überlesen und dann die RGB-Daten auslesen. Das einzige Handycap ist wohl, dass die RGB-Zeilen immer an einer durch 4 teilbaren Bytegrenze ausgerichtet werden. D.h. bei 59 Bytes*3=177 Bytes müßten tatsächlich immer 180 Bytes (durch 4 teilbar) eingelesen werden, wovon 177 Bytes zum Setzen der LEDs verwendet werden.

Ja, ich hab's jetzt so gelöst: Im Bildheader steht in Byte 18/19 die Bildbreite und in Byte 22/23 die Höhe drin. Ich nehme einfach die Breite * 3 und fange mit 'ner While-Schleife an hochzuzählen. Irgendwann ist das Ergebnis durch 4 teilbar (Modulo) und DAS speichere ich ab als Variable dats_bytes_pro_zeile. Funktioniert hervorragend. Bei 59 * 3 = 177 ist dann eben das erste durch 4 teilbare Ergebnis 180.

jurs:
Normalerweise ist es nicht notwendig, die Datei gesondert aufzubereiten, wenn Du das Bild in einem Grafikprogramm entsprechend abspeicherst: Du kannst ein im falschen Zeilen/Spalten-Format vorliegendes Bild durch Drehen um 90 oder 270 Grad immer so hinbekommen, dass die Spalten/Zeilen passen.

Ja, das mache ich jetzt auch so. Anfangs habe ich das als "unschön" empfunden, aber das ist mir jetzt egal. Der Aufwand rechtfertigt keine selbstprogrammierte Drehung des Bitmaps.

jurs:
Das mit dem Einlesen der Puffer muß in der loop gemacht werden. Deklaration bei mir:

Das habe ich jetzt noch nicht implementiert - aber was soll ich sagen: Die beiden Verbesserungen

  • Binärdaten nutzen
  • Lesezugriffe minimieren

haben dazu geführt, dass ich jetzt schon bei ca 2 ms pro Spalte angekommen bin. Das ist fantastisch!! Wenn endlich meine neue Hardware da ist und nichts mehr zu verbessern ist, dann überlege ich mir, ob ich den Ansatz mit den Puffern nicht noch für schnellere Gefährte einbaue (Auto, Modellflugzeug usw.), aber eigentlich bin ich was die Geschwindigkeit angeht jetzt schon am Ziel.

Also: Ich widerrufe öffentlich: SD IST NICHT LANGSAM!!! SD ist schnell, wenn man weiß wie man zugreifen muss. :slight_smile:

Ich habe auch gerade ein bisschen 'rumprobiert: Ich konnte eine Datei von 59 x 10.000 Pixeln fehlerfrei und in akzeptabler Geschwindigkeit abspielen! Super!!

jurs:
Nachtrag:

Als Anhang ein experimenteller Code zum Einlesen von 59 Pixel breiten 24-Bit RGB BMP-Dateien.
Das ist nur Proof-of-Concept Code, es sind noch keine Timer-Interrupts drin, sondern nur ein Test zum Auslesen.

Als Dateipuffer habe ich ein "union" angelegt, das gleichermaßen zum Einlesen

  • des Datei-Headers
  • des BMP-Headers
  • der Bildzeilen
    dienen kann. Von den acht deklarierten Puffern für Bildzeilen wird in diesem Programm nur der erste genutzt.

So wie ich den Code drangehängt habe, wird beim Auslesen nur ein Geschwindigkeitstest gemacht und Du kannst die maximale Leserate ermitteln.

Wenn Du die Kommentarstriche vor
// if (bildbuffers.buffers[0][start+2]==255) digitalWrite(13,true); else digitalWrite(13,false);
entfernst, kannst Du sehen, wie die Geschwindigkeit einbricht, wenn mit den gelesenen Daten etwas gemacht wird, z.B. per digitalWrite eine LED ein- und ausschalten. Die Leserate bricht mit digitalWrite sofort weit unter 90 kB/s ein. D.h. wenn Du LEDs mit 90 kB/s setzen möchtest, müßtest Du dafür eine Möglichkeit (Fast SPI?) haben, die deutlich(!) schneller ist als digitalWrite.

Wenn Du die Kommentarstriche vor
// #define DEBUG
entfernst, werden alle Bytes mit ausgelesen und über Serial angezeigt.
(Dann wird's trotz Baudrate von 11520 richtig langsam, weil die Datenrate der seriellen Schnittstelle bremst.)

Wie setzt Du denn die LEDs auf ihre RGB-Werte, wie schnell ist das?
Mit welchem Datendurchsatz kannst Du die LEDs einstellen, wenn nichts aus Datei gelesen werden muß?
Du müßtest auf jeden Fall die 59 LEDs in weniger als einer Millisekunde auf ihre RGB-Werte setzen können, sonst reicht es von der Geschwindigkeit her nicht.

Wau - super, danke! Ich werde den Code mal genau unter die Lupe nehmen um vielleicht an der einen oder anderen Stelle zu optimieren.

Die LEDs spreche ich mit FastSPI an, was sich als absolut geeignet erwiesen hat. Bei Schriftzügen kommt's mir auch auf die typografischen Besonderheiten an, da bin ich sehr genau. Und das bedeutet z.B. dass die Verhältnisse von Höhe und Breite eines Buchstabens gewahrt bleiben müssen und keine sonstige Verzerrung entstehen darf. Das ist damit astrein möglich. Den Datendurchsatz kann ich gar nicht benennen, aber die Verzögerung hat (als ich noch alles aus dem Arduino-Speicher gelesen habe) überhaupt keine Rolle gespielt. Wenn ich nicht künstlich mit delay() eine Verzögerung eingebaut habe, dann hat die LED-Zeile nur einmal geflackert und das wars (ich will damit sagen, dass das Bild extrem schnell abgespielt wurde).

Das Ganze funktioniert jetzt SO gut, dass ich versucht bin, noch einen Meter mit 60 LEDs dranzulöten ... Dann brauche ich sicher die Pufferlösung ... :slight_smile:

Danke und Gruß, kuahmelcher!

kuahmelcher:
haben dazu geführt, dass ich jetzt schon bei ca 2 ms pro Spalte angekommen bin. Das ist fantastisch!!

Leider kommst Du nur "im Schnitt" auf unter 2ms. Das Einlesen einer Bildzeile dauert zwischen 0,12 und 5,9 ms. Die zum Einlesen von 180 Bytes benötigte Zeit ist stark schwankend.

D.h. wenn Du das Einlesen der RGB-Bits und das Setzen der LEDs beides in der loop machst, und die Anzeigedauer ist weniger als 6ms, dann bekommst Du nur schwankende Anzeigedauern realisiert. Z.B. bei einer Soll-Anzeigedauer von 2ms werden zwar viele Bildzeilen 2ms lang dargestellt (ein notwendiges delay kann man über delayMicroseconds() steuern), aber wenn das Nachladen von SD mal wieder 6 ms dauert, werden unvermeidbar einzelne Bildzeilen auch 6 ms lang angezeigt.

Eine gleichmäßige Anzeigedauer kannst Du nur mit Timer-Interrupts erreichen

  • in der loop werden mehrere Zeilenpuffer vollgeladen (mit schwankender Lesegeschwindigkeit)
  • ein Timer-Interrupt alls 2 ms stellt die LED-Zustände dar (mit gleichbleibender Abspielgeschwindigkeit)

kuahmelcher:
Ja, ich hab's jetzt so gelöst: Im Bildheader steht in Byte 18/19 die Bildbreite und in Byte 22/23 die Höhe drin. Ich nehme einfach die Breite * 3 und fange mit 'ner While-Schleife an hochzuzählen. Irgendwann ist das Ergebnis durch 4 teilbar (Modulo) und DAS speichere ich ab als Variable dats_bytes_pro_zeile. Funktioniert hervorragend. Bei 59 * 3 = 177 ist dann eben das erste durch 4 teilbare Ergebnis 180.

Ich habe einfach Konstanten definiert, die der Präprozessor ausrechnet:
#define NUMLEDS 59
#define LEDBUF_SIZE NUMLEDS3 // 177 Bytes RGB
#define BMPBUF_SIZE (LEDBUF_SIZE+3)/4
4 // 180 Bytes per line in image file

Diese definierten Konstanten rechnet der Präprozessor aus, der Compiler verwendet dann direkt 59, 177 und 180 als Konstanten.

kuahmelcher:
Also: Ich widerrufe öffentlich: SD IST NICHT LANGSAM!!! SD ist schnell, wenn man weiß wie man zugreifen muss. :slight_smile:

Ja, man hat zwar nicht so viel Datendurchsatz wie bei RAM-Speicher, aber bis über 200 kByte/s sind auch mit SD-Speicher als Leserate möglich.

jurs:
Leider kommst Du nur "im Schnitt" auf unter 2ms. Das Einlesen einer Bildzeile dauert zwischen 0,12 und 5,9 ms. Die zum Einlesen von 180 Bytes benötigte Zeit ist stark schwankend.

D.h. wenn Du das Einlesen der RGB-Bits und das Setzen der LEDs beides in der loop machst, und die Anzeigedauer ist weniger als 6ms, dann bekommst Du nur schwankende Anzeigedauern realisiert. Z.B. bei einer Soll-Anzeigedauer von 2ms werden zwar viele Bildzeilen 2ms lang dargestellt (ein notwendiges delay kann man über delayMicroseconds() steuern), aber wenn das Nachladen von SD mal wieder 6 ms dauert, werden unvermeidbar einzelne Bildzeilen auch 6 ms lang angezeigt.

Ja, so langsam komme ich auch dahinter. Ich teste halt hier in Auerbachs Keller herum und habe noch kein Bild aufgenommen - da wär's wahrscheinlich sofort aufgefallen. :~ Ein Test mit millis() sagt aber unbestechlich: Du hast Recht, da sind Probleme zu erwarten. Zwar kann ich die Anzeigedauer auf ca 5,7 ms nivellieren, aber dann brauche ich quasi nicht mehr mit dem Fahrrad zu fahren - oder ich fahre nur noch gut 3 Km/h, dann würd's gehen. Humpf. Also doch den Multipuffer mit dem Timer.

jurs:
Eine gleichmäßige Anzeigedauer kannst Du nur mit Timer-Interrupts erreichen

  • in der loop werden mehrere Zeilenpuffer vollgeladen (mit schwankender Lesegeschwindigkeit)
  • ein Timer-Interrupt alls 2 ms stellt die LED-Zustände dar (mit gleichbleibender Abspielgeschwindigkeit)

Ja, klingt prima. Leider waren meine ersten Timer-Tests ernüchternd. Die Libs beißen sich irgendwie mit meinen anderen Bibliotheken, ich muss da morgen noch einmal testen. Die Beispiele funktionieren, mein Code nicht. :0 Ich habe bisher TImer1 und FlexyTimer2 ausprobiert.

jurs:
Ich habe einfach Konstanten definiert, die der Präprozessor ausrechnet:
#define NUMLEDS 59
#define LEDBUF_SIZE NUMLEDS3 // 177 Bytes RGB
#define BMPBUF_SIZE (LEDBUF_SIZE+3)/4
4 // 180 Bytes per line in image file

OK, ich würd's gerne so lösen, dass die Bildhoehe eingelesen wird und daraus die Variable Byte_pro_Zeile errechnet wird. Das würde ja nicht funktionieren - weil die Konstante ja dann eine Variable wäre! :slight_smile: Das hätte nämlich den Vorteil: Wenn ich irgendwann die LED-Zeile verlängern sollte, dann müsste ich einfach nur andere Bilddateien füttern und nicht den Sketch umschreiben. Deswegen versuche ich möglichst mit Variablen auszukommen die ich aus dem BMP-Header entnehme.

Ich habe übrigens heute mal deinen Code studiert - sehr interessant! Ich verstehe nicht alles, aber sehr lehrreich. Ich würde dir gerne dazu noch einmal ein paar allegemeine Fragen stellen, wenn's erlaubt ist! Ich glaube nämlich, dass du viele Dinge sehr viel direkter, kürzer und eleganter löst als ich. Muss aber erst noch einmal eintauchen!

Danke und Gruß, kuahmelcher!