Arduino程序读取回混乱的数据

Arduino progmem reads back garbled data

本文关键字:数据 混乱 程序 读取 Arduino      更新时间:2023-10-16

我正在一个小型HTTP服务器上工作。我正在建造一个路由器,因为可能有很多路由,我想把它们放进闪存,这样我就不必使用有价值的SRAM。然而,要么我没有正确理解,要么发生了一些奇怪的事情,因为我似乎无法从闪存中读取存储的数据。

我有一个结构,它包含一个函数指针和一个字符指针。我想把这些结构的数组存储到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:
}

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:
}

我想route1route2可能会导致所有的问题,因为它是用来复制到routingTable中的。如果像我一样初始化routingTable的元素,它的工作效果会更好。CCD_ 5也有大量断裂。

无论如何,如果你有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");
}
char *f = (char *)pgm_read_word(r->URI);
Serial.println(f);

f是指向PROGMEM中的字符数组的指针,但Serial.println不知道!它最终试图从RAM中读取字符串,而不是从RAM中。

Arduino Serial库似乎不支持PROGMEM中的字符串。您需要在每次打印一个字符的字符串上循环,使用另一个库,或者将字符串存储在RAM中。

@KIIV指出,最好直接在routingTable的声明中指定Route。作为另一种解决方案,您可以将结构体Route重新定义为

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

这样,从闪存读取URIfunction地址可以通过对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:
}