ZX Spectrum de coronavírus e paus (na verdade, não realmente)

O auto-isolamento é o flagelo da humanidade moderna. Aqui, por exemplo, na cidade vizinha, às sextas e sábados, depois das tradicionais palmas às 20h, eles organizam concertos na varanda. Eles se sentem bem, suas casas são altas e seus vizinhos são jovens. Nossos vizinhos são idosos, não querem shows. E as casas são baixas, o que também não contribui para a ociosidade. Portanto, somos salvos como podemos.

À tarde, em um site remoto, não é tão ruim. Como à noite, até as crianças adormecerem. Como nos primeiros dias, até os livros acabarem e a série ficar entediada. Mas um mês passa, seguido por outro. A alma requer ferro velho. Mas não apenas, mas com perversão. E eu vasculhei as lixeiras e encontrei o processador Zilog Z80 lá:

imagem

Devo dizer, eu realmente amo este processador. Provavelmente, a única coisa que eu gosto nele é o 486º chip, mas minhas mãos não o alcançarão em breve, porque é difícil e inútil inseri-lo na tábua de pão. Tem que soldar. Mas eu não quero soldar ainda. E ainda mais do que o próprio Z80, eu amo o computador ZX Spectrum construído em sua base. Mas o Spectrum nativo sofre um desastre na forma de um chip lógico personalizado do ULA, e seus clones do lado frouxo, embora não sejam particularmente difíceis de construir e refinar, ainda não são para o modelo da placa de ensaio e, de fato, por que tantas preocupações quando existe arduino?

Um leitor inteligente, equilibrado e adequado aqui interrompe a leitura ou lança algo como "1 microcircuito FPGA acomodará a classe de computadores Spectrum" antes de parar. Não sou inteligente, não sou adequado, apesar de equilibrado, mas só sei sobre o FPGA que é legal. Eu só posso fazer arduino. Mas realmente quer cutucar os fios no Z80. Altamente.

Vamos começar

Claro, vamos começar. Mas primeiro, Isenção de responsabilidade., , , . — . , , . , (, ?), , , , , . , , , , , .

Para começar, o que é um computador de 8 bits adequado. Este é, de fato, um processador conectado à ROM e à RAM e, ao lado, há alguns contadores para exibir na tela composta. Às vezes, um cronômetro para chiar. O ZX Spectrum não é diferente do esquema tradicional, exceto um mas. Existe um ULA. Este, de fato, é o "chipset" do Spectrum. O ULA gerencia periféricos, como um gravador, tweeter, teclado (parcialmente), saída para a tela (sim, sim, a placa de vídeo integrada apareceu no chipset Spectrum antes de se tornar mainstream). Havia também um memorial compartilhado, os primeiros 16 KiB de RAM (endereços de 0x4000 a 0x5B00). A partir dele, o ULA desenhou um composto na tela e, para que o Z80 não ficasse lá quando não era necessário, o ULA poderia parar o processador, se necessário, porque o sinal do relógio no Z80 vinha dele. Ou seja, se o ULA trabalhou com memória e detectou,que o processador também entra nessa memória (para isso, monitora constantemente as linhas MREQ e A15 e A14), simplesmente interrompe o clock do processador até terminar de fazer o que é necessário. A propósito, para evitar corrupção de dados no barramento, partes do barramento no lado do processador e no ULA foram delimitadas por ... resistores ... Além disso, a memória estava no barramento do lado do ULA e, portanto, no caso de uma colisão, ignorava completamente os dados e o endereço do lado do processador.ignorou completamente os dados e o endereço do processador.ignorou completamente os dados e o endereço do processador.

Além disso, o Spectrum possuía ROMs (endereços 0x0000 - 0x3FFF) e memória própria do processador (0x8000 - 0xFFFF), às quais o ULA não tinha acesso e que funcionava mais rápido que 16 KiB de memória compartilhada, pois o processador não interferia no ULA nessa área. . Mas isso foi apenas na versão 48K do computador. Na versão básica, havia apenas ROMs e 16 KiB compatíveis com o ULA. Vamos começar com ela.

É conveniente que o processador Z80 possa regenerar a DRAM, mas de alguma forma não quero me preocupar com isso, porque a SRAM é mais fácil de encontrar e não tenho um multiplexador (ou não consigo encontrá-lo). Então, usaremos SRAM. Para começar, montaremos o esqueleto principal, no qual todo o resto pode ser pendurado. O esqueleto será um processador, ROM com firmware, mapeado para o endereço da ROM do Spectrum, RAM, mapeado para os primeiros 16 KiB após a ROM e alguns chips para embrulhar tudo ... Devo dizer que, durante muito tempo, não quis rodar, porque tenho layouts chineses US $ 1 por 2 peças no ibee. Mas, para mim, o barulho vale a pena. Se você não quiser mexer por muito tempo, faça bons layouts.

Então, instale o Z80.

Como você pode ver na folha de dados ,



O processador possui 40 pinos, divididos em grupos: barramento de endereços, barramento de dados, controle do sistema, controle do processador, controle do barramento do processador, poço, potência e relógio. Nem todas essas conclusões são usadas em sistemas reais, como o ZX Spectrum, como pode ser visto no diagrama.. Do grupo "controle do processador" no Spectrum, apenas os sinais INT e RESET são usados. O sinal M1 não é utilizado no grupo "controle do sistema", o grupo "controle do barramento" não é utilizado. Há uma razão para isto. Os antigos sistemas de 8 bits eram muito simples, e o Spectrum foi criado com a idéia de ser o mais simples possível e tudo o que podia ser ignorado era ignorado. Obviamente, os fabricantes de periféricos podiam usar interrupções (sinais INT e NMI), eles eram roteados para o slot de expansão, mas a NMI não era usada no próprio espectro. Como pode ser visto no diagrama acima, os sinais NMI, WAIT, BUSREQ são puxados pelos resistores de potência, pois são entradas ativadas de baixo nível (isso é indicado pela barra acima do nome do sinal) e deve haver uma unidade lógica (ou seja, + 5V) para que Deus não permita que o sinal desnecessário não funcione. E aqui estão as conclusões, BUSACK, HALT, M1,e fique no ar, não conectado a nada. A propósito, observe que não há botão de reinicialização no Spectrum. O pino de reset está conectado viaCorrente RC para energia (RESET também é ativado por um nível baixo), pois, de acordo com a folha de dados, após ativar o RESET, pelo menos 3 ciclos de clock devem estar ativos para que o processador entre no modo de operação. Esse circuito RC mantém um nível baixo até que o capacitor seja carregado em um nível alto através de um resistor.

Vamos examinar brevemente o resto dos sinais:
M1. Nós não precisamos. Ele relata que o processador começou a executar a próxima instrução.
MREQ. Eu preciso disso. Ele relata que o processador está acessando a memória. Se esse sinal ficar baixo (ou seja, conectado ao aterramento), precisaremos ativar a memória conectada ao processador.
IOREQ . Eu preciso disso. Ele relata que o processador está acessando um dispositivo periférico. Por exemplo, para o teclado.
RD . Eu preciso disso. Informa que o processador irá ler dados da memória (se MREQ estiver ativo) ou periféricos (IOREQ).
Wr . Eu preciso disso. Relata que o processador gravará dados na memória / periféricos.
RFSH . Necessário. Em geral, esse sinal é necessário para a memória dinâmica (DRAM). Não pretendo usá-lo, pois seu endereçamento é mais difícil (matriz, não linear, ou seja, será necessário instalar um multiplexador) e, em geral, em nosso tempo, os microcircuitos SRAM de baixa capacidade são mais fáceis de obter. Porém, como o próprio processador regenera a DRAM classificando os endereços no barramento de memória, esse sinal nos permitirá ignorar os ciclos de regeneração e não ativar a memória com o RFSH ativo.
MEIO . Não é necessário. Indica que o processador está parado.
ESPERAR. Não é necessário. Este sinal é necessário para solicitar que o processador pare e espere um pouco. Geralmente usado por periféricos lentos ou memória. Mas não no Spectrum. Quando nos periféricos Spectrum (ULA) decide parar o processador, ele simplesmente deixa de enviar um sinal de relógio para ele. Isso é mais confiável, porque depois de receber WAIT, o processador não para imediatamente.
INT . Interromper. Ainda não está claro. Assumimos que ainda não é necessário. Então vamos descobrir.
A MNI . Interrupção desmascarável. Super interrupção. Não é necessário.
RESET . Sem ele, não voará.
BUSREQ . Não é necessário. Solicita que o processador desconecte os barramentos de dados / endereço, bem como os sinais de controle. É necessário se algum dispositivo quiser obter controle sobre o barramento.
BUSACK. Não é necessário. Serve para informar ao dispositivo que executou o BUSREQ que o barramento está livre.
O RELÓGIO . Sinal de relógio. Claramente, ele é necessário.
As refeições também são necessárias. Glória aos desenvolvedores, apenas + 5V / GND. Não há 3 estresses para você.
A0-A15 é o barramento de endereços. Nele, o processador exibe um endereço de memória (MREQ está ativo) ou um endereço de porta de E / S (IOREQ está ativo) com as chamadas apropriadas. Como você pode ver, o barramento tem 16 bits de largura, o que permite endereçar diretamente 64 KiB de memória.
D0-D7 - barramento de dados. O processador envia a ele (WR ativo) ou lê (RD ativo) os dados solicitados.

Então, colocaremos o processador na placa de ensaio. Portanto, suas conclusões estão fisicamente localizadas:

imagem

Conecte a energia (pinos 11 e 29). Só para garantir, eu também coloquei um capacitor de 10 pF entre essas pernas. Mas ele não me ajudou no final. Os pinos 27, 23, 18 podem permanecer desconectados de qualquer coisa. Os pinos 26, 25, 24, 17, 16 são conectados via resistores (usei 10 kOhm) à fonte de alimentação. Levei o barramento de endereços (pinos 1-5 e 30-40) para o lado oposto da placa de ensaio e o barramento de dados (pinos 7-10 e 12-15) para um barramento de dados separado, feito a partir de protótipos de barramento de força.
Os pinos 6 (sinal de relógio) e 26 (RESET) são conectados (posteriormente) ao Arduin para que você possa controlar o processador a partir dele.

Aconteceu assim:



Até que você preste atenção nos fios de cima, eles partem da ROM, passaremos a ele um pouco mais tarde. Além disso, na foto ao lado do processador, mais um chip é visível. Precisamos decodificar os bits superiores do endereço. Como eu disse acima, existem 3 tipos de memória no Spectrum. Os 16 KiB mais baixos do espaço de endereço são ROM. Portanto, se os terminais A14 e A15 estiverem em um estado baixo (0 Volts), precisamos desconectar tudo, exceto o chip ROM do barramento. Em seguida, são 16 KiB de memória compartilhada. Portanto, precisamos conectar essa memória ao barramento (e desconectar o restante) se a saída A15 for baixa e A14 for alta (+5 Volts). Bem, então vem 32 KiB de memória rápida. Anexaremos essa memória posteriormente e a ativaremos se a saída A15 estiver em um estado alto. Além disso, não esqueça que somente ativamos a memória quando ativos (aqui, ativo - baixo,0 Volt) MREQ e inativo (aqui, inativo - alto, + 5V) RFSH. Tudo isso é muito simples de implementar na lógica padrão, nas mesmas NANDs, como 74HC00 ou K155LA3 ortodoxa, e eu entendo que esta tarefa é para o grupo preparatório do jardim de infância, no entanto, só consigo pensar nas tabelas da verdade em liberdade e em cativeiroEu tenho um diagrama Harlequin completo lá , do qual você pode simplesmente assumir a parte em que o U4 é desenhado (74HC138, felizmente eu tenho cerca de cem deles). Iremos ignorar o U11 por questões de clareza, pois os 32KiB superiores não nos interessam até agora.

A conexão é muito simples.



Como pode ser visto na breve descriçãoO microcircuito é um decodificador que recebe números binários de 000 a 111 nos terminais 1 a 3 e ativa uma das 8 saídas (pernas 7 e 9 a 15) correspondentes a esse número. Como apenas 8 números diferentes podem ser armazenados em 3 bits, existem apenas oito saídas. Como você pode ver, as conclusões são invertidas, ou seja, a que estará ativa terá um nível de 0V, e todas as outras + 5V. Além disso, uma chave na forma de uma porta de 3 entradas do tipo “I” está embutida no chip e duas de suas três entradas também são invertidas.

No nosso caso, conectamos o decodificador em si da seguinte forma: o bit mais significativo (terceira perna) ao chão, sempre haverá 0. O bit do meio é a linha A15. Haverá 1 apenas se o processador acessar os 32KiB superiores de memória (endereços 0x8000 - 0xFFFF ou 1000000000000000 - 1111111111111111 em binário, quando o bit mais significativo é sempre definido como 1). Conectamos o bit menos significativo à linha A14, onde o nível alto será no caso de acessar a memória após os primeiros 16 KiB, mas até os 32 KiB superiores (endereços 0x4000 - 0x7FFF ou 0100000000000000 - 0111111111111111 em formato binário) ou os 16 KiB mais recentes do endereço espaços (endereços 0xB000 - 0xFFFF ou 1100000000000000 - 1111111111111111 em formato binário).

Vamos ver qual será a saída em cada um dos casos:

  • 14 15 , 16 , , 000, 0 ( ), Y0 (15 ). , .
  • 14 , 15 — , 16 , 32 , 001, 1 , Y1 (14 ). , 16 , .
  • 14 , 15 — , - 32 48 , 010, Y2 (13 ). , .
  • Se as duas linhas (A14 e A15) estiverem ativas, o processador acessa os 16 KiB de memória principais, de 48 a 64 KiB, não temos, portanto o pino Y3 (12º pino) também está no ar.

Além disso, graças a outro elemento, o microcircuito ativará suas descobertas apenas se as entradas 4 e 5 forem baixas e 6 forem altas. A quarta entrada está sempre no estado baixo (está conectada diretamente ao terra), a quinta somente será baixa quando o processador estiver acessando a memória (lembre-se, o MREQ no estado baixo significa acessar a memória) e a sexta estará alta quando o processador não executar um ciclo de atualização DRAM (temos SRAM, portanto, os ciclos de atualização de DRAM são a maneira mais segura de ignorar). Acontece ótimo.

Em seguida, coloque a ROM.

Peguei o W27C512 porque é barato, alegre, tudo vai ficar bem e você também pode pagar. 64KiB! 4 firmware podem ser carregados. Bem, eu tenho cerca de um milhão desses microcircuitos. Eu decidi que só costuraria a metade superior, já que no Harlequin a perna A15 está ligada a + 5V, e a A14 é ajustável com um jumper. Assim, posso testar o firmware no Harlequin para não mexer por um longo tempo. Folha de dados Smorim . Colocamos o chip na tábua de pão. Mais uma vez, coloquei no canto direito para colocar o barramento de endereços à esquerda. Puxamos a perna A15 para o poder, a fiação A14 para o chão. Fiação - isto é para que você possa alterar os bancos de memória. Como o A15 sempre estará em um nível alto, apenas as 32 principais unidades flash KiB estarão disponíveis para nós. Destes, a linha A14 selecionará os 16 KiB superior (+ 5V) ou inferior (terra). Neles, preenchi a imagem de teste com o programadore firmware básico de 48K .

As 14 linhas de endereço restantes (A0 - A13) estão conectadas ao barramento de endereços à esquerda. Conectamos o barramento de dados (Q0 - Q7) ao barramento improvisado na forma de barramentos de força dos modelos de placa de ensaio. Não se esqueça de comida!

Agora os sinais de controle. OE é uma habilitação de saída. Precisamos que a ROM envie dados ao barramento de dados quando o processador lê. Portanto, nos conectamos diretamente à saída do processador RD. Convenientemente, os dois pinos, OE na ROM e RD no processador, estão ativos em um estado baixo. Isso é importante, você não precisa inverter nada. Além disso, a ROM possui uma entrada CS, também ativa em um estado baixo. Se esta entrada não estiver ativa, a ROM ignorará todos os outros sinais e não emitirá nada para o barramento de dados. Vamos conectar essa entrada ao pino Y0 (15 pinos) do chip 74HC138, que também está ativo no estado baixo. No circuito Harlequin , esse sinal, por algum motivo, é conectado através de um resistor. Nós vamos fazer o mesmo. Porque eu não sei. Talvez pessoas inteligentes me digam nos comentários ... Eles me

disseram. Obrigado,sterr:
. , «» . .




Todos.

Agora RAM.

É mais difícil, pois não apenas o processador, mas também o ULA, ou, no nosso caso, o Arduino, trabalham com RAM (com nossos 16 KiB). Uma vez que é necessário ler algo que é exibido na tela. Portanto, precisamos desconectar os sinais de controle e o barramento de endereço RAM do processador. Não desconectaremos o barramento de dados, atuaremos como no espectro original (e no Harlequin): dividiremos o barramento com resistores (470-500 Ohms). Por um lado, os resistores serão o processador e a ROM, por outro, RAM e Arduino. Portanto, no caso de um conflito no barramento de dados, ele funcionará como 2 barramentos separados. Mas, de resto, usamos 74HC245 , como no Harlequin (U43, U44 no diagrama), embora no presente Speccytambém havia resistores (entre IC1, por um lado, isto é ULA, e IC3, IC4, por outro).

O 74HC245 é um buffer de barramento de 8 bits. Mas temos 2 sinais de controle (RD - no caso de leitura da memória e CE para ativar a própria RAM. Lidaremos com o WR no caso de gravar na memória posteriormente) e 14 bits do endereço: lembre-se, acima, já geramos um sinal para a memória usando apenas 74HC138 no caso em que o processador ativou o A14 com o A15 inativo, portanto não precisamos decodificar o endereço, a memória funcionará apenas ao acessar os primeiros 16 KiB após a ROM. Bem, é claro, para endereçar 16 KiB, você precisa de apenas 14 linhas de endereço (A0-A13). No total, são obtidos 16 sinais, por isso precisamos de 2 microcircuitos 74HC245. Nós os conectamos à placa de ensaio à esquerda, no lugar do barramento de endereços.

A partir da folha de dados do 74HC245, fica claro que, em geral, não importa de que lado conectar os microcircuitos, mas desde que comecei a criar os layouts de baixo para cima, e todos os outros microcircuitos são instalados com o primeiro pino à esquerda, o barramento de endereços se conectará ao lado A (pinos 2 -9 chips, na folha de dados são designados como A0-A7). A direção da transferência é sempre do processador para a RAM, pois a RAM nunca define o endereço, mas apenas o recebe. No 74HC245, o pino 1 (DIR) é responsável pela direção da transmissão. De acordo com a folha de dadospara que no lado B exista uma saída igual à entrada no lado A, o DIR deve estar definido como HIGH. Portanto, conecte o primeiro pino de ambos os circuitos a + 5V. OE (20 pinos, ativado por um nível baixo) é conectado usando a fiação ao terra, para que possa ser rapidamente mudado para + 5V e desconectado do processador. Mais simples. Conecte a energia para os dois chips. Os pinos mais à direita do microcircuito direito (8º e 9º pinos, entradas A6 e A7) serão sinais de controle. Conectei o A7 ao terminal RD do processador e o A6 ao pino Y1 do chip 74HC138, pois haverá um nível baixo apenas quando o processador acessar nossa RAM. As conclusões restantes do lado A de ambos os microcircuitos (pernas 2–9 para a esquerda e pernas 2–7 para a direita) conectei ao barramento de endereço, terminais A13-A0. Não precisamos dos 2 bits superiores do endereço, porque eles já estão decodificados no sinal do 74HC138.Agora sobre a própria RAM. Naturalmente, usei o que já tinha: um chip de cache da antiga placa-mãe. Me depareiIS61C256 em 20 ns, mas qualquer um fará. Speccy trabalhava a uma frequência de 3,5 MHz, mas, por enquanto, geralmente trataremos o Arduinki. Se 100 kHz sair, haverá felicidade! Então, nós nos conectamos. Claro, não se esqueça de comida. Conclusões I / O0 - I / O7 são conectadas à placa de ensaio do barramento de dados APÓS os resistores. Tive sorte (de fato, não). Nas minhas maquetes chinesas, os ônibus elétricos estão divididos exatamente no meio. Eu usei esse recurso para separar o barramento com resistores. Se seus layouts estão errados, você deve ser pervertidofaça um segundo barramento de dados e conecte-o com resistores ao primeiro. As conclusões de A0-A13 são apresentadas nas conclusões B correspondentes dos chips 74HC245, não esquecendo que os mais à direita estão conectados não ao barramento de dados, mas aos sinais de controle. A14 - por opção, seja no chão ou em + 5V. Como um chip de 32 KiB, essa conclusão determinará qual metade usaremos. Se você encontrar uma SRAM de 16 KiB, não haverá linha A14 nela. As saídas são WE (ativação por gravação), CE (ativação por chip) e OE (ativação por saída). Todos são ativados baixo. O OE deve estar conectado ao RD do processador, mas, é claro, não diretamente, mas através do 74HC245 direito, onde o RD chega ao meu pé A7 e, consequentemente, sai do pé B7 (11º pino). Lá e conectar. O CE deve estar conectado ao Y1 a partir do 74HC138, que decodifica o endereço. Seu sinal chega a mim no A6 do chip certo 74HC245, respectivamente,sai do pé B6 (12 pinos). WE eu diretamente conectado à saída do processador WR. Também instalei um fio de ponte do sinal OE e o prendi apenas na parte não utilizada da placa de ensaio. Ao conectar esse fio ao terra de energia, posso forçar a RAM a ser ativada quando o li em Arduinka. Ainda assim, puxei todos os sinais de controle da RAM para + 5V usando resistores de 10 kOhm. Apenas no caso de. Aconteceu assim:



Em geral, aqui, e se houver, desde o início, deve haver um programa educacional sobre o tempo dos sinais nos pneus. Não farei isso, pois isso já foi feito muitas vezes na rede por pessoas muito mais inteligentes que eu. Para os interessados, posso recomendar este vídeo:


Em geral, se você não se inscreveu neste canal e está interessado em eletrônicos como amador, e não como profissional, eu o recomendo. Este é um conteúdo de alta qualidade.

Em geral, isso é quase tudo. Agora você só precisa entender como ler dados da RAM no Arduino. Para começar, vamos calcular quantas conclusões de Arduinki precisamos. Precisamos dar um sinal de relógio e controlar o RESET, estes são 2 pinos. 8 bits de barramento de dados - outros 8 pinos. Mais 13 bits de endereço, total de 23 pinos. Além disso, precisamos nos comunicar com Arduinka, faremos isso através de sua interface serial, este é mais 2 pinos. Infelizmente, existem apenas 20 conclusões no meu DNA.

Bem, isso não importa. Não conheço uma única pessoa que tenha Arduino e não tenha 74HC595. Parece-me que eles são vendidos apenas no kit. Pelo menos eu tenho apenas chips 74HC00 mais que 595x. Então nós os usamos. Além disso, vou economizar espaço no artigo, porque o trabalho do 595x com o arduino está perfeitamente descrito aqui. 595mi iremos gerar o endereço. O chip precisará de 2 peças (já que temos 13 bits do endereço e o 595º possui 8 pinos). Como conectar vários 595x para expansão de barramento é descrito em detalhes no link acima. Notei apenas que nos exemplos desse link OE (pino 13) 595x é puxado para o chão. Nós categoricamente não faremos isso, enviaremos um sinal de Arduinki lá, pois os pinos 595x serão conectados diretamente ao barramento de endereços RAM, e não precisamos de nenhum sinal falso lá. Após conectar os pinos 595x ao barramento de endereço RAM, nada mais precisa ser feito nos modelos. Hora de conectar o arduinka. Mas primeiro, escreva um esboço:

// 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 você pode ver no esboço (bem, de repente, alguém leu), li o barramento de dados para a porta C. Como Arduischik pode se lembrar, no CID, a porta C tem 6 pinos. Ou seja, eu li apenas 6 bits. Sim, pela simplicidade do processo, ignoro os 2 bits altos em cada byte do buffer de tela. Isso resultará no fato de que a cada 2 pixels após 6 sempre haverá cores de fundo. Durante um passeio, conserte-o. Este é o esqueleto.

Agora, a conexão em si. Em princípio, tudo é pintado no topo do esboço:

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

Tudo é simples. Aqui está como parece que estou todo montado (o arduino foi cortado na imagem, mas não há nada de especial para assistir):



Na inicialização, o Arduino alegremente diz Olá para a porta serial do computador (embora virtual) e começa a atormentar o processador. Depois de torturá-lo completamente (alguns minutos), o programa irá parar o pobre rapaz e oferecerá a você para reorganizar os jumpers com as canetas na placa de ensaio, desconectando a memória dos sinais de controle do processador e do barramento de endereços.

Agora precisamos usar as alças para reorganizar a fiação conectada aos pinos 19 de ambos 74HC245 do chão para + 5V. Assim, desconectamos o processador da RAM. O pino 22 do chip de RAM em si deve estar conectado ao terra (escrevi acima sobre a fiação, que acabei de prender na placa de ensaio até agora, em um local não utilizado). Assim, ativamos à força a RAM.

Depois disso, depois de esperar um pouco, o Arduinka começará a ler o conteúdo da memória e enviá-lo em uma coluna para a porta serial. Haverá muitos, muitos números. Agora você pode copiar esses dados de lá e colá-los, digamos, em um arquivo de texto, sem esquecer de limpar todo o texto desnecessário (algumas linhas na parte superior e "Concluído" na parte inferior), precisamos apenas de números. Isso é o que nosso Speccy gravou na memória de vídeo. Resta apenas ver o que havia na memória de vídeo. E a memória de vídeo do Spectrum não é fácil ...

Como você pode ver, os próprios pixels são armazenados separadamente da cor. Vamos ignorar a cor por enquanto, vamos ler apenas os pixels em si. Mas eles não são tão fáceis de decodificar. Após muita dor no Visual Studio, cheguei a esta solução elegante:


#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

O programa abre o arquivo data.txt a partir do diretório Neste arquivo, a saída de texto do arduino (após excluir todas as linhas extras, como mencionado acima.)

Nós o alimentamos com o arquivo resultante e como resultado:



Sim, enquanto o resultado está muito longe do ideal, mas é definitivamente a saída para a tela. Além disso, o que é necessário. Da ROM com firmware de diagnóstico.

Bem, o esqueleto do computador está pronto. Sim, ainda não é possível usá-lo, mas você pode ver como os computadores antigos de 8 bits foram extremamente simples. Ainda bati um pouco demais, mas a conclusão só piorou. Parece que o próximo passo é soldar em uma tábua de pão normal, não soldada, com energia normal.

Mas isso é necessário?

All Articles