带有系统寄存器和中断的 GameBoy 编译器
GameBoy compiler with system registers and interrupts
我花了很多时间学习 GameBoy 编程,因为我已经熟悉 Z80 Assembly,所以我并不害怕直接使用它。我(当然)会发现用 C 或 C++ 编程效率更高,但是找不到适用于 GameBoy 的完整编译器,我能找到的编译器自行管理所有内容,并且不向程序员提供对系统寄存器的访问权限,并且还有一些可怕的缺点,例如 100% CPU 利用率和不支持中断。
是否可以像 Arduino 的 AVR 编译器一样处理系统寄存器?能够简单地使用其名称来寻址 CPU 或系统寄存器,例如 DDRD = %10101011
我需要做什么才能将中断和系统寄存器添加到编译器中?除了一个系统寄存器之外的所有寄存器都只是一个字节的内存地址,中断向量当然是内存位置,唯一一个不是内存地址的系统寄存器只能用两条汇编指令修改 EI
和 DI
但这可能是内联函数正确吗?
通常的策略是创建您自己的系统寄存器指针。我不知道 DDRD 的地址,但像这样的东西应该可以解决问题:
volatile unsigned char *reg_DDRD = (unsigned char *)0xE000;
*reg_DDRD = 0xAB;
大多数 C 编译器不支持二进制常量,但您可以将它们与一些宏 hackery 一起使用。你可以使用宏来使语法稍微更直观:
#define DDRD (*reg_DDRD)
DDRD = 0xAB;
当原始 C 代码也能正常工作时,修改编译器毫无意义。
处理中断归结为解决 3 个问题。第一种是让中断向量地址跳转到 C 函数。由于它在 ROM 中,因此您需要修改 C 运行时环境以对其进行初始化。这非常依赖于系统,但通常您想要做的是添加一个如下所示的汇编语言文件:
org 38h ; or wherever the gameboy CPU jumps to on interrupt
jp _intr_function
这应该会导致 CPU 在您的 C 程序中转到 intr_function()
。您可能需要也可能不需要前导下划线。并且您可能无法使用 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() 并且是否要循环取决于您。的确,Arduino SDK 中使用的类 C 语言有自己内置的无限循环调用您编写的函数,但这不是普通的 C。
首先,您可以使用 GBDK,它是 Gameboy 的 C 编译器和库。它确实提供了对 gb/hardware.h
中寄存器的访问(但未在 doc 文件中列出,因为没有针对每个单独寄存器的注释)。它还通过 gb/gb.h
中的方法提供对中断的访问:add_VBL
、add_LCD
、add_TIM
、add_SIO
和 add_JOY
。 (还有名为 remove_
的删除方法)。
供参考and/or自己使用,这里是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 */
这些操作与 相同,因此可以像普通变量一样使用。
在 libc\gb\crt0.s
中可以找到 GBDK 用于添加和删除中断的代码,但我对汇编的了解还不够,无法将相关部分包含在此 post 中。
我也不确定如何避免忙循环。
我花了很多时间学习 GameBoy 编程,因为我已经熟悉 Z80 Assembly,所以我并不害怕直接使用它。我(当然)会发现用 C 或 C++ 编程效率更高,但是找不到适用于 GameBoy 的完整编译器,我能找到的编译器自行管理所有内容,并且不向程序员提供对系统寄存器的访问权限,并且还有一些可怕的缺点,例如 100% CPU 利用率和不支持中断。
是否可以像 Arduino 的 AVR 编译器一样处理系统寄存器?能够简单地使用其名称来寻址 CPU 或系统寄存器,例如 DDRD = %10101011
我需要做什么才能将中断和系统寄存器添加到编译器中?除了一个系统寄存器之外的所有寄存器都只是一个字节的内存地址,中断向量当然是内存位置,唯一一个不是内存地址的系统寄存器只能用两条汇编指令修改 EI
和 DI
但这可能是内联函数正确吗?
通常的策略是创建您自己的系统寄存器指针。我不知道 DDRD 的地址,但像这样的东西应该可以解决问题:
volatile unsigned char *reg_DDRD = (unsigned char *)0xE000;
*reg_DDRD = 0xAB;
大多数 C 编译器不支持二进制常量,但您可以将它们与一些宏 hackery 一起使用。你可以使用宏来使语法稍微更直观:
#define DDRD (*reg_DDRD)
DDRD = 0xAB;
当原始 C 代码也能正常工作时,修改编译器毫无意义。
处理中断归结为解决 3 个问题。第一种是让中断向量地址跳转到 C 函数。由于它在 ROM 中,因此您需要修改 C 运行时环境以对其进行初始化。这非常依赖于系统,但通常您想要做的是添加一个如下所示的汇编语言文件:
org 38h ; or wherever the gameboy CPU jumps to on interrupt
jp _intr_function
这应该会导致 CPU 在您的 C 程序中转到 intr_function()
。您可能需要也可能不需要前导下划线。并且您可能无法使用 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() 并且是否要循环取决于您。的确,Arduino SDK 中使用的类 C 语言有自己内置的无限循环调用您编写的函数,但这不是普通的 C。
首先,您可以使用 GBDK,它是 Gameboy 的 C 编译器和库。它确实提供了对 gb/hardware.h
中寄存器的访问(但未在 doc 文件中列出,因为没有针对每个单独寄存器的注释)。它还通过 gb/gb.h
中的方法提供对中断的访问:add_VBL
、add_LCD
、add_TIM
、add_SIO
和 add_JOY
。 (还有名为 remove_
的删除方法)。
供参考and/or自己使用,这里是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 */
这些操作与
在 libc\gb\crt0.s
中可以找到 GBDK 用于添加和删除中断的代码,但我对汇编的了解还不够,无法将相关部分包含在此 post 中。
我也不确定如何避免忙循环。