... atau cara menembak diri sendiri dengan Arduino

Di sekolah komputer musim panas, kami menggunakan
komputer buatan kami sendiri untuk mengajarkan pengembangan game.
Sekarang ia memiliki papan Arduino Mega dengan prosesor ATmega2560, di mana memori flash sebanyak 256 kilobyte. Diasumsikan bahwa ini akan bertahan lama, karena gimnya sederhana (layarnya hanya 64x64 piksel). Pada kenyataannya, kami mengalami beberapa masalah ketika firmware mencapai sekitar 128 kilobyte.
Dalam memori program, terlepas dari namanya, di samping kode permainan yang dapat dieksekusi, semua jenis data yang tidak berubah seperti sprite dan tabel level disimpan. Data ini tidak banyak.
Tetapi ketika kami
menghubungkan chip suara YM2149F ke komputer kami , dan mengunduh beberapa lusin lagu ke dalam memori program yang sama, masalah pun dimulai.
Awalan jatuh ketika mencoba memainkan melodi, atau menarik beberapa sampah di menu permainan. Tidak jelas bagaimana cara men-debug ini sama sekali, karena prosesor tidak hanya berurusan dengan logika permainan, tetapi juga menampilkan gambar dan suara. Akibatnya, ternyata kompiler gcc-avr menggunakan variabel berukuran dua byte untuk menyimpan pointer. Tetapi mengatasi 256 kilobyte hanya dengan dua byte adalah mustahil! Bagaimana dia keluar?
Petunjuk Kode
Pertama, instruksi panggilan fungsi dan lompatan dapat menggunakan alamat tiga byte. Oleh karena itu, cukup bagi penghubung untuk mengganti alamat lengkap dalam instruksi seperti itu dan itu akan berhasil. Jika alamat fungsi dilewatkan melalui pointer, maka angka seperti itu tidak akan lulus - karena kita memiliki pointer byte ganda.
Dalam situasi ini, gcc memasukkan "loncatan" ke dalam 64kb yang lebih rendah - instruksi jmp, yang beralih ke fungsi yang diinginkan. Kemudian alamat papan loncatan ini akan bertindak sebagai alamat fungsi yang perlu disimpan dalam variabel - setelah semua, itu ditempatkan dalam dua byte. Dan ketika dipanggil, akan ada transisi di mana diperlukan.
Pointer Data
Tetapi kami menyimpan dalam memori program tidak hanya kode yang dapat dieksekusi. Jadi lompatan tidak akan membantu di sini - kita adalah petunjuk dereferencing, tetapi tidak pergi ke mereka.
Pustaka AVR bahkan memiliki fungsi / makro seperti pgm_read_byte_far (addr) untuk melakukan dereferensi penunjuk penuh (mereka memberikan nilai empat byte). Tetapi gcc tidak tahu bagaimana mendapatkan pointer ini menggunakan bahasa C.
Untungnya, ada pgm_get_far_address makro (var) untuk mendapatkan alamat lengkap dari suatu variabel. Ini dilakukan dengan menggunakan assembler built-in (case ketika assembler lebih pintar daripada compiler).
Tetap menulis ulang semua kode yang menggunakan data dalam ROM. Yaitu, pemutar musik, render sprite, output teks, ... Bukan pengalaman yang sangat menyenangkan. Ya, dan kodenya akan menjadi lebih menghambat, dan untuk grafik itu sangat kritis. Oleh karena itu,
Kami mendistribusikan data pada ROM
Linker berusaha sangat keras untuk meletakkan data untuk memori program di 64k yang lebih rendah. Ini tidak berfungsi jika ada terlalu banyak data. Tetapi data terbesar yang kita miliki adalah file musik. Jadi jika Anda hanya menghapusnya, maka semua yang lain akan masuk ke memori yang lebih rendah dan Anda tidak perlu mengulang bagian utama dari kode.
Untuk melakukan ini, kami akan mengeksploitasi fitur skrip tautan. Salah satu bagian terakhir yang ditempatkan oleh tautan di ROM disebut .fini7. Simpan semua array musik di bagian ini:
#define MUSICMEM __attribute__((section(".fini7"))) const uint8_t tetris2[] MUSICMEM = { ... };
Sekarang avr-nm memberi tahu kita bahwa semuanya sudah beres - data dengan sprite dan level ternyata berada di bagian bawah ROM, dan musik di atas.
00002f9c t _ZL10level_menu 00002e0f t _ZL10rope_lines 000006de t _ZL10ShipSprite 00023a09 t tetris2 00024714 T the_last_v8
Tetap mengulang pemain menggunakan pointer empat-byte dan alih-alih menggunakan pointer ke array dengan kode melodi, gunakan fungsi untuk mendapatkan alamatnya. Fungsi diperlukan karena kami memiliki aplikasi pemain di mana Anda dapat mendengarkan semua nada pilihan Anda. Sekarang menyimpan pointer ke fungsi seperti ini:
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
Akhir dunia tertunda hingga sprite mencapai bagian bawah 64k. Ini tidak mungkin, karena masih ada lebih banyak kode daripada sprite, yang berarti bahwa memori mungkin akan berakhir secara umum.
Bonus
Musim panas ini kami menulis permainan dengan gaya Sokoban. Beberapa level ternyata cukup sulit. Coba, misalnya, untuk lulus yang ini:

Referensi
- Halaman proyek Github
- Tampilan Arduino dan LED
- Arduino dan Batu Musik Bertuah
- Beberapa pertandingan tahun lalu