der Soll Zustand meines Arduino Projektes ist der folgende:
Dem Nutzer wird eine mp3 Datei mit einer Zielfrequenz vorgespielt. Dieser muss anschließend singend/pfeiffend/summend ebendiese Zielfrequenz treffen. Das verwendete Mikro ist ein MAX4466, die FFT Funktion ist für meine Zwecke hinreichend genau. Ferner ist es problemlos möglich das ganze auf dem LCD Display anzuzeigen.
Die gewünschte Erweiterung des ganzen, war den Frequenzpegel nicht nur als Zahl, sondern auch als Linienpegel auf einer 9x9 Dotmatrix mit MAX7219 Modul darzustellen. Ab diesem Zeitpunkt kollabiert das Projekt. Von einem Offsetfehlers meiner Frequenz um 600Hz bis zur schlichten Nichtansteuerbarkeit der Matrix bei gleichzeitiger Zerstörung der Frequenzerkennung war da jeder denkbare und undenkbare Fehler dabei. Anbei mein Code, vielleicht kann mir jemand weiterhelfen.
Trenn die Aufgaben und sorge dafür, dass jede Einzelaufgabe funktioniert. Kannst du die Frequenzberechnung weglassen und einfach einen hochlaufenden Zähler auf der Anzeige als Balken ausgeben?
Ab diesem Zeitpunkt kollabiert das Projekt.
Ich tippe auf einen amoklaufenden Pointer...
BTW: Diese level1..8 Bitmustertabellen kann man übrigens auch ganz einfach in Echtzeit berechenen. Dadurch wird die Funktion LedMatrix() auf einemal ganz kurz.
BTW2: Falls dein LCD flackert, dann liegt es am lc.clearDisplay(0). Besser ist es, wenn du nur den Cursor platzierst und die angezeigten Buchstaben überschreibst.
BTW3: welchen Arduino verwendest du? Wenn es einer der AVR-dinos ohne FPU ist, dann sind float und double gnadenlose Performance-Fresser. Und sonst in den meisten Fällen einfach unnötig. Kann dein µC eigentlich double? Oder macht er double automatisch auch in float mit nur 6 signifikanten Vorkommastellen?
Trenn die Aufgaben und sorge dafür, dass jede Einzelaufgabe
funktioniert. Kannst du die Frequenzberechnung weglassen und einfach
einen hochlaufenden Zähler auf der Anzeige als Balken ausgeben?
Ja. Dies habe ich bereits getestet. Es scheitert immer nur an der Fusion der Programmteile - dabei auch völlig unabhängig was die DotMatrix machen soll; auch eine simple void Funktion, bei der alle LED's leuchten sollen, erzeugt Probleme.
Ab diesem Zeitpunkt kollabiert das Projekt.
Ich tippe auf einen amoklaufenden Pointer...
Gut möglich. Bei mir hat sich mittlerweile nur leider etwas Betriebsblindheit eingestellt. Durch den fehlenden Debug-Modus habe ich mir auch schon zahlreiche Zwischenwerte auf den seriellen Monitor ausgeben lassen. Leider hat das aber bisher noch keinerlei Verständnis dafür geschaffen, wo mein Fehler liegen könnte.
BTW2: Falls dein LCD flackert, dann liegt es am lc.clearDisplay(0).
Besser ist es, wenn du nur den Cursor platzierst und die angezeigten
Buchstaben überschreibst.
Großartiges Flackern war nicht, habe es trotzdem behoben.
BTW3: welchen Arduino verwendest du? Wenn es einer der AVR-dinos ohne
FPU ist, dann sind float und double gnadenlose Performance-Fresser.
Der mega328 hat 2 kB SRAM. Allein deine 2x 128 double-Arrays brauchen 1kB davon. Dazu noch 64 Bytes für die Pixel-Arrays und der Puffer für die RS232-Schnitte. Und auch sonst sind auch noch einige speicherfressende Variablen im Spiel.
Sieh dir mal den Speicherverbrauch an. Und auch, was die FFT an lokalen Variablen auf dem Stack anlegt.
Ich hätte gesagt, das die Datenübertragung zur Matrix zu lange dauert, weil das ja auch bei einem fixen Bild passiert.
Dadurch kommt die FFT ins schleudern...
Mach mal eine einzelne LED an einen Pin, und lass die in der Wartezeit auf die FFT-Startzeit leuchten, danach aus.
Dann siehst du: ohne das Display leuchtet sie ein bissel, weil Zeit überbleibt, mit Display leuchtet sie nicht weil der Startzeitpunkt überschritten wurde.
Oder: mach sie an, wenn der Startpunkt überschritten war. Fehlerlampe "CPU-Load >100%" sozusagen.
Ich hätte gesagt, das die Datenübertragung zur Matrix zu lange dauert,
weil das ja auch bei einem fixen Bild passiert.
Dadurch kommt die FFT ins schleudern...
Da läuft nichts nebenher. Das läuft völlig unabhängig und streng sequentiell:
Samples einlesen
FFT
nach dem 5. Durchlauf: Mittelwertbildung und Ausgabe
weiter bei 1.
Fehlerlampe "CPU-Load >100%" sozusagen.
Die CPU-Load ist bei diesem Programmierstil immer genau 100%. Das Ganze wird einfach mit längerem Programm immer langsamer. Es wird jetzt schon nicht arg schnell sein...
Da läuft nichts nebenher. Das läuft völlig unabhängig und streng
sequentiell:
Ja genau.
Und zwischen Einlesen und FFT ist ein Warter, der dafür sorgt das die Samples zur richtigen Zeit genommen und FFTt werden.
Wenn das Display jetzt aber zu lange braucht so das der Warter ein Durchläufer wird, stimmt die Samplefrequenz nicht mehr, oder?
Wenn das Display jetzt aber zu lange braucht so das der Warter ein
Durchläufer wird, stimmt die Samplefrequenz nicht mehr, oder?
Nein, weil die Zeit für die Abtastfrequenz in den Zeilen 60+65 gemacht wird.
Die "nächste" Runde an Samples wird eben zu einem anderen Zeitpunkt genommen.
Aber das dürfte egal sein. Wenn es nicht egal ist, dann hätte man auch in der Version ohne Punktdisplay keine Zeile mehr einfügen oder ändern dürfen (z.B. serielle Schnitte oder LCD).
Lothar M. schrieb:
Die CPU-Load ist bei diesem Programmierstil immer genau 100%.
Ich hoffe du veräppelst mich.
Nein, es ist genau so: immer dann wenn die CPU mit irgendwas fertig ist, muss sie den nächsten Befehl ausführen. Sie hat auch keine Pause und sie muss auch nicht irgendwie "schnell genug" fertig sein. Je nach Durchlaufzeit ergibt sich dann eben eine andere Aktualisierungsrate auf dem Display. Weil dazu aber nichts gefordert ist, ist sie auch immer "schnell genug".
Würde hier aber eine Aktualisierungsrate von z.B. 5x pro Sekunde gefodert, dann wäre die CPU schlagartig völlig überlastet, weil sie das nicht schafft.
Im echten Leben und mit steigender Erfahrung wird man natürlich den Programmierstil signifikant umstellen. Da kopiert man dann (hoffentlich) nicht einfach irgendwelche Codeschnipsel hintereinander und nennt das dann "mein Programm".
Nein, weil die Zeit für die Abtastfrequenz in den Zeilen 60+65 gemacht
wird.
erkannten Zeilen sorgen für das Timing.
Und so lange dreht sich der Computer da drin im Kreis, weil eben noch Zeit über ist.
Meine Vermutung ist nun, das das zusätzlich eingefügte Display diese nach der Warteschleife eingefügte Programmschleife zeitlich zu lang macht, so das eben keine Zeit mehr zum Warten überbleibt, salopp als "CPU-Load >100%" bezeichnet.
Soweit ich den Code verstehe wirkt sich diese Verzögerung direkt aus, weil eben nicht in konstantem Zeitraster gesampelt wird, was dann in der FFT für wilde Signale sorgt, weil die ja rechnerisch nicht nur auf einen festen, sondern sogar einen definiert langen Zeitablauf festgelegt ist.
dann hätte man auch
in der Version ohne Punktdisplay keine Zeile mehr einfügen oder ändern
dürfen (z.B. serielle Schnitte oder LCD).
"Keine Zeile" wird es nicht sein, ein wenig Reserve traue ich dem Controller schon noch zu.
Aber Display oder Com dürfte ebenfalls zu lang sein, ja.
Daher ja der Vorschlag, eine LED einzubauen um diese verwartete Zeit in den Zeilen 60-65 anzuzeigen, im Grunde genau das was im Windows-Taskmanager der Nulltask auch macht: so lange er läuft hat die CPU noch Zeit zum Denken.
Und so lange dreht sich der Computer da drin im Kreis, weil eben noch
Zeit über ist.
Nein, es ist keine Zeit über, die vom Programm für andere Aufgaben verwendet wird. Lediglich die Syseminterrupts (Timer, SIO) kommen durch. Aber die helfen dem Programmablauf auch nicht weiter.
Also dreht sich dort in den Zeilen 60..65 der µC mit voller Rechenleistung "im Kreis". Es wird aktiv Rechenzeit vernichtet, da bleibt keine Zeit übrig, die das Programm anderweitig sinnvoll verwenden kann.
Dein Ansatz geht in die Richtung: wenn man es anders machen würde, dann wäre noch Zeit über, die für was anderes verwendet werden könnte. Aber "würde, wäre, könnte" ist halt eben nur Konjunktiv und ein frommer Wunsch.
dann hätte man auch
in der Version ohne Punktdisplay keine Zeile mehr einfügen oder ändern
dürfen (z.B. serielle Schnitte oder LCD).
"Keine Zeile" wird es nicht sein, ein wenig Reserve traue ich dem
Controller schon noch zu.
Wenn das Timing zwischen 2 der FFT-Wandlungen kritisch wäre, dann käme es auf jede Zeile an, jede eingefügte Zeile würde dieses kritische Timing ändern.
Ich bin mir nach wie vor ziemlich sicher, dass es auf ein Speicherproblem rausläuft. 2kB sind nicht viel, wenn da nach der statischen Analyse nicht noch 500 Bytes übrigbleiben, dann läuft der Stack in den deklarierten Speicher und überschreibt dort Daten.
Nein, es ist keine Zeit über, die vom Programm für andere Aufgaben
verwendet wird.
Doch, es ist Zeit über.
Weil eine neue Wandlung noch nicht dran ist und die CPU eben so lange im Park Tauben füttern kann bzw. sogar muss.
Das der Quarz nicht anhalten kann und so halt einfach im Kreis geNOPt wird sollte jedem µC-Programmierer klar sein.
Es bleiben keine Rechenzyklen über, aber es ist halt gerade nix zu tun. Also muss der Dampf abgelassen werden.
wenn man es anders machen würde, dann
wäre noch Zeit über, die für was anderes verwendet werden könnte.
Ja, das ist auch so. z.B. eben für das Display.
Hier ist es fest in den Zyklus nach dem Warten eingebaut. Da es sich beim Warten aber um einen festen Ablauf "wait till period is um" und eben nicht um ein Delay handelt, kann sich das ganze verschlucken, wenn der Ablauf des Rests zu lange dauert und somit eben keine Zeit mehr im Warteschleifendings verbracht werden muss. Das jittert dann halt, genau als wenn regelmäßig ein Int reinkommt und die ISR länger braucht als Zeit ist. Dann fehlt halt zwischen drin einer, weil das Flag schon wieder kam bevor der µC bereit war.
Wenn das Timing zwischen 2 der FFT-Wandlungen kritisch wäre, dann käme
es auf jede Zeile an, jede eingefügte Zeile würde dieses kritische
Timing ändern.
Nope.
Jede Extrazeile würde dem Warter etwas Zeit wegnehmen. So lange er dennoch zuverlässig "+1" über hat bleibt alles im Lot. Sobald man zuviel einfügt (eben z.B. SPI, I2C, Delay, Display) kann der Warter den Takt nicht mehr halten.
Und ich deute das so, das es sehr wohl auf die Zeit zwischen den FFTs ankommt, denn sonst hätte man den Warter auch einfach rauslassen können und einfach volle Socke samplen & FFTn bis der Prozessor kocht.
Ich bin mir nach wie vor ziemlich sicher, dass es auf ein
Speicherproblem rausläuft.
Das könnte man ja testen in dem man anzeigen lässt ob noch Zeit in der Warteschleife verbrannt wird. Wenn ja, hast du gewonnen. Wenn nein, ich.
Das wäre jedenfalls mein Ansatz, denn weder das Display noch der Rest sind so an sich als Code nicht lauffähig. Zusammen aber nicht mehr, und das ist entweder RAM-Knappheit oder Prozessorzyklenknappheit.
Und da die Displaygeschichte viel Zeit verplempert mit der Datenrausschieberei, aber so gut wie kein RAM braucht, liegen meine Chips auf "CPU-Zeit-Mangel".
Weil eine neue Wandlung noch nicht dran ist und die CPU eben so lange im
Park Tauben füttern kann bzw. sogar muss.
Kann sie aber mit dem obigen Programm nicht (und nur um dieses Programm geht es hier). Sie wartet da mit aller ihr zur Verfügung stehenden Rechenleistung darauf, dass die Zeit bis zum nächsten Sample vergeht.
Jede Extrazeile würde dem Warter etwas Zeit wegnehmen.
Sage ich ja: Konjunktiv. Aber wo im obigen Programm kann irgendwas irgendwem Zeit wegnehmen? Ich kann da zwischen irgendwelche der Ablaufpunkte Sampling-FFT-Anzeige sogar ein Delay mit 1 Sekunde einbauen, dann wird der Ablauf eben noch langsamer, aber er läuft weiterhin in genau gleicher Form ab.
Wenn ja, hast du gewonnen. Wenn nein, ich.
Im Grunde egal, meine virtuelle Wand hängt eh schon voll solcher virtueller Siege. Mal sehen, was der Markus letztlich rausfindet.
Ich habe aber einfach mal den Code compiliert und der Compiler sagt mir:
"Globale Variablen verwenden 1584 Bytes (77%) des dynamischen Speichers, 464 Bytes für lokale Variablen verbleiben." Und in die verbleibenden "464 Bytes für lokale Variablen" muss blöderweise der Stack auch rein.
aber er läuft
weiterhin in genau gleicher Form ab.
Und eben das ist falsch.
Direkt nach dem while, das auf den festgelgten Startpunkt wartet wird der nächste Startpunkt festgelegt.
Wenn der hinzugefügte Code länger läuft als Zeit bis zu eben diesem vorher festgelgten Startpunkt vorhanden ist, tritt eine Verzögerung auf, die die vorher festgelegte Samplingfrequenz versaut.
Was meines Erachtens die FFT stört und vmtl. genau die Fehler verursacht die hier auftreten.
Kennst du einen einfachen Trick, den Stack bzw. das anecken des selbigen zu erkennen?
Meinen "CPU-Load-Trick" hab ich ja schon verraten.
Das 464 Byte für Stack und temporäre Variablen nicht reichen mag ich kaum glauben, aber ausschließen würd ich das auch nicht.
Keine Ahnung, wo du da einen Knoten siehst, aber der ist nicht da.
Direkt nach dem while, das auf den festgelegten Startpunkt wartet wird
der nächste Startpunkt festgelegt.
Und gleich wieder in der for-Schleife weitergemacht, bis 256 Samples eingelesen sind. Es werden in diesem Programm zuerst alle 256 Samples eingelesen, dann wird darauf die FFT genacht, dann nach 5 Durchläufen dieser beiden Schritte der Mittelwert ausgegeben.
Der Programmablauf ist also so:
256 Samples einlesen
FFT darauf machen und Ergebnis speichern
256 Samples einlesen
FFT darauf machen und Ergebnis speichern
256 Samples einlesen
FFT darauf machen und Ergebnis speichern
256 Samples einlesen
FFT darauf machen und Ergebnis speichern
256 Samples einlesen
FFT darauf machen und Ergebnis speichern
Mittelwert bilden
Mittelwert ausgeben
weiter bei 1.
Da ist nur der Zeitablauf beim Einlesen der Samples ist kritisch und der wird dort lokal per micros() abgehandelt.
Damit dieses Timing passt, wird vor jeder der 5 Sampling-Runden die jeweils aktuelle Zeit als Startzeit genommen.
Kann ins Auge gehen, denn bis man mit seinem Programm daherkommt läuft ja schon der Startupcode mit Timertic usw...
und dann "von unten" nachkucken wann das
erste nicht-FF kommt? Das wäre zumindest ein Anhaltspunkt.
Wenn nicht grade wer FF auf den Stack gelegt hat. Na gut, das wäre Pech.
Diese Verfahren gehe nur "offline", man bekommt also erst forensisch heraus, dass was passiert ist, aber nicht wo/wann es passiert ist.
Mit einem anständigen Debugger kann ma sich den Speicher einfach mal an einem Breakpoint ansehen. Aber irgendwie ist genau dieser Debugger das, was mir am Arduino von Anfang an fehlt. Keine Ahnung, wie man ohne Debugger ein Programm effizient debuggen kann. Das Terminal mit der seriellen Schnitte ist grade mal ein feuchter Witz eines Debuggers.
Diese Verfahren gehe nur "offline", man bekommt also erst forensisch
heraus, dass was passiert ist, aber nicht wo/wann es passiert ist.
Wäre aber eine Kontrolle, die sagt "das ist eng" oder "das geht".
Evtl. das Programm 1 Minute laufen lassen und dann via Taste abbrechen und den Stackbegrenzer ausgeben lassen. Bleibt das Problem das man diese Findehilfe in Arduino einfrickeln müsste.
Ich bin mir nach wie vor ziemlich sicher, dass es auf ein
Speicherproblem rausläuft. 2kB sind nicht viel,
Ich sehe das ganz genauso. Vor allem nachdem alle Teile einzeln perfekt laufen, aber sobald er alle in einen Sketch packt, kollabiert alles; allein diese Formulierung des OP ließ mich an ein Speicher-Problem denken.
CPU-Überlastung könnte zwar grundsätzlich auch eine Folge der Vereinigung separat funktionierender Teilprogramme sein, in dem, obigen Sketch aber wohl kaum nicht, da dort keinerlei zeitkritische Passagen aus verschiedenen Teilprogrammen ineinander verwoben werden. Außerdem äußert sowas sich aber eher in Symptomen wie scheinbaren Hängern, fehlerhaften Sampledaten, die zu unsinnigen FFT Ergebnissen führen usw...
statischen Analyse nicht noch 500 Bytes übrigbleiben,
dann läuft der Stack in den deklarierten Speicher
und überschreibt dort Daten.
Genau die Konstellation bei der sich i.A. plötzlich alles in totales Chaos verwandelt oder eben wie der OP formuliert: kollabiert.
Meine Alternativ-Vermutung wäre ein Konflikt aus der speziellen Kombination von Libraries; der gäbe ich aber auch max. ein paar Prozent Wahrscheinlichkeit.
Wenn das Display jetzt aber zu lange braucht
so das der Warter ein Durchläufer wird, stimmt
die Samplefrequenz nicht mehr, oder?
Und weiter? Klar, die FFT wird fehlerhafte Werte liefern, möglicherweise auch kraß unsinnige, aber auch das kann im weiteren Verlauf nicht zum vollständigen Kollaps führen; da wird ja nur noch ein Mittelwert berechnet und ausgegeben. Bliebe also nur die FFT-Lib selbst, aber wenn die nur Aufgrund einer fehlerhaften Samplerate alles kollabieren läßt, das würde ich das als heftige Bug in der Library ansehen - kann man zwar im Arduino-Ökosystem nie völlig ausschließen, halte ich aber doch für sehr unwahrscheinlich; auch weil ArduinoFFT wohl keine Orchideen-Lib mit gerade mal einstelliger Nutzerzahl ist.
oder aber ins EEPROM, dann aber dem Arduino verbieten den EEPROM zu löschen EESAVE.
Deswegen mag ich ja die ATmega1284p Version mit 16KB SRAM und 128KB flash.
Ich bin vor ein paar Jahren an einer Sache gescheitert, die diesem Thread verdammt ähnlich ist, mit OLED-Display: Alle Programmteile für sich funktionierten einwandfrei, auch Kombinationen dieser. Sobald alles eingebunden war, schmierte der A*ProMini direkt ab.
Später kam es zum Laufen, nachdem ich eine deutlich simplere Library für das OELD gefunden hatte. Ich bin mit ziemlich sicher, dass der dynamische Speicher während der Laufzeit übergelaufen ist. Das vermute ich auch hier, die Grafik braucht halt Speicher.
Wie will man dessen Auslastung messen / melden, wenn der Kram direkt abschmiert?