When in gcc 16-bit addresses, and memory suddenly 256k

    ... or how to shoot yourself in the foot on the Arduino




    In the summer computer school, we use our own old computer to teach game development .

    Now it has an Arduino Mega board with an ATmega2560 processor, in which there are as many as 256 kilobytes of flash memory. It was assumed that this would be enough for a very long time, because the games are simple (the screen is only 64x64 pixels). In reality, we ran into some problems when the firmware reached a size of approximately 128 kilobytes.

    In the program memory, despite its name, in addition to the executable game code, all sorts of immutable data such as sprites and level tables are stored. This data is not so much.

    But when we connected the YM2149F sound chip to our computer and loaded a couple of dozen tunes into the same program memory, problems started.


    The prefix hung while trying to lose the melody, or drew some rubbish in the game menu. It was unclear how to debug it at all, because the processor not only deals with the logic of the game, but also displays the image and sound. As a result, it turned out that the gcc-avr compiler uses two byte variables for storing pointers. But it is impossible to address 256 kilobytes in just two bytes! How does he get out?

    Pointers to code


    First, function call instructions and transitions can use three-byte addresses. Therefore, the linker is enough to substitute the full address in such an instruction and it will work. If the address of the function is passed through a pointer, then such a number will not pass - after all, the pointer is two-byte.

    In this situation, gcc inserts a “springboard” into the bottom 64kb — the jmp instruction, which switches to the desired function. Then, the address of this springboard will act as the address of the function, which must be stored in a variable - after all, it is placed in two bytes. And when you call, there will be a transition where necessary.

    Pointers to data


    But we keep in memory of programs not only executable code. So the springboards will not help here - we dereference the pointers, and do not turn on them.

    The AVR library even has functions / macros of the type pgm_read_byte_far (addr) to dereference a full pointer (they are passed four-byte values). But gcc does not know how to extract these pointers with C language.

    Fortunately, there is a macro pgm_get_far_address (var) for getting the full address of the variable. This is done using inline assembly (the case when assembly is smarter than the compiler).

    It remains to rewrite all the code that uses the data in the ROM. That is a music player, drawing sprites, text output, ... Not a very pleasant experience. Yes, and the code will become more inhibitory, and for displaying graphics it is very critical. Therefore,

    We distribute data on ROM


    Linker is trying very hard to place data for program memory in the lower 64k. This does not work if there is too much data. But after all, our biggest data is music files. So if you remove only them, then everything else will fit into the lower memory and you will not have to redo the main part of the code.

    For this we will exploit the features of the linker script. One of the last sections that the linker places in the ROM is called .fini7. Let's save all arrays with music in this section:

    #define MUSICMEM __attribute__((section(".fini7")))constuint8_t tetris2[] MUSICMEM = { ... };
    

    Now avr-nm tells us that everything is in order - the data with sprites and levels were in the bottom of the ROM, and the music in the top.

    00002f9c t _ZL10level_menu
    00002e0f t _ZL10rope_lines
    000006de t _ZL10ShipSprite
    00023a09 t tetris2
    00024714 T the_last_v8
    

    It remains to alter the player to use four-byte pointers and instead of a pointer to an array with a melody code, use the function to get its address. Functions are needed because we have a player application where you can listen to all the tunes of your choice. It now stores pointers to functions of this type:

    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
    

    The end of the world is postponed until the moment when the sprites score lower 64k. This is unlikely, because the code is still more than sprites, which means that the memory will end altogether.

    Bonus


    This summer we wrote a game in the style of Sokoban. Some levels turned out quite difficult. Try, for example, to go through this one:



    Links


    1. Project page on github
    2. Arduino and LED display
    3. Arduino and the Philosopher's Musical Stone
    4. Few last year's games

    Also popular now: