Arduino Frequenzerkennung scheitert an Einbindung einer DOT Matrix

OP #7582437
Lesenswert?

Hallo liebe mikrocontroller.net Community.

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.

1
#include "Wire.h"
2
#include "arduinoFFT.h"
3
#include "LiquidCrystal_I2C.h"
4
#include "LedControl.h"
5
#include "binary.h"
6

7
LiquidCrystal_I2C lcd(0x27, 16, 2);
8
LedControl lc=LedControl(12,11,10,1);
9

10
const int MIC_PIN = A0;
11
const unsigned short SAMPLES = 128;
12
const double SAMPLING_FREQ = 4096;
13
const unsigned short FREQ_SAMPLE_AMOUNT = 5;
14

15
double noise_reject_limit = 550;
16
double mic_value = 0;
17
double avg_freq = 0;
18
double vReal[SAMPLES];
19
double vImag[SAMPLES];
20
double domin_freq[FREQ_SAMPLE_AMOUNT] = {0};
21
double sampling_period = 1e6 * (1/SAMPLING_FREQ);
22
unsigned short freq_sample_count = 0;
23

24
double f = 0;
25
byte level1[8]= {B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000};
26
byte level2[8]= {B00000000,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000};
27
byte level3[8]= {B00000000,B00000000,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000};
28
byte level4[8]= {B00000000,B00000000,B00000000,B11111111,B00000000,B00000000,B00000000,B00000000};
29
byte level5[8]= {B00000000,B00000000,B00000000,B00000000,B11111111,B00000000,B00000000,B00000000};
30
byte level6[8]= {B00000000,B00000000,B00000000,B00000000,B00000000,B11111111,B00000000,B00000000};
31
byte level7[8]= {B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11111111,B00000000};
32
byte level8[8]= {B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B11111111};
33

34
void setup() {
35
  lcd.init();
36
  lcd.backlight();
37
  lcd.setCursor(4,0);
38
  Serial.begin(115200);
39

40
   // put your setup code here, to run once:
41
  lc.shutdown(0,false);
42
  // Set brightness to a medium value
43
  lc.setIntensity(0,8);
44
  // Clear the display
45
  lc.clearDisplay(0); 
46
}
47

48
void loop() {
49
  
50
  unsigned long start_time = micros();
51

52
  for (int i = 0; i < SAMPLES; i++){
53
    mic_value = analogRead(MIC_PIN);
54
    if (mic_value > noise_reject_limit){
55
      vReal[i] = mic_value;
56
    }else{
57
      vReal[i] = 0;
58
    }
59
    vImag[i] = 0;
60

61
    while (micros() - start_time < sampling_period){
62
      //wait till period ends
63
      //do notthing
64
    }
65
    start_time += sampling_period;
66
  }
67

68
  arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
69
  //FFT
70
  FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
71
  FFT.Compute(FFT_FORWARD);
72
  FFT.ComplexToMagnitude();
73

74
  domin_freq[freq_sample_count] = FFT.MajorPeak();
75

76
  freq_sample_count++;
77

78
  if (freq_sample_count == FREQ_SAMPLE_AMOUNT){
79
    lcd.clear();
80
    lc.clearDisplay(0);
81
    double avg_freq = avg_frequency(domin_freq, FREQ_SAMPLE_AMOUNT);
82
    if(avg_freq){
83
      lcd.setCursor(0, 0);
84
      lcd.print(avg_freq);
85
      lcd.setCursor(0, 1);
86
      LedMatrix(avg_freq);
87
     // lc.clearDisplay(0);
88
    }else lcd.print("zu leise!");
89
    freq_sample_count = 0;
90
  }
91
  //LedMatrix(f);
92
  //LedMatrix1();
93
}
94

95
double avg_frequency(double freq_arr[], unsigned short sample_count){
96
      double avr_f = 0;
97
      for (int ii = 0; ii < sample_count; ii++){
98
        avr_f += freq_arr[ii];
99
      }
100
    avr_f /= sample_count;
101
    if(isnan(avr_f)){
102
      return 0;
103
    }else return avr_f;
104
}
105

106

107
int LedMatrix(double f) {
108
  if ((f>=0) && (f<12000)) {
109
    if (f<200) {
110
        lc.setRow(0, 0, level1[0]);
111
        lc.setRow(0, 1, level1[1]);
112
        lc.setRow(0, 2, level1[2]);
113
        lc.setRow(0, 3, level1[3]);
114
        lc.setRow(0, 4, level1[4]);
115
        lc.setRow(0, 5, level1[5]);
116
        lc.setRow(0, 6, level1[6]);
117
        lc.setRow(0, 7, level1[7]);
118
        return 1;
119
    }
120
    //else return;
121
    if ((f>=200) && (f<400)) {
122
        lc.setRow(0, 0, level2[0]);
123
        lc.setRow(0, 1, level2[1]);
124
        lc.setRow(0, 2, level2[2]);
125
        lc.setRow(0, 3, level2[3]);
126
        lc.setRow(0, 4, level2[4]);
127
        lc.setRow(0, 5, level2[5]);
128
        lc.setRow(0, 6, level2[6]);
129
        lc.setRow(0, 7, level2[7]);
130
        return 2;
131
    }
132
    //else return;
133
    if ((f>=400) && (f<500)) {
134
        lc.setRow(0, 0, level3[0]);
135
        lc.setRow(0, 1, level3[1]);
136
        lc.setRow(0, 2, level3[2]);
137
        lc.setRow(0, 3, level3[3]);
138
        lc.setRow(0, 4, level3[4]);
139
        lc.setRow(0, 5, level3[5]);
140
        lc.setRow(0, 6, level3[6]);
141
        lc.setRow(0, 7, level3[7]);
142
        return 3;
143
    }
144
    //else return;
145
    if ((f>=500) && (f<600)) {
146
        lc.setRow(0, 0, level4[0]);
147
        lc.setRow(0, 1, level4[1]);
148
        lc.setRow(0, 2, level4[2]);
149
        lc.setRow(0, 3, level4[3]);
150
        lc.setRow(0, 4, level4[4]);
151
        lc.setRow(0, 5, level4[5]);
152
        lc.setRow(0, 6, level4[6]);
153
        lc.setRow(0, 7, level4[7]);
154
        return 4;
155
    }
156
    //else return;
157
    if ((f>=600) && (f<700)) {
158
        lc.setRow(0, 0, level5[0]);
159
        lc.setRow(0, 1, level5[1]);
160
        lc.setRow(0, 2, level5[2]);
161
        lc.setRow(0, 3, level5[3]);
162
        lc.setRow(0, 4, level5[4]);
163
        lc.setRow(0, 5, level5[5]);
164
        lc.setRow(0, 6, level5[6]);
165
        lc.setRow(0, 7, level5[7]);
166
        return 5;
167
    }
168
    //else return;
169
    if ((f>=700) && (f<800)) {     
170
        lc.setRow(0, 0, level6[0]);
171
        lc.setRow(0, 1, level6[1]);
172
        lc.setRow(0, 2, level6[2]);
173
        lc.setRow(0, 3, level6[3]);
174
        lc.setRow(0, 4, level6[4]);
175
        lc.setRow(0, 5, level6[5]);
176
        lc.setRow(0, 6, level6[6]);
177
        lc.setRow(0, 7, level6[7]);
178
        return 6;
179
    }
180
    //else return;
181
    if ((f>=800) && (f<900)) {     
182
        lc.setRow(0, 0, level7[0]);
183
        lc.setRow(0, 1, level7[1]);
184
        lc.setRow(0, 2, level7[2]);
185
        lc.setRow(0, 3, level7[3]);
186
        lc.setRow(0, 4, level7[4]);
187
        lc.setRow(0, 5, level7[5]);
188
        lc.setRow(0, 6, level7[6]);
189
        lc.setRow(0, 7, level7[7]);
190
        return 7;
191
    }
192
    //else return;
193
    if ((f>=900) && (f<12000)) {      
194
        lc.setRow(0, 0, level8[0]);
195
        lc.setRow(0, 1, level8[1]);
196
        lc.setRow(0, 2, level8[2]);
197
        lc.setRow(0, 3, level8[3]);
198
        lc.setRow(0, 4, level8[4]);
199
        lc.setRow(0, 5, level8[5]);
200
        lc.setRow(0, 6, level8[6]);
201
        lc.setRow(0, 7, level8[7]);
202
        return 8;
203
    }
204
  else return 0;
205
  }
206
  //return;
207
}
Angehängte Dateien:
Moderator (Firma: Titel) Persönliche Seite #7582440
Lesenswert?

