Speicherprobleme

Hallo,

ich habe es nach mehreren kleineren Projekten scheinbar nun zum ersten Mal mit Speicherproblemen zu tun.

In dem mir vorliegenden Projekt verwende ich viele byte_Arrays wie z.B. dieses:

byte melody_05[] = {
    0x00, 0x90, 0x2b,
    0x81, 0x79, 0x80, 0x2b,
    0x81, 0x09, 0x90, 0x2b,
    0x83, 0x0f, 0x90, 0x2e,
    0x2a, 0x80, 0x2b,
    0x81, 0x41, 0x80, 0x2e,
    0x81, 0x1d, 0x90, 0x2e,
    0x82, 0x78, 0x90, 0x33,
    0x39, 0x80, 0x2e,
    0x81, 0x37, 0x80, 0x33,
    0x81, 0x23, 0x90, 0x33,
    0x82, 0x79, 0x90, 0x2e,
    0x2b, 0x80, 0x33,
    0x81, 0x31, 0x90, 0x30,
    0x2a, 0x80, 0x2e,
    0x83, 0x0f, 0x80, 0x30,
    0x4e, 0x90, 0x2b,
    0x81, 0x79, 0x80, 0x2b,
    0x81, 0x09, 0x90, 0x2b,
    0x83, 0x0f, 0x90, 0x2e,
    0x2a, 0x80, 0x2b,
    0x81, 0x41, 0x80, 0x2e,
    0x81, 0x1d, 0x90, 0x2e,
    0x82, 0x78, 0x90, 0x33,
    0x39, 0x80, 0x2e,
    0x81, 0x37, 0x80, 0x33,
    0x81, 0x23, 0x90, 0x33,
    0x82, 0x79, 0x90, 0x2e,
    0x1b, 0x80, 0x33,
    0x81, 0x41, 0x90, 0x30,
    0x2a, 0x80, 0x2e,
    0x83, 0x0f, 0x80, 0x30,
    0x28, 0xff      };

Die einzelnen Arrays werden von mir bei Bedarf folgendermaßen "ausgelesen":

byte* melody[] = {
    melody_00, melody_01, melody_02, melody_03, melody_04, melody_05, melody_06, melody_07    };

  index_value = melody[current_melody][read_index];

Dies klappte bisher soweit hervorragend.

Nun kam es jedoch beim Hinzufügen eines weiteren Arrays zu Problemen. Und zwar funktioniert der komplette Sketch im Anschluss überhaupt nicht mehr.

Entferne (oder verkleinere) ich das zuletzt hinzugefügte Array, funktioniert alles wieder bestens.

An der Gesamtgröße des Sketches kann es nicht liegen, da:

Binäre Sketchgröße: 13.322 Bytes (von einem Maximum von 30.720 Bytes)

Ich habe mich zwar schon an PROGMEM versucht, aber dann funktioniert der Sketch ebenfalls umgehend nicht und dies auch unabhängig davon, ob ich das o.a. Array hinzugefügt habe, oder nicht. Hierzu bin ich wie folgt vorgegangen:

  1. Bibliothek hinzugefügt:
#include <avr/pgmspace.h>
  1. Vor jedes Array ein PROGMEM geschrieben:
PROGMEM byte melody_03[]= {
    0x00, 0x90, 0x2b,
    0x81, 0x3e, 0x90, 0x37,
    0x32, 0x80, 0x2b,
    0x0, 0x80, 0x37,
    0x20, 0xff      };

Ich verwende u.a. die TimerOne_Library und habe auch schon versucht das "Abfrageintervall" zu vergrößern. Dies hatte jedoch in diesem Zusammenhang keine Auswirkungen auf die Lauffähigkeit des Sketches.

Außerdem versuchte ich durch Angabe der Indexanzahl der Arrays das Problem zu beheben- ebenfalls ohne Erfolg. :~

Ich verwende ein Arduino Pro mini.

Nun weiss ich leider nicht mehr, wo ich ansetzten soll/ kann.

Gruß Chris

Flash und RAM haben getrennte Adress-Räume, da der AVR eine Harvard Architektur hat. Du kannst daher nicht einfach normalen Funktionen eine Adresse übergeben und annehmen, dass die ins Flash zeigt. Die adressiert statt dessen das RAM wo was ganz anderes steht.

Statt dessen brauchst du spezielle Makros, die die Adressen umsetzen. Für Bytes ist das pgm_read_byte_near(). Damit kopierst du ein Byte ins RAM.

Siehe:

http://www.nongnu.org/avr-libc/user-manual/pgmspace.html

Mit dem jagged Array hast du aber vielleicht noch mehr Probleme. Probier vielleicht erst mal ein ganz normales zwei-dimensionales Array. Ein jagged Array ist so ähnlich wie ein String Array. Wo man Zeiger auf Arrays abspeichert. Da Zeiger 2 Byte breit sind, braucht man pgm_read_word_near() um den Zeiger zu lesen (aus dem byte* melody).
Aber lerne wie gesagt erst mal wie man einfach PROGMEM Arrays verwendet.

