Acceso a datos más allá del límite de 64k en atmega1280

Necesito poder almacenar y hacer referencia a matrices de datos constantes que deben colocarse después del límite de 64k en el atmega1280. ¿Cómo creo la estructura de datos correcta y luego accedo a ella para el siguiente ejemplo?

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];
}

Actualmente AVRGCC lo compila colocando la estructura de datos después de 64k (Todo el programa está después de 64k, usando una instrucción de enlace para mover la .textsección) pero luego usa la LPMinstrucción para acceder a la estructura de datos.

La LPMinstrucción solo puede acceder a los primeros 64k de la memoria del programa.

Parece que lo siguiente debería funcionar

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

Pero el desmontaje y la simulación muestran que el puntero port_to_mode_PGMtiene solo un valor de 16 bits, a pesar de vivir más allá del límite de 64k.

Respuestas (2)

Acceder a constantes más allá de 64K es complicado, como ya ha descubierto, debido al uso de punteros de 16 bits. Si desea acceder a constantes en la memoria del programa más allá de 64K, debe calcular la dirección usted mismo usando aritmética de 32 bits (por ejemplo, con uint32_ttipos) y usar pgm_read_word_far().

Dado que debe tener instrucciones especiales del compilador para acceder a las constantes ubicadas en el espacio del programa de todos modos (por ejemplo, el uso de PROGMEM), le sugiero que haga que todos los accesos a estas constantes pasen por una función diseñada para acceder a sus datos y devolver los resultados en los tipos nativos que el el compilador puede trabajar más fácilmente con (es decir, no PROGMEMtipos). La abstracción para obtener las constantes y devolverlas no tiene que estar salpicada a través de su código, sino que solo existe en un lugar. Use static inlinefunciones y optimización del compilador para que esto sea eficiente.

Debe vincular los archivos binarios destinados a flash superior/inferior de manera diferente, por lo que casi no hay trabajo adicional para volver a compilar cada archivo binario con uno diferente TEXT_OFFSET #defineque se usa durante la compilación para especificar la ubicación en la que se vinculará el archivo binario. Siempre puede usar las ELPMinstrucciones independientemente de la compilación para flash superior/inferior.

Por ejemplo:

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_OFFSETse pasa en tiempo de compilación y coincide con el valor pasado a su enlazador.

Si observa la lista de ensamblaje producida para una fuente como esta, notará muchas instrucciones para hacer la aritmética de 32 bits. Si la eficiencia es importante, puede implementar su propia función de ensamblaje en línea para realizar el acceso de la manera requerida. El siguiente código muestra un fragmento de código de ensamblaje en línea personalizado, pirateado de ejemplos en <avr/pgmspace.h>. (Este fragmento solo cubre el caso único de la ELPM_word_enhanced__macro que se adapta a su micro).

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

Se usaría de manera similar que antes, pero esta vez el desplazamiento se pasa como un parámetro separado:

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

Esto producirá un código más eficiente ya que no se requiere aritmética de 32 bits. Se puede hacer aún más eficiente si es necesario al requerir que el desplazamiento se pase no en un valor de 32 bits, sino solo en la palabra o byte superior de TEXT_OFFSET. Dado que todos sus accesos a datos constantes pueden pasar por una función como esta, la información acerca de TEXT_OFFSETestá restringida a un solo lugar en su código. Hacer que su acceso a los datos pase por una función como esta en lugar de usar la variable directamente también tiene la ventaja de que puede simular su getdata()método para realizar pruebas.

NOTA: Estos ejemplos de código no se han probado por completo.

Oye, esto es genial, gracias. Todavía usaré mi solución alternativa para esta versión, pero sospecho que a largo plazo tendré que implementar esto ya que esta es la mejor opción general.

Podría escribir el programa C para acceder específicamente a la memoria lejana, pero como se menciona en esta pregunta relacionada, estoy tratando de escribir la aplicación para que pueda ejecutarse en 0x00000 o 0x10000, y me gustaría evitar tener que sazonar el código. con definiciones y diferentes rutas de código basadas en dónde se encuentra en la memoria.

Todavía espero que haya una mejor solución, pero parece que los punteros lejanos simplemente no funcionan perfectamente en AVRGCC en este momento. Parece que AVRGCC espera que el programador decida cuándo y dónde usar el acceso remoto y el almacenamiento remoto, en lugar de manejar esos detalles por sí mismo. Esto tiene sentido ya que no se sabe hasta el momento del enlace hacia dónde va el código, por lo que realmente no puede elegir entre LPMy ELPMen el momento de la generación del código.

Como solución, muevo el inicio del programa secundario a 0xF800, de modo que los primeros 4K estén por debajo del límite de 64k. AVRGCC coloca todas las constantes al comienzo del programa (vectores de interrupción, constantes, código, en ese orden), por lo que siempre que mis constantes ocupen menos de 4k de espacio, estaré bien usando el acceso de matriz regular dentro de AVRGCC.

No es una división del 50% del espacio de código, por lo que un programa necesariamente será más pequeño que el segundo programa, pero creo que se puede hacer una compensación por ahora, y si se determina que tenemos que soportar más memoria, entonces Tendré que usar un proceso más complejo.