本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

基于PIC24EP512GP202的单芯片游戏机

发布时间:2021-12-16
分享到:

基于PIC24EP512GP202的单芯片游戏机

发布时间:2021-12-16
分享到:

经过几个用PIC24生成普通VGA信号的项目后,我成功地实时支持精灵。这让我产生了制作2D电子游戏的想法,我的选择是Spectrum的旧游戏《Jumping Jack》。当然,我修改了它并添加了一些新的细节。

补给品:

数量         组件名称
1    ×    PIC24EP512GP202
1    ×    MCP1702-33
4    ×    BC547
5    ×    1N4148
4    ×    钽电容4u7 16V
4    ×    陶瓷电容 100nF
2    ×    陶瓷电容 27pF
1    ×    石英 8MHz
6    ×    触觉键
3    ×    51欧姆电阻
2    ×    120 欧姆电阻
10    ×    1K5电阻
3    ×    2K2电阻
5    ×    5K6电阻
3    ×    10K电阻
1    ×    1M电阻
1    ×    VGA接口
1    ×    USB MINI 连接器
1    ×    红色 LED
1    ×    同轴立体声连接器
1    ×    ICSP连接器
1    ×    串口连接器
1    ×    同轴电源连接器

 

项目细节:

由于视频信号和相应的同步信号是由软件产生的,因此控制台包含的硬件最少。还有一个音频信号输出带有5个二进制音调通道,由无源电阻网络混合。其中两个频道用于音效,类似于80年代早期的电子游戏,另外三个频道用于背景音乐。这个输出能够驱动线输出的PC扬声器或耳机。

需要注意的是,这里没有视频处理单元,PGA或任何特殊用途的芯片,而且PIC微控制器不是为视频信号产生而设计的。一切都是通过一系列不同的设计技巧和一些妥协来实现的。因为te游戏不是在PC上运行,而是在单机上运行,所以截图是通过VGA监视器上的摄像头或者直接从Photoshop上拍摄的,这是位图创建过程中使用的。

视频和音频发生器是固件的重要组成部分,也是操作系统的组成部分,很快就会被记录下来,可用于任何其他游戏或应用程序。由于时间很重要,这些部分是用汇编语言编写的,但程序的所有其他部分(一些其他游戏或任何其他应用程序的场景)也可以用其他一些编程语言编写,最好是 Microchip 的 C。在这种情况下所有部分都是用汇编程序编写的,但这只是作者偏好的结果。

目前,该平台只编写了 Jumping Jack 游戏,当时玩 Spectrum 个人电脑的人都知道。但是,一旦创建了新游戏,就可以轻松地通过串行端口(在照片中看不到)从计算机下载它。控制台有 USB 接口,但仅用于 5V 电源。不幸的是,封装在 DIP 封装中的微控制器(通孔焊接,方便 DIY 项目和车间)没有 USB 接口,只有串行端口,所以如果你想下载一些新游戏,你必须使用 RS 232。

硬件

本项目基于PIC24EP512GP202微控制器,它是 16 位 Microchip 的 MCU,具有512K的内部闪存程序存储器,48K的内部数据存储器,封装在标准的28 引脚Shrink-DIP 外壳中。这是示意图:

整机采用+5V供电(2.1mm同轴CON1A或mini USB CON1B接口,注意不要同时使用!)。测得的电流消耗为 77 mA。LDO稳压器MCP1702-33用于+ 3.3V供电的MCU。

您可以使用陶瓷谐振器代替石英,作为振荡器,但有些谐振器会产生明显的频率抖动,这在屏幕上的水平像素不稳定中可见一斑。也可以使用带 PLL 的内部 FRC 振荡器,但当然抖动会更糟。

R、G 和 B 视频硬件驱动器由简单的无源衰减器(R1R2、R4R5和R7R8)和NPN晶体管(作为射极跟随器)T1-T3 实现。对这些晶体管没有特殊要求,但建议使用低电流类型 (=< 100 mA),因为高电流类型通常具有较低的带宽。强度控制(“暗”色和灰色)使用标准硅二极管D1-D4。不要使用肖特基二极管,因为它们的正向压降太低,无法在模拟视频信号中正确显示暗色。

5 声道混音器使用简单的无源电阻网络。电容器C9用于无源一阶低通 RC 滤波器。上拉电阻R14用于两级音量控制电路。由于所有输出都是纯二进制(开-关),当输出端口引脚保持开漏状态(在ODCA和ODCB寄存器中可选)时,低音量电平由软件管理,在这种情况下,电阻器R14-R20创建附加电压衰减器,单独用于每个输出。

