Zugriff auf Daten jenseits der 64k-Grenze auf atmega1280

Ich muss in der Lage sein, konstante Datenarrays zu speichern und darauf zu verweisen, die nach der 64k-Grenze im atmega1280 platziert werden müssen. Wie erstelle ich die richtige Datenstruktur und greife dann für das folgende Beispiel darauf zu:

const uint16_t PROGMEM port_to_mode_PGM[] = {
    NOT_A_PORT,
    &DDRA,
    &DDRB,
    &DDRC,
    &DDRD,
    &DDRE,
    &DDRF,
    &DDRG,
    &DDRH,
    NOT_A_PORT,
    &DDRJ,
    &DDRK,
    &DDRL,
};

uint16_t getdata(uint8_t index)
{
   return port_to_mode_PGM[index];
}

Derzeit kompiliert AVRGCC es und platziert die Datenstruktur nach 64 KB (das gesamte Programm befindet sich nach 64 KB, wobei eine Linker-Anweisung zum Verschieben des .textAbschnitts verwendet wird), verwendet dann jedoch die LPMAnweisung, um auf die Datenstruktur zuzugreifen.

Der LPMBefehl kann nur auf die ersten 64 KB des Programmspeichers zugreifen.

Es sieht so aus, als ob das Folgende funktionieren sollte

uint16_t getdata(uint8_t index)
{
   return pgm_read_word_far( port_to_mode_PGM + index);
}

Aber die Disassemblierung und Simulation zeigen, dass der Zeiger port_to_mode_PGMnur ein 16-Bit-Wert ist, obwohl er jenseits der 64k-Grenze lebt.

Antworten (2)

Wie Sie bereits herausgefunden haben, ist der Zugriff auf Konstanten jenseits von 64 KB aufgrund der Verwendung von 16-Bit-Zeigern schwierig. Wenn Sie auf Konstanten im Programmspeicher jenseits von 64K zugreifen möchten, müssen Sie die Adresse selbst mit 32-Bit-Arithmetik berechnen (z. B. mit uint32_tTypen) und verwenden pgm_read_word_far().

Da Sie sowieso spezielle Compiler-Anweisungen benötigen, um auf Konstanten zuzugreifen, die sich im Programmbereich befinden (z. B. Verwendung von PROGMEM), schlage ich vor, dass Sie alle Zugriffe auf diese Konstanten über eine Funktion ausführen, die dafür ausgelegt ist, auf Ihre Daten zuzugreifen und die Ergebnisse in den nativen Typen zurückzugeben, die die Compiler kann leichter mit (dh Nicht- PROGMEMTypen) arbeiten. Die Abstraktion, um die Konstanten zu erhalten und zurückzugeben, muss nicht durch Ihren Code gepfeffert werden, sondern existiert nur an einer Stelle. Verwenden Sie static inlineFunktionen und Compileroptimierung, um dies effizient zu gestalten.

Sie müssen die Binärdateien, die für den oberen/unteren Flash bestimmt sind, unterschiedlich verknüpfen, sodass es fast keine zusätzliche Arbeit gibt, jede Binärdatei mit einer anderen neu zu kompilieren, die während der Kompilierung TEXT_OFFSET #defineverwendet wird, um den Ort anzugeben, an dem die Binärdatei verknüpft wird. Sie können die ELPMAnweisungen unabhängig von der Zusammenstellung immer für den oberen/unteren Blitz verwenden.

Zum Beispiel:

static inline uint16_t getdata(uint8_t index)
{
    return pgm_read_word_far((uint32_t)TEXT_OFFSET + (uint16_t)port_to_mode_PGM + index);
}

where TEXT_OFFSETwird zur Kompilierzeit übergeben und stimmt mit dem Wert überein, der an Ihren Linker übergeben wird.

Wenn Sie sich das Assembler-Listing ansehen, das für solche Quellen erstellt wurde, werden Sie viele Anweisungen zur Durchführung der 32-Bit-Arithmetik bemerken. Wenn Effizienz wichtig ist, können Sie Ihre eigene Inline-Assembly-Funktion rollen, um den Zugriff auf die erforderliche Weise durchzuführen. Der folgende Code zeigt ein benutzerdefiniertes Inline-Assembly-Snippet, das aus Beispielen in gehackt wurde <avr/pgmspace.h>. (Dieser Ausschnitt deckt nur den Einzelfall des ELPM_word_enhanced__Makros ab, der zu Ihrem Mikro passt).