Markus schrieb:

Anbei mein Code

Bitte Bedienungsanleitung lesen...

vielleicht kann mir jemand weiterhelfen.

Teile und herrsche!

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?

Angehängte Dateien:
#7582462
Lesenswert?

Übersehe ich was oder tastest du deine Messwerte in der Loop ab? Wenn dir ein Interrupt dazwischen kommt stimmt deine Abtastung nicht mehr.

Ich würde Abtasten in einem Timer Interrupt oder mit DMA (falls vorhanden). Die Ausgabe dann in Loop alle 50ms oder so.

OP #7582463
Lesenswert?

Lothar M. schrieb:

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.

Arduino UNO R3, dementsprechend keine FPU.

Moderator (Firma: Titel) Persönliche Seite #7582468
Lesenswert?

Markus schrieb:

Arduino UNO R3

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.

#7582477
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7582483
Lesenswert?

Jens M. schrieb:

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:

  1. Samples einlesen
  2. FFT
  3. nach dem 5. Durchlauf: Mittelwertbildung und Ausgabe
  4. 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...

#7582491
Lesenswert?

Lothar M. schrieb:

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?

Lothar M. schrieb:

Die CPU-Load ist bei diesem Programmierstil immer genau 100%.

Ich hoffe du veräppelst mich.

Moderator (Firma: Titel) Persönliche Seite #7582505
Lesenswert?

Jens M. schrieb:

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".

#7582620
Lesenswert?

Lothar M. schrieb:

Nein, es ist genau so: immer dann wenn die CPU mit irgendwas fertig ist, muss sie den nächsten Befehl ausführen

Dann hast du mich nicht veräppelt, aber es auch nicht verstanden. Genau die von dir als

Lothar M. schrieb:

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.

Lothar M. schrieb:

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.

Moderator (Firma: Titel) Persönliche Seite #7582645
Lesenswert?

Jens M. schrieb:

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.

Jens M. schrieb:

so das eben keine Zeit mehr zum Warten überbleibt

Es ist nirgends eine maximale Zeit für irgendwas definiert. Wenn irgend ein Programmteil länger braucht, dann kommt der Rest eben später dran.

Jens M. schrieb:

Lothar M. schrieb:

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.

#7582693
Lesenswert?

Lothar M. schrieb:

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.

Lothar M. schrieb:

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.

Lothar M. schrieb:

Es ist nirgends eine maximale Zeit für irgendwas definiert. Wenn irgend ein Programmteil länger braucht, dann kommt der Rest eben später dran.

Jaha. Deswegen ist da auch kein Delay drin sondern eine getaktete Abfrage. Was eine maximale Zeit definiert. Suche "sampling_period".

Lothar M. schrieb:

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.

Lothar M. schrieb:

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".

Moderator (Firma: Titel) Persönliche Seite #7582968
Lesenswert?

Jens M. schrieb:

Doch, es ist Zeit über.

Demnächst gebe ich nach...

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.

#7583032
Lesenswert?

Lothar M. schrieb:

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.

Moderator (Firma: Titel) Persönliche Seite #7583050
Lesenswert?

Jens M. schrieb:

