ZX Spectrum de coronavirus y sticks (en realidad, no realmente)

El autoaislamiento es el flagelo de la humanidad moderna. Aquí, por ejemplo, en la ciudad vecina, los viernes y sábados, después de los aplausos tradicionales a las 8 pm, organizan conciertos en el balcón. Se sienten bien, sus casas son altas y sus vecinos son jóvenes. Nuestros vecinos son ancianos, no quieren conciertos. Y las casas son bajas, lo que tampoco contribuye a la ociosidad. Por lo tanto, somos salvos como podemos.

Por la tarde, en un sitio remoto, no está tan mal. Como en la noche, hasta que los niños se duerman. Como en los primeros días, hasta que se agoten los libros y la serie se aburra. Pero pasa un mes, seguido de otro. El alma requiere hierro viejo. Pero no solo, sino con perversión. Y rebusqué en los contenedores de basura y encontré el procesador Zilog Z80 allí:

imagen

Debo decir que realmente amo este procesador. Probablemente lo único que me gusta de él es el chip 486, pero mis manos no lo alcanzarán pronto, porque es difícil e inútil insertarlo en el tablero. Hay que soldar. Pero todavía no quiero soldar. E incluso más que el Z80 en sí, me encanta la computadora ZX Spectrum construida sobre esta base. Pero el Spectrum nativo sufre un desastre en forma de un chip lógico personalizado ULA, y sus clones sueltos, aunque no son particularmente difíciles de construir y refinar, todavía no son para el modelo de placa de pruebas, y de hecho, ¿por qué tantas preocupaciones cuando hay arduino?

Un lector inteligente, equilibrado y adecuado aquí dejará de leer o arrojará algo como "1 chip FPGA acomodará la clase de computadora Spectrum" antes de detenerlo. No soy inteligente, ni adecuado, aunque equilibrado, pero solo sé sobre FPGA que es genial. Solo puedo hacer arduino. Pero realmente quiere meter los cables en el Z80. Muy.

Empecemos

Por supuesto, comencemos. Pero primero, descargo de responsabilidad., , , . — . , , . , (, ?), , , , , . , , , , , .

Para empezar, qué es una computadora adecuada de 8 bits. Esto, de hecho, es un procesador conectado a ROM y RAM, y en el lateral hay un par de contadores para mostrar en la pantalla compuesta. A veces, un temporizador para chirriar. ZX Spectrum no es diferente del esquema tradicional, excepto por uno pero. Hay una ULA. Este, de hecho, es el "chipset" del Spectrum. ULA administra periféricos, como una grabadora de cinta, tweeter, teclado (parcialmente), salida a la pantalla (sí, sí, la tarjeta de video integrada apareció en el conjunto de chips Spectrum antes de que se generalizara). También hubo un monumento compartido, los primeros 16 KiB de RAM (direcciones de 0x4000 a 0x5B00). A partir de él, el ULA dibujó un compuesto en la pantalla, y para que el Z80 no cayera allí cuando no fuera necesario, el ULA podría detener el procesador, si es necesario, porque la señal del reloj del Z80 provenía de él. Es decir, si ULA trabajó con memoria y detectó,que el procesador también se arrastra en esta memoria (para esto, monitorea constantemente el MREQ y las líneas A15 y A14), simplemente detuvo el reloj del procesador hasta que terminó de hacer lo que necesitaba. Por cierto, para evitar la corrupción de datos en el bus, partes del bus en el lado del procesador y en el lado ULA fueron delimitadas por ... resistencias ... Además, la memoria estaba en el bus desde el lado ULA y, en consecuencia, en caso de una colisión, ignoraba por completo los datos y la dirección desde el lado del procesador.ignoró completamente los datos y la dirección del procesador.ignoró completamente los datos y la dirección del procesador.

Además, Spectrum tenía ROM (direcciones 0x0000 - 0x3FFF) y su propia memoria de procesador (0x8000 - 0xFFFF), a la que ULA no tenía acceso, y que funcionaba a más de 16 KiB de memoria compartida, ya que el procesador no interfería con ULA en esta área . Pero eso fue solo en la versión 48K de la computadora. En la versión básica, solo había ROM y 16 KiB compatibles con ULA. Comenzaremos con ella.

Es conveniente que el procesador Z80 pueda regenerar DRAM, pero de alguna manera no quiero molestarlo, porque SRAM es más fácil de encontrar y no tengo un multiplexor (o no puedo encontrarlo). Entonces, usaremos SRAM. Para comenzar, ensamblaremos el esqueleto principal, en el que luego se puede colgar todo lo demás. El esqueleto será un procesador, ROM con firmware, mapeado a la dirección de la ROM, RAM de Spectrum, mapeado a los primeros 16 KiB después de la ROM y algunos chips para envolver todo ... Debo decir que durante mucho tiempo no quise rotar, porque tengo diseños chinos $ 1 por 2 piezas en ibee. Pero, para mí, el alboroto vale la pena. Si no desea perder el tiempo durante mucho tiempo, tome buenos diseños.

Entonces, instale el Z80.

Como puede ver en la hoja de datos ,



El procesador tiene 40 pines divididos en grupos: bus de dirección, bus de datos, control del sistema, control del procesador, control del bus del procesador, pozo, alimentación y reloj. No todas estas conclusiones se usan en sistemas reales, como el ZX Spectrum, como se puede ver en el diagrama.. Del grupo "control de procesador" en Spectrum, solo se usan las señales INT y RESET. La señal M1 no se usa del grupo "control del sistema", el grupo "control de bus" no se usa en absoluto. Hay una razón para esto. Los viejos sistemas de 8 bits eran muy simples, y Spectrum fue creado con la idea de ser lo más simple posible y todo lo que podía ignorarse era ignorado. Por supuesto, los fabricantes de periféricos podrían usar interrupciones (señales INT y NMI), se enrutaron a la ranura de expansión, pero NMI no se usó en el espectro en sí. Como se puede ver en el diagrama anterior, las señales de NMI, WAIT, BUSREQ son activadas por resistencias de potencia, ya que estas son entradas activadas de bajo nivel (esto se indica mediante la barra sobre el nombre de la señal), y debe haber una unidad lógica (es decir, + 5V) para que Dios no permita que la señal innecesaria no funcionó. Y aquí están los resultados, BUSACK, HALT, M1,y colgar en el aire, no conectado a nada. Por cierto, tenga en cuenta que no hay un botón de reinicio en el Spectrum. El pin de reinicio se conecta a través deRC encadena al poder (RESET también se activa por un nivel bajo), ya que, de acuerdo con la hoja de datos, después de encender RESET, al menos 3 ciclos de reloj deben estar activos para que el procesador entre en el modo operativo. Este circuito RC mantiene un nivel bajo hasta que el condensador se carga a un nivel alto a través de una resistencia.

Repasemos brevemente el resto de las señales:
M1. No necesitamos. Él informa que el procesador comenzó a ejecutar la siguiente instrucción.
MREQ. Lo necesito. Informa que el procesador está accediendo a la memoria. Si esta señal se vuelve baja (es decir, conectada a tierra), tendremos que activar la memoria conectada al procesador.
IOREQ . Lo necesito. Informa que el procesador está accediendo a un dispositivo periférico. Por ejemplo, al teclado.
RD . Lo necesito. Informa que el procesador leerá datos de la memoria (si MREQ está activo) o periféricos (IOREQ).
Wr . Lo necesito. Informa que el procesador escribirá datos en la memoria / periféricos.
RFSH . Lo necesito. En general, esta señal es necesaria para la memoria dinámica (DRAM). No planeo usarlo, ya que su direccionamiento es más difícil (matriz, no lineal, es decir, será necesario instalar un multiplexor), y en general, en nuestro tiempo, los microcircuitos SRAM de baja capacidad son más fáciles de obtener. Pero dado que el propio procesador regenera DRAM al ordenar las direcciones en el bus de memoria, esta señal nos permitirá ignorar los ciclos de regeneración y no activar la memoria con RFSH activo.
HALT . Innecesario. Indica que el procesador está detenido.
ESPERE. Innecesario. Esta señal es necesaria para pedirle al procesador que se detenga y espere un poco. Usualmente utilizado por periféricos lentos o memoria. Pero no en el espectro. Cuando en los periféricos Spectrum (ULA) decide detener el procesador, simplemente deja de enviarle una señal de reloj. Esto es más confiable, porque después de recibir WAIT, el procesador no se detiene inmediatamente.
INT . Interrumpir. Aún no está claro. Suponemos que aún no es necesario. Entonces lo resolveremos.
El NMI . Interrupción indescifrable. Super interrupción. No es necesario.
REINICIO . Sin ella, no volará.
BUSREQ . Innecesario. Pide al procesador que se desconecte de los buses de datos / direcciones, así como de las señales de control. Es necesario si algún dispositivo quiere controlar el bus.
BUSACK. Innecesario. Sirve para informar al dispositivo que realizó BUSREQ que el bus está libre.
El reloj . Señal de reloj. Claramente, él es necesario. También se necesitan
comidas . Gloria a los desarrolladores, solo + 5V / GND. No 3 te estresa.
A0-A15 es el bus de direcciones. En él, el procesador muestra una dirección de memoria (MREQ está activa) o una dirección de puerto de E / S (IOREQ está activa) con las llamadas apropiadas. Como puede ver, el bus tiene 16 bits de ancho, lo que le permite direccionar directamente 64 KiB de memoria.
D0-D7 - bus de datos. El procesador le envía (WR activo) o lee (RD activo) los datos solicitados.

Entonces, colocaremos el procesador en la placa de pruebas. Entonces sus conclusiones están ubicadas físicamente:

imagen

Conecte la alimentación (pin 11 y 29). Por si acaso, también puse un condensador de 10 pF entre estas patas. Pero al final no me ayudó. Los pines 27, 23, 18 pueden permanecer desconectados de cualquier cosa. Los pines 26, 25, 24, 17, 16 están conectados a través de resistencias (usé 10 kOhm) a la fuente de alimentación. Llevé el bus de direcciones (pines 1-5 y 30-40) al lado opuesto de la placa de pruebas, y el bus de datos (pines 7-10 y 12-15) a un bus de datos separado hecho de prototipos de buses de potencia.
Los pines 6 (señal de reloj) y 26 (RESET) están conectados (más tarde) a Arduin para que pueda controlar el procesador desde él.

Resultó así:



Hasta que preste atención a los cables desde arriba, van desde la ROM, pasaremos a ello un poco más tarde. Además, en la foto al lado del procesador, se ve un chip más. Lo necesitamos para decodificar los bits superiores de la dirección. Como dije anteriormente, hay 3 tipos de memoria en el Spectrum. Los 16 KiB inferiores del espacio de direcciones son ROM. En consecuencia, si los terminales A14 y A15 están en un estado bajo (0 voltios), necesitamos desconectar todo excepto el chip ROM del bus. El siguiente es 16 KiB de memoria compartida. En consecuencia, necesitamos conectar esta memoria al bus (y desconectar el resto) si la salida A15 es baja y A14 es alta (+5 voltios). Bueno, entonces viene 32 KiB de memoria rápida. Adjuntaremos esta memoria más tarde, y la activaremos si la salida A15 está en un estado alto. Además, no olvide que solo activamos la memoria cuando está activa (aquí, activa - baja,0 voltios) MREQ e inactivo (aquí, inactivo - alto, + 5V) RFSH. Todo esto es bastante simple de implementar en la lógica estándar, en las mismas NAND, como 74HC00 u Orthodox K155LA3, y entiendo que esta tarea es para el grupo preparatorio del jardín de infantes, sin embargo, solo puedo pensar en tablas de verdad en libertad y en cautiverioTengo un diagrama de Arlequín completo allí , del que simplemente puede tomar la parte donde se extrae U4 (74HC138, afortunadamente tengo alrededor de un centenar de ellos). Ignoraremos U11 por claridad, ya que los 32KiB superiores no nos interesan hasta ahora.