VGA信号

视频处理器通常不嵌入在微控制器中,因此在游戏机中考虑使用外部视频显示单元。由于这是一个简约的项目,VGA 视频信号是由软件生成的,基于中断驱动的内核。

产生VGA信号的程序是T2(定时器2模块)中断服务程序的一部分。此例程还提供垂直同步脉冲、监视器自动调整标记和底线文本例程。在此版本中,没有其他中断处于活动状态,但用户可以添加自己的中断源,只要它们具有较低的优先级即可。

时间细节

图中显示了800x600分辨率、56Hz刷新率的VGA 时序。以下是详细的计时数据:

水平计时:

  • 像素时钟:36 MHz (13.89 ns)
  • 水平频率/周期:35.16 KHz (28.44 us)
  • 可见区域:800 像素 (22.22 us)
  • 前沿:24 像素 (0.67 us)
  • 同步脉冲:72 像素 (2 us)
  • 后沿: 128 像素 (3.56 us)

垂直时序:

  • 垂直频率/周期:56 Hz (17.86 ms)
  • 可见区域:600 线 (17.067 ms)
  • 前沿:1 线 (28.44 us)
  • 同步脉冲:2 线 (56.89 us)
  • 后沿:22 线 (625.78 us)
  • 整帧: 625 行(17.78 毫秒)

800x600分辨率@56 Hz垂直频率的点时钟恰好为36 MHz,PIC24E系列的最大执行速度为70 MIPS。因此 MCU 必须稍微超频到72 MHz才能获得所需的指令/像素时钟速率。这种超频只有 2.8%,可以忽略不计,不会显着影响操作安全或散热。

如前所述,每个像素代替 2x2 像素的屏幕区域,因此实际的点时钟不是 36,而是 18 MHz。这为处理器提供了足够的时间在一个像素时序中执行四条指令。此外,每条扫描线显示四次两次,因此在水平同步和通道期间有更多时间用于缓冲区设置。

RAM组织

视频内存位于内部 48 KB RAM 中,占用 45600 字节。800x600模式下所有视频信号时序均符合VGA标准,但由于RAM限制,实际显示分辨率仅为380x240,显示在760x480像素的原始屏幕区域。

要在 8 位像素模式下使用整个800x600显示区域,我们需要 800x600= 480,000字节的内存,但在最好的情况下,此时 PIC 微控制器提供的只有 48K(49,152字节),这与我们需要的。有一些96K RAM的16位PIC ,但它们对于视频信号生成来说太慢了,还有一些52K PICS,但它们采用SMD 64引脚封装,间距为0.5毫米,这对于DIY项目来说非常不方便。虽然可以添加外部 RAM,但没有实际用途,因为访问外部 RAM 的速度对于 VGA 信号生成来说太慢了。所以我们必须以某种方式使用48K RAM MCU。

为此,我们必须做出一些调整:

1.每个像素的颜色只由四位定义,所以它工作在16色模式下。更糟糕的是,只使用了 15 种颜色,因为其中一种(二进制表示为 0000)不是“黑色”而是“透明”,这将用于精灵处理(黑色是 1000)。稍后再谈。

2. 视频内存中的每个像素都显示在 VGA 屏幕的 4 像素 (2x2) 区域上。

3. 实际显示分辨率为380x240,占用760x480屏幕像素。显示器顶部、左侧和右侧的 20 像素宽边距未使用,保留为黑色。在底部有一行(39 个字符)的文本。它不需要帧缓冲区,因为例程直接从 RAM 中的文本缓冲区解释文本。

这种组织提供了 380x240=91,200 个图形像素,但由于每个像素由 4 位覆盖,因此视频内存只需要 91,200/2=45,600 字节的内存。底线文本不需要视频缓冲区,它只占用 78 个字节(文本为 39,颜色属性为 39)。所以有 49,152-45,678=3,474 个空闲字节,这对于内务管理(内部缓冲区和通用寄存器)来说已经足够了。

精灵

72 MIPS 的处理能力,如果只需要显示显存的内容的话,很容易通过软件生成视频信号。由于这里没有视频处理单元,MCU 必须一次处理一个像素,这样的概念对于静态图像或非常小的可移动像素块很有用,但不适用于需要大内存实时处理的游戏块。更糟糕的是,由于超过 2/3 的时间 CPU 忙于生成视频信号,因此它只剩下 1/3 用于内务处理和活动内存处理。

