DIY自己动手做游戏机

图片


这篇文章是我从头开始制作的“自制”控制台视频控制台项目的简介。 我既受到复古控制台和现代样品的启发,又得到了自己的体系结构。 我的朋友们经常告诉我,我应该谈论我的项目,而不是专门为自己做所有事情,因此,我在这里发布这篇文章。


注意,这是翻译


一切如何开始


我叫塞尔吉奥·维埃拉(Sergio Vieira),我在80年代和90年代在葡萄牙长大,对怀旧游戏特别是第三代和第四代游戏机怀有浓厚的怀旧之情。


几年前,我决定更好地理解电子学,并尝试使用自己的前缀。


从专业上讲,我是一名程序员,并且没有任何电子工程师经验,除了(并且不应考虑)我的destkop的独立升级。


尽管我没有任何经验,但我对自己说:“为什么不呢?”,我买了几本书,买了几套电子套件,并根据自己对值得学习的东西的感觉开始学习。


我想添加一个使我怀旧的前缀,我想要在NESSuper Nintendo之间,或者在Sega Master SystemMega Drive之间


这些控制台有一个CPU,一个原始的视频芯片(当时不称为GPU)和一个音频芯片,有时是内置的,有时是外部的。


游戏分发在盒带上,盒带通常是铁的延伸,有时只是ROM芯片,有时还带有其他组件。


最初的计划是使用具有以下特征的前缀:


  • 如果没有仿真,游戏和程序应该可以在真实的硬件上运行,而不必在那个时候使用相同的硬件,但是足够快地完成任务,仅此而已。
  • 带有真正的复古CPU。
  • 带模拟电视输出。
  • 有声音
  • 具有双控制器支持
  • 滚动提示和动画精灵
  • 具有支持诸如Mario之类的平台游戏以及其他各种游戏的功能。
  • 从SD卡下载游戏和程序。

为什么说SD卡而不是盒式磁带呢,基本上,它是如此实用,您可以从计算机上复制它们。 墨盒意味着,首先,机顶盒中将有更多的铁,其次,将为每个程序生产铁。


生产量


视频信号


我做的第一件事是生成视频信号。


我所采样的那个时期的任何控制台都具有各种专有的图形芯片,这意味着它们都有不同的规格。
因此,我不想使用现成的图形芯片,我希望控制台具有独特的图形规格。 而且由于我无法制造自己的图形芯片,并且那时我仍然无法使用FPGA,所以我决定将自己局限于使用8位20兆赫微控制器进行软件生成的图形信号生成。


这还不算太多,只是一个我感兴趣的水平图形的足够强大的解决方案。


因此,我开始使用纯度为20 MHz的Atmega644微控制器为电视生成PAL视频信号。 我不得不打败 PAL协议,因为芯片本身不知道如何去做。


imageVPU测试1


imageVPU测试2


微控制器产生8位颜色(RGB332、3位红色,3位绿色和2蓝色),而无源DAC将其全部转换为RGB。 幸运的是,在葡萄牙,几乎所有电视都配备了SCART连接器,并且支持RGB输入。


正确的图形子系统


由于微控制器功能强大,因此我决定将其专用于生成视频信号(我称其为VPU-视频处理单元),因此我决定同时组织一个双缓冲区。


事实证明,第二个微控制器(PPU,图像处理单元,Atmega1284芯片也以20 MHz的频率)在RAM芯片1(我称为VRAM1)中生成了图像,而第一个微控制器同时将第二个芯片(VRAM2)的内容发送到电视。


一帧之后,PAL系统中的两帧为1/25秒,VPU切换VRAM并交换它们,PPU在VRAM2中生成图像,然后VPU将VRAM1转储到TV输出。


显卡非常复杂,因为我必须使用外部硬件,以便两个微控制器都可以使用两个内存模块并加快对RAM的访问,因为它也具有位撞击功能,因此我不得不添加74系列芯片作为计数器,线路选择器,收发器等。 。


