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

4

Necesito poder almacenar y referirme 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 es después de 64k, usando una instrucción de enlace para mover la sección .text ) pero luego usa la instrucción LPM para acceder a la estructura de datos.

La instrucción LPM 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_PGM es solo un valor de 16 bits, a pesar de estar más allá del límite de 64k.

    
pregunta Adam Davis

2 respuestas

2

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

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

Tienes que vincular los binarios destinados a flash superior / inferior de manera diferente, por lo que casi no hay trabajo adicional para recompilar también cada binario con un TEXT_OFFSET #define diferente que se usa durante la compilación para especificar la ubicación en la que binario estará vinculado. Siempre puedes usar las instrucciones ELPM independientemente de la compilación para el 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);
}

donde TEXT_OFFSET se pasa en tiempo de compilación y coincide con el valor pasado a su vinculador.

Si observa el listado de ensamblajes producido para una fuente como esta, notará muchas instrucciones para realizar la aritmética de 32 bits. Si la eficiencia es importante, puede utilizar su propia función de ensamblaje en línea para hacer el acceso de la manera requerida. El siguiente código muestra un fragmento de ensamblaje en línea personalizado, cortado de muestras en <avr/pgmspace.h> . (Este fragmento solo cubre el caso único de la macro ELPM_word_enhanced__ 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 la misma manera 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 se requiere, requiriendo que el desplazamiento no se pase en un valor de 32 bits, sino solo en la palabra superior o en el byte de TEXT_OFFSET . Dado que todos sus accesos de datos constantes pueden pasar por una función como esta, la información sobre TEXT_OFFSET está 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 burlarse de su método getdata() para la prueba.

NOTA: este ejemplo de código no se ha probado completamente.

    
respondido por el Austin Phillips
1

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

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

Como solución alternativa, estoy moviendo el inicio del programa secundario a 0xF800, por lo que el primer 4K está por debajo del límite de 64k. AVRGCC coloca todas las constantes al principio del programa (vectores de interrupción, constantes, código, en ese orden), así que mientras mis constantes ocupen menos de 4k de espacio, estaré bien usando el acceso al arreglo regular dentro de AVRGCC.

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

    
respondido por el Adam Davis

Lea otras preguntas en las etiquetas