0
votes

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:

}
3

3 Answers

1
votes

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");
}
0
votes
char *f = (char *)pgm_read_word(r->URI);
Serial.println(f);

f is a pointer to a character array in PROGMEM, but Serial.println doesn't know that! It ends up trying to read the string from RAM, where it isn't.

The Arduino Serial library doesn't appear to support strings in PROGMEM. You will need to loop over the string printing one character at a time, use another library, or store the string in RAM.

0
votes

As pointed by @KIIV, it's better to specify Route directly inside the declaration of routingTable. As an alternative solution, you could redefined the struct Route to

typedef struct {
    void (*func)();
    char URI[16];  //adjust the size to your need
} Route; 

In this way, reading both URI and function address from flash can be done by single call to memcpy_P. The complete codes:

typedef struct {
    void (*func)();
    char URI[16];  //adjust the size to your need
} Route;

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

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

const Route routingTable[] PROGMEM = {
    {test1, "/route1"},
    {test2, "/route2"}
};

void (*getRoute(char *URI, int idx))() {
    Route r;
    memcpy_P(&r, &routingTable[idx], sizeof(Route));

    Serial.print(idx); Serial.println(". -----------------------------");
    Serial.print("Route: "); Serial.println(r.URI);
    Serial.print("fn address: "); Serial.println((uint16_t)r.func, HEX);
    Serial.print("test1 address: "); Serial.println((uint16_t)test1, HEX);
    Serial.print("test2 address: "); Serial.println((uint16_t)test2, HEX);

    return r.func;
}

void setup() {
    Serial.begin(9600);
    while (!Serial) { }

    Serial.println("started setup");
    void (*fn)();

    const int n = sizeof(routingTable) / sizeof(Route);
    for (int i = 0; i < n; i++) {
      fn = getRoute("sometest", i);
      fn();
    }
    Serial.println("ended setup");
}

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