来自冠状病毒和棒的ZX Spectrum(实际上不是真的)

自我隔离是现代人类的祸害。例如,在这里,在邻近的城市,每逢周五和周六,在晚上8点传统拍手之后,他们会组织阳台音乐会。他们感觉很好,房子很高,邻居很年轻。我们的邻居年纪大了,他们不想听音乐会。而且房屋很矮,这也不会导致闲置。因此,我们会尽可能地保存。

下午,在远程站点上,还不错。和晚上一样,直到孩子们入睡。与前几天一样,直到书籍用完,系列变得无聊为止。但是一个月过去了,接着又是一个月。灵魂需要旧的铁。但不仅如此,而且还有变态。我翻遍垃圾箱,发现那里的Zilog Z80处理器:

图片

我必须说,我真的很喜欢这个处理器。我可能最喜欢他的唯一芯片是486芯片,但我很快就不会拿到它,因为将它插入面包板既困难又毫无意义。必须焊接。但是我还不想焊接。甚至比Z80本身,我还喜欢基于它的ZX Spectrum计算机。但是原生频谱以ULA定制逻辑芯片的形式遭受了灾难,其克隆面虽然没有特别困难,尽管构建和完善起来并不是特别困难,但仍然不适合面包板模型,实际上,当有arduino时,为什么这么多的担忧?

在这里,一个聪明,平衡且足够的读取器将停止读取,或者在停止读取之前抛出“ 1个FPGA芯片可容纳Spectrum计算机类”之类的信息。尽管平衡,但我并不聪明,也不足够,但是我只知道FPGA很酷。我只能做arduino。但确实想戳Z80中的电线。高度。

开始吧

当然,让我们开始吧。但首先,免责声明。, , , . — . , , . , (, ?), , , , , . , , , , , .

首先,什么是合适的8位计算机。实际上,这是一个连接到ROM和RAM的处理器,并且在侧面是几个要在复合屏幕上显示的计数器。有时,要吱吱作响。 ZX Spectrum与传统方案没有什么不同,除了一个。有一个ULA。实际上,这就是频谱的“芯片组”。 ULA管理外围设备,例如磁带录音机,高音扬声器,键盘(部分),输出到屏幕(是的,是的,集成视频卡在成为主流之前已出现在Spectrum芯片组中)。还有一个共同的纪念物,RAM的前16 KiB(地址从0x4000到0x5B00)。 ULA用它在屏幕上绘制了一个合成图,因此Z80在不需要时不会在屏幕上摸索,如果有必要,ULA可以停止处理器,因为Z80上的时钟信号来自该处理器。也就是说,如果ULA使用内存并检测到,处理器也潜入该内存中(为此,它不断监视MREQ以及A15和A14线),只是停止了处理器时钟,直到它本身完成了所需的工作。顺便说一句,为了避免总线上的数据损坏,处理器侧和ULA侧的总线部分由...电阻器界定。此外,内存从ULA侧位于总线上,因此,如果发生冲突,则完全忽略了处理器侧的数据和地址。完全忽略了处理器中的数据和地址。完全忽略了处理器中的数据和地址。

此外,Spectrum还具有ROM(地址0x0000-0x3FFF)和其自己的处理器内存(0x8000-0xFFFF),ULA无法对其进行访问,并且其工作速度比共享内存的16 KiB快,因为处理器不会在该区域干扰ULA。 。但这仅适用于48K版本的计算机。在基本版本中,只有ROM和与ULA兼容的16 KiB。我们将从她开始。

Z80处理器可以再生DRAM很方便,但是我不愿为它烦恼,因为SRAM更容易找到,而且我没有多路复用器(或者我找不到)。因此,我们将使用SRAM。首先,我们将组装主骨架,然后将其他所有物体挂在其上。骨架将是一个处理器,带有固件的ROM,映射到Spectrum ROM,RAM的地址,映射到ROM之后的前16 KiB和一些用于封装所有内容的芯片...我必须说很长一段时间我不想旋转,因为我有中文布局ibee中2件$ 1。但是,对我来说,大惊小怪是值得的。如果您不想长时间闲逛,请选择良好的布局。

因此,安装Z80。

数据表中可以看到



处理器有40个引脚,分为组:地址总线,数据总线,系统控制,处理器控制,处理器总线控制,接口,电源和时钟。从图中可以看出,并非所有这些结论都在实际系统中使用,例如ZX Spectrum 。在频谱的“处理器控制”组中,仅使用INT和RESET信号。 “系统控制”组未使用信号M1,“总线控制”组根本未使用。有一个原因。旧的8位系统非常简单,Spectrum是尽可能简单的想法创建的,所有可以忽略的东西都被忽略了。当然,外围设备制造商可以使用中断(INT和NMI信号),它们被路由到扩展插槽,但是频谱本身并未使用NMI。从上图可以看出,NMI,WAIT和BUSREQ信号由功率电阻上拉,因为它们是低电平激活的输入(信号名称上方的横条表示),并且必须有一个逻辑单元(即+ 5V),以便上帝禁止不必要的信号没有奏效。这些是BUSACK,HALT,M1,并悬挂在空中,没有连接任何东西。顺便说一下,请注意,频谱图中没有重置按钮。复位引脚通过RC链上电(RESET也由低电平激活),因为根据数据手册,在开启RESET后,至少必须激活3个时钟周期,处理器才能进入操作模式。该RC电路保持低电平,直到电容器通过电阻器充电到高电平为止。

让我们简要介绍一下其余信号:
M1。我们不需要。他报告处理器开始执行下一条指令。
MREQ。我需要它。它报告处理器正在访问内存。如果此信号变低(即连接到电源地),则我们需要激活连接到处理器的内存。
IOREQ。我需要它。它报告处理器正在访问外围设备。例如键盘。
RD。我需要它。通知处理器将从内存(如果MREQ处于活动状态)或外围设备(IOREQ)读取数据。
Wr。我需要它。报告处理器将数据写入内存/外围设备。
RFSH。我需要它。通常,动态存储器(DRAM)需要此信号。我不打算使用它,因为它的寻址比较困难(矩阵,不是线性的,也就是说,有必要安装一个多路复用器),通常,在我们这个时代,低容量的SRAM微电路更容易获得。但是由于处理器本身通过对内存总线上的地址进行排序来重新生成DRAM,因此该信号将使我们能够忽略重新生成周期,而不会通过有效的RFSH来激活内存。
HALT。并不需要。表示处理器已停止。
等待。并不需要。需要此信号来要求处理器停止并稍等片刻。通常用于速度较慢的外围设备或内存。但不是在光谱中。在频谱外围设备(ULA)中决定停止处理器时,它只是停止向其发送时钟信号。这是更可靠的,因为在收到WAIT之后,处理器不会立即停止。
INT。打断。目前尚不清楚。我们假设尚不需要它。然后我们会解决。
NMI。无法掩盖的中断。超级中断。没有必要。
RESET。没有它,它就不会飞。
BUSREQ。并不需要。要求处理器断开数据/地址总线以及控制信号的连接。如果某些设备想要获得对总线的控制,则很有必要。
布萨克。并不需要。用于通知执行BUSREQ的设备总线空闲。
时钟。时钟信号。显然,他是需要的。还需要
用餐。荣耀给开发商,只有+ 5V / GND。没有3个压力给您。
A0-A15是地址总线。在其上,处理器显示带有适当调用的内存地址(MREQ有效)或I / O端口地址(IOREQ有效)。如您所见,总线为16位宽,这使您可以直接寻址64 KiB的存储器。
D0-D7-数据总线。处理器向其输出(WR激活),或从中读取(RD激活)请求的数据。

因此,我们将处理器放置在试验板上。因此,他的结论实际上是存在的:

