... o c贸mo dispararte en el pie con un Arduino

En la escuela de inform谩tica de verano, utilizamos
una computadora antigua hecha por nosotros mismos para ense帽ar el desarrollo de juegos.
Ahora tiene una placa Arduino Mega con un procesador ATmega2560, en el que hay hasta 256 kilobytes de memoria flash. Se supon铆a que esto durar铆a mucho tiempo, porque los juegos son simples (la pantalla es de solo 64x64 p铆xeles). En realidad, encontramos algunos problemas cuando el firmware alcanz贸 aproximadamente 128 kilobytes.
En la memoria del programa, a pesar de su nombre, adem谩s del c贸digo ejecutable del juego, se almacenan todo tipo de datos sin cambios, como sprites y tablas de niveles. Estos datos no son tanto.
Pero cuando
conectamos el chip de sonido YM2149F a nuestra computadora y descargamos un par de docenas de melod铆as en la misma memoria de programa, comenzaron los problemas.
El prefijo se bloque贸 al intentar tocar una melod铆a o dibuj贸 algo de basura en el men煤 del juego. No estaba claro c贸mo depurar esto en absoluto, porque el procesador no solo se ocupa de la l贸gica del juego, sino que tambi茅n muestra la imagen y el sonido. Como resultado, result贸 que el compilador gcc-avr usa variables de dos bytes de tama帽o para almacenar punteros. 隆Pero abordar 256 kilobytes con solo dos bytes es imposible! 驴C贸mo sale 茅l?
Punteros de c贸digo
Primero, las instrucciones de llamada a funciones y los saltos pueden usar direcciones de tres bytes. Por lo tanto, es suficiente que el vinculador sustituya la direcci贸n completa en dicha instrucci贸n y funcionar谩. Si la direcci贸n de la funci贸n se pasa a trav茅s de un puntero, ese n煤mero no pasar谩, porque tenemos un puntero de doble byte.
En esta situaci贸n, gcc inserta un "trampol铆n" en los 64 kb inferiores, la instrucci贸n jmp, que cambia a la funci贸n deseada. Entonces, la direcci贸n de este trampol铆n actuar谩 como la direcci贸n de la funci贸n que debe almacenarse en la variable; despu茅s de todo, se coloca en dos bytes. Y cuando se llame, habr谩 una transici贸n donde sea necesario.
Punteros de datos
Pero almacenamos en la memoria del programa no solo el c贸digo ejecutable. Por lo tanto, los saltos no ayudar谩n aqu铆: estamos desreferenciando los punteros, pero no vamos a ellos.
La biblioteca AVR incluso tiene funciones / macros como pgm_read_byte_far (addr) para desreferenciar el puntero completo (se pasan valores de cuatro bytes). Pero gcc no sabe c贸mo obtener estos punteros utilizando el lenguaje C.
Afortunadamente, hay una macro pgm_get_far_address (var) para obtener la direcci贸n completa de una variable. Esto se hace utilizando el ensamblador incorporado (el caso cuando el ensamblador es m谩s inteligente que el compilador).
Queda por reescribir todo el c贸digo que usa los datos en la ROM. Es decir, un reproductor de m煤sica, sprites de renderizado, salida de texto, ... No es una experiencia muy agradable. S铆, y el c贸digo se volver谩 m谩s inhibitorio, y para los gr谩ficos es muy cr铆tico. Por lo tanto,
Distribuimos datos en ROM
Linker est谩 intentando poner datos para la memoria del programa en los 64k inferiores. Esto no funciona si hay demasiados datos. Pero los datos m谩s importantes que tenemos son los archivos de m煤sica. Por lo tanto, si solo los elimina, todo lo dem谩s quedar谩 en la memoria inferior y no tendr谩 que rehacer la parte principal del c贸digo.
Para hacer esto, explotaremos las caracter铆sticas del script enlazador. Una de las 煤ltimas secciones que el enlazador coloca en la ROM se llama .fini7. Guarde todos los arreglos de m煤sica en esta secci贸n:
#define MUSICMEM __attribute__((section(".fini7"))) const uint8_t tetris2[] MUSICMEM = { ... };
Ahora avr-nm nos dice que todo est谩 en orden: los datos con sprites y niveles resultaron estar en la parte inferior de la ROM, y la m煤sica en la parte superior.
00002f9c t _ZL10level_menu 00002e0f t _ZL10rope_lines 000006de t _ZL10ShipSprite 00023a09 t tetris2 00024714 T the_last_v8
Queda por rehacer el reproductor para usar punteros de cuatro bytes y en lugar de usar un puntero a una matriz con el c贸digo de melod铆a, use la funci贸n para obtener su direcci贸n. Las funciones son necesarias porque tenemos una aplicaci贸n de reproductor donde puedes escuchar todas las melod铆as que elijas. Ahora almacena punteros para funciones de este tipo:
00006992 <_Z12tetris2_addrv>: 6992: 61 ef ldi r22, 0xF1 ; 241 6994: 7a e3 ldi r23, 0x3A ; 58 6996: 82 e0 ldi r24, 0x02 ; 2 6998: 99 27 eor r25, r25 699a: 08 95 ret
El fin del mundo se retrasa hasta que los sprites tocan el fondo 64k. Esto es poco probable, porque todav铆a hay m谩s c贸digo que sprites, lo que significa que la memoria probablemente terminar谩 en general.
Bono
Este verano escribimos un juego al estilo de Sokoban. Algunos niveles resultaron ser bastante dif铆ciles. Intenta, por ejemplo, pasar este:

Referencias
- P谩gina del proyecto Github
- Arduino y pantalla LED
- Arduino y la
piedra musical del fil贸sofo - Juegos del a帽o pasado