Компилятор GameBoy с системными регистрами и прерываниями

Я потратил много времени на изучение программирования для GameBoy, так как я уже был знаком с ассемблером Z80 и не боялся начать его использовать. Я бы (конечно) нашел гораздо более продуктивным программировать на C или C++, однако не могу найти полный компилятор для GameBoy, компиляторы, которые я могу найти, управляют всем сами и не дают доступ к системным регистрам программисту, а также имеют некоторые ужасные недостатки, такие как 100% загрузка ЦП и отсутствие поддержки прерываний.
Можно ли адресовать системные регистры так же, как компилятор Arduino AVR? иметь возможность обращаться к ЦП или системному регистру просто по его имени, такому как DDRD = %10101011
Что мне нужно сделать, чтобы добавить прерывания и системные регистры в компилятор? Все системные регистры, кроме одного, представляют собой только однобайтовые адреса памяти, а векторы прерываний, конечно же, представляют собой области памяти, единственный системный регистр, который не является адресом памяти, может быть изменен только с помощью двух ассемблерных инструкций EI и DI, но это могут быть встроенные функции. ?


person Lee Fogg    schedule 20.01.2015    source источник
comment
GBDK — это компилятор C, который позволяет напрямую взаимодействовать с регистрами — используйте gb/hardware.h. Однако предоставленная документация не очень хороша, поэтому вы не можете увидеть, какие имена указаны в документе. Просто откройте файл под include/gb/hardware.h и все увидите. Он также поддерживает прерывания — см. add_VBL, add_LCD, add_TIM, add_SIO и add_JOY в gb/gb.h.   -  person Pokechu22    schedule 20.01.2015
comment
Вау, спасибо @Pokechu22. Извините, что вы не ответили вовремя.   -  person Lee Fogg    schedule 21.01.2015


Ответы (2)


Обычная тактика заключается в создании собственных указателей на системные регистры. Я не знаю адрес DDRD, но что-то вроде этого должно помочь:

volatile unsigned char *reg_DDRD = (unsigned char *)0xE000;
*reg_DDRD = 0xAB;

Большинство компиляторов C не поддерживают двоичные константы, но вы можете использовать их с некоторыми хакерскими макросами. И вы можете использовать макросы, чтобы сделать синтаксис более интуитивным:

#define DDRD (*reg_DDRD)
DDRD = 0xAB;

Нет смысла модифицировать компилятор, когда ванильный код C может работать так же хорошо.

Обработка прерываний сводится к решению 3 проблем. Первый заключается в том, чтобы адрес вектора прерывания выполнял переход к функции C. Поскольку это находится в ПЗУ, вам нужно изменить среду выполнения C, чтобы инициализировать ее. Это сильно зависит от системы, но обычно вам нужно добавить файл на языке ассемблера, который выглядит следующим образом:

     org 38h   ; or wherever the gameboy CPU jumps to on interrupt
 jp _intr_function

Это должно заставить ЦП перейти к intr_function() в вашей программе C. Вам может понадобиться начальное подчеркивание, а может и не понадобиться. И вы, возможно, не сможете установить адрес назначения в файле ассемблера с помощью org, а вместо этого придется возиться с компоновщиком и секциями.

Вторая проблема заключается в том, что функция C не обязательно сохраняет все регистры, которые она должна сохранять. Вы можете сделать это, добавив к нему встроенную сборку, в следующих строках:

void intr_function()
{
     asm(" push af");
     asm(" push bc");
     asm(" push de");
     asm(" push hl");

     // ... now do what you like here.

     asm(" pop hl");
     asm(" pop de");
     asm(" pop bc");
     asm(" pop af");
 }

Наконец, может потребоваться подтверждение прерывания путем манипулирования аппаратным регистром. Но вы можете сделать это в коде C, так что в этом нет ничего особенного.

Мне не ясна проблема с циклами ожидания. Стандартные компиляторы C не имеют такой встроенной функции. Они вызывают main(), и если вы хотите зациклиться, это зависит от вас. Это правда, что C-подобный язык, используемый в Arduino SDK, имеет собственный встроенный бесконечный цикл, который вызывает функции, которые вы пишете, но это не обычный язык C.

person George Phillips    schedule 20.01.2015
comment
На самом деле все эти файлы заголовков, предоставленные поставщиком, делают это за вас. Я не очень хорошо знаком с внутренностями Z80, но знаю об архитектурах, в которых указатель не обязательно является просто любым указателем. Переключение банков и отдельные адресные пространства, к которым можно получить доступ только с помощью специальных инструкций, делают представление C о плоском адресном пространстве памяти недействительным. - person Jonathon Reinhart; 20.01.2015
comment
@JonathonReinhart GameBoy использует переключение банков, но, к счастью, только для 16 КБ ПЗУ, поэтому системные регистры никогда не перемещаются. - person Lee Fogg; 20.01.2015
comment
@LeeAllan Спасибо за разъяснение. Я не ожидал, что они будут - это затруднило бы взаимодействие с системой. - person Jonathon Reinhart; 20.01.2015
comment
Какие-нибудь хитрости или советы по снятию 100% загрузки процессора (цикл ожидания)? Любая идея о том, как добавить векторы прерываний? В GB есть несколько фиксированных векторов местоположения, так что, конечно, это просто вопрос добавления прыжка к моей функции. - person Lee Fogg; 20.01.2015