图片

连接电源(引脚11和29)。为了以防万一,我还在这两个脚之间放了一个10 pF的电容器。但是他最终没有帮助我。针脚27、23、18可能未连接任何东西。引脚26、25、24、17、16通过电阻(我用10 kOhm)连接到电源。我将地址总线(1-5和30-40引脚)连接到面包板的另一侧,并将数据总线(7-10和12-15引脚)连接到由原型电源总线制成的单独数据总线。
销钉6(时钟信号)和销钉26(复位)(后来)连接到Arduin,以便您可以从中控制处理器。

原来是这样的:



在您注意上面的导线之前,它们都是从ROM发出的,稍后我们将进行介绍。另外,在处理器旁边的照片中,还可以看到一个芯片。我们需要它来解码地址的高位。就像我上面说的,Spectrum中有3种类型的内存。地址空间的低16 KiB是ROM。因此,如果端子A14和A15处于低电平(0伏),我们需要从总线上断开除ROM芯片以外的所有设备的连接。接下来是16 KiB的共享内存。因此,如果输出A15为低电平而A14为高电平(+5 V),则需要将该存储器连接到总线(并断开其余部分的连接)。好吧,接下来是32 KiB的快速内存。稍后我们将附加此存储器,如果输出A15处于高状态,我们将激活它。另外,请不要忘记我们仅在活动状态下才激活内存(此处为活动状态-低,0伏)MREQ和无效(此处为无效-高,+ 5V)RFSH。所有这些操作都非常容易在相同的NAND(例如74HC00或Orthodox K155LA3)上的标准逻辑上实现,并且我知道此任务适用于幼儿园的预备班,但是,我只能在自由和囚禁的真相表中进行思考我在那里有一个完整的Harlequin图,您可以从中轻松绘制U4的部分(74HC138,幸运的是我有大约一百个)。为了清楚起见,我们将忽略U11,因为到目前为止,我们对32KiB的上限并不感兴趣。

连接非常简单。



简要说明可以看出微电路是一个解码器,它在端子1到3上接收从000到111的二进制数,并激活与该数字相对应的8个输出之一(支线7和9到15)。由于3位中只能存储8个不同的数字,因此只有8个输出。如您所见,结论是相反的,也就是说,将处于活动状态的结论将具有0V的电平,而所有其他结论将具有5V的电平。此外,芯片中内置了一个类型为“ I”的三输入门形式的按键,并且其三个输入中的两个也被反转了。

在我们的情况下,我们按以下方式连接解码器本身:最高有效位(第3脚)接地,始终为0。中间位是A15行。仅当处理器访问存储器的高32KiB(最高有效位始终设置为1时,地址为0x8000-0xFFFF或二进制的1000000000000000-1111111111111111)时,才会为1。我们将最低有效位连接到A14线,在访问前16 KiB之后访问存储器,但在最前32 KiB(地址为0x4000-0x7FFF或0100000000000000-0111111111111111,以二进制形式)访问时,高电平将被访问,或到该地址的最新16 KiB空格(地址0xB000-0xFFFF或1100000000000000-1111111111111111为二进制格式)。

让我们看看在每种情况下的输出是什么:

  • 14 15 , 16 , , 000, 0 ( ), Y0 (15 ). , .
  • 14 , 15 — , 16 , 32 , 001, 1 , Y1 (14 ). , 16 , .
  • 14 , 15 — , - 32 48 , 010, Y2 (13 ). , .
  • 如果两条线(A14和A15)均处于活动状态,则处理器将访问顶部的16 KiB内存(从48 KiB到64 KiB),所以我们没有它,因此Y3引脚(第12引脚)也处于悬空状态。

另外,由于有另一个因素,只有在输入4和5为低而输入6为高时,微电路才激活其结果。第4个输入始终处于低电平状态(直接连接到地面),第5个输入仅在处理器访问内存时为低电平(请记住,MREQ处于低状态表示正在访问内存),而第6个输入为高电平(当处理器不执行更新周期时) DRAM(我们有SRAM,因此DRAM更新周期是忽略的最安全方法)。事实证明太好了。

接下来,放入ROM。

我买了W27C512,因为它价格低廉,令人愉悦,一切都可以满足,您也可以开户。 64KiB!可以上传4个固件。好吧,我有大约一百万个这样的微电路。我决定只缝上半部分,因为在Harlequin上,A15的腿绑在+ 5V上,而A14可以用跳线调节。因此,我可以在Harlequin上测试固件,以免长时间混乱。 Smorim 数据表。我们将芯片放在面包板上。同样,我将其放在右上角以将地址总线放在左侧。我们将A15脚拉至电源,将A14线接地。接线-这样您就可以更改存储库。由于A15始终处于较高水平,因此只有前32个KiB闪存驱动器可用。其中,A14线将选择较高(+ 5V)或较低(接地)的16 KiB。在其中,我程序员填充了测试图像48K BASIC固件

其余14条地址线(A0-A13)连接到左侧的地址总线。我们以面包板模型中的电源总线的形式将数据总线(Q0-Q7)连接到临时总线。不要忘记食物!

现在控制信号。OE是输出使能。当处理器读取数据时,我们需要ROM将数据发送到数据总线。因此,我们直接连接到RD处理器的输出。方便地,两个引脚(ROM上的OE和处理器上的RD)均处于低电平状态。这很重要;您无需反转任何内容。此外,ROM具有CS输入,该输入也处于低电平状态。如果该输入未激活,则ROM将忽略所有其他信号,并且不会向数据总线输出任何内容。我们将此输入连接到74HC138芯片的Y0引脚(15引脚),该芯片在低电平状态下也处于活动状态。丑角电路中,由于某种原因该信号通过电阻器连接。我们将照做。为什么,我不知道。也许聪明的人在评论中告诉我... 他们告诉

谢谢,立体
. , «» . .




所有。

现在是RAM。

这样做比较困难,因为不仅处理器,而且还有ULA,或者在我们的例子中是Arduino,都使用RAM(使用16 KiB)工作。由于必须阅读屏幕上显示的内容。因此,我们需要能够从处理器断开控制信号和RAM地址总线的连接。我们不会断开数据总线的连接,我们将按照原始频谱(以及在Harlequin中)的方式工作:我们将总线与电阻(470-500欧姆)分开。一方面,电阻将是处理器和ROM,另一方面是RAM和Arduino。因此,如果数据总线发生冲突,它将作为2条独立的总线工作。但是对于其余部分,我们使用74HC245,就像在Harlequin中一样(在图中为U43,U44),尽管在当前Speccy中也有电阻器(一方面在IC1之间,这是ULA,另一方面在IC3,IC4之间)。

74HC245是8位总线缓冲器。但是我们有2个控制信号(RD-在从存储器读取的情况下,CE用于激活RAM本身。在稍后写入存储器的情况下,我们将处理WR)和14位地址:记住,上面我们已经仅使用74HC138向存储器产生信号如果处理器在A15未激活的情况下激活了A14,因此我们不需要对地址进行任何其他解码,则只有在访问ROM之后的前16 KiB时,存储器才能工作。好吧,当然,要寻址16 KiB,您只需要14条地址线(A0-A13)。总共获得16个信号,因此我们需要2个74HC245微电路。我们将它们连接到左侧的面包板上,代替地址总线。

从74HC245的数据表可以看出,通常来说,将微电路连接到哪一侧都没有关系,但是由于我从下至上开始构建布局,并且所有其他微电路都从第一个引脚向左安装,所以地址总线将连接到A侧(引脚2数据手册中的-9芯片称为A0-A7)。传输方向始终是从处理器到RAM的,因为RAM从不设置地址,而仅接收地址。在74HC245中,引脚1(DIR)负责传输方向。根据数据表为了使B侧的输出等于A侧的输入,必须将DIR设置为HIGH。因此,将两个电路的第一个引脚连接到+ 5V。 OE(第20针,由低电平激活)使用接地线连接,以便可以快速切换至+ 5V并与处理器断开连接。更简单。连接两个芯片的电源。右侧微电路的最右侧引脚(第8和9引脚,输入A6和A7)将成为控制信号。我将A7连接到处理器的RD端子,将A6连接到74HC138芯片的Y1引脚,因为只有在处理器访问我们的RAM时才会有低电平。我从两个微电路的A侧(左脚2–9,右脚2–7)的其余结论我连接到地址总线,端子A13-A0。我们不需要地址的高2位,因为它们已经在来自74HC138的信号中解码了。现在有关RAM本身。自然,我使用已经拥有的东西:旧主板上的缓存芯片。我碰到IS61C256在20 ns,但任何方法都可以。 Speccy的工作频率为3.5 MHz,但现在我们通常将对Arduinki进行治疗。如果出现100 kHz,就会有幸福感!因此,我们连接。当然,不要忘记食物。结论在电阻之后,I / O0-I / O7已连接到数据总线的面包板上。我很幸运(实际上不是),在我的中文样机上,动力母线正好在中间分开。我使用此功能将总线与电阻器分开。如果您的布局错误,则必须变态制作第二条数据总线,并用电阻将其连接到第一条。 A0-A13的结论被扔到74HC245芯片的相应B结论上,不要忘记最右边的那些不是连接到数据总线,而是连接到控制信号。 A14-选择接地或+ 5V。 32 KiB芯片,因此此结论将确定我们将使用的一半。如果找到16 KiB SRAM,则其中没有A14线。输出为WE(写使能),CE(芯片使能)和OE(输出使能)。所有被激活为低。 OE必须连接到处理器的RD,但是,当然不能直接连接,而是通过正确的74HC245连接,RD到达我的A7脚,并因此从B7脚(第11针)出来。在那里并连接。 CE必须从74HC138连接到Y1,Y74解码地址。她的信号分别通过正确的芯片74HC245的A6传给我,从脚B6(12针)伸出。我们直接连接到WR处理器的输出。我还从OE信号安装了一根跳线,并将其插在面包板未使用的部分中。通过将这条线连接到电源地,当我从Arduinka读取RAM时,可以强制激活RAM。尽管如此,我还是使用10 kOhm的电阻将RAM的所有控制信号拉至+ 5V。以防万一。原来是这样的:



总的来说,在这里,如果有的话,甚至从一开始,就应该有一个关于轮胎信号定时的教育计划。我不会这样做,因为它是由网络上比我聪明得多的人完成的。对于那些感兴趣的人,我可以推荐这个视频:


通常,如果您没有订阅此频道,并且对电子产品感兴趣的是业余爱好者而不是专业人员,那么我强烈推荐您。这是非常高质量的内容。

一般而言,几乎就是如此。现在,您只需要了解如何从Arduino中的RAM中读取数据即可。首先,让我们计算我们需要多少个Arduinki结论。我们需要提供一个时钟信号并控制RESET,这是2个引脚。 8位数据总线-另外8个引脚。加上13位地址,总共23个引脚。另外,我们需要与Arduinka进行通信,我们将通过她的串行接口进行此操作,这是另外2个引脚。不幸的是,关于我的DNA只有20条结论。

好吧,没关系。我不认识一个拥有Arduino并且没有74HC595的人。在我看来,它们仅以套件形式出售。至少我只有74HC00芯片超过595x。因此,我们使用它们。此外,我会在文章中节省空间,因为595x与Arduino的工作完美地描述了这里。 595mi我们将生成地址。该芯片将需要2个芯片(因为我们有13位地址,而595th有8个引脚)。上面的链接中详细介绍了如何连接多个595x进行总线扩展。我只注意到在该链接的示例中,OE(引脚13)595x被拉到了地面。我们绝对不会这样做,我们将从Arduinki发送一个信号,因为595x引脚将直接连接到RAM地址总线,并且那里不需要任何虚假信号。将595x引脚连接到RAM地址总线后,无需在模型上进行任何处理。是时候连接arduinka了。但首先,写一个草图:

// CPU defines
#define CPU_CLOCK_PIN 2
#define CPU_RESET_PIN 3

// RAM defines
#define RAM_OUTPUT_ENABLE_PIN 4
#define RAM_WRITE_ENABLE_PIN 5
#define RAM_CHIP_ENABLE_PIN 6
#define RAM_BUFFER_PIN 7

// Shift Register defines
#define SR_DATA_PIN 8
#define SR_OUTPUT_ENABLE_PIN 9
#define SR_LATCH_PIN 10
#define SR_CLOCK_PIN 11

//////////////////////////////////////////////////////////////////////////

void setup() {
  // All CPU and RAM control signals need to be configured as inputs by default
  // and only changed to outputs when used.
  // Shift register control signals may be preconfigured

  // CPU controls seetup
  DDRC = B00000000;
  pinMode(CPU_CLOCK_PIN, INPUT);
  pinMode(CPU_RESET_PIN, INPUT);

  // RAM setup
  pinMode(RAM_WRITE_ENABLE_PIN, INPUT);
  pinMode(RAM_OUTPUT_ENABLE_PIN, INPUT);
  pinMode(RAM_CHIP_ENABLE_PIN, INPUT);
  pinMode(RAM_BUFFER_PIN, OUTPUT);
  digitalWrite(RAM_BUFFER_PIN, LOW);

  // SR setup
  pinMode(SR_LATCH_PIN, OUTPUT);
  pinMode(SR_CLOCK_PIN, OUTPUT);
  pinMode(SR_DATA_PIN, OUTPUT);
  pinMode(SR_OUTPUT_ENABLE_PIN, OUTPUT);
  digitalWrite(SR_OUTPUT_ENABLE_PIN, HIGH); // active low

  // common setup
  Serial.begin(9600);
  Serial.println("Hello");
}// setup

//////////////////////////////////////////////////////////////////////////

void shiftReadValueFromAddress(uint16_t address, uint8_t *value) {
  // disable RAM output
  pinMode(RAM_WRITE_ENABLE_PIN, OUTPUT);
  digitalWrite(RAM_WRITE_ENABLE_PIN, HIGH); // active low
  pinMode(RAM_OUTPUT_ENABLE_PIN, OUTPUT);
  digitalWrite(RAM_OUTPUT_ENABLE_PIN, HIGH); // active low
  // set address
  digitalWrite(SR_LATCH_PIN, LOW);
  shiftOut(SR_DATA_PIN, SR_CLOCK_PIN, MSBFIRST, address>>8); 
  shiftOut(SR_DATA_PIN, SR_CLOCK_PIN, MSBFIRST, address);  
  digitalWrite(SR_LATCH_PIN, HIGH);
  digitalWrite(SR_OUTPUT_ENABLE_PIN, LOW); // active low
  // write value to RAM
  digitalWrite(RAM_OUTPUT_ENABLE_PIN, LOW); // active low
  delay(1);
  DDRC = B00000000;
  *value = PINC;
  digitalWrite(RAM_OUTPUT_ENABLE_PIN, HIGH); // active low
  // disable SR
  digitalWrite(SR_OUTPUT_ENABLE_PIN, HIGH); // active low
  pinMode(RAM_WRITE_ENABLE_PIN, INPUT);
  pinMode(RAM_OUTPUT_ENABLE_PIN, INPUT);
}// shiftWriteValueToAddress

//////////////////////////////////////////////////////////////////////////

void runClock(uint32_t cycles) {
  uint32_t currCycle = 0;
  pinMode(CPU_CLOCK_PIN, OUTPUT);
  while(currCycle < cycles) {
    digitalWrite(CPU_CLOCK_PIN, HIGH);
    digitalWrite(CPU_CLOCK_PIN, LOW);
    currCycle++;
  }
  pinMode(CPU_CLOCK_PIN, INPUT);
}// runClock

//////////////////////////////////////////////////////////////////////////

void trySpectrum() {
  pinMode(RAM_WRITE_ENABLE_PIN, INPUT);
  pinMode(RAM_OUTPUT_ENABLE_PIN, INPUT);
  pinMode(CPU_RESET_PIN, OUTPUT);
  digitalWrite(CPU_RESET_PIN, LOW);
  runClock(30);
  digitalWrite(CPU_RESET_PIN, HIGH);
  runClock(12500000);
}// trySpectrum

//////////////////////////////////////////////////////////////////////////

void readDisplayLines() {
  uint8_t value;
  digitalWrite(RAM_BUFFER_PIN, HIGH);
  pinMode(RAM_CHIP_ENABLE_PIN, OUTPUT);
  digitalWrite(RAM_CHIP_ENABLE_PIN, LOW);
  for(uint16_t i=16384; i<16384+6144;i++) {
    shiftReadValueFromAddress(i, &value);
    Serial.println(value);
  }
  pinMode(RAM_CHIP_ENABLE_PIN, INPUT);
}// readDisplayLines

//////////////////////////////////////////////////////////////////////////

void loop() {
  trySpectrum();
  Serial.println("Hope we are ok now. Please set up memory for reading");
  delay(40000);
  Serial.println("Reading memory");
  readDisplayLines();
  Serial.println("Done");
  delay(100000);
}// loop

从草图中可以看到(确实,突然有人读了它),我将数据总线读到了端口C。正如Arduischik所记得的,在CID中,端口C是6针。也就是说,我只读取6位。是的,为了简化过程,我忽略了屏幕缓冲区每个字节中的2个高位。这将导致以下事实:6之后的每2个像素将始终有背景色。一会儿,然后修复它。这是骨骼。

现在用于连接本身。原则上,所有内容都绘制在草图的最上方:

// CPU defines
#define CPU_CLOCK_PIN 2 -  2     6  ( )
#define CPU_RESET_PIN 3 -  3     26  (RESET)

// RAM defines
#define RAM_OUTPUT_ENABLE_PIN 4 -  4     22  (OE)
#define RAM_WRITE_ENABLE_PIN 5 -  5    .     .
#define RAM_CHIP_ENABLE_PIN 6 -  6     .        ,        .   - ,   -  .   ,   .
#define RAM_BUFFER_PIN 7 -  ,    6,    .

// Shift Register defines
#define SR_DATA_PIN 8   -  8     14 "" 595.        9 ,     .
#define SR_OUTPUT_ENABLE_PIN 9 -   13  595
#define SR_LATCH_PIN 10 -   12  595
#define SR_CLOCK_PIN 11 -   11  595.

一切都很简单。这是我收藏中的所有东西的样子(照片中剪下了arduinka,但是没什么可看的):



在启动时,Arduino高兴地向计算机的串行端口(尽管是虚拟的)说“你好”,并开始折磨处理器。在彻底折磨了他(几分钟)之后,该程序将停止可怜的家伙,并为您提供用笔在面包板上重新排列跳线的方法,从而使内存与地址总线和处理器控制信号的连接断开。

现在,我们需要使用手柄将连接到两个74HC245的引脚19的布线从接地重新布置为+ 5V。因此,我们将处理器与RAM断开。 RAM芯片本身的针脚22必须接地(我在上面写过有关布线的信息,到目前为止,我还只是将其插在面包板上,放在未使用的地方)。因此,我们强行打开RAM。

之后,稍等片刻,Arduinka将开始读取内存的内容并将其以列的形式输出到串行端口。会有很多很多的数字。现在,您可以从那里复制此数据并将其粘贴到文本文件中,而不必忘记清除所有不必要的文本(在顶部两行,在底部“完成”),我们只需要数字。这就是我们的Speccy在视频存储器中记录的内容。只能看到视频存储器中的内容。而且,Spectrum的视频存储并不容易 ...

如您所见,像素本身与颜色分开存储。现在我们将忽略颜色,让我们仅读取像素本身。但是它们并不是那么容易解码。在Visual Studio中痛苦不堪后,我想到了这个优雅的解决方案:


#include "stdafx.h"
#include <windows.h>
#include <stdint.h>
#include <stdio.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
uint8_t *scrData;

VOID OnPaint(HDC hdc) {
	size_t arrSize = 6144;//sizeof(scrData) / sizeof(scrData[0]);
	//int currRow = 0, currX = 0, currBlock = 0, currY = 0, currBase = 0;
	for (size_t arrPos = 0; arrPos < arrSize; arrPos++) {
		int blockPos = arrPos % 2048;
		int currBase = (blockPos % 256) / 32;
		int currX = blockPos % 32;
		int currBlock = arrPos / 2048;
		int currRow = blockPos / 256;
		int currY = currBlock * 64 + currBase * 8 + currRow;
		for (int trueX = 0; trueX < 8; trueX++) {
			char r = ((scrData[arrPos] >> trueX) & 1)*255;
			SetPixel(hdc, currX * 8 + (8-trueX), currY, RGB(r, r, r));
		}
	}
}

void loadData() {
	FILE *file;
	errno_t err;
	if ((err = fopen_s(&file, "data.txt", "r"))) {
		MessageBox(NULL, L"Unable to oopen the file", L"Error", 1);
	}
	scrData = (uint8_t*)malloc(6144);
	int currDataPos = 0;
	char buffer[256];
	char currChar = 0;
	int currLinePos = 0;
	while (currChar != EOF) {
		currChar = getc(file);
		buffer[currLinePos++] = currChar;
		if (currChar == '\n') {
			buffer[currLinePos] = 0;
			scrData[currDataPos++] = (uint8_t)atoi(buffer);
			currLinePos = 0;
		}
	}
	fclose(file);
}

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow) {
	HWND                hWnd;
	MSG                 msg;
	WNDCLASS            wndClass;
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = TEXT("GettingStarted");
	RegisterClass(&wndClass);
	hWnd = CreateWindow(
		TEXT("GettingStarted"),   // window class name
		TEXT("Getting Started"),  // window caption
		WS_OVERLAPPEDWINDOW,      // window style
		CW_USEDEFAULT,            // initial x position
		CW_USEDEFAULT,            // initial y position
		CW_USEDEFAULT,            // initial x size
		CW_USEDEFAULT,            // initial y size
		NULL,                     // parent window handle
		NULL,                     // window menu handle
		hInstance,                // program instance handle
		NULL);                    // creation parameters
	loadData();
	ShowWindow(hWnd, iCmdShow);
	UpdateWindow(hWnd);
	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}  // WinMain

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
	HDC          hdc;
	PAINTSTRUCT  ps;
	switch (message) {
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		OnPaint(hdc);
		EndPaint(hWnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
} // WndProc

该程序从其目录打开data.txt文件。在此文件中,arduino的文本输出(如上所述,删除所有多余的行之后。)

我们将结果文件馈入该文件,结果



是的,虽然结果与理想情况相去甚远,但绝对是屏幕上的输出。而且,这是需要的。从具有诊断固件的ROM。

好了,计算机框架已准备就绪。是的,尚无法使用,但是您可以看到旧式8位计算机的布置极为简单。我仍然在试验板上稍作努力,但是结论只会变得更糟。似乎下一步是用普通电源在普通的,未经焊接的面包板上焊接。

但是有必要吗?

All Articles