2016-10-05 140 views
0

我正在一個小型的HTTP服務器上工作。我正在建立一個路由器,因爲可能有很多路由,所以我想把它們放到閃存中,這樣我就不必使用有價值的SRAM。但是,無論我是否正確地理解某些內容或發生了一些奇怪的事情,因爲我似乎無法從閃存中讀回存儲的數據。Arduino progmem讀取亂碼數據

我有一個結構,其中包含一個函數指針和一個字符指針。我想將這些結構的數組存儲到閃存中並將其讀回。但是,通過一個小的調試打印,我可以看到我無法正確讀回字符指針。它將垃圾打印到串口。

這是一個小例子。

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

} 

回答

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: 

} 

我想route1route2,因爲它是用於複製到routingTable可能導致所有的煩惱。如果您像我一樣初始化routingTable的元素,則效果會更好。還有getRoute被打破了很多。

無論如何,如果你有閃光的字符串,你也可以使用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"); 
} 
0
char *f = (char *)pgm_read_word(r->URI); 
Serial.println(f); 

f是一個指向PROGMEM字符數組,但Serial.println不知道!它最終試圖從RAM中讀取字符串,而不是。

Arduino Serial庫似乎不支持PROGMEM中的字符串。您需要遍歷一次打印一個字符的字符串,使用另一個庫或將字符串存儲在RAM中。

0

正如指出的@KIIV,最好是直接指定RouteroutingTable的聲明中。作爲替代方案,你可以重新定義了結構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: 
} 
+0

目前我的URI支持多達254個字符,所以這真的是一個倒退。此外,這會浪費任何少於15個字符的網址的內存。 –

+0

你沒有在問題中提到你的URI會很長。你是正確的,這種方法會浪費內存,但如果順序是幾個字節,我認爲它是可以接受的。在你的情況下,@KIIV答案是更好的解決方案。 – putu