[gelöst]Problem mit PROGMEM und variablem Index

Ich versuche schon seit vielen Stunden hinter einen seltsamen Effekt zu kommen, oder genauer, das Problem zu lösen:
ich habe im PROGMEN mehrere Symbole abgelegt, die ich auf einem 128x64 OLED-Display anzeige, je nach Bedarf. ich benutze dazu die SSD1306_minimal lib, abgeändert von TinyWire auf die Wire . Nun wollte ich einige der Symbole gruppieren , so dass ich sie mittels Index anzeigen kann.
Das Problem: gebe ich den Index direkt als Zahl im Sketch an, wird das dazugehörige Symbol korrekt dargestellt. Ersetze ich die Zahl jedoch durch eine Byte, Int, Uint8 oder 16 - Variable, derich den Wert ich NICHT direkt als Zahl zugewiesen habe, sondern z.B. wia analogRead(A1) eingelesen habe, wird das Symbol nicht mehr korekt dargestellt. Es ist, als ob der Wert der Variablen nicht korekt bei der getFlash Funktion in der Library ankommt.
die tests laufen auf einem Uno-Clone. die IDE ist die 1.8.13. das ZielSystem ist ein Attiny167 , soll es zumindest werden, wenn ich mit dem Flash hinkomme.
kompiler-Warnungen bekomme ich keine, mit ausnahme, dass in der ssd1306 lib die Variable für die I2C- Adresse nicht benutzt, jedoch deklariert ist, der Autor hat die I2C Adresse direkt für die 128x64er reingeschrieben - , warnungen sind aber in der IDE aktiviert.
hier mein Sketch: (nur das Notwendigste)

#include <Wire.h>
#include <SSD1306_minimal_wire.h>

//byte array of bitmap 20x8 <eBat voll>
const unsigned char img_eBat02[] PROGMEM = { 0x7f, 0x5d, 0x5d, 0x5d, 
  0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x4d, 
  0x45, 0x51, 0x59, 0x5d, 0x41, 0x7f, 0x3e, 0x3e,
};

//byte array of bitmap 20x8 <eBat halb>
const unsigned char img_eBat01[] PROGMEM = { 0x7f, 0x5d, 0x5d, 0x5d,
  0x5d, 0x5d, 0x5d, 0x5d, 0x4d, 0x45, 0x41, 0x41,
  0x41, 0x41, 0x41, 0x41, 0x41, 0x7f, 0x3e, 0x3e,
};

//byte array of bitmap 20x8 <eBat leer>
const unsigned char img_eBat00[] PROGMEM = { 0x7f, 0x5d, 0x5d, 0x4d, 
  0x45, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
  0x41, 0x41, 0x41, 0x41, 0x41, 0x7f, 0x3e, 0x3e,
};
const unsigned char *const img_eBat[] PROGMEM = {img_eBat00, img_eBat01, img_eBat02};


uint16_t cap1(){
  uint16_t capacity = 3 - (1024/700);
  return capacity;
}

uint16_t cap2(){
  uint16_t capaci = map(analogRead(A1),0,1023,0,3);
  return capaci;
}

void mainScreen(){
  uint16_t eBat = 3 - (1024/700);
  uint16_t eCap1 = cap1();
  uint16_t eCap2 = cap2();
  
  oled.drawImage( img_eBat[2], 106, 0, 20, 1 ); // funktioniert, das Symbol wird 
                                                // korrekt angezeigt

  oled.drawImage( img_eBat[eBat], 106, 0, 20, 1 ); // funktioniert, das Symbol wird 
                    // korrekt angezeigt egal ob eBat lokal oder global definiert ist
  
  oled.drawImage( img_eBat[eCap1], 106, 0, 20, 1 ); // funktioniert, das Symbol wird 
                    // korrekt angezeigt
  
  oled.drawImage( img_eBat[eCap2], 106, 0, 20, 1 ); // funktioniert NICHT!
      // das Symbol wird NICHT korrekt angezeigt! obwohl eCap2 den Wert 2 hat 
      // (oder zu haben scheint)???
}

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

so sieht die getFlash in der lib aus:
SSD1306Minimal.h

unsigned char getFlash( const unsigned char * mem, unsigned int idx  );

SSD1306Minimal.cpp

unsigned char SSD1306_Mini::getFlash( const unsigned char * mem, unsigned int idx  ){
  unsigned char data= pgm_read_byte( &(mem[idx]) );
  return data;
}

und hier noch die Funktion drawImage:
ssd1306Minimal.h

// draw an image with defined x,y position and width,height definition
   void drawImage( const unsigned char * img, unsigned char col, unsigned char row, unsigned    char w, unsigned char h );

ssd1306Minimal.cpp

void SSD1306_Mini::drawImage( const unsigned char * img, unsigned char col, unsigned char row, unsigned char w, unsigned char h ){
  unsigned int i, data;
  
  clipArea( col, row, w, h);
  
  for (i=0;i< (w*h);i++){

      data= getFlash( img, i);
              
      Wire.beginTransmission(SlaveAddress);
      Wire.write(GOFi2cOLED_Data_Mode);            // data mode

        Wire.write( data );
      Wire.endTransmission();    
  }
}

ich hoffe es kan mir jemand auf die Sprünge helfen, wie ich die Variable korrekt übergeben muss, ohne sie als konstante zu definieren, denn die Variable muss eben variabel sein.

Du suchst pgm_read_ptr(), wie mir scheint

wie muss ich pgm_read_ptr() mit meiner Index-Variablen anwenden und an die oled.drawImage(img_eBat[e_bat] .... übegrgeben?
ich blicke da echt nicht durch.

Das glaube ich dir nicht!

ed.drawImage(pgm_read_ptr(&(img_eBat[2])), 106, 0, 20, 1 ); // funktioniert, das Symbol wird 
                                                // korrekt angezeigt

Dann bin ich bereit zu glauben.

1 Like

ich will mich natürlich nicht mit Dir streiten, zumal Du mir um Lichtjahre voraus bist. jedoch wenn ich den index direkt als Zahl reinschreibe, kommt die Funktion oled.drawImage damit klar und zeigt das symbol (und auch die anderen dieser Gruppe korrekt an.) es funktioniert auch in einer for -schleife wenn ich die schleifen-variable fix z.B. von 0 bis 2 laufen lasse und dabei diese werte direkt als Zahl in der schleife angebe. nehme ich jedoch folgende Konstruktion: for (i=m; i<z;i++) und m ist nicht direkt z.B mit m=1 festgelegt sondern irgendwie engelesen, klappt es nicht mehr) Ich werde Deinen Vorschlag jetzt ausprobieren, und mich dann zurückmelden.
Vorerst: Ganz herzlichen Dank combie für D
eine Mühe:

verflixt combie, bist Du gut! ganz herzlichen Dank, jetzt funktioniert es!!! das Ist die Lösung!!

Das funktioniert wirklich. Hatte ich auch schon einmal festgestellt. Probiere es ruhig aus und staune. Im Gegenteil, wenn du den Addressoperator verwendest meckert der Compiler.

Das ist hier nicht das Problem!

Wenn du jetzt aufmerksam mitliest, wirst du auch verstehen warum!
Sehe als erstes:
ed.drawImage() erwartet einen Zeiger auf ein Bytearray im Flash.
Darum funktioniert auch die direkte Übergabe der Array Bezeichner, da sie zu einem solchen Zeiger zerfallen.

Jetzt möchte der Kollege aber seine Zeiger aus dem Zeigerarray img_eBat holen.

Und genau da liegt der Hase im Pfeffer. Denn dieses Array mit Zeigern liegt auch im Flash. Schau dir die Definition des Zeigerarrays an. Die Zeiger die er erhält, wenn er Zeiger aus dem Ram liest, welche allerdings in Wirklichkeit im Flash liegen, zeigen in die Wiese.

Darum kann sein Weg nicht funktionieren, egal, ob er das glaubt, oder da auch mal zufällig das richtige bei rum kommt.....

Eisenharte Regel:
Wenn ich Zeiger aus dem Flash lesen möchte, muss ich pgm_read_ptr() verwenden.
Und genau dafür benötige ich die Adresse der Zelle. in dem der Zeiger steht.

Resultat,:
Ohne & wird das keiner.
Es geht also nur mit & und nicht ohne &

Warum sollte ich das ausprobieren?
ICH weiß es auch so....

Und jetzt bist du dran mit staunen
:innocent: :innocent: :innocent: :innocent:

Ich weiß.
Dank zurück.
Schön!
Ich weiß.

So, habe die Sache mal analysiert, nicht ganz tief, aber hinreichend tief genug um zu sagen....

Warum:
ed.drawImage( img_eBat[2],... doch gegen meine Erwartung normaler weise funktioniert
Und ed.drawImage( img_eBat[variable],.... eben nicht.

Den Unterschied macht der Optimizer mit -Os funktioniert der erste Ausdruck, aber der zweite nicht.
Mit -O0 versagen beide.

Die Ursache:
Der Compiler weiß nichts über Memory Sections.
Einzig, er nimmt die Attribute und gibt sie an den Linker weiter.

Jetzt ist das Array, ein konstante Array, mit konstanten Zeigern.
Also ist img_eBat[2] auch total konstant. Darum setzt er die so gewonnene, im Array stehende Adresse, auch als Literal in den Code ein.
Was dazu führt, dass das konstante Zeigerarray mit den konstanten Zeigern vollständig wegoptimiert wird.
Und alles funktioniert, zufällig, wie erwartet.

Wenn man jetzt mit einem variablen Arrayindex arbeitet, kann er diese Optimierung nicht durchführen. Und somit greift der Pointer ins RAM, statt ins Flash.

Dass es trotzdem mit einer festen for Schleife funktioniert, liegt daran, dass der Compiler die Schleife ausrollt, und so wieder zu seinen Konstanten kommt.

Mit -O0 geht dann gar keine Optimierung mehr, und das Totalversagen wird zur Regel..

Also: Ja, ich musste auch staunen!

1 Like

Hallo,

Danke für die Analyse.

Hier möchte mal einen reduzierten Test zeigen:

#include <Streaming.h> // die Lib findest du selber ;-)
Stream &cout {Serial}; // cout Emulation für AVR Arduinos


         const byte A PROGMEM =  2;
volatile const byte B PROGMEM =  7;


void setup() 
{ 
  Serial.begin(9600);
  
  cout << "A ohne volatile ohne pgm_read_byte " << A << " zufaellig richtig" << endl; // der Optimizer trägt den eigentlich gewünschten Wert ein
  cout << "B mit volatile ohne pgm_read_byte  " << B << " wahrscheinlich (1 zu 255) falsch" << endl; // der Wert wird aus dem Ram geholt, und ist damit falsch
  cout << "A ohne volatile mit pgm_read_byte  " << pgm_read_byte(&A) << " zuverlaessig richtig"  << endl; // zuverlässig richtig
  cout << "B mit volatile mit ppgm_read_byte  " << pgm_read_byte(&B) << " zuverlaessig richtig"  << endl; // zuverlässig richtig
}

void loop() 
{

}

Ich weiß, das Volatile sieht komisch aus, zwingt aber den Compiler das B frisch aus dem Speicher zu ziehen.
Was dann den Fehler offenbart, der vom Optimizer bei A verborgen wird.

Das trifft natürlich auch auf andere Sections, z.B. auf EEMEM, zu.
Auch da kann einem der Optimizer Streiche spielen.

Edit:
Ach, habe das mein Result vergessen zu zeigen.....

A ohne volatile ohne pgm_read_byte 2 zufaellig richtig
B mit volatile ohne pgm_read_byte  0 wahrscheinlich (1 zu 255) falsch
A ohne volatile mit pgm_read_byte  2 zuverlaessig richtig
B mit volatile mit ppgm_read_byte  7 zuverlaessig richtig
2 Likes

Danke, dann bin ich beruhigt.
Und Danke für die plausible Erklärung.

herzlichen Dank combie, für Deine bemühungen und Deine Analyse. Ich hatte mich Stritweise herangetastet. Zuerst Habe ich die Symbole, auch die animierten, einfach direkt über den Namen ohne Index angesprochen (if Abfragen und Switch/Casees. dann wollte ich das ganze auf Indexes umbauen in der Hoffnung, etwas Flash-Speicher zu sparen, also habe ich einiges an Code rausgeschmissen und dafür die Zeiger-Arrays angelegt. zum Test, ob die Symbole funktionieren, habe ich zuerst direkt die Zahl (konstante da reingesetzt) was ja (zufällig , wie ich jetzt von Dir gelernt habe), funktionierte. dann eine Schleife um die Symbole als Animation abzuspielen, damit hatte ich ja bereits eine (zumindest für mich) Variable als Index eingesetzt. Das klappte auch. Als ich dann jedoch die Variable, die mit den Werten der Sensoren besetzt wurde, ging es nicht mehr, es wurde zwar was angezeigt, aber ausser der Grösse, hatte das mit dem gewünschten Symbol nix zu tun. Habe dann verschiedenes probiert, bis sich in meinem Kopf ein eol Error (end of latein Error) einstellte.
dann habe ich meine Problem ans forum übergeben.
Dich dann auch noch ins Staunen zu versetzen war nicht wirklich meine Absicht.

1 Like

Mach dir mal keine unnützen Hoffnungen!
Denn: Das hast du gut getan.

Würde mal sagen, solche Effekte sind genau das, warum ich hier aktiv bin.
(Der Kopf ist rund, damit das Denken die Richtung ändern kann)

Bisher kannte ich es so, dass der Optimizer einem Stöcke zwischen die Beine wirft, wenn man unsauberen Code schreibt.
Hier bügelt er Irrtümer aus, so gut er kann (ohne es zu bemerken).
Das ist es, was mich erstaunt.

Aus eigener Kraft, wäre ich vermutlich niemals auf diesen Aspekt gestoßen.
Er war es wert, untersucht zu werden, so ich meine.

! ! !

Iss klar..
Das glaube ich dir... :japanese_ogre:

Gerne doch.
Immer wenn es möglich ist.....