Forum: Mikrocontroller und Digitale Elektronik Arduino Uno struct in PROGMEM lesen ?


von Tom (tom_iphi)


Lesenswert?

Hallo,

ich habe Probleme auf eine komplexere Datenstruktur im Programmspeicher 
des Atmega328P mittels Arduino C zuzugreifen.
Es geht um den Zugriff mit:

  strcpy_P(s, (char*)pgm_read_byte(&(SubArray[i].submenu[j])));

Wenn i eine Konstante ist gehts, wenn i eine Variable ist gehts nicht.
Was geht da schief und wie mach ichs richtig?

Danke, Tom

Hier der Arduino Sketch mit meiner Datenstruktur und meinen 
Leseversuchen:
1
const char sub0_0[] PROGMEM = "IT00"; 
2
const char sub0_1[] PROGMEM = "IT01"; 
3
const char* const sub0[] PROGMEM = { sub0_0, sub0_1}; 
4
                                                                 
5
const char sub1_0[] PROGMEM = "IT10";                                                                  
6
const char sub1_1[] PROGMEM = "IT11";
7
const char sub1_2[] PROGMEM = "IT12";
8
const char* const sub1[] PROGMEM = { sub1_0, sub1_1, sub1_2}; 
9
                                                                
10
// Define a data structure containing an array of submenus and an integer
11
// indicating the number of submenus
12
struct TSubs{
13
char count;
14
char** submenu; 
15
};
16
17
const struct TSubs SubArray[2] PROGMEM = {
18
{2, sub0},
19
{3, sub1}
20
};
21
22
void test(void){
23
  char s[5];
24
  //accessing submenus with constant first index works!
25
  Serial.print("constant index i=0");
26
  Serial.write(0x0D);
27
  Serial.write(0x0A);
28
  char i=0;
29
  char n=pgm_read_byte(&(SubArray[i].count));
30
  for (char j=0;j<n;j++){
31
    strcpy_P(s, (char*)pgm_read_byte(&(SubArray[i].submenu[j])));
32
    Serial.print(s);
33
    Serial.write(0x0D);
34
    Serial.write(0x0A);    
35
  }
36
  Serial.print("constant index i=1");
37
  Serial.write(0x0D);
38
  Serial.write(0x0A);
39
  i=1;
40
  n=pgm_read_byte(&(SubArray[i].count));
41
  for (char j=0;j<n;j++){
42
    strcpy_P(s, (char*)pgm_read_byte(&(SubArray[i].submenu[j])));
43
    Serial.print(s);
44
    Serial.write(0x0D);
45
    Serial.write(0x0A);    
46
  }
47
  
48
  //accessing submenus with variable first index fails!
49
  for (i=0;i<2;i++){
50
    Serial.print("variable index i=");
51
    Serial.print((int)i);  
52
    Serial.write(0x0D);
53
    Serial.write(0x0A);
54
    n=pgm_read_byte(&(SubArray[i].count));
55
    for (char j=0;j<n;j++){
56
      strcpy_P(s, (char*)pgm_read_byte(&(SubArray[i].submenu[j])));
57
      Serial.print(s);
58
      Serial.write(0x0D);
59
      Serial.write(0x0A);    
60
    }
61
  }
62
}
63
64
void setup() {
65
  Serial.begin(9600);
66
}
67
68
void loop() {
69
  // put your main code here, to run repeatedly:
70
  test();
71
  delay(1000);
72
}

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Tom schrieb:
> Serial.write(0x0D);
> Serial.write(0x0A);
1
Serial.println();

Tom schrieb:
> ich habe Probleme auf eine komplexere Datenstruktur im Programmspeicher
> des Atmega328P mittels Arduino C zuzugreifen.
Ich sehe keine komplexe Datenstruktur.
Das ist C++, kein C

Mein Vorschlag!
Verwende Codetags.
Reduziere das Programm auf ein Minimum.
Sage, was du erreichen willst, dem Code sehe ich das nicht an.

Warum Machst du das nicht so, wie in der Doku beschrieben?

: Bearbeitet durch User
von Rainer W. (rawi)


