c++carduinoprogmem

Arduino progmem reads back garbled data


I'm working on a small HTTP server. I am building a router and since there could be quite a few routes, I wanted to put them into flash memory so that I don't have to use the valuable SRAM. However either I don't understand something correctly or something weird is happening since I can't seem to be able to read back my stored data from flash.

I have a struct which contains a function pointer and a char pointer. I want to store an array of these structs into flash and read them back. However with a small debug print I can see I can't read back the char pointer correctly. It prints garbish to the serial port.

Here is a small example.

#include <avr/pgmspace.h>

typedef struct {
    void (*func)();
    const char *URI;
} Route;

void test1() {
    Serial.println("Executed testfunc1");
}

void test2() {
    Serial.println("Executed testfunc2");
}

const char route1URI[] PROGMEM = "/route1";
const Route route1 PROGMEM = {
    test1,
    route1URI
};

const char route2URI[] PROGMEM = "/route2";
const Route route2 PROGMEM = {
    test2,
    route2URI
};

const Route routingTable[] PROGMEM = {
    route1,
    route2
};

void (*getRoute(char *URI))() {
    Route *r = (Route *)pgm_read_word(routingTable + 0);
    char *f = (char *)pgm_read_word(r->URI);

    Serial.println(f);

    return r->func;
}
void setup() {
    Serial.begin(9600);
    while (!Serial) { }

    Serial.println("started setup");
    void (*fn)() = getRoute("sometest");
    // will cause errors if called
    //fn();
    Serial.println("ended setup");
}

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

}

Solution

  • The PROGMEM is not that easy to use. And it can be little bit simplified:

    #include <avr/pgmspace.h>
    
    struct Route {
        void (*func)();
        const char *URI;
    };
    
    void test1() {
        Serial.println(F("Executed testfunc1")); // if you are using progmem, why not for string literals?
    }
    
    void test2() {
        Serial.println(F("Executed testfunc2"));
    }
    
    const char route1URI[] PROGMEM = "/route1";
    const char route2URI[] PROGMEM = "/route2";
    
    const Route routingTable[] PROGMEM = {
        {test1,route1URI},
        {test2,route2URI}
    };
    
    void (*getRoute(char *URI))() {
        Route r;
        memcpy_P((void*)&r, routingTable, sizeof(r)); // read flash memory into the r space. (can be done by constructor too)
    
        Serial.println((__FlashStringHelper*)r.URI); // it'll use progmem based print
        // for comparing use: strcmp_P( URI, r.URI)
    
        return r.func; // r.func is already pointer to the function
    }
    
    void setup() {
        Serial.begin(57600);
        while (!Serial) { }
    
        Serial.println("started setup");
        void (*fn)() = getRoute("sometest");
        // will cause errors if called
        //fn();
        Serial.print((uint16_t)test1, HEX); Serial.print(' ');
        Serial.print((uint16_t)test2, HEX); Serial.print(' ');
        Serial.println((uint16_t)fn, HEX);
    
        Serial.println("ended setup");
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
    
    }
    

    I suppose route1 and route2 might cause all the troubles as it was used for copy into the routingTable. If you initialize elements of routingTable as I did, it works much better. And also getRoute was broken a lot.

    Anyway, if you have flash string, you can use also String str {(__FlashStringHelper*)r.URI}; and then use compare operator: str == URI:

    #include <avr/pgmspace.h>
    
    // get size of array[]
    template<typename T, int size> int GetArrLength(T(&)[size]){return size;} 
    
    struct Route {
        void (*func)();
        const char *URI;
    };
    
    void test1() {
        Serial.println(F("Executed testfunc1")); // if you are using progmem, why not for string literals?
    }
    
    void test2() {
        Serial.println(F("Executed testfunc2"));
    }
    void test3() {
        Serial.println(F("Executed testfunc3"));
    }
    
    const char route1URI[] PROGMEM = "/route1";
    const char route2URI[] PROGMEM = "/route2";
    const char route3URI[] PROGMEM = "/route3";
    
    const Route routingTable[] PROGMEM = {
        {test1,route1URI},
        {test2,route2URI},
        {test3,route3URI}
    };
    
    void (*getRoute(char *URI))() {
      for (int8_t i = 0; i < GetArrLength(routingTable); ++i) {
        Route r;
        memcpy_P((void*)&r, routingTable+i, sizeof(r)); // read flash memory into the r space. (can be done by constructor too)
    
        String uri {(__FlashStringHelper*)r.URI};
        if (uri == URI) {
          return r.func; // r.func is already pointer to the function
        }
      }
    
      return nullptr;
    }
    
    void setup() {
        Serial.begin(57600);
        while (!Serial) { }
    
        Serial.println("started setup");
        void (*fn)() = getRoute("/route3");
        // will cause errors if called
        //fn();
        Serial.print((uint16_t)test1, HEX); Serial.print(' ');
        Serial.print((uint16_t)test2, HEX); Serial.print(' ');
        Serial.print((uint16_t)test3, HEX); Serial.print(' ');
        Serial.println((uint16_t)fn, HEX);
    
        Serial.println("ended setup");
    }