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.