So you're a Noob? Post your questions here until you graduate! Don't be shy.

User avatar
By DTrain123
#69154 After some gnashing of teeth and pulling of hair, I came up with a solution:

In the WString.h file, there is a constructor: String(const __FlashStringHelper *str);

Internally this will use the strlen_P() to determine the length of the PROGMEM string, then it will use strcpy_P() to copy the PROGMEM into a RAM buffer. The String class also has a c_str() method which returns a const char* pointer to the internal buffer.

This means you can reuse the String() class to do the heavy lifting:
Code: Select allString apName(F("My Wifi AP Name"));
String apPassword(F("My Wifi Password"));
    wifiManager.autoConnect(apName.c_str(), apPassword.c_str());

You can also take it one step further and do it as a one liner:
Code: Select all    wifiManager.autoConnect( String(F("My Wifi AP Name")).c_str(), String(F("My Wifi Password")).c_str());

It's possible to put the whole thing into a macro to make it easier to use:
Code: Select all#define G(string_literal)  (String(F(string_literal)).c_str())

Then you can use G("") to pass a char* into the method:
Code: Select allwifiManager.autoConnect( G("My Wifi AP Name"), G("My Wifi Password"));

There are a couple of caveats:
1) The print and println methods are optimized to only read one byte from PROGMEM at a time and then write one byte at a time. The G("") macro allocates a buffer to hold the entire string so will use more RAM. Therefore it's better to use F("") for those methods. The String class can also use the F("") macro directly, there is no need to use this G("") macro because it would end up allocating the String memory twice.

2) The G("") macro uses a temporary String object to allocate the RAM buffer. Temporary objects are given expression scope. This means they are de-allocated at the end of the expression (where the semi-colon is). The memory is only valid on the line that it is declared. You can't store the result of the G("") macro, you can only use it to pass a char* into a function.

I did a test to make sure it worked:
Code: Select all#include <ESP.h>

#define G(string_literal)  (String(F(string_literal)).c_str())

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.print("Setup Start Free Heap: ");
  Serial.println(ESP.getFreeHeap());
    charMethod(G("Test long string literal +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++......"));
  Serial.print("Setup End Free Heap: ");
  Serial.println(ESP.getFreeHeap());
}
void charMethod(const char* var) {
  Serial.print("charMethod Free Heap: ");
  Serial.println(ESP.getFreeHeap());
  Serial.print("charMethod var value: ");
  Serial.println(var);
}
void loop() {}

The compiler output was:
Code: Select allSketch uses 225769 bytes (21%) of program storage space. Maximum is 1044464 bytes.
Global variables use 32268 bytes (39%) of dynamic memory, leaving 49652 bytes for local variables. Maximum is 81920 bytes.

The output while running was:
Code: Select allSetup Start Free Heap: 48048
charMethod Free Heap: 47912
charMethod var value: Test long string literal +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++......
Setup End Free Heap: 48048

So you can see that it only allocates space for the string literal while it's performing the charMethod() function call. Once the function returns, the space gets deallocated and is available again.

If you did the same thing without the G macro, the compiler output is:
Code: Select allSketch uses 225529 bytes (21%) of program storage space. Maximum is 1044464 bytes.
Global variables use 32396 bytes (39%) of dynamic memory, leaving 49524 bytes for local variables. Maximum is 81920 bytes.

So it's allocated an extra 128 bytes of global variables in order to hold the string.
Code: Select allSetup Start Free Heap: 47920
charMethod Free Heap: 47920
charMethod var value: Test long string literal +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++......
Setup End Free Heap: 47920

While running there is less heap space available, because the string is sitting in RAM the whole time.
Last edited by DTrain123 on Thu Aug 17, 2017 12:50 am, edited 2 times in total.
User avatar
By DTrain123
#69155
martinayotte wrote:... and in the core/esp8266/Print.h :
Code: Select allsize_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));


Interesting. I don't seem to have that in my Print.h. I'm using the 2.3.0 version.
Looking on GitHub it looks like that function is only available in the 2.4.0-rc1 version.
User avatar
By DTrain123
#69215
philbowles wrote:Here's my "diagnostic print" function which does what I think you need:


I can see how that would work for your specific case, but I was looking for a more general solution that would let you pass strings from progmem into ANY method that takes const char*

So if you were using some custom library with a method:
Code: Select allvoid customMethod(const char* parameter) {
    ...
}

You could use the macro I wrote above to send a string from PROGMEM directly into that method:
Code: Select allcustomMethod(G("Progmem string goes here"));

This works on any method that accepts const char*