Arduino Uno struct in PROGMEM lesen ?

OP #7508220
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
}
(Firma: Gast) #7508223
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?

OP #7508241
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?

(Firma: Gast) #7508249
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

(Firma: Gast) #7508927
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
}
#7509018
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

(Firma: Gast) #7509025
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.

#7509048
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.

#7509072
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

(Firma: Gast) #7509079
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.....

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