kuahmelcher:
Oder eher linear byteweise darauf zugreifen?
Äh ... ich glaube so! Es wird immer genau ein Byte aus dem PROGMEM-Bereich ausgelesen
Wunderbar, alles bestens!
Um zu verstehen, weshalb das alles nicht so einfach funktioniert, werfe ich den Blick auf die AVR libc und was im Modul "avr/pgmspace.h: Program Space Utilities" drin sein soll und was tatsächlich in der Arduino-Software drin ist:
http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html
Drin sein sollten: Eine Haufen Funktionen, die mit großen Pointern ("Far Pointer") über 64 KB Speicher hinaus zugreifen können sollen. Solche Funktionen wie memcpy_PF, strcpy_PF, strlen_PF, strstr_PF und diverse andere.
Tatsächlich enthalten sind diese Funktionen nur in einer "P" Version: memcpy_P, strcpy_P, strlen_P, strstr_P und diverse andere. Diese Funktionen können alle nur mit bis zu maximal 64 KB Variablen im Programm-Flash umgehen, den PROGMEM Variablen. Alle diese Funktionen kann man daher für Zugriffe über 64 KB Variablenspeicher komplett vergessen.
Jetzt gibt es in pgmspace.h aber auch Funktionen, die am Ende den Namen "far" haben, und hier wird es interessant: pgm_read_byte_far, pgm_read_word_far, pgm_read_float_far gibt es nämlich tatsächlich in der Arduino-Software, man kann sie verwenden und diese funktionieren tatsächlich: Man übergibt ihnen eine long-Zahl, und die Funktion liefert den Inhalt aus dem gewünschten Speicherbereich zurück.
Es gibt nur ein Problem: Woher bekommt man eine long-Zahl mit der Speicheradresse der Variablen, die man im Programmspeicher abgelegt hat? Alle Pointer in der Arduino-Software haben ha nur 16 Bit? Und hier kommt nun irgendein genialer Open Source Programmierer ins Spiel, der dafür eine Funktion gemacht hat: Es hat jemand ein Makro gemacht, mit dem man aus einer Far-Pointer-Flash-Variablen die Adresse herausbekommt:
#define GET_FAR_ADDRESS(var)
Ist mit Inline-Assembler Code drin, aber alles so verpackt, dass die Arduino-Software es frißt.
Und nun ist es einfach:
- Die Flash-Variablen auf Flash-Speichersektionen von max. 64KB Größe aufteilen
- Startadresse des Speichers als long-Variable mittels GET_FAR_ADDRESS ermitteln
- Linear auf diese und nachfolgende Speicheradressen zugreifen mittels pgm_read_byte_far und anderen "_far" Funtkonen
Wenn man weiß, wie es funktioniert, ist es eigentlich ganz einfach.
Im Beispielcode erzeuge ich vier Variablen mit je 32000 Bytes Größe als Char-Arrays und verteile diese auf zwei Sektionen.
Im Setup lasse ich einmal die Vier long-Startadressen des Speichers ausgeben.
In der loop lasse ich dann die ersten 32 Bytes jeder Variable ausgeben, einmal als Text und einmal als Bytes in Dezimaldarstellung. Alles rein zur Demonstration.
#include <avr/pgmspace.h>
/* GET_FAR_ADDRESS() macro
This macro facilitates the obtention of a 32 bit "far" pointer (only 24 bits
used) to data even passed the 64KB limit for the 16 bit ordinary pointer. It
is similar to the '&' operator, with some limitations.
Comments:
- The overhead is minimal and it's mainly due to the 32 bit size operation.
- 24 bit sizes guarantees the code compatibility for use in future devices.
- hh8() is an undocumented feature but seems to give the third significant byte
of a 32 bit data and accepts symbols, complementing the functionality of hi8()
and lo8(). There is not an equivalent assembler function to get the high
significant byte.
- 'var' has to be resolved at linking time as an existing symbol, i.e, a simple
type variable name, an array name (not an indexed element of the array, if the
index is a constant the compiler does not complain but fails to get the address
if optimization is enabled), a struct name or a struct field name, a function
identifier, a linker defined identifier,...
- The natural place for this macro should be the header avr/pgmspace.h and the
name... pgm_get_far_address?
- The returned value is the identifier's VMA (virtual memory address) determined
by the linker and falls in the corresponding memory region. The AVR Harvard
architecture requires non overlapping VMA areas for the multiple address spaces
in the processor: Flash ROM, RAM, and EEPROM. Typical offset for this are
0x00000000, 0x00800xx0, and 0x00810000 respectively, derived from the linker
script used and linker options. The value returned can be seen then as a
universal pointer.
*/
#define GET_FAR_ADDRESS(var) \
({ \
uint_farptr_t tmp; \
\
__asm__ __volatile__( \
\
"ldi %A0, lo8(%1)" "\n\t" \
"ldi %B0, hi8(%1)" "\n\t" \
"ldi %C0, hh8(%1)" "\n\t" \
"clr %D0" "\n\t" \
: \
"=d" (tmp) \
: \
"p" (&(var)) \
); \
tmp; \
})
// max array size not above 32 KB
#define ARRAYSIZE 32000
// Mal eben 4 arrays a 32000 Bytes = 128000 Bytes deklarieren
// In jede Sektion nur max. 64 KB Daten hineinpacken!
const char text01[ARRAYSIZE] __attribute__ ((section (".fini1"))) ={"Das ist Text1 in Sektion .fini1"};
const char text02[ARRAYSIZE] __attribute__ ((section (".fini1"))) ={"Das ist Text2 in Sektion .fini1"};
const char text03[ARRAYSIZE] __attribute__ ((section (".fini2"))) ={"Das ist Text3 in Sektion .fini2"};
const char text04[ARRAYSIZE] __attribute__ ((section (".fini2"))) ={"Das ist Text4 in Sektion .fini2"};
void setup() {
long longAddress;
Serial.begin(9600);
pinMode(13,OUTPUT);
Serial.println("Die folgenden Variablen liegen an diesen long-Adressen:");
Serial.print("text01: ");
Serial.println(GET_FAR_ADDRESS(text01));
Serial.print("text02: ");
Serial.println(GET_FAR_ADDRESS(text02));
Serial.print("text03: ");
Serial.println(GET_FAR_ADDRESS(text03));
Serial.print("text04: ");
Serial.println(GET_FAR_ADDRESS(text04));
}
void loop() {
// Ein bischen LEDs blinken lassen
digitalWrite(13,HIGH);
delay(1000);
digitalWrite(13,LOW);
delay(1000);
// Und die Inhalte aus den Flash-Variablen ziehen:
long longAddress;
for (int num=1; num<=4; num++)
{
if (num==1) longAddress=GET_FAR_ADDRESS(text01);
if (num==2) longAddress=GET_FAR_ADDRESS(text02);
if (num==3) longAddress=GET_FAR_ADDRESS(text03);
if (num==4) longAddress=GET_FAR_ADDRESS(text04);
for(int i=0;i<32;i++)
Serial.write(pgm_read_byte_far(longAddress+i));
Serial.println();
for(int i=0;i<32;i++)
{
Serial.print(pgm_read_byte_far(longAddress+i),DEC);
Serial.print(" ");
}
Serial.println();
}
Serial.println();
}
Den etwas längeren Kommentar zum GET_FAR_ADDRESS-Makro habe ich mal rein informationshalber mit in den Code kopiert, der kann da natürlich rausgelöscht werden.
Zur Aufteilung in 64kb Sektionen: Ich glaube, man kann sogar komplett alle Variablen in nur eine einzige Sektion packen, z.B. ".fini1". Ich habe das mal mit meinem Programm gemacht und es funktioniert mit 128000 Bytes nur in der ".fini1" Sektion.
Die Variablen über 64 KB müssen aber zwingend in eine andere Sektion als PROGMEM definiert werden, d.h. PROGMEM darf definitiv NICHT für so große Datenstrukturen verwendet werden, sonst läuft das Programm hinterher nicht. Aber das weißt Du natürlich schon, weil das ja der Grund für Dich war, diesen Thread zu starten.
Viel Spaß damit!
War ein lustiges Googeln und Herumprobieren, bis ich alle Puzzleteilchen zur Lösung beisammenhatte.