Consola de juegos de bricolaje h√°galo usted mismo

imagen


Esta publicación es una introducción a mi proyecto de consolas de video de consola "caseras" hechas desde cero. Me inspiraron tanto las consolas retro como las muestras modernas, pero obtuve mi propia arquitectura. Mis amigos me decían constantemente que debería hablar sobre mi proyecto y no hacer todo exclusivamente "por mí mismo", así que aquí estoy publicando esta publicación.


Atención, esta es una traducción.


Como empezó todo


Mi nombre es Sergio Vieira, crec√≠ en Portugal en los a√Īos 80 y 90, tengo una larga nostalgia por los juegos retro, especialmente para las consolas de tercera y cuarta generaci√≥n.


Hace unos a√Īos, decid√≠ entender mejor la electr√≥nica e intentar hacer mi propio prefijo.


De profesión, soy programador y no tenía ninguna experiencia como ingeniero electrónico, excepto por (y no debería considerarse) actualizaciones independientes de mi destkop.


Aunque no ten√≠a ninguna experiencia, me dije "¬Ņpor qu√© no?", Compr√© varios libros, varios kits electr√≥nicos y comenc√© a estudiar en base a mis sentimientos sobre lo que val√≠a la pena estudiar.


Quería hacer un prefijo similar a los que me ponen nostálgico, quería algo entre NES y Super Nintendo , o tal vez entre Sega Master System y Mega Drive .


Estas consolas tenían una CPU, un chip de video original (no se llamaban GPU en ese momento) y un chip de audio, a veces incorporado, y a veces externo.


Los juegos se distribuyeron en cartuchos, que en general eran extensiones de hierro, a veces solo chips ROM, y a veces tenían componentes adicionales.


El plan original era hacer un prefijo con las siguientes características:


  • Sin emulaci√≥n, los juegos y programas deber√≠an funcionar en hardware real, no necesariamente el mismo de aquellos tiempos, pero lo suficientemente r√°pido para la tarea, y nada m√°s.
  • Con una verdadera CPU retro.
  • Con salida de TV anal√≥gica.
  • Con sonido
  • Con soporte de controlador dual
  • Desplazamiento de becks y sprites de animaci√≥n.
  • Con caracter√≠sticas para soportar juegos de plataformas como Mario y, por supuesto, todo tipo de otros juegos.
  • Con la descarga de juegos y programas desde tarjetas SD.

Por qué las tarjetas SD, y no los cartuchos, bueno, básicamente es mucho más práctico, puedes copiarlas desde tu computadora. Y los cartuchos significarían, en primer lugar, más hierro en el decodificador y, en segundo lugar, producir hierro para cada programa.


Producción


Se√Īal de video


Lo primero que hice fue generar una se√Īal de video.


Cualquier consola del período que tomé como muestra tenía varios chips gráficos propietarios, lo que significa que todos tenían especificaciones diferentes.
Por esta raz√≥n, no quer√≠a usar un chip gr√°fico ya preparado, quer√≠a que mi consola tuviera especificaciones gr√°ficas √ļnicas. Y como no pod√≠a hacer mi propio chip gr√°fico, y en ese momento todav√≠a no pod√≠a usar FPGA, decid√≠ limitarme a la generaci√≥n de una se√Īal gr√°fica generada por software utilizando un microcontrolador de 8 bits y 20 megahercios.


Esto no es demasiado, y solo es una solución lo suficientemente poderosa para los gráficos del nivel que me interesaba.


Y as√≠, comenc√© a usar el microcontrolador Atmega644 a una pureza de 20 MHz para generar una se√Īal de video PAL para el televisor. Tuve que superar el protocolo PAL porque el chip en s√≠ no sabe c√≥mo hacerlo.


Prueba 1 de imageVPU


Prueba 2 de imageVPU


El microcontrolador produce un color de 8 bits (RGB332, 3 bits rojo, 3 bits verde y 2 azul) y el DAC pasivo lo convierte todo en RGB. Afortunadamente en Portugal, casi todos los televisores est√°n equipados con un conector SCART y admiten entrada RGB.


El subsistema gr√°fico correcto


Como el microcontrolador es bastante potente y decid√≠ usarlo exclusivamente para generar una se√Īal de video (lo llam√© VPU - Unidad de procesamiento de video), decid√≠ organizar un doble buffer al mismo tiempo.


Resultó que el segundo microcontrolador (PPU, unidad de procesamiento de imagen, chip Atmega1284 también a 20 MHz) generó una imagen en el chip RAM 1 (lo llamé VRAM1), y el primero envió el contenido del segundo chip (VRAM2) al televisor al mismo tiempo.


Después de un cuadro, y dos cuadros en el sistema PAL son 1/25 de segundo, la VPU cambia las VRAM y se intercambian, la PPU genera una imagen en VRAM2, y la VPU descarga VRAM1 a la salida de TV.


La tarjeta de video resultó ser muy complicada porque tuve que usar hardware externo para que ambos microcontroladores pudieran usar ambos módulos de memoria y acelerar el acceso a la RAM, ya que también tiene golpes de bits, así que tuve que agregar 74 chips de la serie como contadores, selectores de línea, transceptores, etc. .


El firmware para VPU y PPU también resultó ser engorroso porque tuve que escribir mucho código para obtener la máxima velocidad de los gráficos. Al principio todo fue escrito en ensamblador, luego parte fue reescrita en C.


imageVideo Board 1


imageVideo Board 2


Como resultado, la PPU genera una imagen de 224x192 píxeles, que luego se envía al televisor a través de la VPU. Puede encontrar baja la resolución, pero en realidad es casi tanto como las consolas de la época, generalmente 256x224. Una resolución ligeramente inferior, pero me permitió agregar más funciones que el sistema logra calcular en un cuadro.


Como en los viejos tiempos, PPU tiene su propia mec√°nica r√≠gida que debe poder usar. El respaldo (respaldo) se representa a partir de caracteres de 8x8 p√≠xeles, tambi√©n llamados mosaicos. Resulta que el tama√Īo del fondo es de 28x24 mosaicos.


Para que el respaldo se pueda desplazar suavemente, píxel por píxel, lo hice, hay 4 pantallas virtuales, cada una de 28x24 mosaicos que se almacenan secuencialmente en memoria y se envuelven entre sí, en la imagen es más clara.


imagen de fondo


imagen fondo virtual


En la parte superior del fondo, PPU puede representar 64 sprites que pueden tener 8 o 16 píxeles de alto o ancho, es decir, 1, 2 o 4 mosaicos y también se pueden voltear horizontal y / o verticalmente.


En la parte superior de la parte posterior, tambi√©n puede renderizar una superposici√≥n con un tama√Īo de mosaico de 28x6 b√ļfer, esto estaba destinado a renderizar HUD, puntajes para no interferir con los sprites principales y el desplazamiento de la parte posterior.


Una característica "avanzada" es que el respaldo puede desplazarse no completamente, sino cada línea por separado, lo que permite todo tipo de efectos interesantes como pantallas divididas o casi paralaje .


También hay una tabla de atributos que le permite establecer cada mosaico en un valor de 0 a 3, y luego puede especificar una página de mosaicos para todos los mosaicos con un atributo o incrementar su valor simbólico. Esto es conveniente cuando hay partes de la copia de seguridad que deben cambiarse regularmente y la CPU no tiene que calcular cada mosaico individualmente, es suficiente decir algo como: "todos los mosaicos con el atributo 1, incrementa el valor numérico de tu personaje en 2", tales cosas pueden implementarse mediante diferentes técnicas Observe, por ejemplo, en bloques de bloques en Mario donde el signo de interrogación está animado o en juegos donde hay una cascada en la que todos los cuadros cambian constantemente creando el efecto de la caída del agua.


CPU


Cuando mi tarjeta de video funcionó, comencé a trabajar con la CPU como Zilog 80 para mi decodificador.


Una de las razones por las que se eligi√≥ el Z80, bueno, adem√°s del hecho de que es una CPU retro genial, es su capacidad para abordar dos espacios de 16 bits, uno para memoria y el segundo para puertos de E / S, no menos legendario 6502 , por ejemplo, no puede , solo puede direccionar un espacio de 16 bits, y debe asignarlo a la memoria, as√≠ como a varios dispositivos externos, video, audio, joysticks, generador de n√ļmeros aleatorios de hardware, etc. Es m√°s conveniente tener dos espacios de direcciones, uno con hasta 64 kilobytes de c√≥digo y datos en la memoria y el segundo para acceder a dispositivos externos.


Primero, conecté la CPU a la EEPROM en la que se encontraba mi programa de prueba y también lo conecté a través del espacio de E / S al microcontrolador que instalé para poder comunicarme con mi computadora a través de RS232 y monitorear cómo funcionaba la CPU y todo lo demás. Este microcontrolador Atmega324 que funciona a 20 MHz lo llamo IO MCU - unidad de microcontrolador de entrada / salida, es responsable de controlar el acceso a los controladores de juego (joysticks), lector de tarjetas SD, teclado PS / 2 y comunicador a través de RS232.


Junta 1 de imageCPU


La CPU se conecta a un chip de memoria de 128 kilobytes, de los cuales solo 56 kilobytes están disponibles, esto por supuesto no tiene sentido, pero podría obtener solo chips de 128 o 32 kilobytes. Resultó que la memoria consta de 8 kilobytes de ROM y 56 kilobytes de RAM.


Después de eso, actualicé el firmware de IO MCU usando esta biblioteca y obtuve soporte para lectores de tarjetas SD.


Ahora la CPU podría revisar los directorios, ver qué hay en ellos, abrir y leer archivos. Todo esto se hace escribiendo y leyendo direcciones específicas en el espacio de E / S.


Conecte la CPU a la PPU


Lo siguiente que he hecho es la conexi√≥n entre la CPU y la PPU. Para hacer esto, apliqu√© una "soluci√≥n simple" que era comprar RAM de doble puerto, este es un chip RAM que se puede conectar directamente a dos buses diferentes. Esto le permite deshacerse de chips adicionales como selectores de l√≠nea y, adem√°s, permite un acceso casi simult√°neo a la memoria desde ambos chips. Otra PPU puede acceder directamente a la CPU en cada cuadro activando sus interrupciones no enmascarables . Resulta que la CPU recibe una interrupci√≥n en cada cuadro, lo cual es √ļtil para varias tareas de temporizaci√≥n y para comprender cu√°ndo es el momento de hacer una actualizaci√≥n de gr√°ficos.


Cada cuadro de interacción de la CPU, PPU y VPU se produce de acuerdo con el siguiente esquema:


  1. La PPU copia información de la memoria PPU a la memoria interna.
  2. La PPU env√≠a una se√Īal de interrupci√≥n a la CPU.
  3. Al mismo tiempo:
    • La CPU salta a la funci√≥n de interrupci√≥n y comienza a actualizar la memoria PPU con un nuevo estado de gr√°ficos. El programa debe regresar desde la interrupci√≥n hasta el siguiente bloque.
    • PPU renderiza una imagen basada en informaci√≥n previamente copiada a una de VRAM.
    • VPU env√≠a una imagen desde otra VRAM a la salida de TV.

Casi al mismo tiempo, comencé a admitir controladores de juegos, al principio quería usar los controladores de Nintendo, pero sus enchufes son propietarios y generalmente difíciles de encontrar, así que me decidí por controladores de 6 botones compatibles con Mega Drive / Genesis, tienen enchufes DB-9 estándar. que están en todas partes


imageJoint Board 1


Escribiendo el primer juego real


En este momento, ya tenía una CPU capaz de controlar PPU, trabajar con joysticks, leer tarjetas SD ... era hora de escribir el primer juego , por supuesto en el ensamblador Z80, me tomó varios días de tiempo libre.



Agregar gr√°ficos din√°micos


Todo estaba s√ļper, ten√≠a mi propia consola de juegos, pero esto no era suficiente para m√≠, porque en el juego ten√≠a que usar gr√°ficos cosidos en la memoria PPU y era imposible dibujar mosaicos para un juego espec√≠fico y solo se pod√≠a cambiar al actualizar la ROM. Comenc√© a pensar en c√≥mo agregar m√°s memoria para que la CPU pudiera cargar caracteres para los mosaicos, y luego la PPU podr√≠a leerlo todo desde all√≠ y c√≥mo hacerlo m√°s f√°cil porque el prefijo ya era complicado y grande.


Y se me ocurrió lo siguiente: solo la PPU tendrá acceso a esta nueva memoria, y la CPU cargará datos allí a través de la PPU y, mientras este proceso de carga está en progreso, esta memoria no se puede usar para dibujar, pero será posible extraerla de la ROM en este momento.


Después del final de la carga, la CPU cambiará la memoria ROM interna a esta nueva memoria, a la que llamé Character RAM (CHR-RAM) y, en este modo, la PPU comenzará a dibujar gráficos dinámicos, esta probablemente no sea la mejor solución, pero funciona. Como resultado, se instaló una nueva memoria de 128 kilobytes y puede almacenar 1024 caracteres de 8x8 píxeles cada uno para el fondo y la misma cantidad de caracteres para los sprites.


imageJoint Board 2


Y finalmente el sonido


Las manos alcanzaron el √ļltimo sonido. Al principio, quer√≠a un sonido como el de Uzebox , es decir, que el microcontrolador genera 4 canales de sonido PWM.


Sin embargo, result√≥ que puedo obtener f√°cilmente los chips antiguos y ped√≠ varios chips de s√≠ntesis FM YM3438, estos tipos son totalmente compatibles con el YM2612 utilizado en Mega Drive / Genesis. Al instalarlos, puede obtener m√ļsica Mega Drive de calidad y efectos de sonido producidos por el microcontrolador.


Instalé otro microcontrolador y lo llamé SPU (Unidad de procesador de sonido), controla el YM3438 y puedo generar sonidos yo mismo. La CPU lo controla a través de una memoria de doble puerto, esta vez solo pesa 2 kilobytes.


Al igual que en la unidad gráfica, la unidad de sonido tiene 128 kilobytes de memoria para almacenar muestras de PCM y parches de sonido, la CPU carga datos en esta memoria accediendo a la SPU. Resultó que la CPU le dice a la SPU que ejecute comandos desde esta memoria o actualiza los comandos para la SPU en cada cuadro.


La CPU controla cuatro canales PWM a trav√©s de cuatro memorias intermedias circulares en la memoria de la SPU. La SPU pasa por estos b√ļferes y ejecuta los comandos escritos en ellos. Tambi√©n hay un b√ļfer para el chip de s√≠ntesis FM.


En total, como en el gráfico, la interacción entre la CPU y la SPU va de acuerdo con el esquema:


  1. La SPU copia datos de la SPU a la memoria interna.
  2. SPU está esperando una interrupción de PPU (esto es para sincronización)
  3. Al mismo tiempo
    • La CPU actualiza los b√ļferes de canal PWM y los b√ļferes de sintetizador FM.
    • La SPU ejecuta comandos en memorias intermedias seg√ļn los datos en la memoria interna.
    • Junto con todo esto, la SPU actualiza los sonidos PWM a una frecuencia de 16 kilohercios.

imageSound Board 1


Lo que salió al final


Después de que todos los bloques estuvieron listos, algunos fueron al tablero.
Para el bloque de CPU, pude desarrollar y ordenar una PCB personalizada, no sé si vale la pena para los otros módulos, creo que tuve mucha suerte de que mi PCB funcionara de inmediato.


En el tablero ahora (hasta ahora) solo hay sonido.
Así es como se ve hoy:


imageConsole 1


Arquitectura


El diagrama ilustra los componentes en cada bloque y c√≥mo interact√ļan entre s√≠. Lo √ļnico que no se muestra es la se√Īal de la PPU a la CPU en cada cuadro como una interrupci√≥n y la misma se√Īal que va a la SPU.


imageArquitectura


  • CPU: Zilog Z80 a 10 MHz
  • CPU-ROM: EEPROM de 8 KB, contiene el c√≥digo del gestor de arranque
  • CPU-RAM: 128 KB de RAM (56 KB disponibles), c√≥digo y datos para programas / juegos
  • IO MCU: Atmega324, es la interfaz entre la CPU y RS232, el teclado PS / 2, joysticks y el sistema de archivos de la tarjeta SD
  • PPU-RAM: 4 kilobytes de memoria de doble puerto, memoria intermedia entre CPU y PPU
  • CHR-RAM: 128 KB de RAM, almacena mosaicos din√°micos para respaldo (sustrato) y sprites (en caracteres de 8x8 p√≠xeles).
  • VRAM1, VRAM2: 128 KB de RAM (43008 est√° realmente disponible), se usan para el framebuffer, escriben PPU y leen VPU de ellos.
  • PPU (Unidad de procesamiento de im√°genes): Atmega1284, dibuja un marco en el framebuffer.
  • VPU (Unidad de procesamiento de video): Atmega324, lee el framebuffer y genera se√Īales RGB y PAL y sincronizaci√≥n.
  • SPU-RAM: RAM de doble puerto de 2 KB, sirve como interfaz entre la CPU y la SPU.
  • SNDRAM: 128 KB de RAM, almacena parches PWM, muestras PCM y bloques de instrucciones para el sintetizador FM.
  • YM3438: YM3438, chip de s√≠ntesis FM.
  • SPU (Unidad de procesamiento de sonido): Atmega644, genera sonidos utilizando el principio de modulaci√≥n de ancho de pulso (PWM) y controla el YM3438.

Especificaciones finales


CPU:


  • CPU Zilog Z80 de 8 bits a una frecuencia de 10Mhz.
  • ROM de 8 KB para el gestor de arranque.
  • 56 KB de RAM.

IO:


  • Lectura de datos del lector de tarjetas SD FAT16 / FAT32.
  • Leer / escribir en el puerto RS232.
  • 2 controladores de juegos compatibles con MegaDrive / Genesis.
  • Teclado PS2.

Video:


  • Resoluci√≥n 224x192 p√≠xeles.
  • 25 cuadros por segundo (medio FPS de PAL).
  • 256 colores (RGB332).
  • Fondo virtual 2x2 (448x384 p√≠xeles), con desplazamiento bidireccional basado en p√≠xeles, basado en cuatro p√°ginas de pantalla completa.
  • 64 sprites con un ancho y una altura de 8 o 16 p√≠xeles con la posibilidad de voltear tanto vertical como horizontalmente.
  • El fondo y los sprites consisten en caracteres de 8x8 p√≠xeles cada uno.
  • Memoria simb√≥lica de video de 1024 caracteres para el fondo y 1024 para sprites.
  • 64 desplazamiento horizontal independiente a lo largo de las l√≠neas establecidas
  • 8 desplazamientos verticales independientes a lo largo de las l√≠neas establecidas
  • Superposici√≥n de 224x48 p√≠xeles con transparencia de clave de color opcional.
  • Tabla de atributos de fondo.
  • RGB y PAL compuesto a trav√©s del conector SCART.

Sonido:


  • PWM para 8 bits y 4 canales, con formas de onda integradas: cuadrada, senoidal, sierra, ruido, etc.
  • Muestras de 8 bits y 8 kHz en uno de los canales PWM.
  • Chip de s√≠ntesis FM YM3438 cargado con instrucciones a una frecuencia de 50 hertzios.

Desarrollo para la consola.


Para la consola, se escribió un gestor de arranque. El cargador de arranque se coloca en la CPU ROM y puede tomar hasta 8 kilobytes. Utiliza los primeros 256 bytes de RAM. El cargador es lo primero que ejecuta la CPU. Es necesario para mostrar los programas ubicados en la tarjeta SD.


Estos programas están en archivos que contienen código compilado y también pueden contener gráficos y sonido.


Despu√©s de seleccionar un programa, se carga en la memoria de la CPU, la memoria CHR y la memoria SPU. Despu√©s de lo cual se ejecuta el c√≥digo del programa. El tama√Īo m√°ximo del c√≥digo cargado en la consola es de 56 kilobytes, adem√°s de los primeros 256 bytes, y, por supuesto, debe tener en cuenta el espacio para la pila y los datos.
Y este gestor de arranque y otros programas escritos para esta consola se crearon de la misma manera que se describe a continuación.


Mapeo de memoria / IO


Lo importante al desarrollar este prefijo es tener en cuenta cómo la CPU accede a los diversos bloques y asignar correctamente el espacio de direcciones para la entrada de entrada y el espacio de direcciones de memoria.


La CPU accede a la memoria de acceso aleatorio del cargador de arranque a través del espacio de direcciones de la memoria.


Espacio de direcciones de memoria
Asignación de imágenes


Y a PPU-RAM, SPU-RAM e IO MCU a través del espacio de direcciones de E / S.


Espacio de dirección de E / S
mapeo de imageIO


Como puede ver en la tabla, las direcciones para todos los dispositivos, IO MCU, PPU y SPU se asignan dentro del espacio de direcciones de E / S.


Gestión de PPU


De la información en la tabla se puede ver que para el control de PPU es necesario escribir en la memoria de PPU que está disponible en las direcciones 1000h-1FFFh en el espacio de direcciones de E / S.


Asignación de espacio de direcciones PPU


Asignación de imagePPU


El estado de PPU puede tomar los siguientes valores:


  1. Modo de gr√°ficos integrados
  2. Modo de gr√°ficos din√°micos (CHR-RAM)
  3. Modo de grabación en memoria CHR
  4. La grabación se completa, esperando la confirmación del modo de la CPU

Aquí, por ejemplo, cómo puedes trabajar con sprites:
El prefijo puede dibujar 64 sprites a la vez. CPU - 1004h-1143h (320 ), 5 (5 * 64 = 320):


  1. , : Active, Flipped_X, Flipped_Y, PageBit0, PageBit1, AboveOverlay, Width16, Height16.
  2. , ( ).
  3. ( ‚ÄĒ )
  4. X
  5. Y

, , Active 1, X Y , 32/32 , .


.


Por ejemplo, si necesitamos mostrar el n√ļmero de sprite 10, entonces la direcci√≥n ser√° 4145 (1004h + (5 x 9)), escriba el valor 1 para la activaci√≥n y las coordenadas, por ejemplo, x = 100 e y = 120, escriba el valor 100 en la direcci√≥n 4148 y direcci√≥n 4149 valor 120.


Usando ensamblador


Uno de los métodos de programación para la consola es el ensamblador.


Aquí hay un ejemplo de cómo mostrar un sprite y animarlo para que se mueva y empuje los bordes de la pantalla.


ORG 2100h PPU_SPRITES: EQU $1004 SPRITE_CHR: EQU 72 SPRITE_COLORKEY: EQU $1F SPRITE_INIT_POS_X: EQU 140 SPRITE_INIT_POS_Y: EQU 124 jp main DS $2166-$ nmi: ;    (NMI) ld bc, PPU_SPRITES + 3 ld a, (sprite_dir) and a, 1 jr z, subX in a, (c) ;  X inc a out (c), a cp 248 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a jp updateY subX: in a, (c) ;  X dec a out (c), a cp 32 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a updateY: inc bc ld a, (sprite_dir) and a, 2 jr z, subY in a, (c) ;  Y inc a out (c), a cp 216 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a jp moveEnd subY: in a, (c) ;  Y dec a out (c), a cp 32 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a moveEnd: ret main: ld bc, PPU_SPRITES ld a, 1 out (c), a ;   0 inc bc ld a, SPRITE_CHR out (c), a ;    0 inc bc ld a, SPRITE_COLORKEY out (c), a ;     0 inc bc ld a, SPRITE_INIT_POS_X out (c), a ;     0 inc bc ld a, SPRITE_INIT_POS_Y out (c), a ;   Y  0 mainLoop: jp mainLoop sprite_dir: DB 0 