解决这个问题的方法是使用精灵,它们是位于视频 RAM 之外的 2D 图像,并以某种方式叠加在主场景中。一些最早的个人计算机中的视频单元可以在硬件中处理精灵,但在这个项目中它是在软件中实现的。精灵位于 MCU 的内部程序存储器中,它们与视频 RAM 内容组合以生成完整的视频信号。这意味着无法操纵精灵内容,它只能显示在屏幕的所需位置。由于这个游戏中的大部分角色都是动画的,所以有大量的像素,每个像素代表动画中那个像素的一帧。幸运的是,程序内存中有足够的空间用于程序、背景图像和精灵帧。在这种情况下,使用了 512 KB 程序存储空间的 95% 以上。

这是杰克跳跃的例子。请注意,屏幕上的 X 和 Y 绝对位置在跳转过程中永久调整,以及跳转序列中的幻灯片顺序(在固件的脚本表中列出),因此它在创建现场演出对于游戏 - 实际上,这种跳跃比仅观看这些幻灯片时看起来要高得多且持续时间更长。这些幻灯片有些是重复的,有些是随机使用的,而且大多数都在屏幕上动态重新定位。所以没有必要再次绘制相等的幻灯片,因为它们中的每一个都可以在脚本表中重复调用。在这个例子中,最后五张幻灯片只是因为头发“飞溅”而重复,否则幻灯片 11、12、13、14 和 15 可以省略并在脚本中列为 9、8、6、4 和 3。相同的幻灯片用于跳上和跳下到较低的楼层,但使用不同的脚本表。

在为视频场景提供服务时,软件所要做的就是预设特殊的精灵寄存器,确定 X 和 Y 位置(相对于屏幕活动部分的左边界和上边界)、一张幻灯片图像的宽度和高度,和当前幻灯片在程序存储器中的地址。位于中断例程中的视频固件将在 RGB 信号生成期间将该精灵叠加在背景视频存储器的内容中。

还有一点需要注意的是,精灵中的橙色表示“透明”(游戏调色板中没有橙色,只有在精灵设计过程中PC绘图程序的调色板中才有)。精灵上的每个橙色像素都将从视频内存中显示,视频内存通常会保存背景图像。然而,这一原则有一个缺点。如果两个或多个精灵重叠,则其中第一个(拥有最高优先级,即位于 RAM 中特殊精灵表中较高的位置)上的透明(橙色)像素将部分覆盖较低的精灵,显示背景而不是较低精灵的活动像素。第一个(模拟)屏幕截图显示了该示例。

有办法解决这个问题,但由于执行时间限制,只能针对有限数量的精灵。一些精灵可以被视为“特殊”,并且它们没有那个缺点(参见第二个屏幕截图)。这些精灵的唯一问题是它们需要多 18 倍的时间来执行视频程序,因此程序员必须注意,如果没有必要,不要使用此选项,因为它可能导致屏幕上丢失扫描线。

如何告诉视频程序哪个精灵是特殊的,哪个不是?精灵列表(位于 RAM 中并命名为 SPRITELIST)包含活动精灵的指针。视频例程可以随时放置(或删除)该列表中的任何精灵,以及当前未占用的任何表位置。此表最多可同时容纳 20 个精灵。只有四个精灵(编号 17、18、19 和 20)被视为“特殊”精灵——它们的执行速度要慢得多,但它们不会在重叠冲突中产生所描述的问题,或者至少将其最小化,使其不会出现显。在这个游戏中,只有一个精灵(杰克本身)有这个特权,因为游戏场景是这样的,所有其他精灵永远不会重叠。

操作原理

视频程序最重要的部分使用 SPRITEBUFFER,它是 190 字节长(等于显存中的一条扫描线),其中视频程序为当前行准备 sprite 内容,然后将其与背景图像和输出那条扫描线。所以显存有两层:下层是大显存本身,主要包含背景图片,上层只有一条扫描线大,包含该行的像素。在视频例程开始输出数据之前,这些像素是从位于程序存储器中的精灵表复制的。因此,在水平同步和前后水平前进期间,该层针对每条扫描线(更具体地说,每两条相等的扫描线)动态改变。

以下是视频程序如何将 RGB 视频信号输出到端口引脚 B8、B10、B12 和 B14(分别为红色、绿色、蓝色和强度)。单个像素使用4条指令(共55.55 ns),这部分程序(重复190次)输出380个像素。当来自 SPRITEBUFFER 相应字节的位#0、#2、#4 和 #6被复制到端口引脚 B8、B10、B12 和 B14(红色列表)时,会生成奇数像素(1、3、5... ),甚至像素 (2, 4, 6...) 的生成方式相同,只是它们被旋转,因此位#1、#3、#5 和 #7复制到相同的引脚(蓝色列表)。W13 寄存器已经指向 LATB 寄存器的高字节(列表中未显示),w3 寄存器指向 SPRITEBUFFER 减 1 的开始,w12 寄存器包含从 SPRITEBUFFER 到主后台(显存)缓冲区的偏移量(应该是在每个扫描线执行之前正确计算)。W7 和 w14 是用于奇数/偶数像素分离的简单掩码。

如果你必须重新设计这个项目的硬件,你必须知道高字节LATB部分的剩余位(#9、#11、#13和#15)不能用于简单的输出功能,因为它们会被破坏在此例程中(这不适用于可重新映射的引脚功能,因为它们不会被 LATB 改变)。如您所见,每个 4 指令部分(蓝色和红色)首先从 SPRITEBUFFER 获取单个字节并同时测试它是否为零。如果它为零(如果精灵中的像素包含“透明颜色”),它会从视频内存中获取像素内容。最后,像素(无论是来自精灵还是背景)输出到端口。以下是视频程序的重要部分:

mov __ #0b10101010,w7 ___ ; mask bits 1,3,5,7 to isolate even pixels
mov __ #0b01010101,w14 __ ; mask bits 0,2,4,6 to isolate odd pixels
__ .rept 190
and.b _ w14,[++w3],w0 ____ ; get next byte from SPRITEBUFFER, test bits 0,2,4,6
btsc __ SR,#Z __________ ; if bits (0|2|4|6)≠0, then skip next instruction
mov.b _ [w3+w12],w0 _____ ; ... else get background pixel from video mem
mov.b _ w0,[w13] ________ ; *** ODD pixel out
and.b _ w7,[w3],w0 ______ ; get same byte from SPRITEBUFFER, test bits 1,3,5,7
btsc __ SR,#Z __________ ; if bits (1|3|5|7)≠0, then skip next instruction
mov.b _ [w3+w12],w0 _____ ; ... else get background pixel from video mem
lsr.b ___ w0,[w13] ________ ; *** EVEN pixel out
__ .endr

当然,在T2中断中当前扫描线开始显示之前,SPRITEBUFFER必须正确加载精灵像素。这只能在水平同步和前后水平门廊期间完成,剩下 6.23 us(约 448 条指令)可用于 SPRITEBUFFER 准备。实际上,其中一些指令会在开始时用于寄存器预设和 w12(偏移)计算、水平同步同步和 SPRITEBUFFER 清除,因此在最佳情况下我们可以指望大约 300 条指令。这肯定不足以测试 20 个可能的精灵、检查它们是否存在于当前扫描线、计算精灵查找表中的位置以及将它们的内容从程序存储器移动到 SPRITEBUFFER。大部分时间都会花在最后一项上,读取程序存储器并将其内容移动到 SPRITEBUFFER。更糟糕的是,从程序存储器中读取每个字需要 5 个指令周期,但幸运的是,如果您使用 PSV(程序空间可见性)模式,则只有第一个字传输需要 5 个指令周期,其他的只需一个。这当然是在这个项目中使用的,否则是不可能的。

不幸的是,这仅在您在 PSV 模式下移动 16 位字时有效(例如mov [w3++],[w4++]),但如果您在字节模式下使用相同的技术(例如mov.b [w3++],[w4++] ) 每个字节仍然需要 5 个指令周期(这在 Microchip 的手册中没有记录,所以我不得不以更难的方式学习)。这种 PIC24E 缺点的后果是无法移动视频内容的单个字节(2 个像素),而只能逐字移动,即 4 个像素。所以每个精灵的X指针应该指向0, 4, 8, 12, 16, 20...而不是不能被4整除的位置。 这让程序员更头疼,即使在幻灯片设计中精灵动画。

表中最后四个精灵有什么特别之处,以至于它们可以正确覆盖另一个优先级较低的精灵?它们不使用快速(和“盲”)PSV 模式,而是使用缓慢的逐字节比较和传输。这需要 18 倍的时间来处理一个精灵,所以应该特别注意使用它,并且对于不太宽的精灵(高度无关紧要)。当重叠精灵之间的区域可能包含一些单个透明像素时,重叠精灵中仍然存在一个可能的“错误”像素,但这在屏幕上是不明显的。

如前所述,没有足够的时间在每条扫描线之前处理所有精灵。幸运的是,每条视频线都有两条相等的扫描线,所以如果我们同时使用它们,我们将有两倍的时间。唯一的问题是没有办法在SPRITEBUFFER完全显示在第二条扫描线之前开始准备。这就是为什么有两个独立的精灵缓冲区而不是 SPRITEBUFFER 的原因 - SPRITEBUF1 和 SPRITEBUF2。当视频例程显示第一个的内容时,将准备第二个,反之亦然。那个小管道并没有看起来那么混乱,它是使项目实现的最后一个技巧。

所以有四个基本步骤,每个步骤都在扫描线输出到端口之前执行:

1. 测试 SPRITELIST 中的每个精灵并计算出现在扫描线 N+2(和 N+3)中的精灵的指针,然后用这些指针加载 COPYLIST 表......然后使用 SPRITEBUF1
2生成扫描线 N。使用 COPYLIST 将像素数据从程序存储器传输到 SPRITEBUF2...然后使用 SPRITEBUF1 生成相等的扫描线 N+1
3. 测试SPRITELIST 中的精灵,如果精灵出现在扫描线 N+4(和 N +5),然后使用这些指针加载 COPYLIST 表...然后使用 SPRITEBUF2 生成新的扫描线 N+2
4. 使用 COPYLIST 将像素数据从程序存储器传输到 SPRITEBUF1...然后生成相等的扫描线 N +3,使用 SPRITEBUF2

顺便说一下,SPRITEBUF1 和 SPRITEBUF2 被三个名为 DUMMUSPACE1、DUMMUSPACE2 和 DUMMUSPACE3 的区域隔开并包围,每个区域的宽度为 86 字节。除了为一些靠近屏幕边界甚至屏幕外的精灵存储虚拟像素外,它们没有任何用途。所以 X 指针最多可以指向左边 -172 或右边(380+172-sprite 宽度),如果精灵在屏幕外,它们将被正确隐藏。Y 指针可以无限拉伸,无需特别注意。

如何绘制自己的精灵并将其转换为数据表

在视频内存和精灵表中,像素的组织方式相同:位#0、#2、#4、#6用于第一个像素,位#1、#3、#5、# 7 相同下一个字节,依此类推。这就是在创建精灵和创建像素数据表时必须如何排列它们。它可以是.byte或.word数据列表,因此视频程序可以访问它。视频程序不使用程序存储器的位 16...23。Sprite 表可以位于程序存储器的任何页面。

有很多方法可以创建图像或精灵数据表。一种可能的方法是使用一些绘图程序(例如 Photoshop)来创建 16 色调色板,颜色是这样排列的:

0 橙色 4 深蓝色 8 黑色 12 浅蓝色
1 深红色 5 深紫色 9 浅红色 13 浅紫色
2 深绿色 6 深青色 10 浅绿色 14 浅青色
3 深黄色 7 灰色 11 浅黄色 15白

现在在索引颜色模式下为动画绘制精灵或幻灯片(所有透明区域都涂成橙色),并将其保存为 .RAW 格式。如果您在某个十六进制编辑器中查看 .RAW 文件,您将看到每个像素的颜色以单个字节表示。现在您必须创建一个简单的程序,该程序将文件转换为 ASCII 数据表,并遵守图纸上表示的位顺序。

该程序应创建 ASCII 指令.WORD或.BYTE、数字常量前缀0x(如果字节转换为十六进制)、逗号作为表格分隔符和换行符,因此输出可能如下所示:

.word 0x0000,0x0000,0x8000,0xC0C0,0x0040,0x0000,0x0000,0x0000,0xC000,0xC0C0,0x0040,0x0000
.word 0x0040,0x0000,0x0000,0x0000,0xCF80,0xC5CF,0x0040,0x0000,0x0000,0x0000,0xC580,0xCFCF
.word 0xCF80,0xCACF,0x0040,0x0000,0x0000,0x0000,0xCF80,0xCFCF,0x0040,0x0000,0x0000,0x0000
.word 0x0000,0x0000,0xCF00,0x45CF,0x0000,0x0000,0x0000,0x0000,0xC300,0x00CB,0x0000,0x0000
...

或者像这样,取决于使用的模式:

.byte 0x00,0x00,0x04,0x08,0x12,0x18,0x1d,0x21,0x26,0x28,0x2a,0x28,0x28,0x22,0x23,0x20
.byte 0x20,0x20x20x20x20x7 ,0x2a,0x27,0x20,0x1c,0x15,0x0e,0x00,0x02
.BYTE 0x0B中,0x15,0x1f,0x28,0x2d,0x31,0x35,0x37,0x36,0x2b,0x20,0x1b,0X1A,0x19,0x1a,0x1b
。字节 0x1e,0x23,0x30,0x33,0x35,0x31,0x2f,0x2a,0x20,0x18,0x12,0x0a,0x00,0x24,0x37,0x38
...

Yoy 可以将您的表格作为文本复制到您的应用程序中的源文件中。

声音

音频信号由软件和内部 MCU 外设产生。PIC 外设中没有专用的声音控制器,而是使用了一些其他资源。这种妥协的结果是,音频输出信号是纯二进制(方波),就像在旧电脑和游戏机中一样。为了使声音更悦耳,有被动的一阶低通 RC 滤波器(R15-R19 和 C9)。有五个音频通道,每个通道都由软件控制。支持开关、音调频率和两级音量控制。三个通道通常用于音乐,一个用于 Jack 的音效,一个用于他的敌人音效。

最方便的音频信号生成外设是OC(输出比较),它预设为中心对齐PWM模式。不幸的是,PIC24EP512GP202(或任何其他28引脚PIC MCU)中只有四个OC通道,其中一个已经用于水平同步发生器,因此我们只有三个用于音频应用。它们用于音乐。

其余两个音频通道(音效)使用来自 UART1 和 UART2 外设的 TX 单元创建。TX 缓冲器(U1TXREG 和 U2TXREG)永久加载 10101010s,因此整个传输序列,包括开始和停止位,包含 0101010101s,无休止地重复。BRG (Baud Rate Generator) 决定频率,On-Off 通过将 Peripheral Pin Select 重新映射到 TX out 或 Port Latch 来获得。

在菜单(或暂停)页面中,每个通道都被调整为关闭、低和高音量。确定音量级别的唯一区别是 ODCx 寄存器中的 OD(开漏)位。

音乐(或效果)脚本包含 4 字节组。第一个字节用于音调通道 #1,然后是音调通道 #2、音调通道 #3 和持续时间(以 17.86 毫秒为步长,相当于 56 Hz,与 VGA 垂直频率相同)。底部的表格是音高字节(红色数字,1-75),显示相应的音调(蓝色,A2-B8)和频率(黑色,110-7920 Hz)。如果音字节包含 0,则为暂停(该通道上无音),如果包含 99,则为“无变化”(前一音继续)。所有表成员必须包含四个字节。表终止符包含 0,0,0,255,然后跟随一个字地址(低端)和新表将被执行。所以“0,0,0,255”是操作码“跳转”,后面的字是跳转地址。

对于音效,脚本包含 2 字节成员。第一个是音高,第二个是 17.86 ms 步长 (56 Hz) 的音高。终结符包含两个零字节。没有像音乐脚本中那样的循环地址,因为对于每个游戏事件,音效应该只执行一次。暂停表示为 0(后面是 >0 的暂停持续时间字节),并且没有“相等”字节,因为这是单调效果表。

有两个全局变量,名为music.shift和effects.shift,它们用作全局音高移位器,用于单音步调的最终频率。默认情况下,它们是8和6。分别。

第一个例子是Jumping Jack主旋律部分(pp为pause,等于00,ee为“等于”,表中等于99):

.byte pp,19,38,11
.byte pp,ee,pp,1
.byte pp,21,38,12
.byte 14,24,41,12
.byte 14,21,ee,12
.byte pp,pp ,ee,12
.byte 14,21,ee,23
.byte pp,pp,ee,1
.byte 14,21,38,12
.byte pp,19,ee,12
.byte pp,21,ee,12
. byte pp,24,41,24
.byte pp,19,38,12
.byte pp,21,41,12
.byte pp,24,ee,24
.byte pp,19,43,12
.byte pp,21, 42,12
.byte 14,24,ee,12
.byte 14,21,ee,12
.byte pp,pp,ee,12

第二个是杰克跳跃的音效:

.byte 29,2 .byte
30,2
.byte 31,2
.byte
32,1 .byte
33,1 .byte
34,1 .byte
35,1 .byte 36,1
.byte 0,0

注意:特别感谢 Marko Antonić,他为该媒体制作了音乐改编版。多亏了他,音乐听起来出奇的好,即使使用这种普通的硬件。

同步

水平同步由 OC1(输出比较 1)寄存器生成,该寄存器与 T1(定时器 1)同步。对于全水平时钟同步,在每个水平同步信号后执行特殊程序,否则会产生永久性水平抖动。该例程在软件驱动的视频信号中非常重要,它计算(蓝色)并执行(红色)一系列 NOP 以获得正确的水平时序。

1:

btss _ IFS0,#T1IF __ ; 测试水平同步中断标志
胸罩 __ 1b ________ ; 等待水平同步
mov _ #14,w0 ____ ; 被减数(可更改以调整水平图片位置)
subr _ TMR1,WREG ; 14 - TMR1 ---> w0

重复 w0 nop ____________ ;重复 14-TMR1 次,直到 TMR1 正好是 14

bclr __ IFS0,#T1IF _ ;重置水平同步中断标志

垂直同步由软件生成。定时器 2 在可变的定时周期内产生中断,它服务于四种不同的事件:

1.垂直同步(1条虚拟线加2条线)
2.显示器自动调整标记(1条虚拟线加1条线)
3.主图形(1条虚拟线加240*2线)
4.底线文字图(1条)虚拟线加10*2线)

这些事件中的每一个都由 T2(定时器 2)中断启动。特殊计数器 (VGATASK) 对 0...3 范围内的中断进行计数,确定将执行哪个例程。在该例程结束时,行数(直到下一个例程的时间)被写入 PR2,以便 T2 在正确的时刻触发新的中断。这些时序可用于调整每个画面部分的垂直位置。

上图中的红色坐标表示非中断程序代码(游戏场景和逻辑、键盘扫描、指针和精灵服务等)的执行周期。Black ondes 属于视频同步(绘图顶部的黑线)程序,监视器自动调整标记(顶部的两条蓝线),主图像和底部行文本。蓝色坐标(每个 1 行)用于时间同步。

两个同步信号的极性都是正的。尽管 24E 系列 PIC MCU 仅使用 3.3 V 电源,但某些端口引脚可承受 5V 电压,当它们定义为 Open Drain 输出引脚时,不仅可以作为输入,还可以作为输出。在这种情况下,+5 V 的上拉电阻用于定义高输出电压电平。

跳跃的杰克

这个游戏起源于个人计算的早期。场景非常简单:有地面层和六层楼,有几个永久左右移动的洞。杰克可以通过这些洞跳起来,但也可以跌倒,在这种情况下,他会昏迷一段时间。如果他掉到地面上,他就会失去一条生命。他还必须避开来自左、右和上方的几个敌人,因为每次与他们接触都会让他付出生命的代价。其中一个人物(截图左侧的盒装旋转心脏)不会带走而是给他一条生命,而盾牌(大虚线旋转圆圈,图片右上角)不仅会增加一条生命,而且也保护他免受所有敌人一段时间。一开始一共有五个生命。

有两部电梯不能带他上楼,只能下楼。当他接近电梯时,电梯会自动到来。当他进入楼上的电梯时,电梯里的灯会自动亮起。他的目标是打开两部电梯的灯,然后开始下一层。总共有 13 个级别,每个级别都比前一个更难,因为出现的敌人越来越多。7级后,部分楼层也会设置一些障碍物。

光标键用于 Jack 的导航。“向上”键可以让杰克粘在天花板上,避开他身下的洞,“向下”键可以让他毫无后果地跳下一层。如果您在适当的时候按下“Jump”键,Jack 会向上跳一层。

“暂停”键暂时停止游戏并通过游戏关卡、音乐选择和音乐和音效的两级音量控制打开导航。如果您更喜欢右手方向键,您可以将“旋转操纵杆”切换为“是”,当您退出暂停/菜单屏幕时,所有控件将旋转 180°。

感谢您的阅读和关注。

外文原文:点击进入
声明:本文由Hackaday授权电路城翻译,系电路城的原创内容,转载请注明出处! 

 

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论