VPU和PPU的固件也很麻烦,因为我不得不编写大量代码才能获得图形的最大速度。 最初,所有内容都用汇编器编写,然后用C重写了部分。


image视频板1


image视频板2


结果,PPU生成224x192像素的图片,然后通过VPU发送到电视。 您可能会发现分辨率很低,但实际上它几乎是当时控制台的分辨率,通常为256x224。 分辨率略低,但允许我添加更多功能,系统可以在一帧中计算这些功能。


与过去一样,PPU具有自己的刚性机制,您必须能够使用它。 背衬(背衬)由8x8像素字符(也称为图块)渲染而成。 事实证明,背景尺寸为28x24瓦片。


为了使背景可以逐像素平滑滚动,我这样做了,共有4个虚拟屏幕,每个28x24瓦片依次进入内存并彼此缠绕,在图片中更加清晰。


imageBackground


imageVirtual背景


在背景之上,PPU可以渲染64个精灵 ,其高度或宽度可以是8或16个像素,即1、2或4个图块,还可以水平和/或垂直翻转。


在背面的顶部,您还可以渲染一个大小为28x6的缓冲区的叠加层,用于渲染HUD,刻痕,以免干扰主画面和背面滚动。


一个“高级”功能是可以不完全滚动背景,而可以单独滚动每行,从而可以实现各种有趣的效果,例如分屏或近视


还有一个属性表,可让您将每个图块的值设置为0到3,然后可以为具有一个属性的所有图块指定一个图块页面或增加其符号值。 当某些部分的备份需要定期更改并且CPU不必单独计算每个图块时,这很方便,只需说一句类似的话:“所有具有属性1的图块,将角色的数值增加2”,这样的事情可以通过不同的技术来实现例如,在马里奥(Mario)的块状砖中观察问号的动画,或者在瀑布中所有砖块不断变化的瀑布中产生滴水效果的游戏中观察。


中央处理器


当视频卡工作时,我开始使用CPU作为机顶盒的Zilog 80


选择Z80的原因之一,除了它是一个很酷的复古CPU之外,还在于它能够寻址两个16位空间,一个用于内存,第二个用于I / O端口,例如传奇般的6502 ,例如,它不能,它只能寻址16位空间,您必须将其映射到内存以及各种外部设备,视频,音频,操纵杆,硬件随机数生成器等。 拥有两个地址空间更为方便,其中一个地址空间在内存中完全分配了多达64 KB的代码和数据,第二个地址空间用于访问外部设备。


首先,我将CPU连接到测试程序所在的EEPROM,并通过I / O空间将其连接到我安装的微控制器,以便我可以通过RS232与我的计算机通信,并监视CPU和其他部件的工作方式。 我将这个运行在20 MHz的Atmega324微控制器称为IO MCU-输入/输出微控制器单元,它负责控制通过RS232对游戏控制器(操纵杆),SD卡读卡器, PS / 2键盘和通信器的访问。


imageCPU板1


CPU连接到128 KB的内存芯片,其中只有56 KB可用,这当然是胡说八道,但是我只能得到128或32 KB的芯片。 事实证明,该内存由8 KB的ROM和56 KB的RAM组成。


之后,我使用该库更新了IO MCU固件,并获得了对SD卡读取器的支持。


现在,CPU可以浏览目录,查看其中的内容,打开并读取文件。 所有这些都是通过写入和读取I / O空间中的特定地址来完成的。


将CPU连接到PPU


接下来要做的是CPU与PPU之间的连接。 为此,我应用了一个“简单解决方案”,即购买双端口RAM,这种RAM芯片可以直接连接到两条不同的总线。 这使他可以摆脱行选择器之类的其他芯片,此外,还允许几乎同时从这两个芯片访问内存。 另一个PPU可以通过激活其不可屏蔽的中断直接访问每个帧上的CPU。 事实证明,CPU在每个帧上都会收到一个中断,这对于各种计时任务以及了解何时进行图形更新非常有用。


CPU,PPU和VPU的每个交互框架均按照以下方案发生:


  1. PPU将信息从PPU存储器复制到内部存储器。
  2. PPU向CPU发送中断信号。
  3. 同时:
    • CPU跳至中断功能,并开始以新的图形状态更新PPU存储器。 程序必须从中断返回,直到下一个程序段。
    • PPU基于先前复制到VRAM之一的信息来渲染图片。
    • VPU将图片从另一个VRAM发送到电视输出。

大约在同一时间,我开始支持游戏控制器,起初我想使用Nintendo控制器,但是它们的插槽是专有的,通常很难找到,因此我选择了与Mega Drive / Genesis兼容的6键控制器,它们具有标准的DB-9插槽。到处都是。


imageJoint板1


编写第一个真实游戏


这时候,我已经有一个能够控制PPU,操纵杆,读取SD卡的CPU了……是时候编写第一个游戏了 ,当然是在Z80汇编器中,空闲时间花了我几天的时间。



添加动态图形


一切都很棒,我有自己的游戏机,但这对我来说还不够,因为在游戏中我必须使用缝合在PPU内存中的图形,并且不可能为特定游戏绘制图块,并且只能通过刷新ROM进行更改。 我开始考虑如何添加更多的内存,以便CPU可以将图块的字符加载到其中,然后PPU可以从那里读取所有内容以及如何更轻松地进行处理,因为前缀已经变得非常复杂。


我想出了以下几点:只有PPU可以访问该新内存,CPU将通过那里的PPU加载数据,并且在此加载过程中,该内存不能用于绘图,但是此时可以从ROM进行绘图。


加载结束后,CPU会将内部ROM存储器切换到这个新的存储器(我称为字符RAM(CHR-RAM)),在这种模式下,PPU将开始绘制动态图形,这可能不是最好的解决方案,但它可以工作。 结果,安装了一个新的内存128 KB,可以存储1024个字符,每个字符8x8像素作为背景,相同数量的精灵字符。


imageJoint Board 2


最后是声音


双手终于到达了声音。 一开始,我想要一个类似于Uzebox的声音,即微控制器产生4通道的PWM声音。


但是,事实证明,我可以轻松获得老式芯片,并订购了几块FM合成芯片YM3438,这些家伙与Mega Drive / Genesis中使用的YM2612完全兼容。 通过安装它们,您可以获得高质量的音乐Mega Drive和微控制器产生的声音效果。


我安装了另一个微控制器,并将其称为SPU(声音处理器单元),它控制YM3438并可以自己产生声音。 CPU通过双端口内存控制它,这次只有2 KB。


与图形单元一样,声音单元具有128 KB的内存,用于存储PCM样本和声音补丁,CPU通过访问SPU将数据加载到该内存中。 事实证明,CPU要么告诉SPU从该内存执行命令,要么每帧更新SPU的命令。


CPU通过SPU存储器中的四个循环缓冲区控制四个PWM通道。 SPU通过这些缓冲区并执行写入它们的命令。 FM合成芯片也有一个这样的缓冲器。


总体上,如图所示,CPU和SPU之间的交互根据方案进行:


  1. SPU将数据从SPU复制到内部存储器。
  2. SPU正在等待来自PPU的中断(用于同步)
  3. 同时
    • CPU更新PWM通道缓冲区和FM合成器缓冲区。
    • SPU根据内部存储器中的数据在缓冲区中执行命令。
    • 除此之外,SPU还以16 kHz的频率更新PWM声音。

imageSound板1


到底发生了什么


所有块准备就绪后,有一些去了面包板。
对于CPU模块,我能够开发和订购定制的PCB,但我不知道其他模块是否值得,我认为我很幸运我的PCB立即可以工作。


现在(到目前为止)在面包板上只有声音。
今天的样子:


imageConsole 1


建筑学


该图说明了每个块中的组件以及它们之间的交互方式。 唯一未显示的是每帧从PPU到CPU的信号(作为中断),以及到达SPU的相同信号。


imageArchitecture


  • CPU:10 MHz时的Zilog Z80
  • CPU-ROM:8KB EEPROM,包含引导加载程序代码
  • CPU-RAM:128KB RAM(可用56KB),用于程序/游戏的代码和数据
  • IO MCU:Atmega324,是CPU和RS232,PS / 2键盘,操纵杆和SD卡文件系统之间的接口
  • PPU-RAM:4 KB双端口内存,CPU和PPU之间的中间内存
  • CHR-RAM:128KB RAM,用于存储动态图块(用于底材)和子画面(以8x8像素为字符)。
  • VRAM1,VRAM2:128KB RAM(实际上是43008),它们用于帧缓冲区,它们写入PPU并从中读取VPU。
  • PPU(图片处理单元):Atmega1284,将一个帧绘制到帧缓冲区中。
  • VPU(视频处理单元):Atmega324,读取帧缓冲区并生成RGB和PAL信号以及同步。
  • SPU-RAM:2KB双端口RAM,用作CPU和SPU之间的接口。
  • SNDRAM:128KB RAM,用于存储FM合成器的PWM补丁,PCM样本和指令块。
  • YM3438:YM3438,FM合成芯片。
  • SPU(声音处理单元):Atmega644,使用脉冲宽度调制(PWM)原理生成声音并控制YM3438。

最终规格


CPU:


  • 8位CPU Zilog Z80,频率为10Mhz。
  • 自举程序的8KB ROM。
  • 56KB RAM。

IO:


  • 从FAT16 / FAT32 SD卡读取器读取数据。
  • 读/写到RS232端口。
  • 2个兼容MegaDrive / Genesis的游戏控制器。
  • 键盘PS2。

影片:


  • 分辨率224x192像素。
  • 每秒25帧(来自PAL的半帧)。
  • 256色(RGB332)。
  • 基于四个全屏页面的2x2虚拟背景(448x384像素),基于双向像素的滚动。
  • 宽度和高度为8或16像素的64个精灵,可以同时进行垂直和水平翻转。
  • 背景和图片由每个8x8像素的字符组成。
  • 符号视频存储器的背景为1024个字符,子画面为1024个字符。
  • 沿设定线进行64个独立水平滚动
  • 沿设定线8个独立的垂直滚动
  • 224x48像素叠加层,具有可选的颜色键透明度。
  • 背景属性表。
  • RGB和复合PAL通过SCART连接器。

声音:


  • 具有8位和4通道的PWM,具有内置波形:方波,正弦波,锯齿波,噪声等
  • PWM通道之一中的8位,8 kHz采样。
  • FM合成芯片YM3438装有频率为50赫兹的指令。

控制台开发


对于控制台,编写了引导加载程序。 引导加载程序放置在ROM CPU中,最多可占用8 KB。 它使用RAM的前256个字节。 加载程序是CPU执行的第一件事。 需要显示SD卡上的程序。


这些程序位于包含编译代码的文件中,还可能包含图形和声音。


选择程序后,将其加载到CPU存储器,CHR存储器和SPU存储器中。 之后执行程序代码。 除前256个字节外,加载到控制台中的代码的最大大小为56 KB,当然,您需要考虑堆栈和数据的空间。
此引导加载程序和为此控制台编写的其他程序的创建方法与以下所述相同。


内存/ IO映射


开发此前缀时,重要的是要考虑CPU如何访问各个块并为输入输入和存储器地址空间正确分配地址空间。


CPU通过内存的地址空间访问引导加载程序的随机访问内存。


内存地址空间
imageMemory映射


并通过I / O地址空间到PPU-RAM,SPU-RAM和IO MCU。


I / O地址空间
imageIO映射


从表中可以看出,所有设备,IO MCU,PPU和SPU的地址都分配在I / O地址空间内。


PPU管理


从表中的信息可以看出,对于PPU控制,有必要写入I / O地址空间中地址为1000h-1FFFh的PPU存储器。


PPU地址空间分配


imagePPU映射