Usando lenguaje C


También puede usar el lenguaje C, para esto necesitamos el compilador SDCC y algunas utilidades adicionales.


El código C puede ser más lento, pero escribirlo es más rápido y fácil.


Aquí hay un ejemplo de código que hace lo mismo que el código de ensamblador anterior, usa una biblioteca que ayuda a realizar llamadas a PPU:


 #include <console.h> #define SPRITE_CHR 72 #define SPRITE_COLORKEY 0x1F #define SPRITE_INIT_POS_X 140 #define SPRITE_INIT_POS_Y 124 struct s_sprite sprite = { 1, SPRITE_CHR, SPRITE_COLORKEY, SPRITE_INIT_POS_X, SPRITE_INIT_POS_Y }; uint8_t sprite_dir = 0; void nmi() { if (sprite_dir & 1) { sprite.x++; if (sprite.x == 248) { sprite_dir ^= 1; } } else { sprite.x--; if (sprite.x == 32) { sprite_dir ^= 1; } } if (sprite_dir & 2) { sprite.y++; if (sprite.y == 216) { sprite_dir ^= 2; } } else { sprite.y--; if (sprite.x == 32) { sprite_dir ^= 2; } } set_sprite(0, sprite); } void main() { while(1) { } } 

Gr√°ficos din√°micos


(En los gr√°ficos personalizados originales. Aprox. Por.)


En la ROM del prefijo, se cosen 1 p√°gina de mosaicos para respaldo y otra p√°gina de sprites listos para usar), por defecto solo puede usar estos gr√°ficos fijos, pero puede cambiar a din√°mico.


Mi objetivo era tal que todos los gr√°ficos necesarios en forma binaria se cargaron inmediatamente en la RAM de CHR, y el c√≥digo en el gestor de arranque desde la ROM puede hacer esto. Para hacer esto, hice varias im√°genes del tama√Īo correcto con diferentes s√≠mbolos √ļtiles:


imagen de componentes de mosaico


Como la memoria de gráficos dinámicos consta de 4 páginas con 256 caracteres de 8x8 píxeles cada una y 4 páginas de los mismos caracteres para sprites, convertí las imágenes a formato PNG, borré las duplicadas:


imagen de hoja de caracteres


Y luego usó una herramienta auto escrita para traducirlo todo al formato binario RGB332 con bloques de 8x8.


línea de comando imageGraphics


Como resultado, tenemos archivos con caracteres, donde todos los caracteres van secuencialmente uno tras otro y cada uno ocupa 64 bytes.


Sonido


Muestras RAW de onda convertidas en muestras PCM de 8 bits y 8 kilohercios.


Los parches para efectos de sonido en PWM y m√ļsica se escriben con instrucciones especiales.


En cuanto al chip de s√≠ntesis Yamaha YM3438 FM, encontr√© un programa llamado DefleMask que produce m√ļsica sincronizada PAL para el chip Genesis YM2612, que es compatible con el YM3438.


DefleMask exporta m√ļsica en formato VGM y la convierto con otra utilidad patentada en mi propio formato binario.


Todos los archivos binarios de los tres tipos de sonido se combinan en un archivo binario, que mi gestor de arranque puede leer y cargar en la memoria de sonido RAM SDN.


imagen de línea de comando


Enlace al archivo final


El código ejecutable binario, los gráficos y el sonido se combinan en un solo archivo PRG. El archivo PRG tiene un encabezado en el que se describe todo si hay datos de audio y gráficos, cuánto ocupan y los datos en sí.


Dicho archivo puede escribirse en una tarjeta SD, y el gestor de arranque de la consola lo considera y descarga todo en los lugares apropiados y lanza el código ejecutable del programa.


línea de comando imagePRG


Emulador


Escribí un emulador de mi consola en C ++ usando wxWidgets para que sea más fácil desarrollarlo.


La CPU es emulada por la biblioteca libz80 .


Se han agregado características al emulador para la depuración, puedo detenerlo en cualquier momento y hacer una depuración paso a paso del ensamblador, hay una asignación al código fuente en C. si este lenguaje se usó para el juego.


Seg√ļn el gr√°fico, puedo mirar en la memoria de video, en las tablas de s√≠mbolos y en la memoria CHR misma.


Aquí hay un ejemplo de un programa que se ejecuta en un emulador con las herramientas de depuración activadas.


Demostración de imagenEmulator


Demo de programación


Estos videos fueron grabados con la cámara de un teléfono inteligente dirigida a la pantalla CRT del televisor, pido disculpas por la calidad de imagen imperfecta.


El intérprete BASIC programable desde el teclado PS / 2, después del primer programa, muestro cómo escribir directamente en la memoria PPU a través del espacio de direcciones de E / S activando y moviendo el sprite:



Una demostración de gráficos, en este video, descarga programáticamente 64 sprites de 16x16, en el contexto de un fondo con desplazamiento dinámico y una superposición que se mueve por debajo y por encima de los sprites:



La demostraci√≥n de sonido muestra las capacidades del sonido YM3438 y PWM, los datos de sonido de esta demostraci√≥n y la m√ļsica FM y los sonidos PWM juntos ocupan casi todos los 128 kilobytes de memoria de sonido disponibles.



Tetris, casi exclusivamente las caracter√≠sticas de fondo, la m√ļsica en el YM3438, los efectos de sonido en los parches PWM se utilizaron para los gr√°ficos.



Conclusión


Este proyecto es realmente un sue√Īo hecho realidad, he estado trabajando en √©l durante varios a√Īos, con interrupciones, mirando mi tiempo libre, nunca pens√© que llegar√≠a tan lejos en la creaci√≥n de mi propia consola de videojuegos retro. Naturalmente, no es perfecto, ciertamente no soy un experto en electr√≥nica, obviamente hab√≠a demasiados elementos en el decodificador, y sin duda podr√≠a hacerse mejor, y probablemente uno de los lectores solo est√° pensando en ello.


Pero a√ļn as√≠, en el proceso de trabajar en este proyecto, aprend√≠ mucho sobre electr√≥nica, consolas de juegos y dise√Īo de computadoras, lenguaje ensamblador y otras cosas interesantes, y lo m√°s importante, recib√≠ una gran satisfacci√≥n al jugar juegos que yo mismo escrib√≠ en hardware que yo mismo desarroll√©. y recogido.


Tengo planes de hacer consolas / computadoras y m√°s. En realidad, ya estoy haciendo un nuevo decodificador, est√° casi listo, y es un decodificador retro simplificado basado en una placa FPGA y varios componentes adicionales (en una cantidad mucho menor que en este proyecto, sin duda), la idea es ser mucho m√°s barata y m√°s repetible.


Aunque escrib√≠ mucho sobre este proyecto aqu√≠, sin duda se puede discutir mucho m√°s, apenas mencion√© c√≥mo funciona el motor de sonido, c√≥mo interact√ļa la CPU con √©l y hay mucho m√°s que se puede hacer sobre el sistema gr√°fico y otras entradas / salidas y toda la consola Ser√≠a para contarlo.


Mirando la reacción de los lectores, puedo escribir más artículos centrados en actualizaciones, detalles sobre bloques de prefijos individuales u otros proyectos.


Proyectos, sitios, canales de Youtube que me inspiraron y me ayudaron con conocimientos técnicos:


Estos sitios / canales no solo me inspiraron, sino que también me ayudaron a encontrar soluciones a problemas complejos que surgieron durante el trabajo en este proyecto.



Gracias por leer hasta aquí. :)


Si tiene preguntas o comentarios, escriba los comentarios a continuación (Artículo original en inglés en Github. Aprox. Per.)

Source: https://habr.com/ru/post/444006/


All Articles