Und eben das ist falsch.

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:

  1. 256 Samples einlesen
  2. FFT darauf machen und Ergebnis speichern
  3. 256 Samples einlesen
  4. FFT darauf machen und Ergebnis speichern
  5. 256 Samples einlesen
  6. FFT darauf machen und Ergebnis speichern
  7. 256 Samples einlesen
  8. FFT darauf machen und Ergebnis speichern
  9. 256 Samples einlesen
  10. FFT darauf machen und Ergebnis speichern
  11. Mittelwert bilden
  12. Mittelwert ausgeben
  13. 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.

Jens M. schrieb:

Kennst du einen einfachen Trick, den Stack bzw. das anecken des selbigen zu erkennen?

Nein, nicht beim AVR...

Moderator (Firma: Titel) Persönliche Seite #7583135
Lesenswert?

Jens M. schrieb:

Speicher mit FF vollmachen

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.

  1. Wenn nicht grade wer FF auf den Stack gelegt hat. Na gut, das wäre Pech.
  2. Diese Verfahren gehe nur "offline", man bekommt also erst forensisch heraus, dass was passiert ist, aber nicht wo/wann es passiert ist.
  3. 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.
#7583210
Lesenswert?

Lothar M. schrieb:

Kann ins Auge gehen, denn bis man mit seinem Programm daherkommt läuft ja schon der Startupcode mit Timertic usw...

Ja, das muss man natürlich hart programmieren können. Arduino ist dazu verkehrt, wenn man nicht in die Untiefen eintaucht.

Lothar M. schrieb:

Wenn nicht grade wer FF auf den Stack gelegt hat. Na gut, das wäre Pech.

Ja, kann man sich halt verschätzen. Wenn bei "464byte frei" das erste FF bei 460 liegt, weiß man das ist eng. Bei 350 würd ich sagen das passt.

Lothar M. schrieb:

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.

Lothar M. schrieb:

Aber irgendwie ist genau dieser Debugger das, was mir am Arduino von Anfang an fehlt.

Die Leute, für die Arduino ursprünglich mal gedacht war, könnten mit einem Debugger nicht umgehen. ;)

#7585586
Lesenswert?

Lothar M. schrieb:

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.

Jens M. schrieb:

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.

Jens M. schrieb:

Die Leute, für die Arduino ursprünglich mal gedacht war, könnten mit einem Debugger nicht umgehen. ;)

In dem Punkt würde ich Jens vollinhaltlich zustimmen.

Ob das aber der Hauptgrund ist, daß es nach wie vor keinen gibt, würde ich trotzdem nicht wetten wollen.

#7585873
Lesenswert?

Veit D. schrieb:

Wegen freien RAM "sichtbar" machen. Das sollte helfen können denke ich. https://github.com/arduino/ArduinoCore-API/issues/73

Wie soll das denn helfen?

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?

Joachim B. schrieb:

Deswegen mag ich ja die ATmega1284p Version mit 16KB SRAM und 128KB flash.

Wenn Du eigene Leiterplatten machst, ja. Als fertiges Board auf dem A*Mega braucht das Ding unverschämt viel Platz.

Beitrag #7585882 wurde von einem Moderator gelöscht.
#7585902
Lesenswert?

Manfred P. schrieb:

Joachim B. schrieb:

Deswegen mag ich ja die ATmega1284p Version mit 16KB SRAM und 128KB flash.

Wenn Du eigene Leiterplatten machst, ja. Als fertiges Board auf dem A*Mega braucht das Ding unverschämt viel Platz

dann bestelle die Boards bei OSH, das Löten geht so, macht aber mir keinen Spass mehr.

Beitrag "Re: Minutengenaue 24 Stunden-Wortuhr - wer will mitbauen?" https://github.com/JChristensen/mini1284

gibt auch Andere https://www.tindie.com/products/prominimicros/pro-mini-xl-v2-atmega-1284p/ https://www.tindie.com/products/aptinex/lakduino-dwee-1284-mini-pro-atmega-1284p/

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren