Programador para Computadora de Protoboard de 8 Bits con Arduino UNO: Construye y Programa tu Sistema
Componentes y materiales
![]() |
Arduino UNO
|
× | 1 |
Acerca de este proyecto
¿Qué es este proyecto?
Esta es una extensión del proyecto de computadora de protoboard de 8 bits de Ben Eater. He disfrutado enormemente siguiendo los videos de Ben sobre cómo construir una computadora de protoboard y construyendo mi propia versión. Si no has seguido los videos de Ben, te recomiendo encarecidamente verlos antes de continuar; este proyecto tendrá muy poco sentido sin ese contexto.
Después de construir la computadora, quedó claro rápidamente que si tenía que introducir manualmente un programa mediante interruptores DIP cada vez que la encendía, apenas la usaría. Incluso introducir solo 16 bytes repetidamente se vuelve tedioso muy rápido. Además, planeaba ampliar la memoria a 256 bytes, lo cual solo sería útil si de alguna manera se pudieran cargar y guardar programas.
¿Qué puedo hacer con él?
- Guardar un programa desde la RAM de la computadora de protoboard
- Cargar un programa en la RAM de la computadora de protoboard
- Ejecutar automáticamente un programa guardado al iniciar la computadora de protoboard
- Inspeccionar y modificar el contenido de la memoria mediante una conexión serie
- Programar la computadora de protoboard en lenguaje ensamblador
- Desensamblar el contenido de la memoria
- Ejecutar paso a paso instrucción por instrucción
- Establecer puntos de interrupción y ejecutar hasta el punto de interrupción
Aquí hay un video que demuestra estas características. Disculpen la calidad del video; no soy un creador de contenido profesional:
¿Cómo funciona?
Tenía algunos objetivos en mente al crear este proyecto:
- Requerir la menor cantidad posible de hardware adicional
- Realizar la menor cantidad posible de cambios en la computadora de protoboard
Inicialmente pensé en usar una EEPROM para almacenar programas y luego alguna lógica para transferir los programas de la EEPROM a la RAM de la computadora de protoboard. Sin embargo, idear la lógica de transferencia resultó más complicado de lo que podía manejar (soy más de software, aunque disfruto trabajar cerca del hardware). Soy un gran fan de Arduino, así que en algún momento empecé a pensar en reemplazar la lógica de transferencia con un Arduino. Me llevó un tiempo convencerme de que usar un Arduino no era "hacer trampa" (después de todo, incluso el Arduino UNO es mucho más potente que la propia computadora de protoboard), pero estoy contento con el resultado.
¿Qué necesita poder hacer el Arduino? Debe ser capaz de leer datos de la memoria y escribir datos en la memoria. La forma más fácil y menos invasiva (para la computadora de protoboard) es utilizar la misma interfaz que ya tiene el módulo de memoria: el bus y las señales de control. Los pines digitales de E/S del Arduino son bidireccionales, por lo que conectar 8 de ellos directamente al bus permite al Arduino leer y escribir en el bus. Para hacer que el módulo RAM lea o escriba en el bus, solo es necesario establecer las señales MI/RI/RO adecuadamente. Normalmente, esas señales son controladas por las EEPROM de la lógica de control, por lo que si el Arduino también las controla, surgirían conflictos y posibles cortocircuitos. Sin embargo, las EEPROM AT28C16 usadas por Ben tienen una entrada Chip Enable (CE) que pone todas las salidas de datos en alta impedancia (high-Z), lo que permite al Arduino manipular las señales.
Para leer la RAM, el Arduino debe hacer lo siguiente:
- Poner la señal CE de la EEPROM en alto (deshabilitar la lógica de control)
- Sacar la primera dirección a leer en el bus
- Poner la señal MI en alto y esperar una transición de flanco de subida del reloj
- Poner MI en bajo y RO en alto y esperar una transición de flanco de subida del reloj
- Leer el byte de datos del bus
- Repetir desde el paso 2 para las 16 direcciones
- Poner la señal CE de la EEPROM en bajo (rehabilitar la lógica de control)
Escribir en la RAM es muy similar; el Arduino solo necesita escribir en el bus y poner RI en alto en lugar de RO. Por supuesto, hay algunos problemas técnicos que resolver, pero el mecanismo básico anterior es el corazón de este proyecto.
¿Qué cambios debo hacer en la computadora de protoboard?
Se necesitan dos cambios:
- Poner resistencias pull-down en las salidas de datos de las EEPROM de la lógica de control
- Deshabilitar (o reiniciar continuamente) el contador de anillo mientras la EEPROM está deshabilitada
Resistencias pull-down
Las resistencias pull-down son necesarias porque, una vez que la EEPROM se deshabilita, las resistencias pull-up de las compuertas lógicas a las que están conectadas las señales de control (como AO, AI, EO, etc.) llevarán esas señales a nivel alto. Eso significaría que múltiples registros escribirían en el bus, causando conflictos.
Las resistencias pull-up en las entradas de compuertas 74LS son de aproximadamente 10k. Por lo tanto, las resistencias pull-down deben ser lo suficientemente pequeñas para bajar el voltaje a nivel bajo. Para la mayoría de las líneas de señal usé resistencias de 3.3 kΩ. Sin embargo, hay dos excepciones: primero, la señal "SU" está conectada a 8 compuertas XOR exclusivas, lo que significa que la resistencia pull-up efectiva es 10kΩ/8 = 1.25kΩ. Una resistencia pull-down tendría que ser significativamente menor a 1k para bajar esta señal. Afortunadamente, la señal SU (resta) no controla ninguna interacción con el bus, así que podemos simplemente ignorarla y no poner resistencia pull-down. En segundo lugar, la señal CE (habilitación del contador) necesitaba una resistencia pull-down de 1k; valores mayores causaban comportamiento aleatorio del contador de programa en algunos casos.
Encontré más fácil agregar las resistencias pull-down en la protoboard que contiene todos los LEDs azules, es decir, entre los ánodos de los LEDs (que están conectados a las salidas de la EEPROM) y GND.

[No pude colocar las resistencias para las señales HLT/MI/RI aquí, así que las agregué en otras ubicaciones de la protoboard]
Reinicio del contador de anillo
La otra modificación es reiniciar el contador de anillo. Técnicamente, esto no es estrictamente necesario para permitir la carga/guardado de programas, pero permite una transferencia suave del control del Arduino de vuelta a la lógica de control. El objetivo es mantener el contador de anillo en 0 mientras CE está en alto (es decir, la lógica de control deshabilitada). Cuando el Arduino vuelve a poner CE en bajo (habilitando la lógica de control), el contador de anillo estará en cero y la computadora de protoboard comenzará a ejecutar la siguiente instrucción.
En mi construcción, no necesité hacer nada para esto porque estoy usando una salida de la EEPROM para reiniciar el contador de anillo. Esto mejora el rendimiento al reiniciar el contador de anillo tan pronto como termina una instrucción. También me da automáticamente el reinicio del contador de anillo cuando la EEPROM de la lógica de control se deshabilita: la resistencia pull-down en la salida de la EEPROM llevará la señal a bajo, lo que reinicia el contador de anillo.
Si estás usando la implementación de Ben de un contador de anillo fijo de 5 pasos, creo que la siguiente extensión a su circuito de reinicio debería reiniciar el contador cuando CE está en alto (haz clic en las flechas izquierda/derecha abajo para alternar entre el circuito original de Ben y la versión extendida):

Como puedes ver, requiere 3 compuertas NAND más, es decir, un chip 74LS00 adicional. Ten en cuenta que no he probado este enfoque, pero debería funcionar en la medida en que puedo ver.
Esta modificación no es absolutamente necesaria; puedes omitirla al principio. La carga y el guardado, así como el monitor/ensamblador/desensamblador, seguirán funcionando perfectamente. Sin embargo, cualquier acción que requiera transferencia de control del Arduino a la lógica de control no funcionará. Lo más notable es la ejecución automática de programas guardados al inicio, así como la ejecución paso a paso y la ejecución en el depurador.
¿Cómo configuro el Arduino?
Sube el sketch desde el archivo GIT al Arduino y conecta el Arduino a la computadora de protoboard de la siguiente manera:
- Pin 5V del Arduino (no Vin!) al riel de 5V en la protoboard
- Pin GND del Arduino a GND en la protoboard
- Pines digitales 2-9 del Arduino al bus 0-7
- Pin digital 10 del Arduino a la salida de la EEPROM de lógica de control que controla RO
- Pin digital 11 del Arduino a la salida de la EEPROM de lógica de control que controla RI
- Pin digital 12 del Arduino a la salida de la EEPROM de lógica de control que controla MI
- Pin analógico 0 del Arduino a la señal CLOCK
- Pin analógico 3 del Arduino a CE (pin 18) de todas las EEPROM de lógica de control y mediante una resistencia de 10k a +5V
Aparte de eso, necesitarás cablear los pines analógicos 1 y 2 del Arduino a los interruptores DIP y botones como se muestra en los esquemas (para más detalles, consulta el archivo Fritzing adjunto).

Para una versión mínima absoluta (pero aún funcional), puedes hacer lo siguiente:
- Agregar resistencias pull-down de 3.3kΩ a los pines de salida de la EEPROM que controlan AO, CO, EO, IO, RO
- Omitir las instrucciones de "Reinicio del contador de anillo" anteriores
- Realizar el cableado Arduino-computadora de protoboard como se muestra arriba (puedes omitir la resistencia de 10k en el último paso si lo deseas)
- Conectar tanto el pin analógico 1 como el analógico 2 a GND
Para usar los botones de carga/guardado, solo necesitas conectar los botones, interruptores DIP y resistencias asociadas a los pines analógicos 1 y 2 según los esquemas.
Para usar la función de autoarranque, el Arduino necesita mantener el contador de programa y el contador de anillo en 0 durante el reinicio y mientras se transfiere el programa. La resistencia pull-up entre el pin analógico 3 y +5V mantiene la lógica de control deshabilitada (y por lo tanto el contador de programa en 0) mientras el Arduino se reinicia. Para el contador de anillo, sigue las instrucciones de "Reinicio del contador de anillo" anteriores.
¿Cómo cargo y guardo programas?
La configuración mínima anterior te permitirá controlar el Arduino a través de la interfaz serie. Para comunicarte con el Arduino necesitarás un programa de terminal como Putty o TeraTerm. El monitor serie del software Arduino también funcionará, pero la separación entre área de entrada y salida en el monitor serie lo hace un poco incómodo en este escenario.
- Enciende la computadora de protoboard
- Conecta una PC al Arduino mediante el cable USB
- Inicia el programa de terminal y configúralo a 9600 baudios, 8 bits, sin paridad, 1 bit de parada
- Presiona ESC en la ventana del terminal para entrar en el modo Monitor
- Deberías ver un "." como indicador de comando
- Escribe "h" y presiona enter para obtener una lista de comandos soportados
Con la configuración mínima, deberías poder usar los comandos "m", "M", "C", "l" y "s". Estos te permiten ver el contenido de la memoria, modificar el contenido de la memoria y cargar y guardar programas.
Para guardar o cargar un programa mediante el botón:
- Apaga el reloj de la computadora de protoboard
- Configura los interruptores DIP para seleccionar el número de archivo bajo el cual se deben guardar los datos
- Presiona el botón "guardar" o "cargar". El LED conectado a CE se encenderá, indicando que el Arduino ha tomado el control
- Enciende el reloj de la computadora de protoboard. Deberías ver al Arduino recorriendo las direcciones (observa los LEDs del registro de dirección de memoria)
- Espera hasta que los LEDs del bus dejen de parpadear y los LEDs del registro de dirección de memoria muestren 1111
- Apaga el reloj de la computadora de protoboard. El LED conectado a CE se apagará, indicando que el control ha vuelto a la lógica de control
Para ejecutar automáticamente un programa al inicio (asegúrate de tener toda la circuitería necesaria), solo configura los interruptores DIP al número de archivo bajo el cual está guardado el programa y enciende la computadora de protoboard (o presiona el botón de reinicio). Hay dos casos especiales: si todos los interruptores DIP están apagados, la computadora se inicia normalmente, sin autoarranque. Si todos los interruptores DIP están encendidos, el Arduino entra directamente en el modo Monitor al inicio.
¿Cómo uso el ensamblador y el desensamblador?
Para usar las funciones de ensamblador/desensamblador y depurador, primero necesitarás modificar el programa en el Arduino para que se ajuste a tu configuración específica. Busca la sección en el código fuente que define la estructura opcodes_4bit:
struct opcodes_struct opcodes_4bit [] = {
{"NOP ", B00000000, 0, false},
{"LDA ", B00010000, 2, true},
...
{".OR ", B11111110, 0, true}, // establecer dirección de inicio
{".BY ", B11111111, 0, true}, // definir un byte de datos
{NULL, 0, 0, false}
};
Cada línea especifica un opcode:
- El primer campo es el mnemónico ("LDA"). Para direccionamiento inmediato, incluye un "#" en el mnemónico. Así, lo que Ben llama "LDI" se llamaría "LDA #" aquí. ¿Se nota que crecí programando en ensamblador 6510 en un C64?
- El segundo campo es el opcode en sí. Los cuatro bits inferiores siempre deben ser 0 (excepto para los opcodes especiales .OR y .BY, ver abajo).
- El tercer campo es el número de ciclos que el opcode tarda en ejecutarse (esto se suma a los ciclos de búsqueda). Por ejemplo, en mi implementación LDA tiene opcode 0001 y tarda un total de cuatro ciclos en ejecutarse, dos de los cuales son el ciclo de búsqueda. Si seguiste las instrucciones de Ben (donde todos los opcodes usan 5 ciclos), esto debería ser siempre 3.
- El último campo especifica si este opcode requiere un argumento. Por ejemplo, LDA requiere un argumento pero OUT no.
Deberás ajustar esta lista para reflejar los opcodes que has implementado para tu computadora de protoboard. Las dos últimas líneas son opcodes especiales usados por el ensamblador y deben dejarse como están.
Después de ingresar todos tus opcodes, sube el software al Arduino. Conecta tu terminal y entra en el modo Monitor (ya sea presionando ESC en la ventana del terminal o poniendo todos los interruptores DIP en ON). Ahora deberías poder desensamblar tu programa. Escribir solo "d" en el monitor comenzará a desensamblar desde la dirección 0.
El ensamblador es mínimo pero funciona bastante bien. Escribe "a" para comenzar a ensamblar en la dirección 0. Las convenciones son:
- Si una línea no comienza con un espacio en blanco, debe comenzar con una definición de etiqueta. Las etiquetas deben comenzar con un carácter alfabético seguido de caracteres alfanuméricos, pueden tener como máximo 3 caracteres y distinguen mayúsculas/minúsculas.
- Después del primer espacio en blanco en una línea, el ensamblador espera un mnemónico (LDA, STA, OUT...).
- El mnemónico especial ".BY" especifica directamente un byte de datos para almacenar en la ubicación actual.
- El mnemónico especial ".OR" le dice al ensamblador que continúe ensamblando en una nueva dirección.
- Si un argumento comienza con un carácter alfabético, se asume que es una etiqueta.
- Cualquier argumento numérico se espera que sea un número decimal. Para especificar hexadecimal, precede el argumento con un "$". Por ejemplo, para cargar el número hexadecimal FF en el registro A, usa "LDA #$FF".
- Todo lo que esté después de un ";" se asume que es un comentario y se ignora.
Por ejemplo, el código de Fibonacci se puede ingresar de la siguiente manera:
rst LDA #0 ; x = 0
STA x
LDA #1 ; y = 1
STA y
lp ADD x ; z = y + x
STA z
JC rst ; reiniciar si desbordamiento
OUT ; imprimir z
LDA y ; x = y
STA x
LDA z ; y = z
STA y
JMP lp ; bucle
x .BY 0
y .BY 0
z .BY 0
¿Cuáles son las limitaciones?
Para ahorrar RAM en el Arduino, el ensamblador funciona como un ensamblador de un solo paso (de lo contrario, el Arduino tendría que almacenar en búfer todo el código fuente). El ensamblador escribe los opcodes en la memoria de la computadora de protoboard a medida que se ingresan. Esto significa que el ensamblado se ralentiza por la velocidad del reloj de la computadora de protoboard. Si copias y pegas texto en la ventana del terminal, eso puede provocar pérdida de caracteres porque el Arduino no puede seguir el ritmo de los caracteres que llegan a 9600 baudios (porque pasa demasiado tiempo esperando el reloj de la computadora de protoboard). Para solucionar eso, reduce la velocidad en baudios o usa TeraTerm, que proporciona una configuración para especificar un retraso entre caracteres enviados. La otra solución es aumentar la velocidad del reloj en la computadora de protoboard. Mi reloj llega hasta 160kHz y a esa velocidad puedo copiar y pegar código a 9600 baudios sin problemas.
En su configuración predeterminada, el sketch de Arduino puede manejar frecuencias de reloj en la computadora de protoboard de hasta aproximadamente 1-2kHz (quizás un poco más). Ten en cuenta que el reloj de Ben en su configuración predeterminada no supera los 500Hz. Si tu reloj es más rápido, busca el interruptor #ifdef FAST_IO en el código. Activar FAST_IO debería hacer que el Arduino funcione con velocidades de reloj de hasta 250kHz. Lo he probado hasta 160kHz. Probablemente sería posible soportar velocidades más altas implementando los bucles críticos en tiempo directamente en ensamblador, pero honestamente una velocidad de reloj de 160kHz ya se siente demasiado rápida en la computadora de protoboard con sus capacidades limitadas. Asegúrate de leer los comentarios correspondientes en el código antes de activar FAST_IO.
El Arduino tiene 1k de EEPROM y, por lo tanto, puede almacenar 1024/16 = 64 programas diferentes. En realidad son 63, ya que 16 bytes están reservados para guardar datos de configuración. No es mucho, pero probablemente sea suficiente para contener todos los programas que puedas idear. Solo los programas numerados del 0 al 15 de esos pueden seleccionarse mediante los interruptores DIP (1-14 para autoarranque), pero los comandos "s" y "l" funcionarán con el rango completo 0-62.
Se ve un poco desordenado, ¿puedes ordenarlo?
¡Sí! En mi versión final aquí, en realidad usé solo el chip Atmega 328P desnudo (junto con un cristal de 16MHz y condensadores) en lugar de un Arduino UNO. El regulador de voltaje del UNO no es necesario aquí ya que el Arduino está usando directamente los 5V de nuestra fuente de alimentación de todos modos. La única pérdida es que ahora tenemos que usar un convertidor USB-a-serie separado (FTDI o similar) para hablar con el Atmega. El aspecto general encaja mucho mejor con el resto de la computadora de protoboard:

Otra optimización es eliminar el bootloader del Arduino/Atmega. Eso elimina el retraso de 2 segundos mientras se inicia el bootloader del Arduino. Publicaré instrucciones sobre cómo hacerlo si hay interés. ¡Avísenme en los comentarios!
Código
Programador para computadora de protoboard
El sketch de Arduino para el Programador de Computadora de Protoboard: https://github.com/dhansel/programmer
Esquemas
drive_pD8k28E85v.fzz
Proceso de manufactura
- La evolución del ratón de computadora: Diseño, fabricación y tendencias futuras
- Construya Efectos de Audio Controlados por Sensores con el Kit Satellite CCRMA
- Construye un Spybot controlado por Bluetooth con Arduino
- FlickMote: Mando IR por gestos para Smart Home
- TV‑B‑Gone DIY con Arduino: Mando universal para apagar cualquier TV
- Construye un Reloj Maestro LED Personalizado con Alarma – Visible desde 12 Metros
- Find Me: Localizador Inteligente de Objetos con Arduino y Bluetooth
- Soluciones de Alimentación Optimizadas para Proyectos Arduino
- Tres en raya con Arduino, matriz LED MAX7219 y carcasa de cartón
- Construye un Robot Cuadrúpedo Inteligente con Arduino: Guía Paso a Paso
- Monitorización Meteorológica en Tiempo Real con Arduino y ThingSpeak IoT