“ PPU状态”可以采用以下值:


  1. 嵌入式图形模式
  2. 动态图形模式(CHR-RAM)
  3. CHR存储器中的记录模式
  4. 记录完成,等待CPU的模式确认

例如,在这里,如何使用精灵:
该前缀一次可以绘制64个精灵。 CPU - 1004h-1143h (320 ), 5 (5 * 64 = 320):


  1. , : Active, Flipped_X, Flipped_Y, PageBit0, PageBit1, AboveOverlay, Width16, Height16.
  2. , ( ).
  3. ( — )
  4. X
  5. Y

, , Active 1, X Y , 32/32 , .


.


, 10, 4145 (1004h + (5 x 9)), 1 , , x=100 y=120, 4148 100 4149 120.



.


.


ORG 2100h PPU_SPRITES: EQU $1004 SPRITE_CHR: EQU 72 SPRITE_COLORKEY: EQU $1F SPRITE_INIT_POS_X: EQU 140 SPRITE_INIT_POS_Y: EQU 124 jp main DS $2166-$ nmi: ;    (NMI) ld bc, PPU_SPRITES + 3 ld a, (sprite_dir) and a, 1 jr z, subX in a, (c) ;  X inc a out (c), a cp 248 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a jp updateY subX: in a, (c) ;  X dec a out (c), a cp 32 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a updateY: inc bc ld a, (sprite_dir) and a, 2 jr z, subY in a, (c) ;  Y inc a out (c), a cp 216 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a jp moveEnd subY: in a, (c) ;  Y dec a out (c), a cp 32 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a moveEnd: ret main: ld bc, PPU_SPRITES ld a, 1 out (c), a ;   0 inc bc ld a, SPRITE_CHR out (c), a ;    0 inc bc ld a, SPRITE_COLORKEY out (c), a ;     0 inc bc ld a, SPRITE_INIT_POS_X out (c), a ;     0 inc bc ld a, SPRITE_INIT_POS_Y out (c), a ;   Y  0 mainLoop: jp mainLoop sprite_dir: DB 0 

使用C语言


您也可以使用C语言,为此,我们需要SDCC编译器和一些其他实用程序。


C代码可能会比较慢,但是编写它会更快,更容易。


这是一个与上面的汇编代码相同的代码示例,它使用一个有助于调用PPU的库:


 #include <console.h> #define SPRITE_CHR 72 #define SPRITE_COLORKEY 0x1F #define SPRITE_INIT_POS_X 140 #define SPRITE_INIT_POS_Y 124 struct s_sprite sprite = { 1, SPRITE_CHR, SPRITE_COLORKEY, SPRITE_INIT_POS_X, SPRITE_INIT_POS_Y }; uint8_t sprite_dir = 0; void nmi() { if (sprite_dir & 1) { sprite.x++; if (sprite.x == 248) { sprite_dir ^= 1; } } else { sprite.x--; if (sprite.x == 32) { sprite_dir ^= 1; } } if (sprite_dir & 2) { sprite.y++; if (sprite.y == 216) { sprite_dir ^= 2; } } else { sprite.y--; if (sprite.x == 32) { sprite_dir ^= 2; } } set_sprite(0, sprite); } void main() { while(1) { } } 

动态图形


(在原始“自定义”图形中。大约。)


在前缀ROM中,缝制了一页用于支持的图块和另一页现成的Sprite,默认情况下,您只能使用此固定图形,但可以切换到动态图形。


我的目标是将所有必需的二进制格式的图形立即加载到CHR RAM中,并且ROM中的引导加载程序中的代码可以做到这一点。 为此,我用不同的有用符号制作了几张正确大小的图片:


imageSample瓷砖组件


由于动态图形的内存由4页组成,每页包含256个8x8像素的字符和4个用于sprite的相同字符的页面,因此我将图片转换为PNG格式,删除了重复的图片:


imageSample字符表


然后,他使用一个自写工具将其全部转换为具有8x8块的二进制RGB332格式。


imageGraphics命令行


