我正在使用小型HTTP服务器。我正在构建路由器,因为可能有很多路由,我想将它们放入闪存中,这样我就不必使用有价值的SRAM。然而,要么我不理解某些东西或者发生了一些奇怪的事情,因为我似乎无法从闪存中读回我存储的数据。
我有一个包含函数指针和char指针的结构。我想将这些结构的数组存储到flash中并将其读回。但是,通过一个小的调试打印,我可以看到我无法正确读回char指针。它将垃圾打印到串口。
这是一个小例子。
#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:
}
答案 0 :(得分:1)
PROGMEM并不那么容易使用。它可以简化一点:
#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:
}
我认为route1
和route2
可能导致用于复制routingTable
的所有麻烦。如果你像我一样初始化routingTable
的元素,它会更好。而getRoute
也被打破了很多。
无论如何,如果你有flash字符串,你也可以使用String str {(__FlashStringHelper*)r.URI};
然后使用比较运算符: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");
}
答案 1 :(得分:0)
char *f = (char *)pgm_read_word(r->URI);
Serial.println(f);
f
是指向PROGMEM中字符数组的指针,但Serial.println
并不知道!它最终试图从RAM读取字符串,它不是。
Arduino Serial
库似乎不支持PROGMEM中的字符串。您需要循环遍历字符串,一次打印一个字符,使用另一个库,或将字符串存储在RAM中。
答案 2 :(得分:0)
正如@KIIV所指出的,最好在Route
的声明中直接指定routingTable
。作为替代解决方案,您可以将结构Route
重新定义为
typedef struct {
void (*func)();
char URI[16]; //adjust the size to your need
} Route;
通过这种方式,可以通过单次调用URI
来完成从闪存中读取function
和memcpy_P
地址。完整的代码:
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:
}