#define __custom_ELPM_word_enhanced__(offset, addr)    \
(__extension__({                        \
uint32_t __offset = (uint32_t)(offset);\
uint16_t __addr16 = (uint16_t)(addr);\
uint16_t __result;                  \
__asm__                             \
(                                   \
    "out %3, %C2"   "\n\t"          \
    "movw r30, %1"  "\n\t"          \
    "elpm %A0, Z+"  "\n\t"          \
    "elpm %B0, Z"   "\n\t"          \
    : "=r" (__result)               \
    : "r" (__addr16),               \
      "r" (__offset),               \
      "I" (_SFR_IO_ADDR(RAMPZ))     \
    : "r30", "r31"                  \
);                                  \
__result;                           \
}))

#define custom_pgm_read_word_far(offset, address_long)  __custom_ELPM_word_enhanced__((uint32_t)(offset), (uint16_t)(address_long))

Es würde ähnlich wie zuvor verwendet werden, aber diesmal wird der Offset als separater Parameter übergeben:

static inline uint16_t getdata(uint8_t index)
{
    return custom_pgm_read_word_far(TEXT_OFFSET, (uint16_t)port_to_mode_PGM + index);
}

Dies erzeugt einen effizienteren Code, da keine 32-Bit-Arithmetik erforderlich ist. Es kann bei Bedarf noch effizienter gemacht werden, indem verlangt wird, dass der Offset nicht in einem 32-Bit-Wert übergeben wird, sondern nur im oberen Wort oder Byte von TEXT_OFFSET. Da alle Ihre ständigen Datenzugriffe eine Funktion wie diese durchlaufen können, TEXT_OFFSETsind die Informationen über auf eine einzige Stelle in Ihrem Code beschränkt. Wenn Sie Ihren Datenzugriff über eine solche Funktion laufen lassen, anstatt die Variable direkt zu verwenden, hat dies auch den Vorteil, dass Sie Ihre getdata()Methode zum Testen nachahmen können.

HINWEIS: Diese Codebeispiele wurden nicht vollständig getestet.

Hey, das ist großartig, danke. Ich werde meine Problemumgehung für diese Version weiterhin verwenden, aber ich vermute, dass ich dies langfristig implementieren muss, da dies die bessere Gesamtoption ist.

Ich könnte das C-Programm schreiben, um speziell auf den fernen Speicher zuzugreifen, aber wie in dieser verwandten Frage erwähnt, versuche ich, die App so zu schreiben, dass sie entweder bei 0x00000 oder 0x10000 ausgeführt werden kann, und ich möchte vermeiden, den Code pfeffern zu müssen mit Definitionen und unterschiedlichen Codepfaden, je nachdem, wo es sich im Speicher befindet.

Ich hoffe immer noch, dass es eine bessere Lösung gibt, aber es sieht so aus, als würden Fernzeiger im Moment in AVRGCC einfach nicht nahtlos funktionieren. Es scheint, dass AVRGCC erwartet, dass der Programmierer entscheidet, wann und wo er Fernzugriff und Fernspeicherung verwendet, anstatt sich selbst um diese Details zu kümmern. Dies ist sinnvoll, da bis zur Verknüpfungszeit nicht bekannt ist, wohin der Code geht, sodass zur Codegenerierungszeit wirklich nicht zwischen LPMund gewählt werden kann.ELPM

Als Problemumgehung verschiebe ich den Start des sekundären Programms auf 0xF800, sodass die ersten 4K unter der 64K-Grenze liegen. AVRGCC stellt alle Konstanten an den Anfang des Programms (Interrupt-Vektoren, Konstanten, Code, in dieser Reihenfolge). Solange meine Konstanten also weniger als 4 KB Speicherplatz beanspruchen, kann ich den regulären Array-Zugriff innerhalb von AVRGCC verwenden.

Es ist keine 50%ige Teilung des Coderaums, also wird ein Programm notwendigerweise kleiner sein als das zweite Programm, aber das ist ein Kompromiss, von dem ich glaube, dass er vorerst gemacht werden kann, und wenn es feststeht, dass wir mehr Speicher unterstützen müssen, dann werden wir ' Ich muss einen komplexeren Prozess verwenden.