Um dir das freie RAM anzuzeigen gibt es diese Funktion (solange der Sketch noch lauffähig ist):

int getFreeRAM() 
{
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Ok, hier erst mal ein Test Sketch um ein einzelnes Array auszulesen:

#include <avr/pgmspace.h>

const uint8_t melody_1[] PROGMEM =
{
    0x00, 0x90, 0x2b,
    0x81, 0x3e, 0x90, 
    0x37, 0x32, 0x80, 
    0x2b, 0x00, 0x80, 
    0x37, 0x20, 0xff  
};

void setup() 
{
  Serial.begin(115200);
  delay(300);
  Serial.print(F("RAM: "));
  Serial.println(getFreeRAM());
}

void loop() 
{
  for(int i = 0; i < 15; i++)
    Serial.println(pgm_read_byte_near(&melody_1[i]), HEX);
    
  Serial.println("\n");
  delay(3000);
}

Wichtig ist hier:

pgm_read_byte_near(&melody_1[i])

Besonders das &. Du musst dem Makro die Adresse des Wertes übergeben. Alternativ geht hier auch "melody_1 + i", da Array Variablen Zeiger auf das erste Element sind.

Dann zum jagged Array. Das geht doch ganz einfach. Auch mit pgm_read_byte_near(). Also vergiss was ich vorher gesagt habe:

const byte melody_1[] PROGMEM =
{
    0x00, 0x90, 0x2b,
    0x81, 0x3e, 0x90, 
    0x37, 0x32, 0x80, 
    0x2b, 0x00, 0x80, 
    0x37, 0x20, 0xff  
};

const byte melody_2[] PROGMEM =
{
    0x11, 0x12, 0x13,
    0x14, 0x15, 0x16, 
    0x17, 0x18, 0x19,
};

const byte* melodies[] PROGMEM = { melody_1, melody_2 };

void setup() 
{
  Serial.begin(115200);
  delay(300);
  Serial.print(F("RAM: "));
  Serial.println(getFreeRAM());
}

void loop() 
{
  for(int i = 0; i < 15; i++)
    Serial.println(pgm_read_byte_near(&(melodies[0][i])), HEX);
    
  Serial.println();
  
 for(int i = 0; i < 9; i++)
    Serial.println(pgm_read_byte_near(&(melodies[1][i])), HEX);
    
  Serial.println("\n");
  delay(3000);
}

Eigentlich logisch. Sollte ja auch im Flash nicht anders gehen als ein normales zwei-dimensionales Array. Also einfach mit [1][2]

1000 Dank soweit.. kann leider es jedoch jetzt nicht testen.
Werde mich dann aber melden, ob es geklappt hat.

Gruß Chris

Hallo,

ich habe ein paar Tests mit Deinem Code gemacht und diesen soweit verstanden.

Leider konnte ich mein Problem bisher nicht lösen, konnte aber den Fehlerbereich eingrenzen.

Ich habe mit dieser Zeile ein Problem:

index_value = pgm_read_byte_near(&(melodies[current_melody][read_index]));

Wenn ich anstatt current_melody einfach eine 1 schreibe, wird der Code unter Verwendung von melody_01 wie erwartet durchlaufen.

Scheinbar gibt es also ein Problem mit der Variablen current_melody, von der ich nach Tests an mehreren Stellen definitiv sagen kann dass sie den Wert 1 hat.

current_melody hatte ich ursprünglich als byte definiert. Jedoch funktioniert es auch als int oder unsigned int nicht.

Gruß Chris

Wow. Hätte nicht gedacht, dass ich das so einfach reproduzieren kann. Es muss mit dem jagged Array zusammenhängen und hat vielleicht etwas damit zu tun wie der Speicher angelegt wird. Genau weiß ich es aber nicht.

Und wenn ich den Test-Code anders gemacht hätte, wäre mir es auch aufgefallen:

  for(int current_melody = 0; current_melody < 2; current_melody++)
  { 
    for(int i = 0; i < 15; i++)
    {
      Serial.println(pgm_read_byte_near(&(melodies[current_melody][i])), HEX);
    }
    Serial.println();
  }

Es geht sobald eine der beiden Index-Variablen fest ist. Wenn beide variabel sind, geht es schief. Sehr seltsam.

Es geht aber mit einem normalen zwei-dimensionalen Array:

const byte melodies[][15] PROGMEM =
{
  {
    0x00, 0x90, 0x2b,
    0x81, 0x3e, 0x90, 
    0x37, 0x32, 0x80, 
    0x2b, 0x00, 0x80, 
    0x37, 0x20, 0xff  
  },

  {
    0x11, 0x12, 0x13,
    0x14, 0x15, 0x16, 
    0x17, 0x18, 0x19,
    0x20, 0x21, 0x22, 
    0x23, 0x24, 0x25
  }
};

Das könnte ein Workaround sein. Du kannst die zweite Dimension im Array so groß machen wie dein größtes Array. Dann verschwendest du bei den kleineren Arrays natürlich Speicher. Nicht so toll, aber im Flash vielleicht auch nicht so schlimm. Kommt drauf an wie viel du abspeichern möchtest.

War für mich als Anfänger wirklich extrem schwierig, dieses Fehlverfalten zu diagnostizieren.

Vielen Dank Dir fürs Testen!!

Bin gespannt, wer uns das wie erklären wird.

Gruß Chris

Da jagged Arrays im RAM funktionieren, liegt es vielleicht an der Umrechnung der RAM Adressen auf die Adressen im Flash durch das Makro. Schwer zu sagen. Es ist aber trotzdem schwer zu verstehen wieso es dann geht wenn man einen der Indices als Konstante hat.

Was mich erstaunt ist, wie gut der Compiler konstante Indexe optimiert, so dass myjaggedarray[ 2 ][ i ] eine gültige PROGMEM Adresse ist.

Zweidimensionale Arrays und Arrays aus Pointern auseinanderzuhalten ist schon schwer genug, wenn die Pointer dann auch noch selbst im Flash liegen und erstmal mit pgm_read_word oder so geholt werden müssten ( was hier gefehlt hat ), wird es richtig knifflig. Und wenn es dann beim MEGA2560 evtl. noch das richtige 64kB Segment sein muss, wäre ich noch vorsichtiger...

Mein theoretischer Tip:

  • Entweder zweidimensionales Array ( mit Verschnitt )
  • Oder die Pointer auf PROGMEM im RAM halten ( Bei einer Handvoll Melodien vertretbar )
  • Oder genau testen, was man tut.

:wink:

Ein großes Problem bei der PROGMEM Geschichte ist dass man keine Fehler bekommt wenn die Adressen falsch ist. Der Code läuft munter weiter, aber kopiert die falschen Sachen

Die Idee die ich am Anfang hatte, dass man erst mal den Pointer selbst aus dem Flash kopieren muss war dann doch korrekt (ich hatte es aber erst falsch gemacht):

const byte melody_1[] PROGMEM =
{
    0x00, 0x90, 0x2b,
    0x81, 0x3e, 0x90, 
    0x37, 0x32, 0x80, 
    0x2b, 0x00, 0x80, 
    0x37, 0x20, 0xff  
};

const byte melody_2[] PROGMEM =
{
    0x11, 0x12, 0x13,
    0x14, 0x15, 0x16, 
    0x17, 0x18, 0x19,
};

const byte* melodies[] PROGMEM = { melody_1, melody_2 };

void setup() 
{
  Serial.begin(115200);
}

void loop() 
{
  for(int current_melody = 0; current_melody < 2; current_melody++)
  { 
    for(int i = 0; i < 15; i++)
    {
      unsigned int pointer = pgm_read_word_near(&melodies[current_melody]);
      Serial.println(pgm_read_byte_near(pointer + i), HEX);
    }
    Serial.println();
  }
  
  Serial.println(F("--------------\n"));
  delay(5000);
}

Dabei beachten dass er beim zweiten Array mehr kopiert als eigentlich im Array definiert sind. Das ist aber nur wegen der for-Schleife.

Wie gesagt kann man das Pointer Array aber auch im RAM lassen. Das sind nur zwei Bytes pro Zeiger:

const byte* melodies[] = { melody_1, melody_2, melody_3 };

Dann geht es auch wie man erwartet:

Serial.println(pgm_read_byte_near(&melodies[current_melody][i]), HEX);

Ich glaube, ich spar sowohl das jagged Array, als auch mehrdimensionale Array und spreche einfach einzelne per PROGMEM hinterlegte Einzel-Arrays per switch/case direkt an.

Dein letztes Beispiel habe ich leider nicht verstanden, Serenifly.

War darin eine Alternative zu der bei mir nicht funktionierenden Zeile enthalten?

Gruß Chris

Beim letzten Beispiel fehlt das PROGMEM in dem Array aus byte*. Das heißt diese Zeiger stehen im RAM und nicht im Flash. Dann funktioniert die Adressierung mit [][] ohne dass man den Zeiger extra kopieren muss.

Wenn man die Zeiger auch im Flash speichert (was sich hier nicht unbedingt lohnt) muss man erst mal den Zeiger mit pgm_read_word_near() kopieren. Dann kann man den Index einfach drauf addieren. "word" weil Zeiger eben 16 Bit breit sind.

Konnte meine Probleme dank Deiner tollen Erklärungen lösen.

Echt topp! 1000 Dank nochmals.

Hatte schon befürchtet, dass ich mich in einer Sackgasse verrannt habe, aus der ich nicht wieder heraus finde.

Gruß Chris