Const char aus PROGMEM in String = Exception 3

Hallo mal wieder zammen :grinning:

Ich kämpfe mich gerade durch eine HTML Seite auf einem ESP8266 Server, die aktuelle Werte anzeigt und änderbar macht.

Dazu fand ich folgenden Code ganz verständlich (da durch replace() im String für mich übersichtlich):

https://github.com/buxtronix/arduino/blob/master/esp8266-ledclock/esp8266-ledclock.ino

Es gibt eine HTML Seite mit den Platzhaltern:


const char MAIN_page[] PROGMEM = R"=====(
<html>
<head>
 <meta name="viewport" content="initial-scale=1">
 <style>
 body {font-family: helvetica,arial,sans-serif;}
 table {border-collapse: collapse; border: 1px solid black;}
 td {padding: 0.25em;}
 .title { font-size: 2em; font-weight: bold;}
 .name {padding: 0.5em;}
 .heading {font-weight: bold; background: #c0c0c0; padding: 0.5em;}
 .update {color: #dd3333; font-size: 0.75em;}
 </style>
</head>
<div class=title>NTP Clock</div>
<div class=name>Location: @@CLOCKNAME@@</div>
<form method="post" action="/form">
<table>
<tr><td colspan=2 class=heading>Status</td></tr>
<tr><td>Current time:</td><td><b>@@HOUR@@:@@MIN@@</b></td></tr>
<tr><td>Sync Status:</td><td>@@SYNCSTATUS@@</td></tr>
<tr><td>Name:</td><td><input type=text name="clockname" value="@@CLOCKNAME@@"></td></tr>

<tr><td colspan=2 class=heading>WiFi Setup</td></tr>
<tr><td>SSID:</td><td><input type=text name="ssid" value="@@SSID@@"></td></tr>
<tr><td>PSK:</td><td><input type=password name="psk" value="@@PSK@@"></td></tr>
<tr><td colspan=2>Update Wifi config:<input type=checkbox name=update_wifi value=1></td></tr>

<tr><td colspan=2 class=heading>Time Setup</td></tr>
<tr><td>NTP Server:</td><td><input type=text name="ntpsrv" value="@@NTPSRV@@"></td></tr>
<tr><td>Sync Interval:</td><td><input type=text name="ntpint" value="@@NTPINT@@">s</td></tr>
<tr><td>Timezone:</td><td><input type=text name="timezone" value="@@TZ@@">h</td></tr>
</table>
<p/>
<input type="submit" value="Update">
</form>
<div class="update">@@UPDATERESPONSE@@</div>
</html>
)=====";

Und die Funktion in der die Platzhalter ersetzt werden:

void handleRoot() {
  String s = MAIN_page; // Ich glaube hier passiert der Fehler
  s.replace("@@SSID@@", settings.ssid);
  s.replace("@@PSK@@", settings.psk);
  s.replace("@@TZ@@", String(settings.timezone));
  s.replace("@@HOUR@@", String(hour()));
  s.replace("@@MIN@@", String(minute()));
  s.replace("@@NTPSRV@@", settings.timeserver);
  s.replace("@@NTPINT@@", String(settings.interval));
  s.replace("@@SYNCSTATUS@@", timeStatus() == timeSet ? "OK" : "Overdue");
  s.replace("@@CLOCKNAME@@", settings.name);
  s.replace("@@UPDATERESPONSE@@", httpUpdateResponse);
  httpUpdateResponse = "";
  server.send(200, "text/html", s); // Kann man hier mit send_P was bewirken?
}

Leider erhalte ich einen

Exception 3: LoadStoreError: Processor internal physical address or data error during load or store

und der ESP startet immer wieder neu bis man ihn stromlos macht.

Meine 4 Fragen:

  • Wie kann ich das Problem lösen? Ich vermute es hängt mit dem PROGMEM zusammen, das in den String gepackt werden soll…
  • Wozu erst ein const char mit der Webseite, wenn es dann eh in einen String gepackt wird? Wenn ich die Seite gleich als String definiere, geht es!
  • Ist die RAM Fragmentierung bei solch einem Konstrukt sehr groß, also wird der RAM jedes mal “zerstückelt” wenn man die Seite lädt ?
  • Oder ist das bei einem ESP vernachlässigbar weil viel RAM da ist?

Ich danke euch mal wieder :+1:

Zu 1. Lesestoff pgmspace.h Möglicherweise pgmspace.h nicht eingebunden?

Zu 2. Weil ich keinen ESP8266 habe, halte ich mich lieber zurück, aber auf einem ESP32 würde ich PROGMEM nicht verwenden.

Danke dir!

Also laut dem Link ist in den neuen IDEs pgmspace integriert.

Ich fand auch noch:
„ The following code WILL work, even if locally defined within a function:

const static char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n"

Mit static geht es aber auch nicht.

Warum nutzt Du kein Progmem?

HTML-Dateien sind m. E. im Dateisystem LittleFS besser aufgehoben. Aktualisierung beispielsweise mittels JSON und JavaScript.

Ich will Dich aber nicht von Progmem wegbringen. Fips nutzt das beispielsweise in der LittleFS.ino seiner Tabs.

Dort aber nur für die konstante Seite zum Hochladen der fs.html.

Gruß Tommy

Wenn ich eine FlashStringHelper Funktion einsetze, funktioniert es:

 String s = (const __FlashStringHelper*) Main_page;

Warum weiß ich allerdings nicht wirklich :roll_eyes:

Ich fand noch dies:
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
" To retrieve and manipulate flash strings they must be read from flash in 4byte words. In the Arduino IDE for esp8266 there are several functions that can help retrieve strings from flash that have been stored using PROGMEM. Both of the examples above return const char * . However use of these pointers, without correct 32bit alignment you will cause a segmentation fault and the ESP8266 will crash. You must read from the flash 32 bit aligned."

Ich denke damit hängt es zusammen :expressionless:

Und ich dachte wirklich, dass dies der einfachste Weg für einen Anfänger sei :crazy_face:

Ohne Progmem schon.

Es tut mir leid, das zu sagen, aber es ist keine Funktion, sondern ein Cast.

Aber ich!
Du teilst der String Instanz damit mit, dass der Pointer ins Flash zeigt, und nicht ins RAM. Dem nackten Pointer ist das nämlich nicht anzusehen.

String s = FPSTR(Main_page);

Viele Dank für die Erklärung :slight_smile:

Eine Frage noch bitte:
So wie ich es verstehe wird ja durch PROGMEM mein Char Array ins Flash geschoben und dann temporär in den String geschrieben.

Macht das beim ESP überhaupt Sinn?
Ich könnte ja auch (wie schon probiert) die Seite direkt als String im RAM deklarieren.
Ich bin nur unsicher wegen der RAM Fragmentierung und ob das negative Auswirkungen hat im Dauerbetrieb.

Irgendwie irrational, deine Ängste…

z.B. wer Angst vor Fragmentierung hat, sollte auf die String Klasse verzichten. Aber das tust du in beiden Fällen nicht.

Dann:
Die Zeichenkette muss ja irgendwo liegen um einen PowerDown zu überleben.
Also im Flash. Da liegt sie sowieso.
Dann kommst du mit deinem PROGMEM ins Spiel.

Verwendest du PROGMEM, ist sie im Flash, bis du sie da raus holst und ins RAM kopierst.

Verwendest du kein PROGMEM, wird sie beim Systemstart sowieso ins RAM Kopiert. Und dann nochmal in die String Instanz. Die Zeichenkette liegt dann doppelt im RAM und einmal im Flash.

Du kannst dir also aussuchen, ob sie 3 mal deinen Speicher verbrauchen soll, oder nur 2 mal.

Und welche Geister du bei deinen “negative Auswirkungen im Dauerbetrieb” siehst, KA…
Sind halt deine Geister.

Aber da gibts einen Merksatz:

Alles hat Auswirkungen!

Evtl. sollte man anstelle des Suchen/Ersetzen gleich snprintf_P benutzen, wobei dann die Formatzeichenkette (das HTML) im Progmem liegt.

Gruß Tommy

:joy: Das trifft es im Zusammenhang mit einer nüchternen Programmierung nun so gar nicht!

Spitzbubenhaftes Interesse möglicher Auswirkungen schon sehr genau :wink:

Danke für deine Erläuterungen :+1:

Auch dir, Tommy! Das gucke ich mir nochmal an :+1: