cursos:ensamblador:interrupciones

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
cursos:ensamblador:interrupciones [19-01-2024 08:11] sromerocursos:ensamblador:interrupciones [21-01-2024 11:23] (actual) – [Instrucción HALT] sromero
Línea 105: Línea 105:
 ==== Instrucción HALT ==== ==== Instrucción HALT ====
  
- La instrucción ''HALT'' es una instrucción muy útil que detiene el proceso de ejecución de la CPU. Al llamarla, la CPU comienza a ejecutar continuamente instrucciones ''NOP'' de 4 t-estados (sin incrementar el contador de programa), hasta que se vea interrumpido por una NMI o una MI (INT), en cuyo momento se incrementa PC y se procesa la interrupción. Al volver de la ISR, el procesador continúa la ejecución del programa en la instrucción siguiente al ''HALT''.+ La instrucción ''HALT'' es una instrucción muy útil que detiene el proceso de ejecución de la CPU. Al llamarla, la CPU realiza de forma continuada el mismo procedimiento que cuando se ejecutan instrucciones ''NOP'' de 4 t-estados (pero sin incrementar el contador de programa, e incrementando el registro **R**), hasta que se vea interrumpido por una NMI o una MI (INT), en cuyo momento se incrementa PC y se procesa la interrupción. Al volver de la ISR, el procesador continúa la ejecución del programa en la instrucción siguiente al ''HALT''.
  
 <code z80> <code z80>
Línea 111: Línea 111:
 </code> </code>
  
- Como veremos más adelante, la instrucción ''HALT'' nos será especialmente útil en determinadas ocasiones al trabajar con la manipulación del área de datos de la videomemoria.+ Básicamente, la ejecución de nuestro programa se detiene en el ''HALT'' hasta que ocurra una interrupción.
  
- Como veremos con detalle a continuación, la ULA genera una interrupción 50 veces por segundo (cada vez que se sincroniza con el haz de electrones de la pantalla para su redibujado, en la parte superior izquierda de la pantalla, antes de empezar a dibujar el actual BORDER). Al colocar un ''HALT'' en nuestro programa, la ejecución del mismo se detendrá en ese punto y dejaremos en espera nuestro programa hasta que se produzca dicha interrupción. Es una manera de limitar la velocidad del programa y de sincronizarse con el haz de electrones para hacer efecto concretos con temporizaciones controladas.+ Como veremos con detalle a continuación, la ULA genera una interrupción 50 veces por segundo (cada vez que se sincroniza con el haz de electrones de la pantalla para su redibujado, en la parte superior izquierda de la pantalla, antes de empezar a dibujar el actual BORDER). Al colocar un ''HALT'' en nuestro programa, la ejecución del mismo se detendrá en ese punto y lo dejaremos en espera hasta que se produzca el siguiente retrazo vertical y la ULA genere la interrupción correspondiente. Es una manera de limitar la velocidad del programa y de sincronizarse con el haz de electrones para hacer efectos concretos con temporizaciones controladas
 + 
 + Como veremos más adelante, la instrucción ''HALT'' nos será especialmente útil en determinadas ocasiones al trabajar con la manipulación del área de datos de la videomemoria.
  
 \\ \\
Línea 400: Línea 402:
                   or    l                   ; only incremented when the value                   or    l                   ; only incremented when the value
                   jr    nz,KEY-INT          ; of the lower two bytes is zero                   jr    nz,KEY-INT          ; of the lower two bytes is zero
-                  INC   (IY+40h)            ; inc bYTE_3(FRAMES) ($5c7a)+                  inc   (iy+$40)            ; inc bYTE_3(FRAMES) ($5c7a)
 0048 KEY-INT: 0048 KEY-INT:
                   push  bc                  ; Save the current values held                   push  bc                  ; Save the current values held
Línea 1103: Línea 1105:
  
 waitkeyticks_loop:            ; bucle de espera, la ISR lo ira decrementando waitkeyticks_loop:            ; bucle de espera, la ISR lo ira decrementando
-    xor a +    xor a                     ; A = 0 => leer todas las semifilas del teclado 
-    in a,(254+    in a, ($fe              ; Leer teclado 
-    or 224 +    or %11100000              ; Poner a 1 los 3 bits más altos 
-    inc a                     ; Comprobamos el estado del teclado +    inc a                     ; A=A+1. Comprobamos el estado del teclado 
-    ret nz                    ; Si hay tecla pulsadasalimos+    ret nz                    ; Si A=0  => ZF = 1 => no hay tecla pulsada 
 +                              ; Si A!=0 => ZF = 0 => hay alguna tecla => salimos
  
     ld hl, (timer)     ld hl, (timer)
Línea 1116: Línea 1119:
     ret     ret
 </code> </code>
 +
 +
 +\\
 +===== Utilizar un JP como ISR =====
 +
 + En muchos juegos comerciales y homebrew, la rutina de ISR no es una rutina en sí misma sino un ''jp'' a la rutina real, de modo que podamos tenerla junto al resto de nuestro código, y no en una ubicación específica de memoria:
 +
 +
 +<code z80>
 +; Rutina de ISR ubicada junto a nuestro programa, y no en dirección tipo $XYXY
 +    ORG 40000
 +
 +    ; Generamos una tabla de 257 valores "$a2" desde $a000 a $a101
 +    ld hl, $a000
 +    ld a, $a2                     ; A = $a2
 +    ld (hl), a                    ; Cargamos $a2 en $fe00
 +    ld de, $fe01                  ; Apuntamos DE a $fe01
 +    ld bc, 256                    ; Realizamos 256 ldi para copiar $a2
 +    ldir                          ; en toda la tabla de vectores de int.
 +
 +    ; Activamos im2 con nuestra ISR
 +    di
 +    ld a, $fe                     ; Definimos la tabla a partir de $fe00.
 +    ld i, a
 +    im 2                          ; Saltamos a im2
 +    ei
 +
 +    ; Nuestro programa
 +    ; (...)
 +
 +Rutina_ISR:
 +    ; La rutina ISR
 +    (...)
 +    ei
 +    reti
 +
 +    ; Guardamos en una variable de preprocesador la posicion
 +    ; de este punto en el proceso de ensamblado ($)
 +    PUNTO_ENSAMBLADO EQU $
 +
 +    ;-------------------------------------------------------------
 +    ; Nuestra rutina de ISR ensamblada en $A2A2: JP a rutina real
 +    ;-------------------------------------------------------------
 +    ORG $A2A2
 +
 +PUNTO_ENTRADA_ISR:
 +    jp Rutina_ISR
 +</code>
 +
 +De esta forma, la tabla de vectores de interrupción y la rutina ISR (un simple ''jp'' a la rutina real) están prácticamente juntas en memoria y sabemos que acaba 3 bytes después de **$a2a2**, por lo que podemos poner código a continuación sin tener que calcular cuánto ocupa la rutina de ISR. De esta forma también podemos tener la rutina de ISR junto al resto del código, con la única penalización de 3 bytes y 10 ciclos de reloj del ''jp NN''.
  
  
Línea 1167: Línea 1220:
  
 Necesitaremos buscar un hueco en nuestro programa, para colocar un ORG adecuado delante de la ISR, como por ejemplo $a1a1 (41377), o cualquier otra dirección donde el byte alto y el bajo coincidan, y que nos venga bien según el mapa de memoria de nuestro juego. Necesitaremos buscar un hueco en nuestro programa, para colocar un ORG adecuado delante de la ISR, como por ejemplo $a1a1 (41377), o cualquier otra dirección donde el byte alto y el bajo coincidan, y que nos venga bien según el mapa de memoria de nuestro juego.
 +
 +Por citar ejemplos de direcciones, podemos ver las obtenidas en el artículo "//Disassemble Interrupt Mode on some popular ZX Spectrum 128k Games//", de Andy Dansby, quien estudió la ubicación de la rutina de ISR en diferentes juegos de Spectrum comerciales:
 +
 +\\ 
 +|< 90% >|
 +^ Juego ^ Ubicación tabla vectores ^ Ubicación ISR ^ Tipo de ISR (rutina o\\ salto a rutina real) ^ Notas ^
 +| La Familia Addams (The Addams Family) | $b900-$ba00 | $5b5b | ISR es ''jp $ba6e'' | - |
 +| Where Time Stood Still | $8400-$8500 | $bebe | Rutina ISR | - |
 +| Demo 7th Reality | $6300-$6400 | $6464 | ISR es ''jp $624d'' | - |
 +| La Abadia Del Crimen | $be00-$bf00 | $bfbf | Rutina ISR | - |
 +| Chase HQ 2 | $9b00-$9c00 | $fdfd | ISR es ''jp $ba6e'' | ISR en bloque paginable | 
 +| Grand Prix Circuit | $8200-$8300 | $6363 | ISR es ''jp $a07a'' | - |
 +| Robocop 2 | $7700-$7800 | $5b5b | ISR es ''jp $9cb4'' | - |
 +| Robocop 3 | $7700-$7800 | $7676 | ISR es ''jp $8225'' | - |
 +| Spacegun | $be00-$bf00 | $bfbf | ISR es ''jp $a07a'' | - |
 +| Desafio Total (Total Recall) | $9100-$9200 | $5d5d | ISR es ''jp $71ff'' | - |
 +| Carrier Command | $8300-$8400 | $8585 | Rutina ISR | - |
 +| El Gran Halcón (Hudson Hawk) | $9000-$8100 | $8181 | Rutina ISR | - |
 +| NARC | $be00-$bf00 | $bfbf | ISR es ''jp $de38'' | - |
 +| Navy Seals | $9100-$9200 | $5d5d | ISR es ''jp $6dfd'' | - |
 +| Pang | $8000-$8101 | $8181 | ISR es ''jp $6286'' | - |
 +\\ 
 +
 +Varias curiosidades:
 +
 +  * Como se puede ver, no hay un estándar sobre dónde ubicar la tabla de vectores de interrupción, ni la ISR, es una cuestión de elegir las direcciones deseadas según diferentes criterios (como el mapa de memoria de nuestro programa).
 +
 +  * Sólo 2 juegos de la lista (**Grand Prix Circuit** y **Pang**) tienen una tabla de 257 bytes, teniendo el resto de juegos una tabla de 1 página (256 bytes), con lo que técnicamente, había una posibilidad "baja" de que el juego se colgase al arrancar si IM2 cogía $FF y $FF+1. Estos dos juegos implementan IM2 correctamente técnicamente hablando.
 +
 +  * Sólo 4 juegos de la lista implementan la rutina directamente en la dirección apuntada por el vector de interrupciones, mientras que el resto simplemente tienen un ''JP'' a la rutina de ISR real.
 +
  
 \\ \\
  • cursos/ensamblador/interrupciones.1705651883.txt.gz
  • Última modificación: 19-01-2024 08:11
  • por sromero