Conectarse es muy simple.



Como se puede ver en la breve descripciónEl microcircuito es un decodificador que recibe números binarios del 000 al 111 en los terminales 1 a 3 y activa una de las 8 salidas (patas 7 y 9 a 15) correspondientes a este número. Como solo se pueden almacenar 8 números diferentes en 3 bits, solo hay ocho salidas. Como puede ver, las conclusiones están invertidas, es decir, la que estará activa tendrá un nivel de 0V, y todas las demás + 5V. Además, una llave en forma de una puerta de 3 entradas de tipo "I" está integrada en el chip, y dos de sus tres entradas también están invertidas.

En nuestro caso, conectamos el decodificador en sí de la siguiente manera: el bit más significativo (tercer tramo) al suelo, siempre habrá 0. El bit central es la línea A15. Habrá 1 solo si el procesador accede a los 32 KB superiores de memoria (direcciones 0x8000 - 0xFFFF, o 1000000000000000 - 1111111111111111 en binario, cuando el bit más significativo siempre se establece en 1). Conectamos el bit menos significativo a la línea A14, donde el nivel alto será en caso de acceder a la memoria después de los primeros 16 KiB, pero hasta los 32 KiB superiores (direcciones 0x4000 - 0x7FFF o 0100000000000000 - 0111111111111111 en forma binaria), o al 16 KiB más reciente de la dirección espacios (direcciones 0xB000 - 0xFFFF, o 1100000000000000 - 1111111111111111 en forma binaria).

Veamos cuál será la salida en cada uno de los casos:

  • 14 15 , 16 , , 000, 0 ( ), Y0 (15 ). , .
  • 14 , 15 — , 16 , 32 , 001, 1 , Y1 (14 ). , 16 , .
  • 14 , 15 — , - 32 48 , 010, Y2 (13 ). , .
  • Si ambas líneas (A14 y A15) están activas, el procesador accede a los 16 KiB superiores de la memoria, de 48 a 64 KiB, no lo tenemos, por lo que el pin Y3 (pin 12) también está en el aire.

Además, gracias a otro elemento, el microcircuito activará sus hallazgos solo si las entradas 4 y 5 son bajas y 6 son altas. La cuarta entrada siempre está en estado bajo (está conectada directamente a tierra), la quinta solo estará baja cuando el procesador esté accediendo a la memoria (recuerde, MREQ en el estado bajo significa acceder a la memoria), y la sexta será alta cuando el procesador no realice un ciclo de actualización DRAM (tenemos SRAM, por lo que los ciclos de actualización de DRAM son la forma más segura de ignorar). Resulta genial.

A continuación, coloque la ROM.

Tomé el W27C512 porque es barato, alegre, todo encajará y también puedes realizar operaciones bancarias. 64KiB! Se pueden cargar 4 firmware. Bueno, tengo alrededor de un millón de estos microcircuitos. Decidí que solo cosería la mitad superior, ya que en Harlequin la pierna A15 está atada a + 5V, y la A14 es ajustable con un puente. Por lo tanto, puedo probar el firmware en Harlequin para no perder el tiempo durante mucho tiempo. Ver la hoja de datos . Ponemos el chip en el tablero. Nuevamente, lo puse en la esquina derecha para colocar el bus de direcciones a la izquierda. Tiramos de la pata A15 al poder, A14 cableado al suelo. Cableado: esto es para que pueda cambiar los bancos de memoria. Dado que el A15 siempre estará en un nivel alto, solo las 32 unidades flash KiB principales estarán disponibles para nosotros. De estos, la línea A14 seleccionará el KiB superior (+ 5V) o inferior (tierra) 16. En ellos, llené la imagen de prueba con el programadory firmware básico de 48K .

Las 14 líneas de dirección restantes (A0 - A13) están conectadas al bus de direcciones a la izquierda. Conectamos el bus de datos (Q0 - Q7) a nuestro bus improvisado en forma de buses de potencia de los modelos de placa de pruebas. ¡No te olvides de la comida!

Ahora las señales de control. OE es una habilitación de salida. Necesitamos la ROM para enviar datos al bus de datos cuando el procesador los lea. Entonces nos conectamos directamente a la salida del procesador RD. Es conveniente que ambos pines, tanto OE en ROM como RD en el procesador estén activos en estado bajo. Esto es importante; no necesitas invertir nada. Además, la ROM tiene una entrada CS, también activa en un estado bajo. Si esta entrada no está activa, la ROM ignorará todas las demás señales y no emitirá nada al bus de datos. Conectaremos esta entrada al pin Y0 (15 pines) del chip 74HC138, que también está activo en el estado bajo. En el circuito Harlequin , esta señal, por alguna razón, está conectada a través de una resistencia. Haremos lo mismo. Por qué, no lo sé. Tal vez la gente inteligente me lo diga en los comentarios ...

Me lo dijeron. Gracias,sterr:
. , «» . .




Todas.

Ahora RAM.

Es más difícil con él, ya que no solo el procesador, sino también el ULA o, en nuestro caso, Arduino, funcionan con RAM (con nuestros 16 KiB). Dado que es necesario leer algo que se muestra en la pantalla. Por lo tanto, necesitamos poder desconectar las señales de control y el bus de direcciones RAM del procesador. No desconectaremos el bus de datos, actuaremos como en el espectro original (y en Harlequin): dividiremos el bus con resistencias (470-500 ohmios). Por un lado, las resistencias serán el procesador y la ROM, por otro lado, RAM y Arduino. Por lo tanto, en caso de conflicto en el bus de datos, funcionará como 2 buses separados. Pero para el resto usamos 74HC245 , como en Harlequin (U43, U44 en el diagrama), aunque en el presente Speccytambién hubo resistencias (entre IC1 por un lado, esto es ULA e IC3, IC4 por el otro).

El 74HC245 es un búfer de bus de 8 bits. Pero tenemos 2 señales de control (RD - en el caso de leer desde la memoria y CE para activar la RAM en sí. Trataremos con WR en el caso de escribir en la memoria más adelante) y 14 bits de la dirección: recuerde, arriba ya generamos una señal a la memoria usando solo 74HC138 En el caso de que el procesador active A14 con A15 inactivo, por lo que no es necesario realizar ninguna decodificación adicional de la dirección, la memoria solo funcionará al acceder a los primeros 16 KiB después de la ROM. Bueno, por supuesto, para abordar 16 KiB necesita solo 14 líneas de dirección (A0-A13). En total, se obtienen 16 señales, por lo que necesitamos 2 microcircuitos 74HC245. Los conectamos a la placa de pruebas a la izquierda, en lugar del bus de direcciones.

De la hoja de datos en el 74HC245 está claro que, en general, no importa a qué lado conectar los microcircuitos, pero como comencé a construir los diseños de abajo hacia arriba, y todos los demás microcircuitos se instalan con el primer pin a la izquierda, el bus de direcciones se conectará al lado A (pines 2 -9 chips, en la hoja de datos están designados como A0-A7). La dirección de transferencia siempre es del procesador a la RAM, ya que la RAM nunca establece la dirección, sino que solo la recibe. En el 74HC245, el pin 1 (DIR) es responsable de la dirección de transmisión. De acuerdo a la hoja de datospara que la salida igual a la entrada del lado A aparezca en el lado B, el DIR debe establecerse en ALTO. Así que conecta el primer pin de ambos circuitos a + 5V. El OE (pin 20, activado por un nivel bajo) se conecta mediante cableado a tierra para que pueda cambiarse rápidamente a + 5V y desconectarse del procesador. Más simple. Conecte la alimentación de ambos chips. Los pines más a la derecha del microcircuito derecho (pines 8 y 9, entradas A6 y A7) serán señales de control. Conecté A7 al terminal RD del procesador y A6 al pin Y1 del chip 74HC138, ya que habrá un nivel bajo solo cuando el procesador acceda a nuestra RAM. Las conclusiones restantes del lado A de ambos microcircuitos (patas 2–9 para la izquierda y piernas 2–7 para la derecha) las conecté al bus de direcciones, terminales A13-A0. No necesitamos los 2 bits superiores de la dirección, porque ya están decodificados en la señal del 74HC138.Ahora sobre la RAM en sí. Naturalmente, usé lo que ya tenía: un chip de caché de la antigua placa base. Me encontré conIS61C256 a 20 ns, pero cualquiera servirá. Speccy trabajó a una frecuencia de 3.5 MHz, pero por ahora generalmente trataremos a Arduinki. Si sale 100 kHz, ¡habrá felicidad! Entonces, nos conectamos. Por supuesto, no te olvides de la comida. Conclusiones I / O0 - I / O7 están conectadas a la placa del bus de datos DESPUÉS de las resistencias. Tuve suerte (de hecho, no), en mis maquetas chinas, los autobuses de potencia están divididos exactamente en el medio. Usé esta función para separar el bus con resistencias. Si tus diseños son incorrectos, debes ser pervertidohaga un segundo bus de datos y conéctelo con resistencias al primero. Las conclusiones de A0-A13 se arrojan a las conclusiones B correspondientes de los chips 74HC245, sin olvidar que los más a la derecha están conectados no al bus de datos, sino a las señales de control. A14 - por elección, ya sea al suelo, o a + 5V. Un chip de 32 KiB, por lo que esta conclusión determinará qué mitad usaremos. Si encuentra una SRAM de 16 KiB, no habrá una línea A14 en ella. Las salidas son WE (habilitación de escritura), CE (habilitación de chip) y OE (habilitación de salida). Todos se activan bajo. OE debe estar conectado al RD del procesador, pero, por supuesto, no directamente, sino a través del 74HC245 derecho, donde el RD llega a mi pie A7 y, en consecuencia, sale del pie B7 (undécimo pin). Ahí y conéctate. CE debe estar conectado a Y1 desde el 74HC138, que decodifica la dirección. Su señal me llega en el A6 del chip correcto 74HC245, respectivamente,sale del pie B6 (12 pines). WE Me conecté directamente a la salida del procesador WR. También instalé un cable de puente de la señal de OE y lo pegué justo en la parte no utilizada de la placa de pruebas. Al conectar este cable a tierra, puedo forzar la activación de la RAM cuando lo leo desde Arduinka. Aún así, saqué todas las señales de control de la RAM a + 5V usando resistencias de 10 kOhm. Por si acaso. Resultó así:



En general, aquí, y en todo caso, al principio, debería haber un programa educativo sobre la sincronización de las señales en los neumáticos. No haré esto, ya que muchas personas más inteligentes que yo lo han hecho en la red. Para aquellos interesados, puedo recomendar este video:


En general, si no está suscrito a este canal y está interesado en la electrónica como aficionado, y no como profesional, se lo recomiendo encarecidamente. Este es un contenido de muy alta calidad.

En general, eso es casi todo. Ahora solo necesita comprender cómo leer datos de RAM en Arduino. Para empezar, calculemos cuántas conclusiones de Arduinki necesitamos. Necesitamos dar una señal de reloj y controlar el RESET, estos son 2 pines. 8 bits de bus de datos: otros 8 pines. Más 13 bits de dirección, un total de 23 pines. Además, necesitamos comunicarnos con Arduinka, lo haremos a través de su interfaz serial, estos son otros 2 pines. Desafortunadamente, solo hay 20 conclusiones sobre mi ADN.

Bueno, no importa. No conozco a una sola persona que tenga Arduino y no tenga 74HC595. Me parece que solo se venden en el kit. Al menos solo tengo 74HC00 chips más de 595x. Entonces los usamos. Además, ahorraré espacio en el artículo, porque el trabajo del 595x con arduino se describe perfectamente aquí.. 595mi generaremos la dirección. El chip necesitará 2 piezas (ya que tenemos 13 bits de la dirección, y el 595 tiene 8 pines). La forma de conectar varios 595x para la expansión del bus se describe en detalle en el enlace anterior. Solo noto que en los ejemplos en ese enlace OE (pin 13) 595x se tira al suelo. No lo haremos categóricamente, enviaremos una señal desde Arduinki allí, ya que los pines 595x se conectarán directamente al bus de direcciones RAM, y no necesitamos ninguna señal espuria allí. Después de conectar los pines 595x al bus de direcciones RAM, no se necesita hacer nada más en las maquetas. Hora de conectar el arduinka. Pero primero, escribe un boceto:

// 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

Como puede ver en el boceto (bueno, de repente, alguien lo leyó), leí el bus de datos al puerto C. Como Arduischik puede recordar, en el puerto CID C hay 6 pines. Es decir, leo solo 6 bits. Sí, por la simplicidad del proceso, ignoro los 2 bits altos en cada byte del búfer de pantalla. Esto dará como resultado el hecho de que cada 2 píxeles después de 6 siempre habrá colores de fondo. Mientras viaja, luego arréglenlo. Este es el esqueleto.

Ahora para la conexión en sí. En principio, todo está pintado en la parte superior del boceto:

// 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.

Todo es simple Así es como parece que estoy todo ensamblado (el arduino se cortó en la imagen, pero no hay nada especial que ver):



Al inicio, Arduino saludará alegremente al puerto serie de la computadora (aunque sea virtual), y comenzará a atormentar el procesador. Después de haberlo torturado a fondo (un par de minutos), el programa detendrá al pobre chico y le ofrecerá reorganizar los puentes con los bolígrafos en el tablero, desconectando la memoria del bus de direcciones y las señales de control del procesador.

Ahora necesitamos usar las manijas para reorganizar el cableado conectado a los pines 19 de ambos 74HC245 desde el suelo a + 5V. Por lo tanto, desconectamos el procesador de la RAM. El pin 22 del chip RAM en sí debe estar conectado a tierra (escribí anteriormente sobre el cableado, que acabo de pegar en la placa de prueba hasta ahora, en un lugar sin usar). Por lo tanto, activamos por la fuerza la RAM.

Después de eso, después de esperar un poco, Arduinka comenzará a leer el contenido de la memoria y lo enviará en una columna al puerto serie. Habrá muchos, muchos números. Ahora puede copiar estos datos desde allí y pegarlos, digamos, en un archivo de texto, sin olvidar limpiar todo el texto innecesario (un par de líneas en la parte superior y "Listo" en la parte inferior), solo necesitamos números. Esto es lo que nuestro Speccy grabó en la memoria de video. Solo queda ver lo que había en la memoria de video. Y la memoria de video del Spectrum no es fácil ...

Como puede ver, los píxeles se almacenan por separado del color. Ignoraremos el color por ahora, leamos solo los píxeles. Pero no son tan fáciles de decodificar. Después de mucho dolor en Visual Studio, llegué a esta elegante solución:


#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

El programa abre el archivo data.txt desde su directorio. En este archivo, la salida de texto del arduino (después de eliminar todas las líneas adicionales, como se mencionó anteriormente).

Lo alimentamos con el archivo resultante y, como resultado:



Sí, aunque el resultado está muy lejos de ser ideal, pero definitivamente es la salida a la pantalla. Además, el que se necesita. Desde ROM con firmware de diagnóstico.

Bueno, el esqueleto de la computadora está listo. Sí, todavía es imposible usarlo, pero puedes ver lo extremadamente simple que se arreglaron las viejas computadoras de 8 bits. Todavía golpeé un poco por encima del tablero, pero la conclusión solo empeoró. Parece que el siguiente paso es soldar en una placa de pruebas normal, sin soldar, con potencia normal.

¿Pero es necesario?

All Articles