结果,我们得到了带有字符的文件,其中所有字符一个接一个地依次排列,每个字符占用64个字节。


音效


Wave RAW样本转换为8位8千赫PCM样本。


针对PWM和音乐的音效补丁使用特殊说明编写。


至于Yamaha YM3438 FM合成芯片,我找到了一个名为DefleMask的程序,该程序为Genesis YM2612芯片生成与PAL同步的音乐,与YM3438兼容。


DefleMask以VGM格式导出音乐,然后我使用另一个专有实用程序将其转换为自己的二进制格式。


所有三种类型的声音的所有二进制文件都组合到一个二进制文件中,我的引导加载程序可以将其读取并加载到SDN RAM声音存储器中。


imageSound命令行


链接到最终文件


二进制可执行代码,图形和声音被组合到一个PRG文件中。 PRG文件具有一个标头,其中描述了是否有音频和图形数据,它们占据了多少以及数据本身的所有内容。


可以将此类文件写入SD卡,然后控制台引导加载程序会对其进行考虑,然后将所有内容下载到适当的位置,然后启动程序可执行代码。


imagePRG命令行


仿真器


我使用wxWidgets在C ++中编写了控制台的仿真器,以使其更易于开发。


CPU由libz80库仿真。


已将功能添加到仿真器中进行调试,我可以随时将其停止,并逐步进行汇编程序的调试,如果在游戏中使用了这种语言,则可以使用C语言映射到源代码。


根据该图,我可以查看视频存储器,符号表以及CHR存储器本身。


这是在调试器打开的情况下在模拟器上运行的程序的示例。


imageEmulator演示


编程示范


这些视频是使用针对电视的CRT屏幕的智能手机相机拍摄的,对于图像质量不佳,我深表歉意。


从PS / 2键盘编程的BASIC解释器,在第一个程序之后,我演示了如何通过激活和移动精灵来通过I / O地址空间直接写入PPU存储器:



图形演示,在此视频中以编程方式下载了64个16x16子画面,并具有动态滚动和在子画面上下移动的叠加层的背景:



声音演示展示了YM3438和PWM声音的功能,该演示的声音数据以及FM音乐和PWM声音一起占据了几乎所有可用的128 KB声音存储器。



俄罗斯方块(几乎是背景功能),YM3438上的音乐,PWM补丁上的声音效果都用于图形。



结论


这个项目确实是一个梦想成真,我从事这个项目已经有好几年了,时常被打扰,看着我的空闲时间,我从没想过我会为创建自己的游戏复古视频游戏机走那么远。 自然,这并不完美,我当然不是电子专家,机顶盒中显然有太多元素,毫无疑问它可以做得更好,而且可能其中一位读者正在思考。


但是,在从事这个项目的过程中,我仍然学到了很多关于电子,游戏机和计算机设计,汇编语言以及其他有趣事物的知识,最重要的是,我在自己开发的硬件上玩游戏获得了极大的满意并收集。


我有计划制造控制台/计算机等。 实际上,我已经在制造一个新的机顶盒,它几乎已经准备好了,它是一个简化的复古机顶盒,它基于FPGA板和几个其他组件(当然比这个项目要少得多),其想法是要便宜得多并且可重复使用。


尽管我在这里写了很多关于这个项目的文章,但是毫无疑问,可以讨论的更多了,但是我几乎没有提到声音引擎是如何工作的,CPU是如何与之交互的,以及图形系统,其他输入/输出以及整个控制台还有很多事情要做会告诉你。


着眼于读者的反应,我可以写更多的文章来关注更新,各个前缀块或其他项目的详细信息。


项目,站点,Youtube渠道启发了我并帮助我获得了技术知识:


这些站点/渠道不仅启发了我,而且还帮助我找到了解决该项目过程中出现的复杂问题的解决方案。



感谢您阅读此处的内容。 :)


如果您有任何疑问或反馈,请在下面评论中写(Github上英文原版文章,约Per。)

Source: https://habr.com/ru/post/zh-CN444006/


All Articles