Lesenswert?

Tom schrieb:
> Hier der Arduino Sketch mit meiner Datenstruktur und meinen
> Leseversuchen (sorry, habe nicht herausfinden können, wie man hier Code
> vernünftig formatiert):

"Wichtige Regeln - erst lesen, dann posten!"
Formatierung unter: "C-Code"

: Bearbeitet durch User
von Tom (tom_iphi)


Lesenswert?

Sorry, ich dachte, wenns im Editor zum Formatieren keinen Knopf gibt, 
dann kann man auch nicht formatieren. Habe ich jetzt repariert.

Das obige Beispiel ist bereits das lauffähige Minimalbeispiel.

Was ich erreichen will:

Ich habe diese Menü-Datenstruktur im Flash:
1
const char sub0_0[] PROGMEM = "IT00"; 
2
const char sub0_1[] PROGMEM = "IT01"; 
3
const char* const sub0[] PROGMEM = { sub0_0, sub0_1}; 
4
                                                                 
5
const char sub1_0[] PROGMEM = "IT10";                                                                  
6
const char sub1_1[] PROGMEM = "IT11";
7
const char sub1_2[] PROGMEM = "IT12";
8
const char* const sub1[] PROGMEM = { sub1_0, sub1_1, sub1_2}; 
9
                                                                
10
// Define a data structure containing an array of submenus and an integer
11
// indicating the number of submenus
12
struct TSubs{
13
char count;
14
char** submenu; 
15
};
16
17
const struct TSubs SubArray[2] PROGMEM = {
18
{2, sub0},
19
{3, sub1}
20
};

Die Strings will ich indiziert in ein Array im RAM kopieren, z.B. so:
1
char s[5];
2
strcpy_P(s, (char*)pgm_read_byte(&(SubArray[i].submenu[j])));

Das funktioniert aber nur solange i eine Konstante ist. Sobald i eine 
Schleifenvariable ist, kommt Mist raus. Warum? Wie gehts richtig?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Tom schrieb:
> Wie gehts richtig?

Wenn du ein Array mit Zeigern hast, dann solltest du auch Zeiger aus dem 
Array lesen.

Tom schrieb:
> Die Strings will ich indiziert in ein Array im RAM kopieren,
Warum?
Eine Ausgabe ist auch ohne Kopie möglich.

Vorschlag:
Wenn die Zeichenketten immer gleich lang sind, dann kannst du auch mit 
einem 2 Dimensionalen Array arbeiten.
Den doofen Count mitzuführen ist dann nicht notwendig. (vielleicht)
count als char ist doof, weil man mit char nicht rechnen kann/soll

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Tom schrieb:
> Wenn i eine Konstante ist gehts, wenn i eine Variable ist gehts nicht.
> Was geht da schief und wie mach ichs richtig?

Das ist die interessante Frage....
Warum?
1
#include <Streaming.h> // die Lib findest du selber ;-)
2
Print &cout = Serial; // cout Emulation für "Arme"
3
4
const unsigned a PROGMEM {42};
5
const unsigned b PROGMEM {4711};
6
volatile const unsigned c PROGMEM {22};
7
8
void setup() 
9
{
10
  Serial.begin(9600);
11
  cout << F("a: ") << a << endl;  // falscher Zugriff
12
  cout << F("b: ") << pgm_read_word(&b) << endl; // richtiger Zugriff
13
  cout << F("c: ") << c << endl;  // falscher Zugriff
14
  cout << F("c: ") <<  pgm_read_word(&c) << endl;  // richtiger Zugriff
15
}
16
17
void loop() 
18
{
19
20
}

Es ist die Optimierung die dir einen Streich spielt.
Der Compiler weiß nix über PROGMEM und EEMEM.
Kennt er nicht, darum benötigen wir auch besondere Zugriffsfunktionen um 
in die Speicher zu greifen.
Die Verwendung von Konstanten kann der Optimizer super gut optimieren.
Den Zugriff über Variablen weniger.
Wenn man die Optimierung deaktiviert, dann brichts auch mit Konstanten 
ins Essen.

Hier schalte ich partiell die Optimierung per volatile ab.
a funktioniert trotz falschem Zugriff, dank Optimierung richtig
b funktioniert, zeigt wie es muss

c hat zwei Gesichter ein richtiges, da mit Zugriffsmethode und ein 
falsches, da Optimierung für c abgeschaltet

Das ist genau die Falle in die du getappt bist


Dein Menü:
1
#include <Streaming.h> // die Lib findest du selber ;-)
2
Print &cout = Serial; // cout Emulation für "Arme"
3
4
using FlashStr = __FlashStringHelper *;
5
6
template<typename T, size_t N > constexpr size_t arrayCount(T (&)[N])
7
{
8
  return N;
9
}
10
11
12
const char sub0_0[] PROGMEM {"IT00"};
13
const char sub0_1[] PROGMEM {"IT01"};
14
const char *const sub0[]  PROGMEM { sub0_0, sub0_1};
15
16
const char sub1_0[] PROGMEM {"IT10"};
17
const char sub1_1[] PROGMEM {"IT11"};
18
const char sub1_2[] PROGMEM {"IT12"};
19
const char *const sub1[]  PROGMEM { sub1_0, sub1_1, sub1_2};
20
21
struct Sub
22
{
23
  const size_t count;
24
  const char *const *submenu;
25
};
26
27
const Sub subArray[] PROGMEM 
28
{
29
  {arrayCount(sub0), sub0},
30
  {arrayCount(sub1), sub1}
31
};
32
33
34
35
void setup()
36
{
37
  Serial.begin(9600);
38
  cout << F("Start:") << endl;
39
  for(auto &sub : subArray)
40
  {
41
    const char **menuArray = reinterpret_cast<const char **>(pgm_read_ptr(&sub.submenu));
42
    for(unsigned i = 0; i < pgm_read_word(&sub.count); i++)
43
    {
44
      const char *menuItem = reinterpret_cast<const char*>(pgm_read_ptr(menuArray+i));
45
      cout <<  FlashStr(menuItem) << endl;
46
    }
47
    cout << endl;
48
  }
49
}
50
51
void loop()
52
{
53
54
}

von Oliver S. (oliverso)


Lesenswert?

Arduino F. schrieb:
> Hier schalte ich partiell die Optimierung per volatile ab.
> a funktioniert trotz falschem Zugriff, dank Optimierung richtig
> b funktioniert, zeigt wie es muss
>
> c hat zwei Gesichter ein richtiges, da mit Zugriffsmethode und ein
> falsches, da Optimierung für c abgeschaltet

Sei mir nicht böse, aber das ist Wort für Wort von vorne bis hinten 
völliger Unsinn.

Die richtige Antwort ist schlicht RTFM.

Ob es die von der avrlibc ist, oder die vom Arduino, ist egal. Oder 
gleich hier:
https://www.mikrocontroller.net/articles/AVR-Tutorial:_Speicher

Da ist beschrieben und dokumentiert, wie man auf Daten im Flash 
zugreift. Das hat mit irgendwelchen Gesichtern von C (die es nicht gibt) 
oder abschalten der Optimierung durch volatile (was völliger Unsinn ist) 
nichts zu tun.

Oliver

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Oliver S. schrieb:
> ...
Du hast das Problem des TO nicht verstanden!
Natürlich liegt es an der Optimierung, dass er "versehentlich" richtige 
Daten bekommt, wenn ihm Konstanten verwendet.

Oliver S. schrieb:
> RTFM
Da hast du allerdings wahr.

Oliver S. schrieb:
> Ob es die von der avrlibc ist, oder die vom Arduino, ist egal.
Das ist nicht egal, sondern exakt das selbe.

Oliver S. schrieb:
> Ob es die von der avrlibc ist, oder die vom Arduino, ist egal. Oder
> gleich hier:
> https://www.mikrocontroller.net/articles/AVR-Tutorial:_Speicher
>
> Da ist beschrieben und dokumentiert, wie man auf Daten im Flash
> zugreift.
Was soll die dämliche Nebelkerze?
Wir sind hier in C++ und nicht  in ASM.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Arduino F. schrieb:
> Vorschlag:
> Wenn die Zeichenketten immer gleich lang sind, dann kannst du auch mit
> einem 2 Dimensionalen Array arbeiten.

So mache ich das auch. Ansonsten ist das ein riesen Wust an 
Schreibarbeit und somit gerne eine Fehlerquelle.
Im Flash muß man ja nicht mit jedem Byte geizen, da legt man einfach die 
maximale Länge für alle Strings im Array fest.

Wie schon gesagt wurde, einen Pointer im Flash muß man dann mit 
pgm_read_word() zugreifen, die Pointerschreibweise x[i] funktioniert 
also nicht mehr.

von Oliver S. (oliverso)


Lesenswert?

Arduino F. schrieb:
> Du hast das Problem des TO nicht verstanden!
> Natürlich liegt es an der Optimierung, dass er "versehentlich" richtige
> Daten bekommt, wenn ihm Konstanten verwendet.

Wenn ein Programm mit/ohne Optimierung funktioniert, und ohen/mit nicht, 
liegt es niemals an der Optimierung, sondern immer am Programm (seltene 
Compilerfehler mal ausgenommen). Und den Begriif volatile mit 
Optimierung in einem Satz zu nennen ist ebenso immer garantiert 
unsinnig.

Das Problem, daß folgendes nicht das tut, was der TO erwartet
> strcpy_P(s, (char*)pgm_read_byte(&(SubArray[i].submenu[j])));

hat nichts mit Optimierungen zu tun, sondern damit, daß SubArray in 
Flash liegt, und submenu auch. pgm_read_byte macht das, was es lt. Doku 
macht, und liest ein byte von der übergeben Adresse aus dem Flash. 
SubArray[i] wird in dem Fall aber einer Adressen im SRam gelesen, obwohl 
das eigentlich im Flash stehen. Das liefert in solchen Fällen sogar 
häufig trotzdem das richtige Ergebnis, weil der gleiche Fehler auch beim 
Schreiben gemacht wird, und die Daten daher tatsächlich im SRAM stehen, 
und gar nicht bzw. nicht nur, wie eigentlich gewollt, im Flash.

Falsch ist das trotzdem.

Man kann per PROGMEM ins Flash gelegte Daten, auf die ein ebenfalls im 
Flash liegender Pointer zeigt, nicht in einem Schritt mit pgm_read über 
den Flashpointer auslesen. Das braucht immer zwei pgm_read-Aufrufe, 
einer für den Pointer, den zweiten dann für die Daten.

Arduino F. schrieb:
> Was soll die dämliche Nebelkerze?
> Wir sind hier in C++ und nicht  in ASM.

Das war tatsächlich der falsche link. Hier der richtige:
https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Flash_mit_PROGMEM_und_pgm_read

Oliver

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Oliver S. schrieb:
> liegt es niemals an der Optimierung,

Merke: Ich habe nicht gesagt, dass die Optimierung kaputt ist.
Mit keinem Wort!
Noch nicht einmal angedeutet.


Ja, der TO verwendet die Zugriffsfunktionen nicht, bzw. unzureichend.
Und die Optimierung bügelt seinen Fehler teilweise/manchmal wieder aus.

Das habe ich gesagt/gemeint und sogar mit einem Programm belegt.
Reproduzierbar.
Auch für dich.

Oliver S. schrieb:
> hat nichts mit Optimierungen zu tun,
Offensichtlich doch!
Siehe Beispiel mit  a b c



Oliver S. schrieb:
> Das war tatsächlich der falsche link.
Nicht nur ein falscher Link, sondern auch extrem blöde Anmache, obwohl 
das Verständnisproblem alleine auf deiner Seite liegt.

Oliver S. schrieb:
> Und den Begriif volatile mit
> Optimierung in einem Satz zu nennen ist ebenso immer garantiert
> unsinnig.
Deine Kompetenz leuchtet heute recht schwach!
Eigentlich erwarte ich viel mehr von dir.....

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.