Direktes Initialisieren eines unregelmäßigen mehrdimensionallen Arrays

So etwas kann man ja leider nicht machen:

const uint8_t ARRAY[][] = { {5, 75}, {40, 40, 10, 10}, {20, 20}, {5, 50}, {30, 70}, {20, 10, 20, 10, 50, 20}, {5, 5, 5, 5, 5, 5, 30, 30} };

Warum geht es wenn ich ARRAY[][8] schreibe? Dann würde doch bei den Zeilen wo weniger als 8 Elemente benötigt werden, trotzdem so viele unsinnig reserviert. Das möchte ich nicht. Warum ist ARRAY[7][] oder ARRAY[][] nicht möglich? Das wäre ideal. Hab einige Infos dazu gefunden. Da steht nur dass das nicht geht. Tolle Begründung! Was könnte ich alternativ nehmen? Pointer auf Array? Da müsste ich dann je Array die Größe auch explizit angeben. const soll es auch sein. Und wie ginge das alles im Flashspeicher mit PROGMEM? Ich mache es erstmal im RAM, aber später soll das da hin.

Wie wäre es so?

const uint8_t z1[] PROGMEM = {5, 75};
const uint8_t z2[] PROGMEM = {40, 40, 10, 10};
const uint8_t z3[] PROGMEM = {20, 20};
const uint8_t z4[] PROGMEM = {5, 50};
const uint8_t z5[] PROGMEM = {30, 70};
const uint8_t z6[] PROGMEM = {20, 10, 20, 10, 50, 20};
const uint8_t z7[] PROGMEM = {5, 5, 5, 5, 5, 5, 30, 30};

const uint8_t* const array[] PROGMEM = {
  z1, z2, z3, z4, z5, z6, z7,
};
const uint8_t arrLen[] PROGMEM = {
  sizeof(z1), sizeof(z2), sizeof(z3), sizeof(z4), sizeof(z5), sizeof(z6), sizeof(z7),
};

void setup() {
  Serial.begin(115200);
  for (byte aNum = 0; aNum < sizeof(arrLen); aNum++) {
    PGM_P pPtr = (PGM_P)pgm_read_word(array + aNum);
    byte length = pgm_read_byte(arrLen + aNum);
    Serial.print(F("array["));
    Serial.print(aNum);
    Serial.print(F("] (len "));
    Serial.print(length);
    Serial.print(F("), { "));
    for (byte idx = 0; idx < length; idx++) {
      Serial.print(pgm_read_byte(pPtr + idx));
      Serial.print(F(", "));
    }
    Serial.println(F("}"));
  }
}

void loop() {}
array[0] (len 2), { 5, 75, }
array[1] (len 4), { 40, 40, 10, 10, }
array[2] (len 2), { 20, 20, }
array[3] (len 2), { 5, 50, }
array[4] (len 2), { 30, 70, }
array[5] (len 6), { 20, 10, 20, 10, 50, 20, }
array[6] (len 8), { 5, 5, 5, 5, 5, 5, 30, 30, }

Das erzeugt 3 Byte Overhead pro Zeile plus Platz für den Kode.
Ob sich das gegenüber der rechteckigen Variante lohnt hängt vom Einzelfall ab.
Hier wären das 21 Byte + Kode gegen 26 Byte.

Weil C einfach keine “jagged Arrays” kennt. Zwei-dimensionale Arrays sind immer ein zusammenhängender Speicherbereich. Und die einzige Möglichkeit wie das funktionieren kann ist wenn es genau “rechteckig” ist und eine Dimension bekannt ist.

In Java oder C# geht das nur weil dort Arrays Objekte sind!

Als Alternative kann man auch nur die Arrays im Flash Speichern und die Zeiger im RAM lassen:

const byte array1[] PROGMEM = { 5, 75 };
const byte array2[] PROGMEM = { 40, 40, 10, 10 };
const byte array3[] PROGMEM = { 20, 20 };
const byte array4[] PROGMEM = { 5, 50 };
const byte array5[] PROGMEM = { 30, 70 };
const byte array6[] PROGMEM = { 20, 10, 20, 10, 50, 20 };
const byte array7[] PROGMEM = { 5, 5, 5, 5, 5, 5, 30, 30 };

const byte arraySize[] PROGMEM = { sizeof(array1), sizeof(array2), sizeof(array3), sizeof(array4), sizeof(array5),sizeof(array6), sizeof(array7) };
const byte* const array[] = { array1, array2, array3, array4, array5, array6, array7 };

