Коли в gcc 16-бітові адреси, а пам’яті раптово 256к

… або як вистрілити собі в ногу на Arduino

У літній комп’ютерної школі ми використовуємо для навчання розробці ігор власноруч зроблений старий комп’ютер.

Зараз в ньому встановлена плата Arduino Mega з процесором ATmega2560, в якому цілих 256 кілобайт флеш-пам’яті. Передбачалося, що цього вистачить дуже надовго, адже ігри виходять прості (екран-то всього лише 64×64 пікселів). В реальності ми зіткнулися з деякими проблемами вже по досягненні прошивкою розміру приблизно 128 кілобайт.

В пам’яті програм, незважаючи на її назву, крім виконуваного коду ігор зберігаються всякі незмінні дані типу спрайтів і таблиць рівнів. Цих даних не так вже й багато.

Але коли ми підключили до нашого комп’ютера звуковий чіп YM2149F, і завантажили кілька десятків мелодій в ту ж програмну пам’ять, почалися проблеми.


Приставка зависала при спробі програти мелодію, або малювала якесь сміття в меню гри. Незрозуміло було, як це взагалі налагоджувати, адже процесор не тільки займається логікою гри, але і виводить зображення і звук. У підсумку виявилося, що компілятор gcc-avr використовує для зберігання покажчиків змінні розміром в два байти. Але адресувати 256 кілобайт всього двома байтами неможливо! Як же він викручується?

Покажчики код

По-перше, інструкції виклику функцій і переходи можуть використовувати трехбайтовые адреси. Тому линкеру достатньо підставити повну адресу в таку інструкцію і все запрацює. Якщо ж адреса функції передається через вказівник, то такий номер не пройде — адже покажчик-то у нас двобайтовий.

У такій ситуації gcc вставляє в нижніх 64кб «трамплін» — інструкції jmp, яка переходить на потрібну функцію. Тоді в якості адреси функції, який треба зберігати в змінної, буде виступати адреса цього трампліну — адже він поміщається в два байти. А при виклику буде відбуватися перехід куди треба.

Читайте також  Як зашифрувати файли і папки за допомогою EFS в Windows 10, 8.1 і Windows 7

Покажчики на дані

Але ми зберігаємо в пам’яті програм не тільки виконуваний код. А значить трампліни тут не допоможуть — ми разыменовываем покажчики, а чи не переходимо на них.

В бібліотеці AVR навіть є функції/макроси типу pgm_read_byte_far(addr), щоб разыменовать повний покажчик (їм передаються четырехбайтовые значення). Але gcc не вміє добувати ці покажчики засобами мови Сі.

На щастя, є макрос pgm_get_far_address(var) для отримання повної адреси змінної. Це робиться за допомогою вбудованого асемблера (той випадок, коли асемблер розумніші компілятора).

Залишилося переписати весь код, який використовує дані в ПЗУ. Тобто музичний програвач, малювання спрайтів, виведення тексту,… Не дуже приємне заняття. Та ще й код стане більш гальмівним, а для виводу графіки це дуже критично. Тому,

Розподіляємо дані по ПЗУ

Лінкер дуже намагається розмістити дані для програмної пам’яті в нижніх 64к. Це не спрацьовує, якщо даних занадто багато. Але адже найбільші дані у нас — це музичні файли. А значить якщо прибрати тільки їх, то все інше влізе в нижню пам’ять і основну частину коду переробляти не доведеться.

Для цього будемо експлуатувати особливості линкерного скрипта. Одна з останніх секцій, які лінкер розміщує в ПЗУ, називається .fini7. Збережемо всі масиви з музикою в цій секції:

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

Тепер avr-nm говорить нам, що все в порядку — дані зі спрайтами і рівнями опинилися в нижній частині ПЗУ, а музика у верхній.

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

Залишається переробити програвач на використання байтових покажчиків і замість вказівника на масив з кодом мелодії використовувати функцію для отримання її адреси. Функції потрібні, тому що у нас є додаток-програвач, де можна слухати всі мелодії за вибором. У ньому тепер зберігаються покажчики на функції такого виду:

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

Кінець світу відкладається до моменту, коли спрайт заб’ють нижні 64к. Це малоймовірно, бо коду все-таки більше, ніж спрайтів, а значить швидше закінчиться пам’ять взагалі.

Читайте також  Як «Яндекс» і Google підводять підсумки року

Бонус

Цього літа ми написали гру в стилі Сокобана. Деякі рівні вийшли досить складними. Спробуйте, наприклад, пройти ось цей:

Посилання

  1. Сторінка проекту на github
  2. Arduino і світлодіодний дисплей
  3. Arduino і філософський музичний камінь
  4. Трохи минулорічних ігор

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *