Programador de Computador em Protoboard com Arduino UNO – Construa e Programe seu Sistema de 8 Bits
Componentes e Materiais
| Arduino UNO | × | 1 |
Sobre este Projeto
O que é este projeto?
Esta é uma extensão do projeto de computador em protoboard de 8 bits de Ben Eater. Acompanhei com grande entusiasmo os vídeos de Ben sobre como construir um computador em protoboard e construí minha própria versão. Se você não acompanhou o trabalho de Ben, recomendo fortemente assistir aos vídeos dele antes de prosseguir – este projeto fará muito pouco sentido sem esse contexto.
Após construir o computador, ficou rapidamente claro que, se eu tivesse que inserir manualmente um programa via chaves DIP toda vez que o ligasse, ele não teria muita utilidade. Mesmo inserir apenas 16 bytes repetidamente torna-se cansativo muito rápido. Além disso, eu planejava expandir a memória para 256 bytes, o que só seria útil se os programas pudessem ser carregados e salvos.
O que posso fazer com ele?
- Salvar um programa da RAM do computador em protoboard
- Carregar um programa na RAM do computador em protoboard
- Executar automaticamente um programa salvo na inicialização do computador em protoboard
- Inspecionar e modificar o conteúdo da memória via conexão serial
- Programar o computador em protoboard em linguagem assembly
- Desmontar o conteúdo da memória
- Executar passo a passo instrução por instrução
- Definir pontos de interrupção e executar até o ponto de interrupção
Aqui está um vídeo demonstrando esses recursos. Peço desculpas pela qualidade do vídeo – não sou de forma alguma um criador de conteúdo de vídeo profissional:
Como funciona?
Tinha alguns objetivos em mente ao criar este projeto:
- O mínimo possível de hardware necessário para construí-lo
- O mínimo possível de alterações no próprio computador em protoboard
Inicialmente, pensei em usar uma EEPROM para armazenar programas e alguma lógica para transferir programas da EEPROM para a RAM do computador em protoboard. No entanto, criar a lógica de transferência provou ser mais complicado do que eu podia lidar (sou mais um cara de software, embora goste muito de trabalhar próximo ao hardware). Sou um grande fã do Arduino, então em algum momento comecei a pensar em substituir a lógica de transferência por um Arduino. Levou algum tempo para me convencer de que usar um Arduino não era "trapaça" (afinal, até o Arduino UNO é muito mais poderoso que o próprio computador em protoboard), mas estou satisfeito com o resultado.
O que o Arduino precisa fazer? Precisa ser capaz de ler dados da memória e escrever dados na memória. A maneira mais fácil e menos intrusiva (para o computador em protoboard) de fazer isso é usar a mesma interface que o módulo de memória já possui: o barramento e os sinais de controle. Os pinos de I/O digitais do Arduino são bidirecionais, então conectar 8 deles diretamente ao barramento permite que o Arduino leia e escreva no barramento. Para fazer o módulo de RAM ler ou escrever no barramento, tudo o que é necessário é definir os sinais MI/RI/RO adequadamente. Normalmente, esses sinais são acionados pelas EEPROMs da lógica de controle, então ter o Arduino controlando-os também levaria a conflitos e possíveis curtos-circuitos. No entanto, as EEPROMs AT28C16 usadas por Ben têm uma entrada Chip Enable (CE) que coloca todas as saídas de dados em alta impedância (high-Z) – o que permite que o Arduino manipule os sinais.
Para ler a RAM, o Arduino precisa fazer o seguinte:
- Definir o sinal CE da EEPROM como alto (ou seja, desabilitar a lógica de controle)
- Colocar o primeiro endereço a ser lido no barramento
- Definir o sinal MI como alto e aguardar uma transição de clock de baixo para alto
- Definir MI como baixo e RO como alto e aguardar uma transição de clock de baixo para alto
- Ler o byte de dados do barramento
- Repetir a partir do passo 2 para todos os 16 endereços
- Definir o sinal CE da EEPROM como baixo (ou seja, reabilitar a lógica de controle)
E é isso. Escrever o conteúdo da RAM é muito similar; o Arduino apenas precisa escrever no barramento e definir RI como alto em vez de RO. Existem, claro, alguns problemas técnicos a resolver, mas o mecanismo básico acima é o coração deste projeto.
Que alterações preciso fazer no computador em protoboard?
Duas alterações são necessárias:
- Colocar resistores pull-down nas saídas de dados da EEPROM da lógica de controle
- Desabilitar (ou resetar continuamente) o contador de anel enquanto a EEPROM estiver desabilitada
Resistores Pull-down
Os resistores pull-down são necessários porque, uma vez que a EEPROM é desabilitada, os resistores pull-up das portas lógicas às quais os sinais de controle (como AO/AI/EO...) estão conectados puxarão esses sinais para alto. Isso significaria que vários registradores estariam escrevendo no barramento, causando conflitos.
Os resistores pull-up nas entradas das portas 74LS são de cerca de 10k. Portanto, os resistores pull-down precisam ser pequenos o suficiente para baixar a tensão para o território lógico baixo. Para a maioria das linhas de sinal, usei resistores de 3,3 kΩ. No entanto, existem duas exceções: primeiro, o sinal "SU" está conectado a 8 portas XOR exclusivas, o que significa que o resistor pull-up efetivo é 10kΩ/8 = 1,25kΩ. Um resistor pull-down teria que ser significativamente menor que 1k para puxar isso para baixo. Felizmente, o sinal SU (subtração) não controla nenhuma interação com o barramento, então podemos simplesmente ignorá-lo e não colocar um resistor pull-down. Em segundo lugar, o CE (habilitação do contador) precisou de um resistor pull-down de 1k – valores maiores causavam comportamento aleatório do contador de programa em alguns casos.
Descobri que o local mais fácil para adicionar os resistores pull-down é na protoboard que contém todos os LEDs azuis, ou seja, entre os ânodos dos LEDs (que estão conectados às saídas da EEPROM) e GND.
Reset do Contador de Anel
A outra modificação é resetar o contador de anel. Tecnicamente, isso não é estritamente necessário para permitir o salvamento/carregamento de programas, mas permite uma transferência suave de controle do Arduino de volta para a lógica de controle. O objetivo é manter o contador de anel em 0 enquanto CE estiver alto (ou seja, a lógica de controle desabilitada). Quando o Arduino muda CE de volta para baixo (habilitando a lógica de controle), o contador de anel estará em zero e o computador em protoboard começa a executar a próxima instrução.
Na minha construção, não precisei fazer nada para isso porque estou usando uma saída da EEPROM para resetar o contador de anel. Isso melhora o desempenho resetando o contador de anel assim que uma instrução termina. Também me dá automaticamente o reset do contador de anel quando a EEPROM da lógica de controle é desabilitada: o resistor pull-down na saída da EEPROM puxa o sinal para baixo, resetando o contador de anel.
Se você estiver usando a implementação de Ben de um contador de anel fixo de 5 passos, a seguinte extensão do circuito de reset dele deve resetar o contador quando CE estiver alto (clique nas setas esquerda/direita abaixo para alternar entre o circuito original de Ben e a versão estendida):
Como você pode ver, requer 3 portas NAND a mais, ou seja, mais um chip 74LS00. Note que não testei essa abordagem, mas deveria funcionar na minha análise.
Esta modificação não é absolutamente necessária – você pode omiti-la no início. O salvamento e carregamento, bem como o monitor/assembler/desassembler, ainda funcionarão perfeitamente. No entanto, qualquer ação que exija transferência de controle do Arduino para a lógica de controle não funcionará. Mais notavelmente, isso inclui a execução automática de programas salvos na inicialização, bem como passo a passo e execução no depurador.
Como configurar o Arduino?
Carregue o sketch do arquivo GIT no Arduino e conecte o Arduino ao computador em protoboard da seguinte forma:
- Pino 5V do Arduino (não Vin!) para o trilho de 5V na protoboard
- Pino GND do Arduino para o pino GND na protoboard
- Pinos digitais 2-9 do Arduino para barramento 0-7
- Pino digital 10 do Arduino para saída da EEPROM da lógica de controle que controla RO
- Pino digital 11 do Arduino para saída da EEPROM da lógica de controle que controla RI
- Pino digital 12 do Arduino para saída da EEPROM da lógica de controle que controla MI
- Pino analógico 0 do Arduino para sinal de CLOCK
- Pino analógico 3 do Arduino para CE (pino 18) de todas as EEPROMs da lógica de controle e via resistor de 10k para +5V
Além disso, você precisará conectar os pinos analógicos 1 e 2 do Arduino às chaves DIP e botões conforme mostrado nos esquemáticos (para mais detalhes, veja o arquivo Fritzing anexado).
Para uma versão mínima absoluta (mas ainda funcional), você pode fazer o seguinte:
- Adicionar resistores pull-down de 3,3kΩ aos pinos de saída da EEPROM que controlam AO, CO, EO, IO, RO
- Pular as instruções de "Reset do contador de anel" acima
- Fazer a fiação Arduino-para-computador em protoboard como mostrado acima (pode omitir o resistor de 10k no último passo se quiser)
- Conectar tanto o pino analógico 1 quanto o analógico 2 ao GND
Para usar os botões de carregar/salvar, tudo o que você precisa fazer é conectar os botões, chaves DIP e resistores associados aos pinos analógicos 1 e 2 de acordo com os esquemáticos.
Para usar o recurso de auto-inicialização, o Arduino precisa manter o contador de programa e o contador de anel em 0 durante o reset e enquanto o programa é transferido. O resistor pull-up entre o pino analógico 3 e +5V mantém a lógica de controle desabilitada (e portanto o contador de programa em 0) enquanto o Arduino reinicia. Para o contador de anel, siga as instruções de "Reset do contador de anel" acima.
Como carregar e salvar programas?
A configuração mínima acima permitirá que você controle o Arduino via interface serial. Para comunicar com o Arduino, você precisará de um programa terminal como Putty ou TeraTerm. O monitor serial do software Arduino também funcionará, mas a separação entre área de entrada e saída no monitor serial torna-o um pouco desajeitado neste cenário.
- Ligue o computador em protoboard
- Conecte um PC ao Arduino via cabo USB
- Inicie o programa terminal e configure para 9600 baud, 8 bits, sem paridade, 1 bit de parada
- Pressione ESC na janela do terminal para entrar no modo Monitor
- Você deve ver um "." como prompt de comando
- Digite "h" e pressione enter para obter uma lista de comandos suportados
Com a configuração mínima, você deve ser capaz de usar os comandos "m", "M", "C", "l" e "s". Estes permitem ver o conteúdo da memória, modificar o conteúdo da memória e carregar e salvar programas.
Para salvar ou carregar um programa via botão:
- Desligue o clock do computador em protoboard
- Configure as chaves DIP para selecionar o número do arquivo sob o qual os dados devem ser salvos
- Pressione o botão "salvar" ou "carregar". O LED conectado ao CE acenderá, indicando que o Arduino assumiu o controle
- Ligue o clock do computador em protoboard. Você verá o Arduino percorrendo os endereços (observe os LEDs do registrador de endereço de memória)
- Aguarde até que os LEDs do barramento parem de piscar e os LEDs do registrador de endereço de memória mostrem 1111
- Desligue o clock do computador em protoboard. O LED conectado ao CE se apagará, indicando que o controle foi devolvido à lógica de controle
Para executar automaticamente um programa na inicialização (certifique-se de ter toda a circuitaria necessária), basta configurar as chaves DIP para o número do arquivo sob o qual o programa está salvo e ligar o computador em protoboard (ou pressionar o botão de reset). Existem dois casos especiais: se todas as chaves DIP estiverem desligadas, o computador inicia normalmente, sem auto-inicialização. Se todas as chaves DIP estiverem ligadas, o Arduino entra no modo Monitor diretamente na inicialização.
Como usar o assembler e desassembler?
Para usar os recursos de assembler/desassembler e depurador, você primeiro precisará alterar o programa no Arduino para conformar à sua configuração específica. Encontre a seção no código-fonte que define a estrutura opcodes_4bit:
struct opcodes_struct opcodes_4bit [] = {
{"NOP ", B00000000, 0, false},
{"LDA ", B00010000, 2, true},
...
{".OR ", B11111110, 0, true}, // definir endereço inicial
{".BY ", B11111111, 0, true}, // definir um byte de dados
{NULL, 0, 0, false}
};
Cada linha especifica um opcode:
- Primeiro campo é o mnemônico ("LDA"). Para endereçamento imediato, inclua um "#" no mnemônico. Então o que Ben chama de "LDI" seria chamado de "LDA #" aqui. Dá para perceber que cresci programando em assembler 6510 no C64?
- Segundo campo é o próprio opcode. Os quatro bits inferiores devem ser sempre 0 (exceto para os opcodes especiais .OR e .BY, veja abaixo)
- Terceiro campo é o número de ciclos que o opcode leva para executar (além dos ciclos de fetch). Por exemplo, na minha implementação LDA tem opcode 0001 e leva um total de quatro ciclos para executar, dois dos quais são o ciclo de fetch. Se você seguiu as instruções de Ben (onde todos os opcodes usam 5 ciclos), isso deve ser sempre 3.
- Último campo especifica se este opcode requer um argumento. Por exemplo, LDA requer um argumento mas OUT não.
Você precisará ajustar esta lista para refletir os opcodes que implementou para seu computador em protoboard. As duas últimas linhas são opcodes especiais usados pelo assembler e devem ser deixadas como estão.
Após inserir todos os seus opcodes, carregue o software no Arduino. Conecte seu terminal e entre no modo Monitor (pressionando ESC na janela do terminal ou configurando todas as chaves DIP para ligado). Agora você deve ser capaz de desmontar seu programa. Digitando apenas "d" no monitor, a desmontagem começará no endereço 0.
O assembler é mínimo, mas funciona muito bem. Digite "a" para começar a montar no endereço 0. Convenções:
- Se uma linha não começa com espaço em branco, ela deve começar com uma definição de label. Labels devem começar com um caractere alfanumérico seguido por caracteres alfanuméricos, podem ter no máximo 3 caracteres e diferenciam maiúsculas/minúsculas.
- Após o primeiro espaço em branco em uma linha, o assembler espera um mnemônico (LDA, STA, OUT...).
- Mnemônico especial ".BY" especifica diretamente um byte de dados a ser armazenado na localização atual.
- Mnemônico especial ".OR" diz ao assembler para continuar montando em um novo endereço.
- Se um argumento começa com um caractere alfabético, assume-se que é um label.
- Qualquer argumento numérico é esperado como número decimal. Para especificar hexadecimal, preceda o argumento com "$". Por exemplo, para carregar o número hexadecimal FF no registrador A, use "LDA #$FF".
- Tudo após um ";" é assumido como comentário e ignorado.
Por exemplo, o código de Fibonacci pode ser inserido da seguinte forma:
rst LDA #0 ; x = 0
STA x
LDA #1 ; y = 1
STA y
lp ADD x ; z = y + x
STA z
JC rst ; reiniciar se overflow
OUT ; imprimir z
LDA y ; x = y
STA x
LDA z ; y = z
STA y
JMP lp ; loop
x .BY 0
y .BY 0
z .BY 0
Quais são as limitações?
Para economizar RAM no Arduino, o assembler funciona como um assembler de 1 passagem (caso contrário, o Arduino teria que armazenar todo o código-fonte em buffer). O assembler escreve os opcodes na memória do computador em protoboard conforme são inseridos. Isso significa que a montagem é desacelerada pela velocidade do clock do computador em protoboard. Se você copiar e colar texto na janela do terminal, isso pode levar a caracteres perdidos, pois o Arduino não consegue acompanhar caracteres chegando a 9600 baud (porque gasta muito tempo esperando o clock do computador em protoboard). Para contornar isso, reduza a taxa de baud ou use TeraTerm, que fornece uma configuração para especificar um atraso entre caracteres enviados. A outra alternativa é aumentar a velocidade do clock no computador em protoboard. Meu clock vai até 160kHz e nessa velocidade consigo copiar e colar código a 9600 baud sem problemas.
Na configuração padrão, o sketch do Arduino pode lidar com frequências de clock no computador em protoboard de até cerca de 1-2kHz (talvez um pouco mais). Note que o clock de Ben em sua configuração padrão não passa de 500Hz. Se seu clock for mais rápido, procure a chave #ifdef FAST_IO no código. Ativar FAST_IO deve fazer o Arduino funcionar com velocidades de clock de até 250kHz. Testado até 160kHz. Provavelmente seria possível suportar velocidades maiores implementando os loops críticos de tempo diretamente em assembler, mas honestamente uma velocidade de clock de 160kHz já parece rápida demais no computador em protoboard com suas capacidades limitadas. Certifique-se de ler os comentários correspondentes no código antes de ativar FAST_IO.
O Arduino tem 1k de EEPROM e, portanto, pode armazenar 1024/16=64 programas diferentes. Na verdade, são 63, pois 16 bytes são reservados para salvar dados de configuração. Não é muito, mas provavelmente suficiente para todos os programas que você possa criar. Apenas os programas de número 0-15 podem ser selecionados via chaves DIP (1-14 para auto-inicialização), mas os comandos "s" e "l" funcionarão com a faixa completa 0-62.
Parece um pouco bagunçado. Dá para organizar?
Sim! Na minha versão final, usei apenas o chip Atmega 328P puro (junto com cristal de 16MHz e capacitores) em vez de um Arduino UNO. O regulador de tensão do UNO não é necessário aqui, pois o Arduino usa diretamente o 5V da nossa fonte de alimentação. A única perda é que agora precisamos usar um conversor USB-para-serial separado (FTDI ou similar) para falar com o Atmega. A aparência geral combina muito melhor com o resto do computador em protoboard:
Outra otimização é remover o bootloader do Arduino/Atmega. Isso elimina o atraso de 2 segundos enquanto o bootloader do Arduino inicia. Publicarei instruções sobre como fazer isso se houver interesse. Deixe nos comentários!
Código
Programador de Computador em Protoboard
O sketch do Arduino para o Programador de Computador em Protoboard: https://github.com/dhansel/programmer
Esquemáticos
drive_pD8k28E85v.fzz
Processo de manufatura
- A Evolução do Mouse de Computador: Design, Fabricação e Tendências Futuras
- Crie Efeitos de Áudio Controlados por Sensores com o Kit Satellite CCRMA
- Construa um Spybot Controlado por Bluetooth com Arduino
- FlickMote: Controle Remoto IR por Gestos para Casa Inteligente
- DIY Arduino TV B-Gone: Controle Remoto Universal para Todas as TVs
- Construa um Relógio Mestre LED Personalizado com Alarme – Visível a 12 Metros
- Find Me: Localizador Inteligente de Objetos com Arduino e Bluetooth
- Soluções de Energia Otimizadas para Projetos Arduino
- Jogo da Velha com Arduino, Matriz LED MAX7219 e Caixa de Papelão
- Construa um Robô Quadrúpede Inteligente com Arduino: Guia Passo a Passo