Во-первых, вы можете использовать GBDK, который является компилятором C и библиотекой для Gameboy. Он предоставляет доступ к регистрам в gb/hardware.h (но это не указано в файле документации, поскольку для каждого отдельного регистра нет комментариев). Он также предоставляет доступ к прерываниям через методы в gb/gb.h: add_VBL, add_LCD, add_TIM, add_SIO и add_JOY. (Есть также методы удаления с именем remove_).

Для справки и/или для вашего собственного использования вот содержимое gb/hardware.h:

#define __REG   volatile UINT8 *

#define P1_REG      (*(__REG)0xFF00)    /* Joystick: 1.1.P15.P14.P13.P12.P11.P10 */
#define SB_REG      (*(__REG)0xFF01)    /* Serial IO data buffer */
#define SC_REG      (*(__REG)0xFF02)    /* Serial IO control register */
#define DIV_REG     (*(__REG)0xFF04)    /* Divider register */
#define TIMA_REG    (*(__REG)0xFF05)    /* Timer counter */
#define TMA_REG     (*(__REG)0xFF06)    /* Timer modulo */
#define TAC_REG     (*(__REG)0xFF07)    /* Timer control */
#define IF_REG      (*(__REG)0xFF0F)    /* Interrupt flags: 0.0.0.JOY.SIO.TIM.LCD.VBL */
#define NR10_REG    (*(__REG)0xFF10)    /* Sound register */
#define NR11_REG    (*(__REG)0xFF11)    /* Sound register */
#define NR12_REG    (*(__REG)0xFF12)    /* Sound register */
#define NR13_REG    (*(__REG)0xFF13)    /* Sound register */
#define NR14_REG    (*(__REG)0xFF14)    /* Sound register */
#define NR21_REG    (*(__REG)0xFF16)    /* Sound register */
#define NR22_REG    (*(__REG)0xFF17)    /* Sound register */
#define NR23_REG    (*(__REG)0xFF18)    /* Sound register */
#define NR24_REG    (*(__REG)0xFF19)    /* Sound register */
#define NR30_REG    (*(__REG)0xFF1A)    /* Sound register */
#define NR31_REG    (*(__REG)0xFF1B)    /* Sound register */
#define NR32_REG    (*(__REG)0xFF1C)    /* Sound register */
#define NR33_REG    (*(__REG)0xFF1D)    /* Sound register */
#define NR34_REG    (*(__REG)0xFF1E)    /* Sound register */
#define NR41_REG    (*(__REG)0xFF20)    /* Sound register */
#define NR42_REG    (*(__REG)0xFF21)    /* Sound register */
#define NR43_REG    (*(__REG)0xFF22)    /* Sound register */
#define NR44_REG    (*(__REG)0xFF23)    /* Sound register */
#define NR50_REG    (*(__REG)0xFF24)    /* Sound register */
#define NR51_REG    (*(__REG)0xFF25)    /* Sound register */
#define NR52_REG    (*(__REG)0xFF26)    /* Sound register */
#define LCDC_REG    (*(__REG)0xFF40)    /* LCD control */
#define STAT_REG    (*(__REG)0xFF41)    /* LCD status */
#define SCY_REG     (*(__REG)0xFF42)    /* Scroll Y */
#define SCX_REG     (*(__REG)0xFF43)    /* Scroll X */
#define LY_REG      (*(__REG)0xFF44)    /* LCDC Y-coordinate */
#define LYC_REG     (*(__REG)0xFF45)    /* LY compare */
#define DMA_REG     (*(__REG)0xFF46)    /* DMA transfer */
#define BGP_REG     (*(__REG)0xFF47)    /* BG palette data */
#define OBP0_REG    (*(__REG)0xFF48)    /* OBJ palette 0 data */
#define OBP1_REG    (*(__REG)0xFF49)    /* OBJ palette 1 data */
#define WY_REG      (*(__REG)0xFF4A)    /* Window Y coordinate */
#define WX_REG      (*(__REG)0xFF4B)    /* Window X coordinate */
#define KEY1_REG    (*(__REG)0xFF4D)    /* CPU speed */
#define VBK_REG     (*(__REG)0xFF4F)    /* VRAM bank */
#define HDMA1_REG   (*(__REG)0xFF51)    /* DMA control 1 */
#define HDMA2_REG   (*(__REG)0xFF52)    /* DMA control 2 */
#define HDMA3_REG   (*(__REG)0xFF53)    /* DMA control 3 */
#define HDMA4_REG   (*(__REG)0xFF54)    /* DMA control 4 */
#define HDMA5_REG   (*(__REG)0xFF55)    /* DMA control 5 */
#define RP_REG      (*(__REG)0xFF56)    /* IR port */
#define BCPS_REG    (*(__REG)0xFF68)    /* BG color palette specification */
#define BCPD_REG    (*(__REG)0xFF69)    /* BG color palette data */
#define OCPS_REG    (*(__REG)0xFF6A)    /* OBJ color palette specification */
#define OCPD_REG    (*(__REG)0xFF6B)    /* OBJ color palette data */
#define SVBK_REG    (*(__REG)0xFF70)    /* WRAM bank */
#define IE_REG      (*(__REG)0xFFFF)    /* Interrupt enable */

Это делается так же, как ответ Джорджа Филлипса, и поэтому их можно использовать как обычные переменные.

Код, используемый GBDK для добавления и удаления прерываний, находится в libc\gb\crt0.s, но я недостаточно хорошо разбираюсь в ассемблере, чтобы включить соответствующие разделы в этот пост.

Я также не уверен, как избежать петли занятости.

person Pokechu22    schedule 21.01.2015