0

私は小さなHTTPサーバーに取り組んでいます。私はルーターを構築していますが、かなりの数のルートが存在する可能性があるため、貴重な SRAM を使用する必要がないように、それらをフラッシュ メモリに入れたいと考えていました。ただし、フラッシュから保存されたデータを読み戻すことができないように見えるため、何かが正しく理解されていないか、奇妙なことが起こっています。

関数ポインターと char ポインターを含む構造体があります。これらの構造体の配列をフラッシュに保存して読み返したいと思います。ただし、小さなデバッグ プリントでは、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:

}
4

3 に答える 3

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と思います。私が行ったように要素を初期化すると、はるかにうまく機能します。また、たくさん壊れました。route2routingTableroutingTablegetRoute

とにかく、フラッシュ文字列がある場合は、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");
}
于 2016-10-05T06:20:34.850 に答える
0

@KIIVさんの指摘通り、Routeの宣言内で直接指定したほうがいいですroutingTableRoute別の解決策として、構造体を次のように再定義できます。

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

このように、URIfunctionアドレスの両方をフラッシュから読み取ることが、 への 1 回の呼び出しで実行できます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:
}
于 2016-10-05T06:47:54.437 に答える
0
char *f = (char *)pgm_read_word(r->URI);
Serial.println(f);

fPROGMEM 内の文字配列へのポインターですが、それSerial.printlnを知りません! RAM から文字列を読み取ろうとしますが、そうではありません。

ArduinoSerialライブラリは、PROGMEM の文字列をサポートしていないようです。一度に 1 文字ずつ印刷する文字列をループするか、別のライブラリを使用するか、文字列を RAM に保存する必要があります。

于 2016-10-05T06:20:21.447 に答える