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:07] 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 292: Línea 294:
  Las ISR deben optimizarse lo máximo posible, tratando de que sean lo más rápidas y óptimas posibles, ya que nuestro programa se ha visto interrumpido y no se continuará su ejecución hasta la salida de la ISR. Si tenemos en cuenta que normalmente nuestras ISR se ejecutarán 50 veces por segundo, es importante no ralentizar la ejecución del programa principal llenando de código innecesario la ISR.  Las ISR deben optimizarse lo máximo posible, tratando de que sean lo más rápidas y óptimas posibles, ya que nuestro programa se ha visto interrumpido y no se continuará su ejecución hasta la salida de la ISR. Si tenemos en cuenta que normalmente nuestras ISR se ejecutarán 50 veces por segundo, es importante no ralentizar la ejecución del programa principal llenando de código innecesario la ISR.
  
- Es crítico también que en la salida de la ISR no hayamos modificado los valores de los registros con respecto a su entrada. Para eso, podemos utilizar la pila y hacer ''PUSH'' + ''POP'' de los registros utilizados o incluso utilizar los Shadow Registers si sabemos a ciencia cierta que nuestro programa no los utiliza (con un ''EXX'' y un ''ex afaf<nowiki>'</nowiki>'' al principio y al final de nuestra ISR).+ Es crítico también que en la salida de la ISR no hayamos modificado los valores de los registros con respecto a su entrada. Para eso, podemos utilizar la pila y hacer ''PUSH'' + ''POP'' de los registros utilizados o incluso utilizar los Shadow Registers si sabemos a ciencia cierta que nuestro programa no los utiliza (con un ''EXX'' y un ''EX AFAF<nowiki>'</nowiki>'' al principio y al final de nuestra ISR).
  
  Al principio de nuestra ISR no es necesario desactivar las interrupciones con di, ya que el Z80 las deshabilita al aceptar la interrupción. Debido a este ''DI'' automático realizado por el procesador, las rutinas de ISR deben incluir un ''EI'' antes del ''RET''/''RETI''.  Al principio de nuestra ISR no es necesario desactivar las interrupciones con di, ya que el Z80 las deshabilita al aceptar la interrupción. Debido a este ''DI'' automático realizado por el procesador, las rutinas de ISR deben incluir un ''EI'' antes del ''RET''/''RETI''.
Línea 359: Línea 361:
 Nuestra_ISR: Nuestra_ISR:
     ; Hay que preservar todos los registros que se modifiquen, incluido AF     ; Hay que preservar todos los registros que se modifiquen, incluido AF
-    PUSH XX+    push XX
  
     (código de la ISR)     (código de la ISR)
  
     ; Y recuperar su valor antes de finalizar la ISR     ; Y recuperar su valor antes de finalizar la ISR
-    POP XX+    pop XX
     ei     ei
     reti     reti
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 459: Línea 461:
  
     ; Instalamos la ISR:     ; Instalamos la ISR:
-    ld hl, iSR_ASM_ROUTINE        ; HL = direccion de la rutina de ISR+    ld hl, ISR_ASM_ROUTINE        ; HL = direccion de la rutina de ISR
     di                            ; Deshabilitamos las interrupciones     di                            ; Deshabilitamos las interrupciones
     ld ($feff), hl                ; Guardamos en (65279 = $feff) la direccion     ld ($feff), hl                ; Guardamos en (65279 = $feff) la direccion
Línea 473: Línea 475:
     push af     push af
     push hl     push hl
-    PUSH ..+    push ...
  
     (código de la ISR)     (código de la ISR)
  
-    POP ..+    pop ...
     pop hl     pop hl
     pop af     pop af
Línea 620: Línea 622:
     inc a     inc a
     ld (ticks), a     ld (ticks), a
-    cp iNTS_PER_SECOND +    cp INTS_PER_SECOND 
-    jr c, FRAMES_menor_que_50+    jr c, frames_menor_que_50
  
-    sub iNTS_PER_SECOND       ; Restamos 50 a A (no lo hago 0 directamente+    sub INTS_PER_SECOND       ; Restamos 50 a A (no lo hago 0 directamente
                               ; por si alguien incremento su valor externamente)                               ; por si alguien incremento su valor externamente)
  
Línea 629: Línea 631:
     inc (hl)                  ; Incrementamos segundos     inc (hl)                  ; Incrementamos segundos
  
-FRAMES_menor_que_50:+frames_menor_que_50:
     ld (ticks), a     ld (ticks), a
     pop hl     pop hl
Línea 1050: Línea 1052:
     ld (timer), hl          ; seteamos "timer" con el tiempo de espera     ld (timer), hl          ; seteamos "timer" con el tiempo de espera
  
-Waitnticks_loop:            ; bucle de espera, la ISR lo ira decrementando+waitnticks_loop:            ; bucle de espera, la ISR lo ira decrementando
     ld hl, (timer)     ld hl, (timer)
     ld a, h                 ; cuando (timer) valga 0 y lo decrementen, su     ld a, h                 ; cuando (timer) valga 0 y lo decrementen, su
     cp $ff                  ; byte alto pasara a valer FFh, lo que quiere     cp $ff                  ; byte alto pasara a valer FFh, lo que quiere
                             ; decir que ha pasado el tiempo a esperar.                             ; decir que ha pasado el tiempo a esperar.
-    jr nz, Waitnticks_loop  ; si no, al bucle de nuevo.+    jr nz, waitnticks_loop  ; si no, al bucle de nuevo.
     ret     ret
  
Línea 1102: Línea 1104:
     ld (timer), hl            ; seteamos "timer" con el tiempo de espera     ld (timer), hl            ; seteamos "timer" con el tiempo de espera
  
-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 1113: Línea 1116:
     cp $ff                    ; byte alto pasara a valer FFh, lo que quiere     cp $ff                    ; byte alto pasara a valer FFh, lo que quiere
                               ; decir que ha pasado el tiempo a esperar.                               ; decir que ha pasado el tiempo a esperar.
-    jr nz, Waitkeyticks_loop  ; si no, al bucle de nuevo.+    jr nz, waitkeyticks_loop  ; si no, al bucle de nuevo.
     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.1705651655.txt.gz
  • Última modificación: 19-01-2024 08:07
  • por sromero