void setup()
{
   Serial.begin(9600);

  for (unsigned int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
  {
    for (unsigned int j = 0; j < pgm_read_byte(&arraySize[i]); j++)
   {
    Serial.print(pgm_read_byte(&array[i][j]));
    Serial.print(", ");
   }
   Serial.println();
   }
}

void loop()
{
}

Kostet etwas mehr RAM, aber man muss nicht noch die Zeiger extra auslesen

Serenifly:
Weil C einfach keine “jagged Arrays” kennt. Zwei-dimensionale Arrays sind immer ein zusammenhängender Speicherbereich. Und die einzige Möglichkeit wie das funktionieren kann ist wenn es genau “rechteckig” ist und eine Dimension bekannt ist.

In Java oder C# geht das nur weil dort Arrays Objekte sind!

Als Alternative kann man auch nur die Arrays im Flash Speichern und die Zeiger im RAM lassen:

const byte array1[] PROGMEM = { 5, 75 };

const byte array2 PROGMEM = { 40, 40, 10, 10 };
const byte array3 PROGMEM = { 20, 20 };
const byte array4 PROGMEM = { 5, 50 };
const byte array5 PROGMEM = { 30, 70 };
const byte array6 PROGMEM = { 20, 10, 20, 10, 50, 20 };
const byte array7 PROGMEM = { 5, 5, 5, 5, 5, 5, 30, 30 };

const byte arraySize PROGMEM = { sizeof(array1), sizeof(array2), sizeof(array3), sizeof(array4), sizeof(array5),sizeof(array6), sizeof(array7) };
const byte* const array = { array1, array2, array3, array4, array5, array6, array7 };

void setup()
{
  Serial.begin(9600);

for (unsigned int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
  {
    for (unsigned int j = 0; j < pgm_read_byte(&arraySize[i]); j++)
  {
    Serial.print(pgm_read_byte(&array[i][j]));
    Serial.print(", ");
  }
  Serial.println();
  }
}

void loop()
{
}



Kostet etwas mehr RAM, aber man muss nicht noch die Zeiger extra auslesen

Danke! Das sieht doch super aus! Werd ich gleich mal implementieren. Das mit den jagged Arrays hab ich wohl auf einer C# Seite gesehen und das dann mit den C Infos vermischt. Wusste garnicht, dass das in C nicht geht, bisher offenbar nie benötigt. Die Zeiger werd ich auch im RAM lassen, hauptsache die Datenbytes sind im Flash. Top!

7 Zeiger sind halt auch schon wieder 14 Bytes. Nicht viel, aber man kann sie auch ins Flash packen. Siehe der Code von Whandall. Dann brauchst du aber einen Zwischenschritt um den Zeiger auszulesen und dann über diesen Zeiger die eigentlichen Daten.

Serenifly:
7 Zeiger sind halt auch schon wieder 14 Bytes. Nicht viel, aber man kann sie auch ins Flash packen. Siehe der Code von Whandall. Dann brauchst du aber einen Zwischenschritt um den Zeiger auszulesen und dann über diesen Zeiger die eigentlichen Daten.

Ich bekomms noch nicht hin mit dem Zugriff. Habe mit Progmem noch nicht viel gemacht.
Wie kann ich eine Array-Zeile einer Funktion übergeben? Also den Zeiger auf eine Zeile?
Atmel Studio spuckt wie so oft unbrauchbare Fehlermeldungen aus.

void function(const uint8_t* row, const uint8_t size);

Ohne den Kode und ohne die Fehlermeldung(en) ist das eher unsichtbar als unbrauchbar.

Mein Beispiel mit Unterprogramm:

const uint8_t z1[] PROGMEM = {5, 75};
const uint8_t z2[] PROGMEM = {40, 40, 10, 10};
const uint8_t z3[] PROGMEM = {20, 20};
const uint8_t z4[] PROGMEM = {5, 50};
const uint8_t z5[] PROGMEM = {30, 70};
const uint8_t z6[] PROGMEM = {20, 10, 20, 10, 50, 20};
const uint8_t z7[] PROGMEM = {5, 5, 5, 5, 5, 5, 30, 30};

const uint8_t* const array[] PROGMEM = {
  z1, z2, z3, z4, z5, z6, z7,
};
const uint8_t arrLen[] PROGMEM = {
  sizeof(z1), sizeof(z2), sizeof(z3), sizeof(z4), sizeof(z5), sizeof(z6), sizeof(z7),
};

void printArray(PGM_P arr, uint8_t length) {
  Serial.print(F("array (len "));
  Serial.print(length);
  Serial.print(F("), { "));
  for (byte idx = 0; idx < length; idx++) {
    Serial.print(pgm_read_byte(arr + idx));
    Serial.print(F(", "));
  }
  Serial.println(F("}"));
}

void setup() {
  Serial.begin(115200);
  for (byte aNum = 0; aNum < sizeof(arrLen); aNum++) {
    printArray((PGM_P)pgm_read_word(array + aNum), pgm_read_byte(arrLen + aNum));
  }
}

void loop() {}
array (len 2), { 5, 75, }
array (len 4), { 40, 40, 10, 10, }
array (len 2), { 20, 20, }
array (len 2), { 5, 50, }
array (len 2), { 30, 70, }
array (len 6), { 20, 10, 20, 10, 50, 20, }
array (len 8), { 5, 5, 5, 5, 5, 5, 30, 30, }

Whandall:
Ohne den Kode und ohne die Fehlermeldung(en) ist das eher unsichtbar als unbrauchbar.

Mein Beispiel mit Unterprogramm:

const uint8_t z1[] PROGMEM = {5, 75};

const uint8_t z2 PROGMEM = {40, 40, 10, 10};
const uint8_t z3 PROGMEM = {20, 20};
const uint8_t z4 PROGMEM = {5, 50};
const uint8_t z5 PROGMEM = {30, 70};
const uint8_t z6 PROGMEM = {20, 10, 20, 10, 50, 20};
const uint8_t z7 PROGMEM = {5, 5, 5, 5, 5, 5, 30, 30};

const uint8_t* const array PROGMEM = {
  z1, z2, z3, z4, z5, z6, z7,
};
const uint8_t arrLen PROGMEM = {
  sizeof(z1), sizeof(z2), sizeof(z3), sizeof(z4), sizeof(z5), sizeof(z6), sizeof(z7),
};

void printArray(PGM_P arr, uint8_t length) {
  Serial.print(F(“array (len “));
  Serial.print(length);
  Serial.print(F(”), { “));
  for (byte idx = 0; idx < length; idx++) {
    Serial.print(pgm_read_byte(arr + idx));
    Serial.print(F(”, “));
  }
  Serial.println(F(”}”));
}

void setup() {
  Serial.begin(115200);
  for (byte aNum = 0; aNum < sizeof(arrLen); aNum++) {
    printArray((PGM_P)pgm_read_word(array + aNum), pgm_read_byte(arrLen + aNum));
  }
}

void loop() {}




array (len 2), { 5, 75, }
array (len 4), { 40, 40, 10, 10, }
array (len 2), { 20, 20, }
array (len 2), { 5, 50, }
array (len 2), { 30, 70, }
array (len 6), { 20, 10, 20, 10, 50, 20, }
array (len 8), { 5, 5, 5, 5, 5, 5, 30, 30, }

Danke! Habs hinbekommen. Bei dem ganzen byte pgm_read komm ich etwas in tüdel. Läuft gut. Jetzt pack ich vielleicht auch noch die pointer in den Flash. Der ATtiny hat ja nur 512bytes RAM. Danke für die gute und schnelle Hilfe! Hätte ich sonst nicht hingekriegt.

const uint8_t* const array[] PROGMEM = { z1, z2, z3, z4, z5, z6, z7 };

Damit sind die Zeiger im Flash. PROGMEM steht doch dabei

pgm_read_byte() hat als Parameter die Adresse der Variable die man lesen will als ob sie im RAM stünden und liefert die Daten die im Flash stehen. pgm_read_word() geht genauso, aber ist für 2-Byte Variablen wie int oder Zeiger.

Serenifly: const uint8_t* const array[] PROGMEM = { z1, z2, z3, z4, z5, z6, z7 };

Damit sind die Zeiger im Flash. PROGMEM steht doch dabei

pgm_read_byte() hat als Parameter die Adresse der Variable die man lesen will als ob sie im RAM stünden und liefert die Daten die im Flash stehen. pgm_read_word() geht genauso, aber ist für 2-Byte Variablen wie int oder Zeiger.

Ja, das hab ich kapiert. Ich hab bisher aber die Zeiger noch im RAM, also ohne PROGMEM. Jetzt ist das Programm natürlich nicht mehr so übersichtlich. Aber bei Mikrocontrollern muss man immer Kompromisse machen. Die Array sizes muss ich auch vorher ablegen? Später kann ich die nicht mehr bestimmen, oder?

Da der Zugriff nur über das Zeiger-Array erfolgt und nicht direkt über die eigentlichen Arrays, kommt man darüber nicht an die Größe.

sizeof() functioniert nur auf den Einzelarrays, nicht auf den Zeigern in/aus dem anderen Array.

Whandall: sizeof() functioniert nur auf den Einzelarrays, nicht auf den Zeigern in/aus dem anderen Array.

Warum verwendest du den Typ PGM_P? Ich bekomme dann folgende Fehlermeldung: expected 'const uint8_t *' but argument is of type 'const char *' Wie bekomme ich aus dem PGM_P den richtigen Typ? Nochmal casten oder als Übergabe Argument schon char * wählen? Durch die Beispiele mit Serial.print() geht vieles unter, da die Funktion ja verschiede Typen annimmt.

PGM_P ist lediglich in Makro für const char*. Das ist eigentlich für Strings, und daher an dieser Stelle nicht ganz korrekt. Du kannst statt dessen aber auch andere Datentypen verwenden

Serenifly: PGM_P ist lediglich in Makro für const char*. Das ist eigentlich für Strings, und daher an dieser Stelle nicht ganz korrekt

also sollte ich direkt uint8_t nehmen, also so?

uint8_t* rowPointer = (uint8_t*)pgm_read_word(rowNumber);

Warum muss man ein word auslesen, wenn man in ein byte casted?

Weil du nicht in einbyte, sondern in einbyte*castest. Und pointer sind 2 byte groß

michael_x: Weil du nicht in einbyte, sondern in einbyte*castest. Und pointer sind 2 byte groß

ach ja, wie dumm von mir. Das ist aber auch immer wieder verwirrend. Danke!

Whandall:
Wie wäre es so?

const uint8_t z1[] PROGMEM = {5, 75};

const uint8_t z2 PROGMEM = {40, 40, 10, 10};
const uint8_t z3 PROGMEM = {20, 20};
const uint8_t z4 PROGMEM = {5, 50};
const uint8_t z5 PROGMEM = {30, 70};
const uint8_t z6 PROGMEM = {20, 10, 20, 10, 50, 20};
const uint8_t z7 PROGMEM = {5, 5, 5, 5, 5, 5, 30, 30};

const uint8_t* const array PROGMEM = {
  z1, z2, z3, z4, z5, z6, z7,
};
const uint8_t arrLen PROGMEM = {
  sizeof(z1), sizeof(z2), sizeof(z3), sizeof(z4), sizeof(z5), sizeof(z6), sizeof(z7),
};

void setup() {
  Serial.begin(115200);
  for (byte aNum = 0; aNum < sizeof(arrLen); aNum++) {
    PGM_P pPtr = (PGM_P)pgm_read_word(array + aNum);
    byte length = pgm_read_byte(arrLen + aNum);
    Serial.print(F(“array[”));
    Serial.print(aNum);
    Serial.print(F("] (len “));
    Serial.print(length);
    Serial.print(F(”), { “));
    for (byte idx = 0; idx < length; idx++) {
      Serial.print(pgm_read_byte(pPtr + idx));
      Serial.print(F(”, “));
    }
    Serial.println(F(”}"));
  }
}

void loop() {}





array[0] (len 2), { 5, 75, }
array[1] (len 4), { 40, 40, 10, 10, }
array[2] (len 2), { 20, 20, }
array[3] (len 2), { 5, 50, }
array[4] (len 2), { 30, 70, }
array[5] (len 6), { 20, 10, 20, 10, 50, 20, }
array[6] (len 8), { 5, 5, 5, 5, 5, 5, 30, 30, }



Das erzeugt 3 Byte Overhead pro Zeile plus Platz für den Kode.
Ob sich das gegenüber der rechteckigen Variante lohnt hängt vom Einzelfall ab.
Hier wären das 21 Byte + Kode gegen 26 Byte.

Das mit den Pointer im Flash geht so noch nicht. Ich weiß nicht was er ausliest, aber jedenfalls nicht die richtigen Speicherstellen.

Und dabei den Beweis, dass es so geht, zu quoten ist schon verwirrend.

Ich dachte wir hatten gerade geklärt dass PGM_P hier falsch ist?

const uint8_t* pPtr = (const uint8_t*)pgm_read_word(array + aNum);

Ansonsten funktioniert das so wie es soll. Der Code gibt die Arrays korrekt aus.